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

9.5 KiB
Raw Blame History

← Home · Exec Kernel

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

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:

/* 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

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

CloseLibrary((struct Library *)DOSBase);
DOSBase = NULL;   /* Good practice — prevent use-after-close */

What CloseLibrary Does

  1. Call library's Close() vectorlib_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

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

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

/* 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

/* 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

DOSBase = OpenLibrary("dos.library", 40);
/* ... */
CloseLibrary(DOSBase);
Open("RAM:test", MODE_NEWFILE);  /* CRASH — DOSBase is closed */

2. Not Checking OpenLibrary Return

IntuitionBase = OpenLibrary("intuition.library", 99);
/* IntuitionBase is NULL — V99 doesn't exist */
OpenWindowTags(NULL, ...);  /* Guru — calling through NULL base */

3. Version Mismatch

/* 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 — JMP table and LVO details
  • See also: Resident Modules — how libraries are found in ROM
  • See also: Shared Libraries Runtime — full expunge lifecycle
  • Amiga ROM Kernel Reference Manual: Exec — libraries chapter