Assembly språkskola: utveckling av operativsystem. Monteringsskola: Utveckling av operativsystem Montering och kompilering


Jag säger genast, stäng inte artikeln med tankarna "Fan, ännu en Popov." Han har bara en slickad Ubuntu, och jag har allt från grunden, inklusive kärnan och applikationer. Så, fortsättning under skärningen.

OS-grupp: här.
Först ska jag ge dig en skärmdump.

Det finns inga fler av dem, och nu mer i detalj om varför jag skriver det.

Det var en varm aprilkväll, torsdag. Sedan barndomen drömde jag om att skriva ett OS, när jag plötsligt tänkte: "Nu vet jag fördelarna och asm, varför inte förverkliga min dröm?". Jag googlade sidor om detta ämne och hittade en artikel från Habr: "Hur man börjar och inte slutar skriva OS". Tack till dess författare för OSDev Wiki-länken nedan. Jag gick dit och började jobba. Det fanns i en artikel all data om det lägsta operativsystemet. Jag började bygga cross-gcc och binutils och skrev sedan om allt därifrån. Du borde ha sett min glädje när jag såg inskriptionen "Hej, kärnvärlden!" Jag hoppade direkt från stolen och insåg – jag kommer inte att ge upp. Jag skrev "konsol" (inom citattecken, jag hade inte tillgång till ett tangentbord), men sedan bestämde jag mig för att skriva ett fönstersystem. Som ett resultat fungerade det, men jag hade inte tillgång till tangentbordet. Och så bestämde jag mig för att komma på ett namn baserat på X Window System. Googlade Y Window System - det är det. Som ett resultat kallade han Z Window System 0.1, som ingår i OS365 pre-alpha 0.1. Och ja, ingen såg det förutom jag. Sedan kom jag på hur jag skulle implementera tangentbordsstöd. Skärmdump av den allra första versionen, när det inte fanns något, inte ens ett fönstersystem:

Det flyttade inte ens textmarkören, som du kan se. Sedan skrev jag ett par enkla Z-baserade applikationer. Och här är 1.0.0 alpha-versionen. Det fanns många saker, även systemmenyn. Och filhanteraren och kalkylatorn fungerade helt enkelt inte.

Jag blev direkt terroriserad av en vän som bara bryr sig om snygghet (Mitrofan, förlåt). Han sa "Svättade ner VBE-läget 1024 * 768 * 32, sköljde ner, sköljde ner! Nåväl, de blev fulla! Tja, jag var redan trött på att lyssna på honom och sköljde ändå ner honom. Mer om implementeringen nedan.

Jag gjorde allt med min bootloader, nämligen GRUB "ohm. Med den kan du ställa in grafikläget utan komplikationer genom att lägga till några magiska rader i Multiboot-huvudet.

Ställ ALIGN, 1<<0 .set MEMINFO, 1<<1 .set GRAPH, 1<<2 .set FLAGS, ALIGN | MEMINFO | GRAPH .set MAGIC, 0x1BADB002 .set CHECKSUM, -(MAGIC + FLAGS) .align 4 .long MAGIC .long FLAGS .long CHECKSUM .long 0, 0, 0, 0, 0 .long 0 # 0 = set graphics mode .long 1024, 768, 32 # Width, height, depth
Och så tar jag framebuffer-adressen och skärmupplösningen från Multiboot-informationsstrukturen och skriver pixlar där. VESA gjorde allt väldigt förvirrat - RGB-färger måste anges i omvänd ordning (inte R G B, utan B G R). Jag förstod inte på flera dagar - varför visas inte pixlarna!? Till slut insåg jag att jag glömde att ändra värdena för 16 färgkonstanter från 0...15 till deras RGB-ekvivalenter. Som ett resultat släppte jag den, samtidigt som jag sköljde ner gradientbakgrunden. Sedan gjorde jag en konsol, 2 applikationer och släppte 1.2. Åh ja, jag glömde nästan - du kan ladda ner OS på

assemblerare

assemblerare(från engelskan assemble - to assemble) - en kompilator från assemblerspråk till maskinspråkkommandon.
För varje processorarkitektur och för varje OS- eller OS-familj finns det en Assembler. Det finns också så kallade "cross-assemblers", som gör det möjligt att på maskiner med en arkitektur (eller i miljön för ett OS) sätta ihop program för en annan målarkitektur eller ett annat OS, och få körbar kod i ett format som lämpar sig för exekvering på målarkitekturen eller i målmiljön. OS.

x86-arkitektur

Assemblers för DOS

De mest kända montörerna för DOS-operativsystemet var Borland Turbo Assembler (TASM) och Microsoft Macro Assembler (MASM). Också en gång var den enkla montören A86 populär.
Till en början stödde de bara 16-bitars instruktioner (innan Intel 80386-processorn kom). Senare versioner av TASM och MASM stöder både 32-bitars instruktioner, såväl som alla instruktioner som introduceras i modernare processorer, och arkitekturspecifika instruktionssystem (som till exempel MMX, SSE, 3DNow!, etc.) .

Microsoft Windows

Med tillkomsten av operativsystemet Microsoft Windows dök det upp en TASM-tillägg som heter TASM32, som gjorde det möjligt att skapa program för att köras i Windows-miljön. Den senaste kända versionen av Tasm är 5.3, som stöder MMX-instruktioner, och som för närvarande ingår i Turbo C++ Explorer. Men officiellt stoppas utvecklingen av programmet helt.
Microsoft har en produkt som heter Microsoft Macro Assembler. Det fortsätter att utvecklas till denna dag, med de senaste versionerna inkluderade i DDK:erna. Men versionen av programmet som syftar till att skapa program för DOS utvecklas inte. Dessutom skapade Stephen Hutchesson ett MASM-programmeringspaket som heter "MASM32".

GNU och GNU/Linux

GNU-operativsystemet inkluderar gcc-kompilatorn, som inkluderar gassamlaren (GNU Assembler) som använder AT&T-syntax, till skillnad från de flesta andra populära sammanställare som använder Intel-syntax.

Bärbara montörer

