amiga-bootcamp/06_exec_os/library_system.md
2026-04-26 14:46:18 -04:00

311 lines
9.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[← Home](../README.md) · [Exec Kernel](README.md)
# Library System — OpenLibrary Lifecycle, Version Management
## Overview
The AmigaOS library system provides **versioned, shared code** via a standardised interface. Libraries are identified by name, opened with a version check, and reference-counted for safe unloading. This system is the backbone of the Amiga's modular architecture — everything from `dos.library` to `intuition.library` to third-party libraries uses the same open/close/expunge lifecycle.
---
## Architecture
```mermaid
graph TB
subgraph "Application"
APP["OpenLibrary('dos.library', 40)"]
end
subgraph "Exec"
SCAN["Scan SysBase→LibList"]
DISK["Search LIBS: path"]
LOAD["LoadSeg + RomTag init"]
INIT["Call library Open vector"]
end
subgraph "Library"
LIB["struct Library<br/>JMP table + base data"]
OPEN["Open: lib_OpenCnt++"]
CLOSE["Close: lib_OpenCnt--"]
EXPUNGE["Expunge: if OpenCnt==0,<br/>unload and free"]
end
APP --> SCAN
SCAN -->|Found| INIT
SCAN -->|Not found| DISK
DISK -->|Found on disk| LOAD
LOAD --> INIT
INIT --> OPEN
OPEN --> LIB
style APP fill:#e8f4fd,stroke:#2196f3,color:#333
style LIB fill:#e8f5e9,stroke:#4caf50,color:#333
```
---
## Library Node
Every library is an `NT_LIBRARY` node on `SysBase→LibList`:
```c
/* exec/libraries.h — NDK39 */
struct Library {
struct Node lib_Node; /* ln_Name = "dos.library" */
UBYTE lib_Flags; /* LIBF_SUMUSED | LIBF_DELEXP */
UBYTE lib_Pad;
UWORD lib_NegSize; /* size of JMP table in bytes */
UWORD lib_PosSize; /* size of library base struct */
UWORD lib_Version; /* major version */
UWORD lib_Revision; /* minor revision */
APTR lib_IdString; /* "dos.library 40.1 (16.7.93)" */
ULONG lib_Sum; /* JMP table checksum */
UWORD lib_OpenCnt; /* reference count */
};
```
### Field Reference
| Field | Description |
|---|---|
| `lib_Node.ln_Name` | Library name used for `OpenLibrary()` lookup |
| `lib_Flags` | `LIBF_SUMUSED`, `LIBF_CHANGED`, `LIBF_DELEXP` |
| `lib_NegSize` | Total size of the JMP table (negative offsets from base) |
| `lib_PosSize` | Size of the library base structure (positive offsets) |
| `lib_Version` | Major version number — checked by `OpenLibrary()` |
| `lib_Revision` | Minor revision — informational, not checked at open time |
| `lib_IdString` | Human-readable ID string with date |
| `lib_Sum` | Checksum of the JMP table (for integrity verification) |
| `lib_OpenCnt` | Number of active openers — library can't expunge while > 0 |
### Library Memory Layout
```
JMP table (lib_NegSize bytes)
┌─────────────────────────────┐
base - N×6: │ JMP function_N │
│ ... │
base - 24: │ JMP Reserved │
base - 18: │ JMP Expunge │
base - 12: │ JMP Close │
base - 6: │ JMP Open │
├─────────────────────────────┤
base + 0: ───→ │ struct Library (header) │ ← OpenLibrary returns this
│ (library-specific data...) │
base + PosSize: │ (end of base struct) │
└─────────────────────────────┘
```
---
## OpenLibrary / CloseLibrary
### Opening
```c
struct DosLibrary *DOSBase =
(struct DosLibrary *)OpenLibrary("dos.library", 40);
if (!DOSBase)
{
/* Library not found, or version too old */
/* This is how you enforce minimum OS version requirements */
}
```
### What OpenLibrary Does
1. **Scan `SysBase→LibList`** for a node whose `ln_Name` matches
2. **If not found**: search the resident module list (`FindResident`)
3. **If not resident**: search `LIBS:` assign path, `LoadSeg` the file, find RomTag, initialize
4. **Check version**: `lib_Version >= requestedVersion`?
5. **Call library's `Open()` vector** — library-specific initialization, `lib_OpenCnt++`
6. **Return** library base pointer (or NULL on failure)
### Closing
```c
CloseLibrary((struct Library *)DOSBase);
DOSBase = NULL; /* Good practice — prevent use-after-close */
```
### What CloseLibrary Does
1. **Call library's `Close()` vector**`lib_OpenCnt--`
2. **If `lib_OpenCnt == 0` and `LIBF_DELEXP` is set**: call `Expunge()` to unload
3. **If `Expunge()` returns a segment list**: `UnLoadSeg` to free the code
---
## Library Flags
| Flag | Value | Meaning |
|---|---|---|
| `LIBF_SUMUSED` | `$01` | JMP table checksum is maintained — exec verifies on close |
| `LIBF_CHANGED` | `$02` | Checksum needs recalculation (after `SetFunction`) |
| `LIBF_DELEXP` | `$04` | Deferred expunge — will expunge when last opener closes |
---
## The Expunge Lifecycle
```mermaid
sequenceDiagram
participant App as Application
participant Exec as exec.library
participant Lib as Library
App->>Exec: CloseLibrary(base)
Exec->>Lib: Call Close() vector
Lib->>Lib: lib_OpenCnt--
alt lib_OpenCnt == 0
Exec->>Lib: Call Expunge() vector
alt No other openers
Lib->>Lib: Remove from LibList
Lib->>Lib: Free JMP table + base
Lib->>Exec: Return segment list
Exec->>Exec: UnLoadSeg(seglist)
else Memory pressure only
Lib->>Lib: Set LIBF_DELEXP
Lib->>Exec: Return NULL (defer)
end
end
```
### When Expunge Happens
- **Automatic**: When `CloseLibrary` drops `lib_OpenCnt` to 0 and memory is needed
- **System pressure**: Exec calls `Expunge()` on all libraries when `AllocMem` fails (trying to reclaim memory)
- **Manual**: `RemLibrary()` requests expunge regardless of open count
### Implementing Expunge in a Library
```c
BPTR __saveds LibExpunge(void)
{
struct MyLibBase *base = (struct MyLibBase *)REG_A6;
if (base->lib_OpenCnt > 0)
{
/* Can't unload — still in use */
base->lib_Flags |= LIBF_DELEXP;
return 0; /* Signal: deferred */
}
/* Remove from system list */
Remove(&base->lib_Node);
/* Free library-specific resources */
FreeMyResources(base);
/* Free the library base + JMP table */
BPTR segList = base->segList;
ULONG negSize = base->lib_NegSize;
ULONG posSize = base->lib_PosSize;
FreeMem((UBYTE *)base - negSize, negSize + posSize);
return segList; /* Exec calls UnLoadSeg on this */
}
```
---
## Version Numbering Convention
| Version | OS Release | Example Libraries |
|---|---|---|
| 33.x | OS 1.2 | exec 33.180, dos 33.124 |
| 34.x | OS 1.3 | exec 34.2, dos 34.75 |
| 36.x | OS 2.0 | exec 36.174, dos 36.68 |
| 37.x | OS 2.04 | exec 37.175, dos 37.10 |
| 39.x | OS 3.0 | exec 39.46, dos 39.22 |
| 40.x | OS 3.1 | exec 40.70, dos 40.42 |
| 44.x | OS 3.1.4 | exec 44.5 |
| 45.x | OS 3.2 | exec 45.20 |
| 47.x | OS 3.2.2 | exec 47.3 |
### Version Check Pattern
```c
/* Require OS 3.0+ features */
struct Library *base = OpenLibrary("intuition.library", 39);
if (!base)
{
/* Display error using OS 1.x compatible methods */
/* Can't use features that require V39+ */
}
```
---
## Finding a Library Without Opening
```c
/* Read-only peek — no open count increment */
Forbid();
struct Library *lib = (struct Library *)
FindName(&SysBase->LibList, "graphics.library");
if (lib)
{
Printf("Found: %s V%ld.%ld (open: %ld)\n",
lib->lib_Node.ln_Name,
lib->lib_Version,
lib->lib_Revision,
lib->lib_OpenCnt);
}
Permit();
```
> **Caution**: The returned pointer is only valid inside the `Forbid()` section. After `Permit()`, the library could be expunged. If you need to use it, call `OpenLibrary()` instead.
---
## Pitfalls
### 1. Using Library After Close
```c
DOSBase = OpenLibrary("dos.library", 40);
/* ... */
CloseLibrary(DOSBase);
Open("RAM:test", MODE_NEWFILE); /* CRASH — DOSBase is closed */
```
### 2. Not Checking OpenLibrary Return
```c
IntuitionBase = OpenLibrary("intuition.library", 99);
/* IntuitionBase is NULL — V99 doesn't exist */
OpenWindowTags(NULL, ...); /* Guru — calling through NULL base */
```
### 3. Version Mismatch
```c
/* Opened V36 but calling a V39 function */
OpenLibrary("exec.library", 36);
CreatePool(...); /* CreatePool was added in V39 — calling garbage */
```
---
## Best Practices
1. **Always check** the return value of `OpenLibrary()` — NULL means failure
2. **Request the minimum version** you actually need — don't over-specify
3. **Close in reverse order** of opening — prevents dangling references
4. **Set base pointer to NULL** after `CloseLibrary()` — catches use-after-close
5. **Use `OpenLibrary` for runtime version detection** — it's cleaner than checking `lib_Version` manually
6. **Don't use `FindName` as a substitute** for `OpenLibrary` — it doesn't bump the reference count
---
## References
- NDK39: `exec/libraries.h`, `exec/resident.h`
- ADCD 2.1: `OpenLibrary`, `CloseLibrary`, `MakeLibrary`, `RemLibrary`, `FindName`
- See also: [Library Vectors](library_vectors.md) — JMP table and LVO details
- See also: [Resident Modules](resident_modules.md) — how libraries are found in ROM
- See also: [Shared Libraries Runtime](../04_linking_and_libraries/shared_libraries_runtime.md) — full expunge lifecycle
- *Amiga ROM Kernel Reference Manual: Exec* — libraries chapter