Alweer geen Excel

Geheugen en segmenten in real mode

Arnout van Kempen schrijft in deze rubriek over pret maken met computers. Hij gaat aan de slag met Pascal.

Toen de IBM PC op de markt kwam, had je big iron en thuiscomputers/kleine kantoorcomputers, met vrijwel niets daar tussenin. In de thuiscomputers was de 8-bits CPU dominant, met adresruimtes van 64K. Dus toen IBM en Microsoft met MS-DOS en de PC kwamen, was de gedachte dat 640K aanzienlijk veel meer was dan iemand ooit nodig zou hebben. Want wat doen mensen nu helemaal met een computer? Tekstverwerken, spreadsheets, spelletjes. Vooral spelletjes hadden een enorme geheugenhonger en in de 8-bits wereld was men net begonnen om door slimmigheden naar 128K geheugen toe te werken. De enige 16-bits thuiscomputer die op dat moment op de markt was, was de Ti-99/4 en dat was in alle eerlijkheid vooral een bizar, en bizar slecht, design dat minder presteerde dan de meeste 8-bitters uit die tijd.

Hoe dan ook, de eerste PC's werden geleverd met 16K, maximaal uit te breiden tot 256K, wat indertijd dus best gigantisch veel was. Overigens ook zonder harde schijf en met een aansluiting voor cassetterecorders, om data op een muziekcassette te kunnen zetten.

De grens van 640K was dus heel ruim en echt op de toekomst gericht. De IBM PC kon geheugen tot 1M aan, de bijna 400K die niet beschikbaar was als geheugen voor MS-DOS reserveerde IBM voor videogeheugen, uitbreidingskaarten en BIOS.
De CPU van die eerste PC en van vrijwel alle computers die sindsdien als PC, thuiscomputer of kantoorcomputer werden verkocht in de wereld, was gebaseerd op de 80x86 architectuur van Intel. Een CPU die Intel maakte als opvolger van de 8080, die bedoeld was om de markt van de Z80 te heroveren, maar die absoluut niet bedoeld was voor een lang leven. Intel wilde met iets totaal nieuws komen. IBM en Microsoft zorgden echter voor iets anders.

De 8086 moest een paar zaken organiseren. Ten eerste moesten 8080-programma's zo simpel mogelijk kunnen worden overgezet naar een 8086. Dat betekende source compatibility, zodat de vele beschikbare CP/M programma's eenvoudig zouden werken op de 8086, en 16-bits adressering, terwijl tegelijk de geheugenruimte werd vergroot naar 20-bits adressering. Ten tweede moest de chip zelf hetzelfde fysieke formaat krijgen als de Z80 en de 8080, zodat computerfabrikanten de chip makkelijk konden gebruiken. Het aantal pinnen van de chip diende dus gelijk te blijven. Dit laatste werd bereikt door multiplexing, waarbij een pin verschillende functies kan vervullen afhankelijk van de context.

Maar hoe zat dat precies met die 16-bits adresregisters en 20-bits adresbus? Je kon dus niet in één register een volledig adres kwijt.
De oplossing was het gebruik van segmenten en segmentregisters. Een segmentregister (CS, DS, SS, ES) wordt met 16 vermenigvuldigd en de offset wordt daarbij opgeteld. Het resultaat is een fysiek 20-bits adres.

Voorbeeld:
1234h:0010h = (0x1234 × 16) + 0x0010 = 0x12350.
1235h:0000h = (0x1235 × 16) + 0x0000 = 0x12350.

Twee verschillende segment:offset-paren wijzen dus naar hetzelfde fysieke adres. Segmenten overlappen elkaar.

De 8086 kent vier segmentregisters:

  • CS (Code Segment) bevat het segment waarin de instructies staan. Samen met IP (Instruction Pointer) bepaalt dit waar de CPU de volgende instructie haalt.
  • DS (Data Segment) is het standaardsegment voor data en wordt gecombineerd met offsetregisters zoals BX, SI, DI.
  • SS (Stack Segment) bevat het segment voor de stack. Samen met SP (Stack Pointer) bepaalt dit waar de volgende push/pop gebeurt.
  • ES (Extra Segment) is een extra segment, vooral gebruikt bij stringoperaties (MOVS, LODS, STOS) en bij blokverplaatsingen.

Daarnaast zijn er nog de extra offset- en indexregisters: BP (Base Pointer), SI (Source Index), DI (Destination Index). Samen met CS/DS/SS/ES vormen ze het mechaniek waarmee de CPU elk geheugenadres berekent.
Een segment is dus per definitie maximaal 64 KB groot, aangezien de offsetregisters 16-bit groot zijn. Daarmee kreeg de 8086 in theorie 1 MB adresruimte, maar in de praktijk werd je wel begrensd door die 64K-hokjes. En daar komen de memory models van Turbo Pascal (en C, en TASM) vandaan.

Om te werken met segmenten moet je bij het compileren al kiezen hoe je code en data verdeeld worden. Turbo Pascal biedt een aantal memory models:

  • Tiny model: alles (code én data) in één segment. Je hele programma moet dus <64 KB blijven. Het levert .COM-bestanden op.
  • Small model: code in één segment, data in één segment. Programma kan groeien tot 64 KB code + 64 KB data. Meest gebruikt voor gewone DOS-programma’s (.EXE).
  • Medium model: data in één segment, maar code mag meerdere segmenten gebruiken (procedures kunnen "far" worden).
  • Compact model: code in één segment, data mag meerdere segmenten gebruiken (arrays en records kunnen "far" worden).
  • Large/Huge model: zowel code als data mogen meerdere segmenten gebruiken. Bij huge arrays wordt pointer-arithmetiek aangepast om over segmentgrenzen heen te kunnen.