Det finns också ett öppet assembler-projekt, vars versioner finns tillgängliga för olika operativsystem, och som låter dig få objektfiler för dessa system. Denna assembler kallas NASM (Netwide Assembler).
YASM är en omskriven version av NASM licensierad från grunden (med vissa undantag).
FASM (Flat Assembler) är en ung montör under en BSD-licens modifierad för att förbjuda omlicensiering (inklusive under GNU GPL). Det finns versioner för KolibriOS, GNU/Linux, MS-DOS och Microsoft Windows, använder Intel-syntax och stöder AMD64-instruktioner.

RISC-arkitekturer


MCS-51
AVR
Det finns för närvarande 2 Atmel-kompilatorer (AVRStudio 3 och AVRStudio4). Den andra versionen är ett försök att rätta till den inte särskilt framgångsrika första. Det finns även en assembler i WinAVR.
ÄRM
AVR32
MSP430
PowerPC

Montering och sammanställning

Processen att översätta ett assemblerspråksprogram till objektkod kallas assemblering. Till skillnad från kompilering är montering en mer eller mindre entydig och reversibel process. I assemblerspråk motsvarar varje mnemonic en maskininstruktion, medan i programmeringsspråk på hög nivå kan ett stort antal olika instruktioner gömma sig bakom varje uttryck. I princip är denna uppdelning ganska godtycklig, så ibland kallas översättningen av assemblerprogram också för kompilering.

assembleringsspråk

assembleringsspråk- en typ av programmeringsspråk på låg nivå, vilket är ett format för inspelning av maskininstruktioner som är bekvämt för mänsklig uppfattning. Ofta, för korthetens skull, kallas det helt enkelt assembler, vilket inte är sant.

Assembly språkkommandon motsvarar ett till ett processorkommandon och representerar i själva verket en bekväm symbolisk form av notation (mnemonisk kod) av kommandon och deras argument. Monteringsspråket tillhandahåller också grundläggande mjukvaruabstraktioner: länkning av programdelar och data genom etiketter med symboliska namn (vid monteringen beräknas en adress för varje etikett, varefter varje förekomst av etiketten ersätts med denna adress) och direktiv.
Monteringsdirektiv tillåter dig att inkludera datablock (beskrivna explicit eller lästa från en fil) i programmet; upprepa ett visst fragment ett visst antal gånger; kompilera fragmentet enligt tillståndet; ställ in fragmentexekveringsadressen så att den skiljer sig från minnesplatsadressen[specificera!]; ändra etikettvärden under kompilering; använda makrodefinitioner med parametrar osv.
Varje processormodell har i princip sin egen uppsättning instruktioner och motsvarande assemblerspråk (eller dialekt).

Fördelar och nackdelar

Fördelar med assemblerspråk

Den minsta mängden redundant kod, det vill säga användningen av färre instruktioner och minnesåtkomster, gör att du kan öka hastigheten och minska storleken på programmet.
Att säkerställa full kompatibilitet och maximal användning av funktionerna hos den önskade plattformen: användning av speciella instruktioner och tekniska funktioner för denna plattform.
Vid programmering i assembler blir speciella funktioner tillgängliga: direkt tillgång till hårdvara, I/O-portar och speciella processorregister, samt möjligheten att skriva självmodifierande kod (det vill säga metaprogrammering och utan behov av en mjukvarutolk) .
Den senaste säkerhetstekniken implementerad i operativsystem tillåter inte att göra självmodifierande kod, eftersom de utesluter samtidig exekvering av instruktioner och skrivning i samma minnesområde (W^X-teknik i BSD-system, DEP i Windows).

Nackdelar med assemblerspråk

Stora mängder kod och ett stort antal ytterligare små uppgifter, vilket leder till att koden blir mycket svår att läsa och förstå, och därför blir det svårare att felsöka och förfina programmet, samt svårigheten att implementera programmering paradigm och andra konventioner. vilket leder till komplexiteten i samarbetsutveckling.
Färre tillgängliga bibliotek, deras låga kompatibilitet med varandra.
Ej portabel till andra plattformar (andra än binärkompatibla).

Ansökan

Följer direkt av för- och nackdelar.
Eftersom det är extremt obekvämt att skriva stora program på assemblerspråk skrivs de på högnivåspråk. I assembler skriver de små fragment eller moduler som de är kritiska för:
prestanda (förare);
kodstorlek (startsektorer, programvara för mikrokontroller och processorer med begränsade resurser, virus, programvaruskydd);
specialfunktioner: arbeta direkt med hårdvara eller maskinkod, det vill säga operativsystemladdare, drivrutiner, virus, skyddssystem.

Länkar sammansättningskod till andra språk

Eftersom endast fragment av programmet oftast är skrivna i assembler måste de länkas till resten av delarna på andra språk. Detta uppnås på två huvudsakliga sätt:
Vid sammanställningstid- infoga assemblerfragment i programmet (eng. inline assembler) med särskilda språkdirektiv, inklusive skrivprocedurer på assemblerspråk. Metoden är bekväm för enkla datatransformationer, men fullfjädrad assemblerkod, med data och subrutiner, inklusive subrutiner med många ingångar och utgångar som inte stöds av högnivåspråk, kan inte göras med den.
På byggstadiet, eller separat sammanställning. För att länkmoduler ska interagera räcker det att länkningsfunktionerna stöder de erforderliga anropskonventionerna och datatyperna. Separata moduler kan skrivas på alla språk, inklusive assemblerspråk.

Syntax

Det finns ingen allmänt accepterad standard för syntaxen för assemblerspråk. Det finns dock standarder som de flesta assemblerspråkutvecklare följer. De viktigaste sådana standarderna är Intel-syntax och AT&T-syntax.

Instruktioner

Det allmänna formatet för skrivinstruktioner är detsamma för båda standarderna:

[etikett:] opcode [operander] [;kommentar]

