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
12 KiB
IO Requests — IORequest, DoIO, SendIO, CheckIO, AbortIO
Overview
AmigaOS device I/O uses a message-based asynchronous protocol. Every device operation is described by an IORequest structure sent to a device's command port. The device processes it (synchronously or in the background) and replies when done. This model unifies all hardware — disks, serial, parallel, audio, timers, network — under a single consistent API.
The IO system is built directly on top of message ports. An IORequest contains an embedded Message, and device I/O is literally message passing between your task and the device's task.
Architecture
sequenceDiagram
participant App as Application
participant Port as Reply Port
participant Dev as Device Task
App->>Dev: OpenDevice("trackdisk.device", 0, ior, 0)
Note over Dev: Fills ior->io_Device, io_Unit
App->>Dev: SendIO(ior) [async]
Note over Dev: Device processes command
Dev->>Port: ReplyMsg (PutMsg to reply port)
App->>Port: WaitIO(ior) or Wait(portSig)
App->>App: Check ior->io_Error
App->>Dev: CloseDevice(ior)
The IORequest Lifecycle
1. CreateIORequest / CreateStdIO → allocate request
2. OpenDevice → bind to device/unit
3. Fill io_Command, io_Data, io_Length, io_Offset
4. DoIO (sync) or SendIO (async) → submit
5. Check io_Error, io_Actual
6. Repeat 3–5 as needed
7. CloseDevice → unbind
8. DeleteIORequest / DeleteStdIO → free
Structures
/* exec/io.h — NDK39 */
struct IORequest {
struct Message io_Message; /* embedded Message (has MsgPort reply port) */
struct Device *io_Device; /* filled by OpenDevice */
struct Unit *io_Unit; /* filled by OpenDevice */
UWORD io_Command; /* CMD_READ, CMD_WRITE, TD_FORMAT, ... */
UBYTE io_Flags; /* IOF_QUICK = attempt synchronous fast path */
BYTE io_Error; /* result: 0 = success, negative = error code */
};
struct IOStdReq { /* extended version with data fields */
struct IORequest io_Request;
ULONG io_Actual; /* actual bytes transferred */
ULONG io_Length; /* requested byte count */
APTR io_Data; /* data buffer pointer */
ULONG io_Offset; /* byte offset (for random-access devices) */
};
IORequest Field Reference
| Field | Set By | Description |
|---|---|---|
io_Message.mn_ReplyPort |
App | Reply port — device sends reply here when done |
io_Device |
OpenDevice |
Pointer to device base — do not modify |
io_Unit |
OpenDevice |
Pointer to device unit — do not modify |
io_Command |
App | Operation to perform (CMD_READ, CMD_WRITE, etc.) |
io_Flags |
App | IOF_QUICK for synchronous fast path attempt |
io_Error |
Device | 0 = success, negative = error (set after completion) |
io_Actual |
Device | Bytes actually transferred |
io_Length |
App | Bytes to transfer |
io_Data |
App | Buffer pointer |
io_Offset |
App | Device-specific offset |
Standard Command Codes
/* exec/io.h */
#define CMD_INVALID 0 /* not a valid command */
#define CMD_RESET 1 /* reset the device/unit to initial state */
#define CMD_READ 2 /* read io_Length bytes into io_Data from io_Offset */
#define CMD_WRITE 3 /* write io_Length bytes from io_Data at io_Offset */
#define CMD_UPDATE 4 /* flush write cache to media */
#define CMD_CLEAR 5 /* discard device read buffers */
#define CMD_STOP 6 /* suspend device operation */
#define CMD_START 7 /* resume device operation */
#define CMD_FLUSH 8 /* abort all pending requests */
#define CMD_NONSTD 9 /* first device-specific command number */
Device-specific commands start at CMD_NONSTD (9):
| Device | Command | Number | Description |
|---|---|---|---|
| trackdisk.device | TD_FORMAT |
10 | Low-level format a track |
| trackdisk.device | TD_MOTOR |
11 | Motor on/off control |
| trackdisk.device | TD_CHANGESTATE |
14 | Check disk insertion |
| serial.device | SDCMD_SETPARAMS |
10 | Set baud rate, stop bits |
| timer.device | TR_ADDREQUEST |
9 | Schedule a timer event |
| audio.device | ADCMD_ALLOCATE |
32 | Allocate audio channels |
Error Codes (io_Error)
/* exec/errors.h — NDK39 */
#define IOERR_OPENFAIL -1 /* device/unit could not be opened */
#define IOERR_ABORTED -2 /* request was aborted via AbortIO */
#define IOERR_NOCMD -3 /* unknown command */
#define IOERR_BADLENGTH -4 /* io_Length invalid for this command */
#define IOERR_BADADDRESS -5 /* io_Data not aligned or accessible */
#define IOERR_UNITBUSY -6 /* unit in use, cannot complete */
#define IOERR_SELFTEST -7 /* hardware self-test failed */
Device-specific error codes are positive values defined in each device's header.
Opening and Closing Devices
Opening
/* Create a reply port and IO request */
struct MsgPort *replyPort = CreateMsgPort();
struct IOStdReq *ior = (struct IOStdReq *)
CreateIORequest(replyPort, sizeof(struct IOStdReq));
/* Open the device */
BYTE err = OpenDevice("trackdisk.device", 0, /* unit 0 = DF0: */
(struct IORequest *)ior, 0); /* flags = 0 */
if (err != 0)
{
Printf("OpenDevice failed: %ld\n", err);
DeleteIORequest(ior);
DeleteMsgPort(replyPort);
return RETURN_FAIL;
}
Closing
/* Close and free — MUST abort pending requests first */
CloseDevice((struct IORequest *)ior);
DeleteIORequest((struct IORequest *)ior);
DeleteMsgPort(replyPort);
Synchronous I/O: DoIO
Blocks the calling task until the device completes the request:
ior->io_Command = CMD_READ;
ior->io_Data = buffer;
ior->io_Length = 512;
ior->io_Offset = 0;
LONG err = DoIO((struct IORequest *)ior); /* LVO -456 */
if (err == 0)
{
/* Success — io_Actual contains bytes read */
Printf("Read %ld bytes\n", ior->io_Actual);
}
else
{
Printf("Error: %ld\n", ior->io_Error);
}
How DoIO Works Internally
- Sets
IOF_QUICKinio_Flags - Calls the device's
BeginIOvector - If the device completes synchronously (kept
IOF_QUICKset), returns immediately - If the device cleared
IOF_QUICK(will complete async), callsWaitIOto block
Asynchronous I/O: SendIO + WaitIO
/* Queue the request — returns immediately: */
ior->io_Command = CMD_READ;
ior->io_Data = buffer;
ior->io_Length = 65536;
ior->io_Offset = 0;
SendIO((struct IORequest *)ior); /* LVO -462 */
/* Do other work while device operates... */
UpdateUI();
ProcessInput();
/* Block until this specific request completes: */
WaitIO((struct IORequest *)ior); /* LVO -474 */
BYTE err = ior->io_Error;
Combining Async IO with Event Loop
ULONG ioSig = 1L << replyPort->mp_SigBit;
ULONG idcmpSig = 1L << win->UserPort->mp_SigBit;
SendIO((struct IORequest *)ior);
BOOL ioDone = FALSE;
while (!ioDone)
{
ULONG sigs = Wait(ioSig | idcmpSig | SIGBREAKF_CTRL_C);
if (sigs & idcmpSig) HandleGUI();
if (sigs & ioSig)
{
if (CheckIO((struct IORequest *)ior))
{
WaitIO((struct IORequest *)ior); /* Must still call to clean up */
ioDone = TRUE;
}
}
if (sigs & SIGBREAKF_CTRL_C)
{
AbortIO((struct IORequest *)ior);
WaitIO((struct IORequest *)ior);
break;
}
}
CheckIO — Non-Blocking Status Check
/* Returns non-NULL if request is complete: */
struct IORequest *result = CheckIO((struct IORequest *)ior); /* LVO -468 */
if (result)
{
WaitIO((struct IORequest *)ior); /* Must still call to dequeue reply */
/* io_Error is valid now */
}
else
{
/* Still in progress */
}
Warning
: Even after
CheckIO()returns non-NULL, you must callWaitIO()to remove the reply message from the port. Failing to do so leaves a stale message in the queue.
AbortIO — Cancel a Pending Request
AbortIO((struct IORequest *)ior); /* LVO -480 — request cancellation */
WaitIO((struct IORequest *)ior); /* Wait for acknowledgment */
/* io_Error will be IOERR_ABORTED (-2) */
Important:
AbortIOis a request — the device may not abort immediately. Always follow withWaitIOto ensure the request is fully complete before reusing or freeing the IORequest.
The IOF_QUICK Fast Path
When IOF_QUICK is set in io_Flags, the device MAY complete the request synchronously within BeginIO() — without queuing, signaling, or replying via message. This avoids the overhead of message passing for trivial operations.
/* Fast-path attempt */
ior->io_Flags = IOF_QUICK;
BeginIO((struct IORequest *)ior);
if (ior->io_Flags & IOF_QUICK)
{
/* Completed synchronously — no WaitIO needed */
}
else
{
/* Device cleared IOF_QUICK — must wait */
WaitIO((struct IORequest *)ior);
}
DoIO uses this mechanism internally.
Practical: Timer Device Usage
/* Open timer.device */
struct MsgPort *timerPort = CreateMsgPort();
struct timerequest *tr = (struct timerequest *)
CreateIORequest(timerPort, sizeof(struct timerequest));
OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *)tr, 0);
/* Set a 2-second timer */
tr->tr_node.io_Command = TR_ADDREQUEST;
tr->tr_time.tv_secs = 2;
tr->tr_time.tv_micro = 0;
SendIO((struct IORequest *)tr);
/* Wait for timer or user interrupt */
ULONG timerSig = 1L << timerPort->mp_SigBit;
ULONG sigs = Wait(timerSig | SIGBREAKF_CTRL_C);
if (sigs & timerSig)
{
WaitIO((struct IORequest *)tr);
Printf("Timer expired!\n");
}
else
{
AbortIO((struct IORequest *)tr);
WaitIO((struct IORequest *)tr);
}
/* Cleanup */
CloseDevice((struct IORequest *)tr);
DeleteIORequest((struct IORequest *)tr);
DeleteMsgPort(timerPort);
Pitfalls
1. Reusing IORequest While In Flight
/* BUG — request is still being processed by device */
SendIO(ior);
ior->io_Command = CMD_WRITE; /* WRONG — device is reading from this struct */
SendIO(ior); /* Double-send = corruption */
2. Forgetting WaitIO After CheckIO
/* BUG — reply message left in port */
if (CheckIO(ior)) { /* done! */ }
/* But WaitIO was never called — stale message in reply port */
/* Next Wait() on this port returns immediately with garbage */
3. Freeing IORequest Without CloseDevice
/* BUG — device still references this unit */
DeleteIORequest(ior); /* Device's internal pointers now dangle */
Best Practices
- Always pair
OpenDevice/CloseDeviceandCreateIORequest/DeleteIORequest - Always call
WaitIOafterCheckIOreturns non-NULL - Always call
WaitIOafterAbortIO - Use
SendIOfor async operations —DoIOblocks your task completely - Combine IO with event loop using
Wait()on the reply port's signal - Don't reuse IORequest until the previous operation completes
- Check
io_Errorafter every operation — errors are common with removable media - Use
UNIT_VBLANKfor timer.device unless you need microsecond precision (useUNIT_MICROHZ)
References
- NDK39:
exec/io.h,exec/errors.h,exec/devices.h - ADCD 2.1:
OpenDevice,CloseDevice,DoIO,SendIO,WaitIO,CheckIO,AbortIO,CreateIORequest,DeleteIORequest - See also: Message Ports — IORequest is built on message passing
- See also:
10_devices/— per-device command codes and structures - Amiga ROM Kernel Reference Manual: Exec — I/O requests chapter