De 8086 kan de volle 1 MB adresseren, de 640K-grens ontstond door de memory map die IBM koos:
00000h–9FFFFh (0–640K) → conventioneel geheugen: DOS en programma's.
A0000h–BFFFFh (640–768K) → video RAM (CGA/EGA/VGA).
C0000h–DFFFFh (768–896K) → ROM-ruimte voor uitbreidingskaarten (videokaart-BIOS, SCSI-controllers).
E0000h–FFFFFh (896–1024K) → BIOS ROM en ROM-data.

Later kwamen er kunstgrepen: Expanded Memory (EMS), waarbij extra RAM in stukjes van 64K in dat 640K–1MB-blok wordt gepaged, en Extended Memory (XMS) boven de 1 MB, bereikbaar via drivers als HIMEM.SYS.

MS-DOS kende geen uitgebreid systeem van rechten per file, maar werkte met vrij simpele attribuut-vlaggen (read only, hidden, system en archived) en een drie-letterige bestandsnaam-extensie. Uitvoerbare programma's waren herkenbaar aan de extensies .COM en .EXE.

Waarom dit verschil?
.COM-bestanden zijn vooral simpel en lijken het meeste op CP/M programma's:

  • Ze worden altijd geladen op segmentadres CS:0100h. Dat geeft ruimte voor het Program Segment Prefix dat MS-DOS voor ieder programma vult met 256 bytes aan informatie die nodig is voor wat administratie.
  • Code, data en stack delen één segment.
  • Omdat alle adressen “dichtbij” zijn, kan de compiler alles afhandelen met near jumps en near pointers (16-bit offset binnen hetzelfde segment).
  • Relocatie is overbodig: het maakt niet uit waar het segment precies in het geheugen staat, zolang het programma maar in één blok past.
  • Je programmeert feitelijk alsof je in een 8080 met 64K zit: één vlak geheugen, alles dicht bij elkaar.

.EXE-bestanden zijn complexer:

  • Ze beginnen met een header waarin staat welke segmenten er zijn (code, data, stack).
  • Bij het laden kiest DOS een vrij blok geheugen en zet de segmenten daar neer.
  • Omdat code en data vaak in verschillende segmenten staan, zijn far jumps en far pointers nodig.
  • DOS gebruikt de relocation table in de header om segmentreferenties te corrigeren: telkens als je code een segmentadres nodig heeft, staat dat in de relocation table en past DOS het aan met de juiste waarde.
  • Daardoor kan een .EXE “relocatable” zijn: het maakt niet uit waar in geheugen het geladen wordt, zolang de relocations maar goed verwerkt worden.

DOS is single-user en single-tasking, zoals thuiscomputers in die tijd waren. De noodzaak programma's relocatable te maken kenden de Apple II, TRS-80 of Commodore 64 niet. MS-DOS is daarin anders, omdat niet vooraf vast stond hoeveel geheugen het systeem zelf nodig heeft. Bij het opstarten neemt DOS zelf een deel van het conventionele geheugen in voor kernel en drivers. TSR’s (Terminate and Stay Resident programs) en device drivers vullen ook gaten in het geheugen, daardoor is het moment waarop je een programma start bepalend voor waar in het geheugen nog ruimte vrij is.

Voor de stap naar multi-tasking en multi-user, indertijd ook aangeduid als time sharing systemen, was relocation alleen niet genoeg. Een Memory Management Unit (MMU) is dan nodig.

In de 80x86 architectuur kwam de eerste MMU met de 80286, en een betere met de 80386. Deze werken in real mode, als een snelle 8086, of in protected mode:

  • Segmentregisters verwijzen naar descriptors in een descriptor table. Die beschrijven basisadres, lengte en toegangsrechten.
  • Segmenten hebben niet meer een grens van 64 KB.
  • Processen kunnen van elkaar geïsoleerd worden:
  • Met de 386 kwam er paging, zodat je een flat adresruimte van 4 GB kan aanbieden.

Dit was de sleutel tot moderne besturingssystemen zoals UNIX, OS/2, Windows NT en later Linux. Elk proces kreeg zijn eigen virtuele geheugenruimte; een gecrasht proces trok niet meer het hele systeem mee.

De moderne programmeur heeft met dit soort details niets meer te maken, tenzij het om een systeemprogrammeur gaat. Wie in Python programmeert, hoeft al helemaal niets meer te weten over geheugen, maar ook een moderne C programmeur kan prima uit de voeten door het echte geheugenbeheer compleet te negeren; zelfs al denkt hij dat hij dicht op het ijzer zit. Er zit altijd een besturingssysteem tussen.

Waarom ik zo dol ben op MS-DOS is nu juist dat het besturingssysteem je wel helpt, maar niet tussen jou en het ijzer in gaat staan. Het betekent wel dat je ook in hogere programmeertalen als Pascal maar beter kan snappen hoe dat ijzer werkt.

Arnout van Kempen is naast computernerd ook directeur compliance & risk bij aaff. Hij schrijft op persoonlijke titel.

Gerelateerd

reacties

Reageer op dit artikel

Spelregels debat

    Aanmelden nieuwsbrief

    Ontvang elke werkdag (maandag t/m vrijdag) de laatste nieuwsberichten, opinies en artikelen in uw mailbox.

    Bent u NBA-lid? Dan kunt u zich ook aanmelden via uw ledenprofiel op MijnNBA.nl.