mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-12 16:16:28 +00:00
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
336 lines
9.6 KiB
Markdown
336 lines
9.6 KiB
Markdown
[← Home](../README.md) · [Exec Kernel](README.md)
|
|
|
|
# Message Ports — MsgPort, Message, PutMsg, GetMsg, WaitPort
|
|
|
|
## 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. 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.
|
|
|
|
---
|
|
|
|
## Core Structures
|
|
|
|
```c
|
|
/* exec/ports.h — NDK39 */
|
|
|
|
struct MsgPort {
|
|
struct Node mp_Node; /* ln_Name = port name (for public ports) */
|
|
UBYTE mp_Flags; /* PA_SIGNAL, PA_SOFTINT, PA_IGNORE */
|
|
UBYTE mp_SigBit; /* signal bit used for PA_SIGNAL ports */
|
|
APTR mp_SigTask; /* task to signal on message arrival */
|
|
struct List mp_MsgList; /* queue of pending messages */
|
|
};
|
|
|
|
struct Message {
|
|
struct Node mn_Node; /* ln_Type = NT_MESSAGE */
|
|
struct MsgPort *mn_ReplyPort; /* port to send reply to (or NULL) */
|
|
UWORD mn_Length; /* total size of message including header */
|
|
};
|
|
```
|
|
|
|
### MsgPort Field Reference
|
|
|
|
| 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` 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 and Destroying Ports
|
|
|
|
### OS 2.0+ (Preferred)
|
|
|
|
```c
|
|
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);
|
|
port->mp_SigTask = FindTask(NULL);
|
|
NewList(&port->mp_MsgList);
|
|
|
|
/* Cleanup (reverse order): */
|
|
FreeSignal(port->mp_SigBit);
|
|
FreeMem(port, sizeof(struct MsgPort));
|
|
```
|
|
|
|
---
|
|
|
|
## Sending Messages
|
|
|
|
```c
|
|
/* PutMsg: add message to queue, signal receiver */
|
|
PutMsg(targetPort, (struct Message *)myMsg);
|
|
/* Non-blocking — returns immediately
|
|
Can be called from interrupt context
|
|
Sender must NOT modify msg until reply received */
|
|
```
|
|
|
|
### 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
|
|
|
|
### Blocking Wait
|
|
|
|
```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 */
|
|
}
|
|
```
|
|
|
|
### Non-Blocking Poll
|
|
|
|
```c
|
|
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: */
|
|
port->mp_Node.ln_Name = "myapp.port";
|
|
AddPort(port); /* LVO -354 — adds to SysBase→PortList */
|
|
|
|
/* Find from another task: */
|
|
Forbid();
|
|
struct MsgPort *remote = FindPort("myapp.port"); /* LVO -390 */
|
|
if (remote)
|
|
PutMsg(remote, myMsg);
|
|
Permit();
|
|
|
|
/* Unregister: */
|
|
RemPort(port); /* LVO -360 */
|
|
```
|
|
|
|
> **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.
|
|
|
|
---
|
|
|
|
## Pitfalls
|
|
|
|
### 1. Not Replying to Messages
|
|
|
|
```c
|
|
/* 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`, `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
|