där opkoden är direkt minnesmärket för instruktionen till processorn. Prefix kan läggas till (upprepningar, ändringar av adresseringstyp, etc.).
Operanderna kan vara konstanter, registernamn, RAM-adresser etc. Skillnaderna mellan Intel- och AT&T-standarderna hänför sig främst till den ordning i vilken operanderna listas och deras syntax för olika adresseringsmetoder.
Mnemonics som används är vanligtvis desamma för alla processorer med samma arkitektur eller familj av arkitekturer (bland de allmänt kända mnemonics är Motorola, ARM, x86-processorer och kontroller). De beskrivs i processorspecifikationen. Möjliga undantag:
Om assemblern använder AT&T-syntax på flera plattformar (original mnemonics konverteras till AT&T-syntax)
Om det från början fanns två standarder för att skriva minnesminnen (instruktionssystemet ärvdes från en annan tillverkares processor).
Till exempel ärvde Zilog Z80-processorn Intel i8080-instruktionsuppsättningen, utökade den och ändrade mnemonics (och registerbeteckningar) på sitt eget sätt. Till exempel ändrade jag Intels mov till ld. Motorola Fireball-processorer ärvde Z80-instruktionsuppsättningen, vilket minskade det lite. Motorola har dock officiellt återvänt till Intel mnemonics. Och för tillfället arbetar hälften av Fireball-montörerna med Intel-mnemonics och hälften med Zilog-mnemonics.

direktiv

Förutom instruktioner kan programmet innehålla direktiv: kommandon som inte översätts direkt till maskininstruktioner, utan styr kompilatorns funktion. Deras uppsättning och syntax varierar avsevärt och beror inte på hårdvaruplattformen, utan på kompilatorn som används (som ger upphov till dialekter av språk inom samma familj av arkitekturer). Som en "gentleman's set" av direktiv kan vi peka ut:
definiera data (konstanter och variabler)
hantera organisationen av programmet i minnet och parametrarna för utdatafilen
ställa in kompilatorläget
alla typer av abstraktioner (dvs element av högnivåspråk) - från design av procedurer och funktioner (för att förenkla implementeringen av det procedurmässiga programmeringsparadigmet) till villkorliga strukturer och loopar (för det strukturerade programmeringsparadigmet)
makron

Programexempel

Ett exempel på ett Hello world-program för MS-DOS för x86-arkitekturen på TASM-dialekten:

.MODEL LITEN KOD SEGMENT ANSUM CS:CODE, DS:CODE ORG 100h START: mov ah,9 mov dx,OFFSET Msg int 21h int 20h Msg DB "Hello World",13,10,"$" KOD SLUTAR SLUT START

Ursprung och kritik av termen "sammansättningsspråk"

Denna typ av språk har fått sitt namn från namnet på översättaren (kompilatorn) från dessa språk - assembler (engelsk assembler - assembler). Namnet på den senare beror på det faktum att det på de första datorerna inte fanns några språk på högre nivå, och det enda alternativet till att skapa program med hjälp av assembler var programmering direkt i koder.
Monteringsspråk på ryska kallas ofta "assembler" (och något relaterat till det - "assembler"), vilket, enligt den engelska översättningen av ordet, är felaktigt, men passar in i det ryska språkets regler. Men själva assemblern (programmet) kallas också helt enkelt "assembler" och inte "assembler language compiler", etc.
Användningen av termen "sammansättningsspråk" kan också leda till missuppfattningen att det finns ett enda lågnivåspråk, eller åtminstone en standard för sådana språk. När man namnger det språk som ett visst program är skrivet på är det önskvärt att specificera för vilken arkitektur det är avsett och på vilken dialekt av språket det är skrivet.

Original: AsmSchool: Gör ett operativsystem
Författare: Mike Saunders
Publiceringsdatum: 15 april 2016
Översättning: A. Panin
Datum för överföring: 16 april 2016

Del 4: Med de färdigheter du har lärt dig från tidigare artiklar i serien kan du börja utveckla ditt eget operativsystem!

Vad är det för?

  • För att förstå hur kompilatorer fungerar.
  • För att förstå instruktionerna för den centrala bearbetningsenheten.
  • För att optimera din kod vad gäller prestanda.

Under loppet av några månader har vi kommit långt, som började med utvecklingen av enkla assemblerspråksprogram för Linux och slutade i den sista artikeln i serien med utvecklingen av fristående kod som körs på en persondator utan operativsystem. Nåväl, nu ska vi försöka samla all information tillsammans och skapa ett riktigt operativsystem. Ja, vi kommer att gå i Linus Torvalds fotspår, men först är det värt att svara på följande frågor: "Vad är ett operativsystem? Vilka av dess funktioner måste vi återskapa?".

I den här artikeln kommer vi bara att fokusera på operativsystemets huvudfunktioner: ladda och köra program. Komplexa operativsystem utför många fler funktioner, som att hantera virtuellt minne och bearbeta nätverkspaket, men de kräver år av kontinuerligt arbete för att implementeras korrekt, så i den här artikeln kommer vi bara att överväga huvudfunktionerna som finns i alla operativsystem. Förra månaden utvecklade vi ett litet program som passade i en 512-byte sektor på en diskett (dess första sektor), och nu kommer vi att modifiera det lite för att lägga till funktionen att ladda ytterligare data från disken.

Utveckling av bootloader

Vi skulle kunna försöka hålla vårt operativsystem binärt så litet som möjligt för att placera det i den första 512-bytesektorn på disketten, den som laddas av BIOS, men i det här fallet kommer vi inte att kunna implementera några intressanta funktioner. Därför kommer vi att använda dessa 512 byte för att placera den binära koden för en enkel systemladdare, som kommer att ladda den binära koden för OS-kärnan i RAM och exekvera den. (Efter det kommer vi att utveckla själva OS-kärnan, som kommer att ladda binärkoden för andra program från disken och även köra den, men vi kommer att prata om detta lite senare.)

Du kan ladda ner källkoden för exemplen som diskuteras i den här artikeln på www.linuxvoice.com/code/lv015/asmschool.zip . Och det här är koden för vår bootloader från en fil som heter boot.asm:

BITS 16 jmp kort start ; Hoppa till etikett hoppa över diskbeskrivning nop ; Tillägg före diskbeskrivning %inkludera "bpb.asm" start: mov ax, 07C0h ; Ladda adress mov ds, axe ; Datasegment mov ax, 9000h ; Förbered stack mov ss, ax mov sp, 0FFFFh ; Högen växer ner! cld ; Ställ in riktningsflagga mov si, kern_filnamn anrop load_file jmp 2000h:0000h ; Hoppa till OS-kärnan-binär laddad från filen kern_filename db "MYKERNELBIN" %include "disk.asm" gånger 510-($-$$) db 0 ; Noll utfyllnad av binär kod upp till 510 byte dw 0AA55h; Boot loader binär kod slutmarkeringsbuffert: ; Starta buffert för diskinnehåll

I den här koden är den första CPU-instruktionen jmp-instruktionen, som finns efter BITS-direktivet, som talar om för NASM-montören att 16-bitarsläge används. Som du säkert kommer ihåg från den tidigare artikeln i serien börjar exekveringen av den 512-byte binära koden som laddas av BIOS från disken från början, men vi måste hoppa till etiketten för att hoppa över den speciella datamängden. Förra månaden skrev vi uppenbarligen koden till början av disken (med hjälp av dd-verktyget), och lämnade resten av diskutrymmet tomt.

Nu måste vi använda en diskett med ett lämpligt MS-DOS (FAT12) filsystem, och för att fungera korrekt med detta filsystem måste vi lägga till en uppsättning specialdata nära början av sektorn. Denna uppsättning kallas ett "BIOS Parameter Block" (BPB) och innehåller data som disketikett, antal sektorer och så vidare. Det borde inte intressera oss i detta skede, eftersom sådana ämnen kan ägnas åt mer än en serie artiklar, varför vi placerade alla instruktioner och data relaterade till det i en separat källkodsfil som heter bpb.asm .

Baserat på ovanstående är detta direktiv från vår kod extremt viktigt:

%inkludera "bpb.asm"

Detta är ett NASM-direktiv som tillåter innehållet i den angivna källfilen att inkluderas i den aktuella källfilen under montering. Således kommer vi att kunna göra koden för vår systemladdare så kort och begriplig som möjligt genom att flytta alla detaljer om implementeringen av BIOS-parameterblocket till en separat fil. BIOS-parameterblocket måste placeras tre byte efter starten av sektorn, och eftersom jmp-instruktionen bara upptar två byte, måste vi använda nop-instruktionen (dess namn står för "no operation" - det här är en instruktion som inte gör någonting men slösa CPU-cykler ) för att fylla den återstående byten.

Jobbar med stacken

Därefter måste vi använda instruktioner som liknar de som diskuterades i den föregående artikeln för att förbereda registren och stacken, såväl som cld-instruktionen (står för "clear direction"), som låter dig ställa in riktningsflaggan för vissa instruktioner, såsom lodsb-instruktionen, som, när den exekveras, kommer att öka värdet i SI-registret snarare än att minska det.

Efter det lägger vi adressen till strängen i SI-registret och anropar vår load_file-funktion. Men tänk efter ett ögonblick – vi har inte utvecklat den här funktionen än! Ja, det är sant, men dess implementering kan hittas i en annan källkodsfil som vi inkluderar kallad disk.asm .

FAT12-filsystemet, som används på disketter som är formaterade i MS-DOS, är ett av de enklaste filsystemen som finns, men det kräver också en hel del kod för att fungera med dess innehåll. Subrutinen load_file är cirka 200 rader lång och kommer inte att visas i den här artikeln, eftersom vi överväger utvecklingen av ett operativsystem, inte en drivrutin för ett specifikt filsystem, därför är det inte särskilt klokt att slösa utrymme på loggsidor på det här sättet. I allmänhet inkluderade vi disk.asm-källkodsfilen nästan innan slutet av den aktuella källfilen och vi kan glömma det. (Om du fortfarande är intresserad av strukturen för FAT12-filsystemet kan du läsa den utmärkta översikten på http://tinyurl.com/fat12spec , och sedan titta på disk.asm-källkodsfilen - koden som finns i den är bra kommenterat.)

Hur som helst laddar subrutinen load_file den binära koden från filen med det namn som anges i SI-registret till segment 2000 med offset 0, varefter vi hoppar till dess början för exekvering. Och det är allt - kärnan i operativsystemet är laddad och systemladdaren har slutfört sin uppgift!

Du kanske har märkt att vår kod använder MYKERNELBIN istället för MYKERNEL.BIN som operativsystemets kärnfilnamn, vilket passar bra in med namnschemat 8+3 som används på disketter i DOS. Faktum är att FAT12-filsystemet använder den interna representationen av filnamn, och vi sparar utrymme genom att använda ett filnamn som garanterat inte kräver vår load_file-subrutin för att implementera en mekanism för att leta efter punkttecknet och konvertera filnamnet till den interna representationen av filsystem.

Efter raden med direktivet för anslutning av källkodsfilen disk.asm, finns det två rader utformade för att fylla ut den binära koden för systemladdaren med nollor upp till 512 byte och inkludera slutmärket för dess binära kod (detta diskuterades i den sista artikeln). Slutligen, i slutet av koden finns "buffert"-etiketten, som används av subrutinen load_file. I allmänhet behöver subrutinen load_file ledigt utrymme i RAM-minnet för att utföra några mellanliggande åtgärder i processen att hitta en fil på disken, och vi har tillräckligt med ledigt utrymme efter att ha laddat starthanteraren, så vi placerar bufferten här.

För att montera starthanteraren, använd följande kommando:

nasm -f bin -o boot.bin boot.asm

Nu måste vi skapa en MS-DOS virtuell diskettavbildning och lägga till vår bootloader-binär till dess första 512 byte med hjälp av följande kommandon:

Mkdosfs -C floppy.img 1440 dd conv=notrunc if=boot.bin of=floppy.img

Detta avslutar starthanterarens utvecklingsprocess! Vi har nu en startbar diskettavbildning som låter oss ladda operativsystemets kärnbinär från en fil som heter mykernel.bin och köra den. Därefter väntar vi på en mer intressant del av arbetet - utvecklingen av själva operativsystemets kärna.

operativsystemets kärna

Vi vill att vår operativsystemkärna ska utföra många viktiga uppgifter: att visa en hälsning, acceptera input från användaren, avgöra om inmatningen är ett kommando som stöds och att köra program från disken efter att användaren har angett sina namn. Det här är operativsystemets kärnkod från filen mykernel.asm:

Mov axe, 2000h mov ds, ax mov es, ax loop: mov si, prompt call lib_print_string mov si, user_input call lib_input_string cmp byte , 0 je loop cmp word , "ls" je list_files mov ax, si mov cx call, 32768 jc load_fail anrop 32768 jmp loop load_fail: mov si, load_fail_msg anrop lib_print_string jmp loop list_files: mov si, file_list anrop lib_get_file_list anrop lib_print_string jmp loop prompt db 13, 10, "Mitt OS > 1, 0, 3, 0, 0, 0, 0, 1, 1, 1, 1, 2 ", 0 user_input gånger 256 db 0 file_list gånger 1024 db 0 %inkludera "lib.asm"

Innan du tittar på koden, var uppmärksam på den sista raden med direktivet för att inkludera källkodsfilen lib.asm, som också finns i asmschool.zip-arkivet från vår webbplats. Detta är ett bibliotek med användbara subrutiner för att arbeta med skärmen, tangentbordet, linjerna och diskarna som du också kan använda - i det här fallet inkluderar vi denna källkodsfil i slutet av huvudkällkodsfilen för operativsystemets kärna i ordning att göra den senare så kompakt och vacker som möjligt . Se avsnittet "lib.asm biblioteksrutiner" för mer information om alla tillgängliga rutiner.

I de första tre raderna av operativsystemets kärnkod fyller vi segmentregistren med data för att peka på 2000-segmentet där den binära koden laddades in. Detta är viktigt för att säkerställa att instruktioner som lodsb , som ska läsa data från det aktuella segmentet och inte från något annat, fungerar korrekt. Därefter kommer vi inte att utföra några ytterligare operationer på segmenten; vårt operativsystem kommer att köras med 64 KB RAM!

Längre in i koden finns en etikett som motsvarar början av slingan. Först och främst använder vi en av rutinerna från lib.asm-biblioteket, nämligen lib_print_string , för att skriva ut hälsningen. Byte 13 och 10 före hälsningsraden är nyradstecken, på grund av vilka hälsningen inte kommer att visas direkt efter utmatningen av något program, utan alltid på en ny rad.

Efter det använder vi en annan rutin från lib.asm-biblioteket som heter lib_input_string , som tar de tecken som användaren matat in med tangentbordet och lagrar dem i en buffert, vars pekare finns i SI-registret. I vårt fall deklareras bufferten mot slutet av operativsystemets kärnkod enligt följande:

User_input gånger 256 db 0

Denna deklaration tillåter en nollfylld buffert med 256 tecken, som bör vara tillräckligt lång för att hålla kommandon för ett enkelt operativsystem som vårt!

Därefter utför vi validering av användarinmatning. Om den första byten i user_input-bufferten är null, tryckte användaren helt enkelt på Enter utan att ange något kommando; glöm inte att alla strängar slutar med nolltecken. Så i det här fallet ska vi bara hoppa till början av slingan och skriva ut hälsningen igen. Men om användaren anger något kommando måste vi först kontrollera om han skrev in kommandot ls. Hittills har du bara sett jämförelser av enstaka bytes i våra assemblerprogram, men glöm inte att det också är möjligt att jämföra tvåbytevärden eller maskinord. I denna kod jämför vi det första maskinordet från user_input-bufferten med maskinordet som motsvarar raden ls, och om de är identiska går vi till kodblocket nedan. Inom detta kodblock använder vi en annan rutin från lib.asm-biblioteket för att få en kommaseparerad lista med filer på disken (som ska lagras i file_list-bufferten), skriva ut den listan till skärmen och gå tillbaka till processen användarinmatning.

Utförande av tredjepartsprogram

Om användaren inte anger kommandot ls, antar vi att han skrev in programnamnet från disken, så det är vettigt att försöka ladda det. Vårt lib.asm-bibliotek innehåller en implementering av en användbar subrutin lib_load_file , som analyserar tabellerna i FAT12-filsystemet på en disk: det tar en pekare till början av en rad med ett filnamn med hjälp av AX-registret, samt en offsetvärde för att ladda en binär kod från en programfil med hjälp av CX-registret. Vi använder redan SI-registret för att lagra en pekare till användarens inmatningssträng, så vi kopierar den pekaren till AX-registret och sätter sedan värdet 32768, som används som en offset för att ladda den binära koden från programfilen, in i CX-registret.

Men varför använder vi detta värde som en offset för att ladda binär kod från en programfil? Tja, det är bara ett av minneskartalternativen för vårt operativsystem. Eftersom vi arbetar i ett enda 64KB segment och vår kärnbinär laddas med offset 0, måste vi använda de första 32KB minnet för kärndata och de återstående 32KB för laddningsbara programdata. Således är offset 32768 mitten av vårt segment och låter oss tillhandahålla en tillräcklig mängd RAM till både operativsystemets kärna och laddade program.

Efter det utför rutinen lib_load_file en mycket viktig operation: om den inte kan hitta en fil med det angivna namnet på disken, eller av någon anledning inte kan läsa den från disken, avslutas den helt enkelt och ställer in en speciell bärflagga. Detta är en CPU-tillståndsflagga som ställs in under vissa matematiska operationer och bör inte vara av intresse för oss för tillfället, men vi kan fastställa förekomsten av denna flagga för att fatta snabba beslut. Om subrutinen lib_load_asm ställer in bärflaggan använder vi instruktionen jc (hopp om bär) för att hoppa till kodblocket som skriver ut felmeddelandet och återgår till början av användarinmatningsslingan.

I samma fall, om överföringsflaggan inte är inställd, kan vi dra slutsatsen att subrutinen lib_load_asm framgångsrikt laddade den binära koden från programfilen till RAM-minnet på adress 32768. Allt vi behöver i det här fallet är att initiera exekveringen av den binära koden laddas på denna adress , det vill säga börja köra programmet som specificerats av användaren! Och efter att ret-instruktionen har använts i det här programmet (för att återgå till anropskoden), måste vi bara återgå till användarinmatningsslingan. Således har vi skapat ett operativsystem: det består av de enklaste mekanismerna för att tolka kommandon och ladda program, implementerade inom cirka 40 rader assemblerkod, om än med mycket hjälp från subrutiner från biblioteket lib.asm.

För att sätta ihop operativsystemets kärnkod, använd följande kommando:

Nasm -f bin -o mykernel.bin mykernel.asm

Efter det måste vi på något sätt lägga till filen mykernel.bin till diskettavbildningsfilen. Om du är bekant med tricket med att montera skivbilder med loopback-enheter, kan du komma åt innehållet i diskavbildningen floppy.img med den, men det finns ett enklare sätt att använda GNU Mtools (www.gnu.org/software /mtools ). Detta är en uppsättning diskettprogram som använder MS-DOS/FAT12-filsystem, tillgängliga från programvarupaketförråden för alla populära Linux-distributioner, så du behöver bara använda apt-get , yum , pacman eller något annat verktyg som brukade installera mjukvarupaket på din distribution.

Efter att ha installerat lämpligt programpaket, för att lägga till filen mykernel.bin till diskavbildningsfilen floppy.img, måste du köra följande kommando:

Mcopy -i floppy.img mykernel.bin::/

Lägg märke till de roliga karaktärerna i slutet av kommandot: kolon, kolon och snedstreck. Nu är vi nästan redo att lansera vårt operativsystem, men vad är poängen med det tills det finns applikationer för det? Låt oss rätta till detta missförstånd genom att utveckla en extremt enkel applikation. Ja, nu kommer du att utveckla en applikation för ditt eget operativsystem - tänk dig bara hur mycket din auktoritet kommer att stiga i nördarna. Spara följande kod i en fil som heter test.asm:

Org 32768 mov ah, 0Eh mov al, "X" int 10h ret

Denna kod använder helt enkelt BIOS-funktionen för att visa tecknet "X" på skärmen, varefter den återställer kontrollen till koden som kallade den - i vårt fall är denna kod koden för operativsystemet. Organisationsraden som startar applikationens källkod är inte en CPU-instruktion, utan ett NASM assembler-direktiv som säger att den binära koden kommer att laddas in i RAM-minnet vid offset 32768, därför är det nödvändigt att räkna om alla offsets med hänsyn till denna omständighet .

Den här koden måste också monteras, och den resulterande binära filen måste läggas till i diskettavbildsfilen:

Nasm -f bin -o test.bin test.asm mcopy -i floppy.img test.bin::/

Ta nu ett djupt andetag, gör dig redo att bevittna de oöverträffade resultaten av ditt eget arbete och starta upp diskettavbildningen med en PC-emulator som Qemu eller VirtualBox. Till exempel kan följande kommando användas för detta ändamål:

Qemu-system-i386 -fda floppy.img

Voila: boot.img-starthanteraren som vi integrerade i den första sektorn av diskavbildningen laddar operativsystemkärnan mykernel.bin, som visar en hälsning. Skriv kommandot ls för att få namnen på de två filerna på disken (mykernel.bin och test.bin), skriv sedan namnet på den sista filen som ska köras och visa X-tecknet på skärmen.

Det är coolt, eller hur? Nu kan du börja anpassa ditt operativsystems skal, lägga till implementeringar av nya kommandon och lägga till ytterligare programfiler på disken. Om du vill köra det här operativsystemet på en riktig PC bör du hänvisa till avsnittet "Köra starthanteraren på en riktig hårdvaruplattform" från föregående artikel i serien - du behöver exakt samma kommandon. Nästa månad kommer vi att göra vårt operativsystem kraftfullare genom att tillåta nedladdningsbara program att använda systemfunktioner, och på så sätt implementera konceptet med kodseparation för att minska kodduplicering. Mycket av arbetet ligger fortfarande framför oss.

lib.asm biblioteksrutiner

Som nämnts tidigare, tillhandahåller biblioteket lib.asm en stor uppsättning användbara subrutiner för användning inom dina operativsystemkärnor och individuella program. Vissa av dem använder instruktioner och begrepp som ännu inte har behandlats i artiklarna i denna serie, andra (som rutiner för att arbeta med diskar) är nära besläktade med strukturen i filsystemen, men om du anser dig vara kompetent i dessa frågor, du kan bekanta dig med deras implementeringar och förstå principen för arbetet. Det är dock viktigare att förstå hur man ringer dem från din egen kod:

  • lib_print_string - Tar en pekare till en nollterminerad sträng via SI-registret och skriver ut den strängen till skärmen.
  • lib_input_string - tar en pekare till en buffert via SI-registret och fyller denna buffert med tecken som angetts av användaren med tangentbordet. Efter att användaren tryckt på Enter-tangenten nolltermineras strängen i bufferten och kontrollen återgår till det anropande programmets kod.
  • lib_move_cursor - Flyttar markören på skärmen till positionen med koordinaterna passerade genom DH (radnummer) och DL (kolumnnummer) registren.
  • lib_get_cursor_pos - anrop den här subrutinen för att få aktuell rad- och kolumnnummer med hjälp av DH- respektive DL-registren.
  • lib_string_uppercase - Tar en pekare till början av en noll-terminerad sträng med hjälp av AX-registret och konverterar tecknen i strängen till versaler.
  • lib_string_length - Tar en pekare till början av en nollterminerad sträng med hjälp av AX-registret och returnerar dess längd med hjälp av AX-registret.
  • lib_string_compare - Tar pekare till början av två nollterminerade strängar via SI- och DI-registren och jämför dessa strängar. Ställer in bärflaggan om strängarna är identiska (för att använda en hoppinstruktion beroende på jc carry-flaggan) eller rensa denna flagga om strängarna skiljer sig åt (för att använda jnc-instruktionen).
  • lib_get_file_list - Tar en pekare till början av en buffert via SI-registret och lägger en nollterminerad sträng som innehåller en kommaseparerad lista med filnamn från disken i den bufferten.
  • lib_load_file - Tar en pekare till början av en sträng som innehåller ett filnamn med hjälp av AX-registret och laddar filens innehåll med den offset som ges av CX-registret. Returnerar antalet byte som kopierats till minnet (det vill säga storleken på filen) med hjälp av BX-registret, eller ställer in bärflaggan om ingen fil med det angivna namnet hittas.

Idag, i vår Kunstkamera, är ett märkligt exempel ett operativsystem skrivet i ren assembler. Tillsammans med drivrutiner, ett grafiskt skal, dussintals förinstallerade program och spel tar det mindre än en och en halv megabyte. Möt den exceptionellt snabba och övervägande ryska OS Hummingbird.

Utvecklingen av Hummingbird gick ganska snabbt fram till 2009. Fågeln lärde sig att flyga på olika hårdvara, vilket krävde minst den första Pentium och åtta megabyte RAM. Minsta systemkrav för Hummingbird är:

  • CPU: Pentium, AMD 5x86 eller Cyrix 5x86 utan MMX vid 100 MHz;
  • RAM: 8 MB;
  • Grafikkort: VESA-kompatibelt med stöd för VGA-läge (640 × 480 × 16).

Den moderna Hummingbird är en regelbundet uppdaterad "nightly build" av den senaste officiella versionen, släppt i slutet av 2009. Vi testade build 0.7.7.0+ daterad 20 augusti 2017.

VARNING

I standardinställningarna har KolibriOS inte tillgång till diskar som är synliga via BIOS. Tänk noga och gör en säkerhetskopia innan du ändrar den här inställningen.

Även om förändringarna i nattbyggen är små, har de ackumulerats tillräckligt med åren. Den uppdaterade Hummingbird kan skriva till FAT16-32 / ext2 - ext4-partitioner och stöder andra populära filsystem (NTFS, XFS, ISO-9660) i läsläge. Den lade till stöd för USB och nätverkskort, en TCP / IP-stack och ljudcodecs lades till. I allmänhet kan du redan göra något i det, och inte bara titta en gång på ett ultralätt operativsystem med ett GUI och bli imponerad av starthastigheten.



Liksom tidigare versioner är den senaste Hummingbird skriven i flat assembler (FASM) och upptar en diskett - 1,44 MB. Tack vare detta kan den helt placeras i något specialiserat minne. Till exempel skrev hantverkare KolibriOS direkt in i Flash BIOS. Under drift kan den vara helt placerad i cachen hos vissa processorer. Föreställ dig bara: hela operativsystemet, tillsammans med program och drivrutiner, är cachelagrat!

INFO

När du besöker sajten kolibrios.org kan webbläsaren varna för fara. Anledningen är tydligen assemblerprogrammen i distributionen. Nu definierar VirusTotal sajten som helt säker.

"Hummingbird" laddas enkelt från en diskett, hårddisk, flashenhet, Live CD eller i en virtuell maskin. För emulering räcker det att ange OS-typen "annan", allokera en processorkärna och lite RAM till den. Det är inte nödvändigt att ansluta en disk, och om det finns en router med DHCP kommer Hummingbird omedelbart att ansluta till Internet och det lokala nätverket. Direkt efter nedladdning kommer du att se ett meddelande.


Ett problem - HTTPS-protokollet stöds inte av den inbyggda Hummingbird-webbläsaren. Därför var det inte möjligt att titta på webbplatsen i den, samt öppna sidorna på Google, Yandex, Wikipedia, Sberbank ... faktiskt, ingen vanlig adress. Alla gick över till ett säkert protokoll för länge sedan. Den enda sidan med gammaldags ren HTTP som jag stötte på var "Rysslands regerings portal", men den såg inte bäst ut i en textwebbläsare.



Utseendeinställningarna i Hummingbird har förbättrats under åren, men är fortfarande långt ifrån idealiska. En lista över videolägen som stöds visas på Hummingbirds startskärm när du trycker på bokstaven a.



Listan över tillgängliga alternativ är liten och den önskade upplösningen kanske inte finns i den. Om du har ett grafikkort med en AMD (ATI) GPU kan du direkt lägga till anpassade inställningar. För att göra detta måste du skicka parametern -m till ATIKMS bootloader x x , Till exempel:

/RD/1/DRIVERS/ATIKMS -m1280x800x60 -1

Här är /RD/1/DRIVERS/ATIKMS sökvägen till starthanteraren (RD - RAM-disk).

När systemet körs kan det valda videoläget ses med kommandot vmode och (teoretiskt) växlas manuellt. Om Hummingbird körs i en virtuell maskin kommer detta fönster att förbli tomt, men med en ren uppstart kan Intel-videodrivrutiner läggas till från i915 till Skylake inklusive.

Överraskande nog passar ett gäng spel i KolibriOS. Bland dem finns logiska spel och arkadspel, taggar, en orm, tankar (nej, inte WoT) - ett helt "Game Center"! Till och med Doom och Quake portades till Hummingbird.



En annan viktig sak var FB2READ-läsaren. Det fungerar korrekt med kyrilliska och har textvisningsinställningar.



Jag rekommenderar att du lagrar alla användarfiler på ett USB-minne, men det måste anslutas via en USB 2.0-port. Vår USB 3.0-flashenhet (i en USB 2.0-port) med en kapacitet på 16 GB med NTFS-filsystemet bestämdes omedelbart. Om du behöver skriva filer bör du ansluta ett USB-minne med en FAT32-partition.



Hummingbird-distributionen inkluderar tre filhanterare, verktyg för att visa bilder och dokument, ljud- och videospelare och andra användarapplikationer. Fokus ligger dock på assemblerspråksutveckling.



Den inbyggda textredigeraren har ASM-syntaxmarkering och låter dig till och med köra maskinskrivna program direkt.



Bland utvecklingsverktygen finns Oberon-07/11-kompilatorn för i386 Windows, Linux och KolibriOS, samt lågnivåemulatorer: E80 - ZX Spectrum-emulator, FCE Ultra - en av de bästa NES-emulatorerna, DOSBox v.0.74 och andra. Alla av dem var speciellt portade till Hummingbird.

Om du lämnar KolibriOS i några minuter kommer skärmsläckaren att starta. Kodrader kommer att köras på skärmen, där du kan se en referens till MenuetOS.

Fortsatt tillgänglig endast för medlemmar

Alternativ 1. Gå med i "site"-gemenskapen för att läsa allt material på sajten

Medlemskap i communityn under den angivna perioden ger dig tillgång till ALLT hackermaterial, ökar din personliga kumulativa rabatt och låter dig samla ett professionellt Xakep Score-betyg!

Nyligen bestämde jag mig för att lära mig assembler, men jag var inte intresserad av att slösa bort rader med kod. Jag tänkte att när jag studerar assembler kommer jag att behärska något ämnesområde. Så mitt val föll på att skriva en bootloader. Resultatet av mina fynd finns här i den här bloggen.

Jag vill genast säga att jag älskar teori i kombination med praktik, så låt oss börja.

Först ska jag visa dig hur du skapar en enkel MBR så att vi kan njuta av resultatet så snart som möjligt. När vi blir mer komplexa med praktiska exempel kommer jag att ge teoretisk information.

Låt oss göra en USB-flashenhet bootloader först!

Uppmärksamhet!!! Vårt första assemblerprogram kommer att fungera både för en flashenhet och för andra enheter som en diskett eller en hårddisk. Därefter, för att alla exempel ska fungera korrekt, kommer jag att ge ett antal förtydliganden om hur koden fungerar på olika enheter.

Vi kommer att skriva till Fasm, eftersom det anses vara den bästa kompilatorn för att skriva loaders, vilket är MBR. Det andra skälet till att välja Fasm är att det avsevärt förenklar processen att kompilera filer. Inga kommandoradsdirektiv etc. nonsens som helt kan avskräcka lärande assembler och uppnå dina mål. Så i det inledande skedet behöver vi två program och några onödig liten flash-enhet. Jag grävde upp 1 Gb (snabbt formaterat, och det är inte synd om något). Efter arbetet med vår bootloader kommer flashenheten att sluta fungera normalt. min Windows 7 vägrar att formatera flashenheten. Jag råder dig att återställa flash-enheten till liv med ett verktyg HP USB Disk Storage Format Tool ( HPUSBFW.EXE) eller andra verktyg för att formatera flash-enheter.

Installera dem och placera lämpliga genvägar på skrivbordet eller var du vill.

Förberedelserna är över, låt oss gå vidare till handling

Öppna Fasmw.exe och skriv följande där. Vi kommer att skissa upp det absoluta minimum av kod för att se resultatet. Senare kommer vi att analysera vad som är nakalyakano här. Jag kommer kort kommentera.

FASM-kod: ============= boot.asm ================

org 7C00h ; adresserna till vårt program beräknas enligt detta direktiv

användning16 ; hexadecimal kod genereras

cli ; inaktivera avbrott för att ändra adresser i segmentregister

mov yxa, 0

mov sp, 7C00h

sti ; aktivera avbrott (efter adressändring)

mov axe, 0003h ; ställ in videoläge för att visa sträng på skärmen

int 10h

mov axe, 1301h ; faktisk strängutgångsfunktion 13h int 10h (mer om det senare)

mov bp, stroka ;adress till utdatasträngen

mov dx, 0000h ;rad och kolumn där text visas

mov cx, 15 ;antal tecken i utdatasträngen

mov bx, 000eh ;00-videos sidnummer (bättre att inte röra) 0e-teckenattribut (färg, bakgrund)

int 10h

jmp $ ;skaft på plats (slingrar programmet vid denna punkt)

line db "Ok, MBR laddad!"

gånger 510 - ($ - $$) db 0 ;fyllning av gapet mellan föregående byte och den sista med nollor

db 0x55 ,0xAA ;de två sista byten

Kompilera den här koden (Ctrl + F9) till fasm "e och spara den resulterande binära filen som boot.bin på någon lämplig plats. Innan du skriver vår binära fil till ett USB-minne, lite teori.

När du anslutit USB-minnet till datorn är det absolut inte självklart för BIOS-systemet att du vill starta från USB-minnet, så i BIOS-inställningarna måste du välja vilken enhet du vill starta från. vi valde att boota från USB (du måste ta reda på hur du gör detta själv, eftersom BIOS-gränssnittet har olika varianter... du kan googla BIOS-inställningarna för ditt moderkort, det är inget komplicerat, som regel).

Nu när BIOS vet att du vill starta från flashenheten måste den se till att nollsektorn på flashenheten är startbar. För att göra detta ser BIOS ut sista två byten av nollsektorn och, om de är lika med 0x55 0xAA, kommer det bara att laddas in i RAM. Annars kommer BIOS helt enkelt att passera din flashenhet. Efter att ha hittat dessa två magiska bytes, laddar den nollsektorn i RAM-minnet på adressen 0000: 7C00h, och glömmer sedan flash-enheten och överför kontrollen till denna adress. Nu tillhör all makt över datorn din bootloader, och den, som redan fungerar från RAM, kan ladda ytterligare kod från USB-minnet. Nu ska vi se hur just denna sektor ser ut i DMDE-programmet.

1. Sätt i din flash-enhet i datorn och se till att den inte innehåller den information du behöver.

2.Öppna DMDE-programmet. Läs alla ytterligare åtgärder i bilderna:

Efter att ha sett denna serie har du förmågan att ladda ner din MBR till en flashenhet. Och så här ser det efterlängtade resultatet av vår bootloader ut:


Förresten, om vi pratar om minimilastarkoden, kan det se ut så här:

Org 7C00h
jmp$
db 508 dup(0)
db 0x55.0xAA

En sådan laddare, efter att ha fått kontroll, lägger helt enkelt på datorn och utför ett meningslöst kommando jmp $ i en cykel. Jag kallar henne stagnation.

Jag har lagt upp en video på YouTube som kan hjälpa dig:

Till sist, några korta fakta om bootloaderns arbete:

1. Bootloader, aka bootloader, aka MBR har en storlek på 512 byte. Historiskt sett,
att detta villkor måste vara uppfyllt för att stödja äldre media och enheter.
2. Starthanteraren är alltid placerad i nollsektorn av en flashenhet, diskett, hårddisk, från DMDE-programmets eller andra hex-redigerares synvinkel som låter dig arbeta med enheter. För att ladda en binär (vår boot.bin) på en av de listade enheterna behöver vi inte tänka på deras interna fysiska struktur. DMDE-programmet vet bara hur man läser sektorer på dessa enheter och visar dem i LBA-läge (numrerar dem helt enkelt från 0 till den sista sektorn). Du kan läsa om LBA
3. Starthanteraren måste alltid sluta med två byte 0x55 0xAA.
4. Laddaren laddas alltid in i minnet vid 0000:7C00h.
5. Starthanteraren startar operativsystemet.








2022 gtavrl.ru.