mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-13 00:26:28 +00:00
exec_os: enrich all stubs to bootcamp-quality reference articles
Complete rewrite of 14 exec_os articles from stubs to comprehensive deep-dive technical references with architecture diagrams, pitfalls, and best practices. New: multitasking.md (scheduler, IPC, memory safety, real-world scenarios) Enriched: exec_base, tasks_processes, library_system, library_vectors, interrupts, exceptions_traps, memory_management, message_ports, signals, semaphores, io_requests, lists_nodes, resident_modules Updated indexes: 06_exec_os/README.md, root README.md
This commit is contained in:
parent
4d136b0672
commit
59929047d4
16 changed files with 4463 additions and 678 deletions
|
|
@ -4,7 +4,42 @@
|
|||
|
||||
## Overview
|
||||
|
||||
AmigaOS inter-task communication uses a **message passing** system. Tasks send `Message` structures to `MsgPort` queues. The receiving task either polls (`GetMsg`) or blocks (`WaitPort`) for incoming messages. No shared memory is touched without the message handshake.
|
||||
AmigaOS inter-task communication uses a **message passing** system. Tasks send `Message` structures to `MsgPort` queues. The receiving task either polls (`GetMsg`) or blocks (`WaitPort`) for incoming messages. This is the fundamental IPC mechanism — everything from [IDCMP](../09_intuition/idcmp.md) to device I/O to ARexx scripting is built on top of message ports.
|
||||
|
||||
Unlike pipes or sockets in Unix, Amiga messages are **zero-copy pointer exchanges**. The sender and receiver share the same physical memory — no data is copied. This makes message passing extremely fast but requires careful ownership discipline.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant S as Sender Task
|
||||
participant P as MsgPort Queue
|
||||
participant R as Receiver Task
|
||||
|
||||
S->>S: Prepare Message (mn_ReplyPort = replyPort)
|
||||
S->>P: PutMsg(port, msg)
|
||||
Note over P: Message added to mp_MsgList<br/>Signal(mp_SigTask, mp_SigBit)
|
||||
P->>R: Signal wakes receiver
|
||||
R->>P: GetMsg(port) → returns msg pointer
|
||||
R->>R: Process message
|
||||
R->>S: ReplyMsg(msg) → PutMsg to mn_ReplyPort
|
||||
Note over S: Sender receives reply on replyPort
|
||||
```
|
||||
|
||||
### Ownership Rules
|
||||
|
||||
This is the most critical concept in Amiga message passing:
|
||||
|
||||
| Phase | Message Owned By | Who Can Read/Write? |
|
||||
|---|---|---|
|
||||
| Before `PutMsg()` | Sender | Sender only |
|
||||
| After `PutMsg()`, before `GetMsg()` | Port (in transit) | **Nobody** — message is in the queue |
|
||||
| After `GetMsg()`, before `ReplyMsg()` | Receiver | Receiver only |
|
||||
| After `ReplyMsg()` | Sender (via reply port) | Sender only |
|
||||
|
||||
> **Critical**: The sender must **not** modify or free the message between `PutMsg()` and receiving the reply. The message structure lives in the sender's memory, but it's logically owned by the receiver until replied.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -28,115 +63,274 @@ struct Message {
|
|||
};
|
||||
```
|
||||
|
||||
`mp_Flags` values:
|
||||
### MsgPort Field Reference
|
||||
|
||||
| Value | Constant | Meaning |
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `mp_Node.ln_Name` | Port name — set for public (findable) ports, NULL for private |
|
||||
| `mp_Flags` | Notification method when message arrives |
|
||||
| `mp_SigBit` | Which signal bit to set when message arrives (for `PA_SIGNAL`) |
|
||||
| `mp_SigTask` | Which task to signal (for `PA_SIGNAL`) or softint to trigger |
|
||||
| `mp_MsgList` | Standard exec List — pending messages queue (FIFO) |
|
||||
|
||||
### mp_Flags Values
|
||||
|
||||
| Value | Constant | Behavior |
|
||||
|---|---|---|
|
||||
| 0 | `PA_SIGNAL` | Signal `mp_SigTask` when message arrives |
|
||||
| 1 | `PA_SOFTINT` | Trigger software interrupt |
|
||||
| 2 | `PA_IGNORE` | Do not wake the task (polling only) |
|
||||
| 0 | `PA_SIGNAL` | Signal `mp_SigTask` with `1L << mp_SigBit` when message arrives |
|
||||
| 1 | `PA_SOFTINT` | Trigger the software interrupt pointed to by `mp_SigTask` |
|
||||
| 2 | `PA_IGNORE` | Do nothing — port must be polled with `GetMsg()` |
|
||||
|
||||
---
|
||||
|
||||
## Creating a Message Port
|
||||
## Creating and Destroying Ports
|
||||
|
||||
### OS 2.0+ (Preferred)
|
||||
|
||||
```c
|
||||
struct MsgPort *port = CreateMsgPort(); /* exec.library LVO -732 (OS 2.0+) */
|
||||
/* or manually for OS 1.x compatibility: */
|
||||
struct MsgPort *port = AllocMem(sizeof(struct MsgPort), MEMF_PUBLIC|MEMF_CLEAR);
|
||||
struct MsgPort *port = CreateMsgPort(); /* LVO -732 */
|
||||
/* Automatically:
|
||||
- Allocates memory
|
||||
- Allocates a signal bit
|
||||
- Sets mp_Flags = PA_SIGNAL
|
||||
- Sets mp_SigTask = FindTask(NULL)
|
||||
- Initializes mp_MsgList */
|
||||
|
||||
if (!port) { /* All signal bits exhausted */ }
|
||||
|
||||
/* Cleanup: */
|
||||
DeleteMsgPort(port); /* LVO -738 */
|
||||
/* Frees signal bit and memory */
|
||||
```
|
||||
|
||||
### Manual Creation (OS 1.x Compatible)
|
||||
|
||||
```c
|
||||
struct MsgPort *port = AllocMem(sizeof(struct MsgPort), MEMF_PUBLIC | MEMF_CLEAR);
|
||||
port->mp_Node.ln_Type = NT_MSGPORT;
|
||||
port->mp_Flags = PA_SIGNAL;
|
||||
port->mp_SigBit = AllocSignal(-1); /* any free signal bit */
|
||||
port->mp_SigTask = FindTask(NULL); /* signal current task */
|
||||
port->mp_SigBit = AllocSignal(-1);
|
||||
port->mp_SigTask = FindTask(NULL);
|
||||
NewList(&port->mp_MsgList);
|
||||
|
||||
/* Cleanup (reverse order): */
|
||||
FreeSignal(port->mp_SigBit);
|
||||
FreeMem(port, sizeof(struct MsgPort));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sending a Message
|
||||
## Sending Messages
|
||||
|
||||
```c
|
||||
/* PutMsg: add message to queue, signal receiver */
|
||||
PutMsg(target_port, (struct Message *)my_msg);
|
||||
/* Non-blocking — returns immediately */
|
||||
PutMsg(targetPort, (struct Message *)myMsg);
|
||||
/* Non-blocking — returns immediately
|
||||
Can be called from interrupt context
|
||||
Sender must NOT modify msg until reply received */
|
||||
```
|
||||
|
||||
PutMsg can be called from interrupt context.
|
||||
### What PutMsg Does Internally
|
||||
|
||||
1. Adds the message to the tail of `targetPort->mp_MsgList`
|
||||
2. Sets `mn_Node.ln_Type = NT_MESSAGE`
|
||||
3. If `mp_Flags == PA_SIGNAL`: calls `Signal(mp_SigTask, 1L << mp_SigBit)`
|
||||
4. If `mp_Flags == PA_SOFTINT`: triggers the software interrupt
|
||||
5. Returns — does not wait
|
||||
|
||||
---
|
||||
|
||||
## Receiving Messages
|
||||
|
||||
```c
|
||||
/* Block until at least one message arrives: */
|
||||
WaitPort(my_port);
|
||||
### Blocking Wait
|
||||
|
||||
/* Then drain the queue: */
|
||||
struct MyMsg *msg;
|
||||
while ((msg = (struct MyMsg *)GetMsg(my_port)) != NULL) {
|
||||
/* process msg */
|
||||
ReplyMsg((struct Message *)msg); /* send reply if mn_ReplyPort != NULL */
|
||||
```c
|
||||
/* Block until at least one message is pending: */
|
||||
WaitPort(myPort); /* LVO -384 */
|
||||
/* Equivalent to: Wait(1L << myPort->mp_SigBit) + signal clear */
|
||||
|
||||
/* Drain the queue (may have multiple messages): */
|
||||
struct Message *msg;
|
||||
while ((msg = GetMsg(myPort)) != NULL)
|
||||
{
|
||||
/* Process message */
|
||||
ReplyMsg(msg); /* Send reply — required for request-reply pattern */
|
||||
}
|
||||
```
|
||||
|
||||
### GetMsg (non-blocking poll)
|
||||
### Non-Blocking Poll
|
||||
|
||||
```c
|
||||
struct Message *msg = GetMsg(my_port);
|
||||
/* Returns NULL if queue is empty */
|
||||
struct Message *msg = GetMsg(myPort); /* LVO -372 */
|
||||
/* Returns NULL if queue is empty — never blocks */
|
||||
```
|
||||
|
||||
### Multi-Port Wait
|
||||
|
||||
```c
|
||||
/* Wait on multiple ports simultaneously */
|
||||
ULONG portSig1 = 1L << port1->mp_SigBit;
|
||||
ULONG portSig2 = 1L << port2->mp_SigBit;
|
||||
|
||||
ULONG received = Wait(portSig1 | portSig2 | SIGBREAKF_CTRL_C);
|
||||
|
||||
if (received & portSig1)
|
||||
{
|
||||
struct Message *msg;
|
||||
while ((msg = GetMsg(port1))) { /* ... */ ReplyMsg(msg); }
|
||||
}
|
||||
if (received & portSig2)
|
||||
{
|
||||
struct Message *msg;
|
||||
while ((msg = GetMsg(port2))) { /* ... */ ReplyMsg(msg); }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The Request-Reply Pattern
|
||||
|
||||
The standard bidirectional communication idiom:
|
||||
|
||||
```c
|
||||
/* --- Sender --- */
|
||||
struct MyMessage {
|
||||
struct Message msg; /* MUST be first field */
|
||||
ULONG command;
|
||||
ULONG result;
|
||||
APTR data;
|
||||
};
|
||||
|
||||
struct MsgPort *replyPort = CreateMsgPort();
|
||||
|
||||
struct MyMessage request;
|
||||
request.msg.mn_ReplyPort = replyPort;
|
||||
request.msg.mn_Length = sizeof(struct MyMessage);
|
||||
request.command = CMD_DO_WORK;
|
||||
request.data = myData;
|
||||
|
||||
PutMsg(serverPort, &request.msg);
|
||||
|
||||
/* Wait for reply */
|
||||
WaitPort(replyPort);
|
||||
GetMsg(replyPort);
|
||||
|
||||
/* Now request.result contains the server's response */
|
||||
DeleteMsgPort(replyPort);
|
||||
|
||||
/* --- Receiver (Server) --- */
|
||||
WaitPort(serverPort);
|
||||
struct MyMessage *req;
|
||||
while ((req = (struct MyMessage *)GetMsg(serverPort)))
|
||||
{
|
||||
/* Process */
|
||||
req->result = DoWork(req->command, req->data);
|
||||
|
||||
/* Reply — wakes sender */
|
||||
ReplyMsg(&req->msg);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Public Named Ports
|
||||
|
||||
Public ports are registered on `SysBase→PortList` and findable by name:
|
||||
|
||||
```c
|
||||
/* Register a port so others can find it by name: */
|
||||
/* Register: */
|
||||
port->mp_Node.ln_Name = "myapp.port";
|
||||
AddPort(port); /* LVO -354 — adds to SysBase→PortList */
|
||||
|
||||
/* Find from another task: */
|
||||
Forbid();
|
||||
AddPort(port);
|
||||
struct MsgPort *remote = FindPort("myapp.port"); /* LVO -390 */
|
||||
if (remote)
|
||||
PutMsg(remote, myMsg);
|
||||
Permit();
|
||||
|
||||
/* From another task: */
|
||||
Forbid();
|
||||
struct MsgPort *remote = FindPort("myapp.port");
|
||||
Permit();
|
||||
if (remote) PutMsg(remote, my_msg);
|
||||
|
||||
/* Cleanup: */
|
||||
Forbid();
|
||||
RemPort(port);
|
||||
Permit();
|
||||
/* Unregister: */
|
||||
RemPort(port); /* LVO -360 */
|
||||
```
|
||||
|
||||
`Forbid()` is required around `FindPort`/`AddPort`/`RemPort` to prevent the task list from changing mid-operation.
|
||||
> **Critical**: `Forbid()` is required around `FindPort()` + `PutMsg()`. Without it, the server task could call `RemPort()` between your `FindPort()` and `PutMsg()`, leaving you with a dangling pointer.
|
||||
|
||||
---
|
||||
|
||||
## Reply Pattern
|
||||
## Pitfalls
|
||||
|
||||
The standard request-reply idiom:
|
||||
### 1. Not Replying to Messages
|
||||
|
||||
```c
|
||||
/* Sender: */
|
||||
my_msg->mn_ReplyPort = reply_port;
|
||||
PutMsg(server_port, &my_msg->mn_Message);
|
||||
WaitPort(reply_port);
|
||||
struct MyMsg *reply = (struct MyMsg *)GetMsg(reply_port);
|
||||
/* reply now contains the server's response */
|
||||
|
||||
/* Server: */
|
||||
WaitPort(server_port);
|
||||
struct MyMsg *req = (struct MyMsg *)GetMsg(server_port);
|
||||
/* process req... */
|
||||
req->result = 42;
|
||||
ReplyMsg(&req->mn_Message); /* sends back to req->mn_ReplyPort */
|
||||
/* BUG — sender is blocked waiting for reply forever */
|
||||
msg = GetMsg(port);
|
||||
/* ... process but forget to ReplyMsg ... */
|
||||
/* Sender hangs on WaitPort(replyPort) indefinitely */
|
||||
```
|
||||
|
||||
### 2. Modifying Message Before Reply
|
||||
|
||||
```c
|
||||
/* BUG — message is logically owned by receiver */
|
||||
PutMsg(server, &msg);
|
||||
msg.data = newData; /* WRONG — receiver may be reading msg.data right now */
|
||||
WaitPort(replyPort); /* Race condition */
|
||||
```
|
||||
|
||||
### 3. Deleting Port with Pending Messages
|
||||
|
||||
```c
|
||||
/* BUG — messages in queue still reference the port */
|
||||
DeleteMsgPort(port); /* Leaked messages, dangling reply port pointers */
|
||||
|
||||
/* SAFE — drain first */
|
||||
struct Message *msg;
|
||||
while ((msg = GetMsg(port)))
|
||||
ReplyMsg(msg);
|
||||
DeleteMsgPort(port);
|
||||
```
|
||||
|
||||
### 4. FindPort Without Forbid
|
||||
|
||||
```c
|
||||
/* RACE — server may RemPort between find and send */
|
||||
struct MsgPort *p = FindPort("SERVER"); /* Port exists... */
|
||||
/* Context switch — server task exits, calls RemPort() */
|
||||
PutMsg(p, msg); /* CRASH — p is now freed memory */
|
||||
```
|
||||
|
||||
### 5. Stack-Allocated Messages with Async Reply
|
||||
|
||||
```c
|
||||
/* BUG — message on stack, function returns before reply */
|
||||
void SendRequest(struct MsgPort *server)
|
||||
{
|
||||
struct MyMessage msg;
|
||||
msg.msg.mn_ReplyPort = replyPort;
|
||||
PutMsg(server, &msg.msg);
|
||||
/* Function returns — stack frame destroyed */
|
||||
/* Reply arrives later → writes to invalid stack memory */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always drain ports** before deleting — `while (GetMsg(port)) ReplyMsg(msg)`
|
||||
2. **Always reply** to received messages — the sender may be waiting
|
||||
3. **Use `Forbid()`** around `FindPort()` + `PutMsg()` for public ports
|
||||
4. **Use heap-allocated messages** for async patterns — never stack-local
|
||||
5. **Set `mn_Length`** correctly — some system code uses it for validation
|
||||
6. **Set `mn_ReplyPort`** to NULL if no reply is expected — receiver checks this
|
||||
7. **Use `PA_SIGNAL`** for normal ports — `PA_SOFTINT` only for device-level code
|
||||
8. **Create separate reply ports** per conversation — don't share reply ports between multiple pending requests
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- NDK39: `exec/ports.h`, `exec/messages.h`
|
||||
- ADCD 2.1: `CreateMsgPort`, `PutMsg`, `GetMsg`, `WaitPort`, `ReplyMsg`
|
||||
- ADCD 2.1: `CreateMsgPort`, `DeleteMsgPort`, `PutMsg`, `GetMsg`, `WaitPort`, `ReplyMsg`, `AddPort`, `RemPort`, `FindPort`
|
||||
- See also: [Signals](signals.md), [Multitasking](multitasking.md) — IPC strategies comparison
|
||||
- *Amiga ROM Kernel Reference Manual: Exec* — messages and ports chapter
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue