Monteringsskola: utveckling av operativsystem. Kolibriens flygning. Vad ett operativsystem helt och hållet skrivet i assemblerspråk är kapabelt till. Montering och kompilering

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

Jag skulle genast vilja 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 först göra en bootloader för ett USB-minne!

Uppmärksamhet!!! Vårt första assemblerprogram kommer att fungera både för en flash-enhet och för andra enheter som en diskett eller HDD. 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 vidare 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 gör det mycket enkelt att kompilera filer. Inga direktiv kommandorad och så vidare. nonsens som helt kan avskräcka dig från att lära dig assembler och uppnå dina mål. Så i det inledande skedet kommer vi att behöva två program och några onödig Minsta storlek flash-enhet. Jag grävde upp 1 Gb (det formateras snabbt, och det är inte synd om något). Efter att vår bootloader fungerar kommer flashenheten inte längre att fungera normalt. Min Windows 7 vägrar att formatera flashenheten. Jag rekommenderar att du använder ett verktyg för att återuppliva flashenheten HP USB-disk Storage Format Tool ( HPUSBFW.EXE) eller andra verktyg för att formatera flash-enheter.

Vi installerar dem och slänger motsvarande genvägar på skrivbordet eller var du vill.

Förberedelserna är klara, 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 klottrat här. Jag kommer att ge mina kommentarer kort.

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

org 7C00h ; Våra programadresser beräknas med hänsyn till detta direktiv

användning16; hexadecimal kod genereras

cli ;inaktivera avbrott för ändring av adresser i segmentregister

mov yxa, 0

mov sp, 7C00h

sti ;aktivera avbrott (efter adressändring)

mov ax, 0003h ;ställ in videoläget för att visa en linje på skärmen

int 10h

mov axe, 1301h ;den faktiska utmatningen av strängen är funktionen 13h int 10h (mer information senare)

mov bp, stroka ;adress för utgångssträngen

mov dx, 0000h ;rad och kolumn där texten 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 $ ;trampa vatten (slingrar programmet vid denna punkt)

string db "Ok, MBR laddad!"

gånger 510 - ($ - $$) db 0 ;fylla utrymmet mellan föregående byte och nästa med nollor

db 0x55 ,0xAA ;sista två byte

Kompilera den här koden (Ctrl+F9) i 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 en flashenhet, lite teori.

När du kopplar in en flashenhet i datorn är det absolut inte självklart för BIOS-systemet att du vill starta från flashenheten, så i BIOS-inställningarna måste du välja vilken enhet du vill starta upp från. Så vi valde att starta 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 det BIOS-inställningar för din moderkort. Det är inget komplicerat där, som regel).

Nu när BIOS vet att du vill starta från en flashenhet måste den se till att sektor noll på flashenheten är startbar. För att göra detta skannar BIOS sista två byten av sektor noll och, om de är lika med 0x55 0xAA, kommer det att laddas in först då Bagge. Annars kommer BIOS helt enkelt att kringgå din flashenhet. Efter att ha hittat dessa två magiska bytes, laddar den sektor noll i RAM-minnet på adressen 0000:7С00h, och glömmer sedan flash-enheten och överför kontrollen till denna adress. Nu tillhör all makt över datorn din bootloader och den, agerande från RAM, kan ladda ytterligare kod från en flashenhet. 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 steg i bilderna:

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


Förresten, om vi pratar om den minsta bootloader-koden, kan det se ut så här:

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

En sådan bootloader, efter att ha fått kontroll, hänger helt enkelt datorn och kör ett meningslöst jmp $-kommando i en loop. Jag kallar henne för att trampa vatten.

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

Till sist, några korta fakta om bootloadern:

1. Starthanteraren, även känd som bootloader, även känd som MBR, har en storlek på 512 byte. Historiskt sett,
att detta villkor måste uppfyllas för att stödja äldre media och enheter.
2. Starthanteraren är alltid placerad i nollsektorn på 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 helt enkelt hur man läser sektorerna 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. Bootloadern laddas alltid in i minnet på adressen 0000:7С00h.
5. Operativsystemet börjar med starthanteraren.


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

Del 4: Med de färdigheter du har fått genom att läsa de tidigare artiklarna i den här 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å CPU-instruktioner.
  • För att optimera din kod för prestanda.

Under flera månader gick vi igenom en svår väg som började med utvecklingen enkla program i assemblerspråk för Linux och avslutades 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 följa i Linus Torvalds fotspår, men först måste vi 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 grundläggande funktioner: ladda och köra program. Komplexa operativsystem utför många fler funktioner, som hantering och bearbetning av virtuellt minne nätverkspaket, men deras korrekta implementering kräver år av kontinuerligt arbete, så i den här artikeln kommer vi bara att överväga de grundläggande funktionerna som finns i alla operativsystem. Förra månaden utvecklade vi ett litet program som passade in i 512-byte-sektorn 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 startladdare

Vi skulle kunna försöka minska storleken på vårt operativsystems binära kod så mycket som möjligt för att passa in i den första 512-byte sektorn på disketten, den som laddas av BIOS, men i det här fallet skulle vi inte kunna att implementera några intressanta funktioner. Därför kommer vi att använda dessa 512 byte för att hysa den binära koden för en enkel systemstarthanterare, som kommer att ladda OS-kärnans binära kod till RAM och exekvera den. (Efter detta kommer vi att utveckla själva OS-kärnan, som kommer att ladda den binära koden 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 systemstarthanterare från en fil som heter boot.asm:

BITS 16 jmp kort start ; Hoppa till etikett, hoppa över skivbeskrivning nop ; Tillägg före diskbeskrivningen %inkludera "bpb.asm" start: mov ax, 07C0h ; Ladda adress mov ds, axe ; Datasegment mov ax, 9000h ; Stackförberedelse mov ss, yxa mov sp, 0FFFFh ; Högen växer ner! cld ; Ställa in riktningsflaggan mov si, kern_filename call load_file jmp 2000h:0000h ; Övergång till OS-kärnan binär kod laddad från filen kern_filename db "MYKERNELBIN" %include "disk.asm" gånger 510-($-$$) db 0 ; Utfyllning av den binära koden med nollor upp till 510 byte dw 0AA55h ; Boot loader binär slutmarkeringsbuffert: ; Start av 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 minns från den tidigare artikeln i serien börjar exekveringen av den 512-byte binära koden som laddas från BIOS från disken från början, men vi måste hoppa till en etikett för att hoppa över en speciell uppsättning data. Uppenbarligen skrev vi förra månaden helt enkelt koden till början av disken (med hjälp av verktyget dd) och lämnade resten av diskutrymmet tomt.

Nu måste vi använda en diskett med ett lämpligt MS-DOS-filsystem (FAT12), 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 mer än en serie artiklar skulle kunna ägnas åt sådana ämnen, varför vi har placerat alla instruktioner och data som är associerade med den 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 att innehållet i en specificerad källfil inkluderas i den aktuella källfilen under montering. På så sätt kan vi göra vår bootloader-kod så kort och begriplig som möjligt genom att placera alla detaljer om implementeringen av BIOS-parameterblocket i en separat fil. BIOS-parameterblocket måste placeras tre byte efter starten av sektorn, och eftersom jmp-instruktionen bara tar upp två byte, måste vi använda nop-instruktionen (dess namn står för "no operation" - det här är en instruktion som gör inget annat än 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örra artikeln för att förbereda register och stacken, såväl som cld-instruktionen (står för "clear direction"), som låter oss ställa in riktningsflaggan för vissa instruktioner, t.ex. 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 in ​​adressen till strängen i SI-registret och anropar vår load_file-funktion. Men tänk på det en minut – 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 inkluderade kallad disk.asm.

FAT12-filsystemet som används på disketter som är formaterade i MS-DOS är ett av de enklaste tillgängliga filsystem, men att arbeta med dess innehåll kräver också en ansenlig mängd kod. Subrutinen load_file är cirka 200 rader lång och kommer inte att visas i den här artikeln, eftersom vi överväger processen att utveckla ett operativsystem, inte en drivrutin för ett specifikt filsystem, därför är det inte särskilt klokt att slösa utrymme på logga sidor på detta sätt. I allmänhet inkluderade vi disk.asm-källkodsfilen nästan innan slutet av den aktuella källkodsfilen och 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.)

I båda fallen laddar load_file-rutinen den binära koden från filen som är namngiven i SI-registret till segment 2000 vid offset 0, varefter vi hoppar till början av den för exekvering. Och det är allt - operativsystemets kärna är laddad och systemstarthanteraren 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 i namnschemat 8+3 som används på disketter i DOS. Faktum är att FAT12-filsystemet använder en intern representation av filnamn, och vi sparar utrymme genom att använda ett filnamn som garanterat inte kräver vår load_file-rutin för att implementera en mekanism för att slå upp punkttecknet och konvertera filnamnet till intern representation av filsystemet.

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 systemstarthanteraren med nollor upp till 512 byte och inkluderar slutmärket för dess binära kod (detta diskuterades i föregående artikel). Slutligen, i slutet av koden finns "buffer"-etiketten, som används av load_file-rutinen. I grund och botten behöver load_file-rutinen ledigt utrymme i RAM-minnet för att göra en del mellanarbete medan vi söker efter en fil på disken, och vi har gott om ledigt utrymme efter att ha laddat starthanteraren, så vi placerar bufferten här.

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

Nasm -f bin -o boot.bin boot.asm

Nu måste vi skapa en virtuell diskettavbildning i MS-DOS-format och lägga till vår bootloader binära kod 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

Vid denna tidpunkt kan processen att utveckla en systemstarthanterare anses vara avslutad! Vi har nu en startbar diskettavbildning som låter oss ladda operativsystemets kärnbinära kod från en fil som heter mykernel.bin och köra den. Därefter väntar en mer intressant del av arbetet på oss - utvecklingen av själva operativsystemets kärna.

Operativsystems kärna

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

Mov ax, 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, "MyOS 13, 10, "Mitt OS > 1, 0, 0, 0, 0, 0, 0,1 ", 0 user_input gånger 256 db 0 file_list gånger 1024 db 0 %inkludera "lib.asm"

Innan du tittar på koden bör du vara 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 rutiner för att arbeta med skärmen, tangentbordet, strängarna 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 ytterligare information om alla tillgängliga subrutiner.

I de första tre raderna av operativsystemets kärnkod fyller vi segmentregistren med data för att peka på segment 2000 där den binära koden laddades in. Detta är viktigt för garanterat korrekt funktion instruktioner som lodsb, som måste läsa data från det aktuella segmentet och inte från något annat. Efter detta kommer vi inte att utföra några ytterligare operationer på segmenten; vårt operativsystem kommer att fungera 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 escape-tecken. ny linje, tack vare vilket hälsningen inte kommer att visas omedelbart efter utmatningen av något program, utan alltid på en ny rad.

Efter detta använder vi en annan rutin från lib.asm-biblioteket som heter lib_input_string, som tar användarens tangentbordsindata och lagrar den i en buffert som pekas på i SI-registret. I vårt fall deklareras bufferten nära slutet av operativsystemets kärnkod enligt följande:

User_input gånger 256 db 0

Denna deklaration låter dig skapa en buffert på 256 tecken, fylld med nollor - dess längd bör räcka för att lagra 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 noll, 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 gå 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 kunnat observera jämförelser av enskilda byte i våra assemblerprogram, men glöm inte att det också är möjligt att jämföra dubbelbytevärden eller maskinord. I den här koden jämför vi det första maskinordet från user_input-bufferten med maskinordet som motsvarar raden ls och, om de är identiska, flyttar vi till kodblocket nedan. Inom detta kodblock använder vi en annan rutin från lib.asm 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 slingan för att bearbeta användarinmatning.

Utförande av tredjepartsprogram

Om användaren inte anger kommandot ls, antar vi att de skrev in namnet på programmet från disken, så det är vettigt att försöka ladda det. Vårt lib.asm-bibliotek innehåller en implementering av den användbara lib_load_file-rutinen, som analyserar FAT12-diskfilsystemtabellerna: den tar en pekare till början av en rad med filnamnet genom AX-registret, samt ett offsetvärde för laddning binär kod från en programfil via CX-registret. Vi använder redan SI-registret för att lagra en pekare till strängen som innehåller användarinmatning, så vi kopierar denna pekare till AX-registret och placerar 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 just detta värde som offset för att ladda binär kod från en programfil? Tja, detta är bara ett av minneskartalternativen för vårt operativsystem. Eftersom vi arbetar i ett enda 64 KB-segment och vår kärnbinär laddas med offset 0, måste vi använda de första 32 KB minne för kärndata och de återstående 32 KB för att ladda programdata. Offset 32768 är alltså mitten av vårt segment och låter oss tillhandahålla tillräckligt med RAM till både operativsystemets kärna och laddade program.

Rutinen lib_load_file utför sedan en mycket viktig operation: om den inte kan hitta en fil med det givna 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 utförandet av vissa matematiska operationer och bör inte intressera oss för tillfället, men samtidigt kan vi bestämma närvaron av denna flagga för att fatta snabba beslut. Om rutinen lib_load_asm ställer in bärflaggan använder vi instruktionen jc (hopp om bär) för att hoppa till ett kodblock 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 har laddat 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 binären kod laddad på denna adress, det vill säga börja köra det användarspecificerade programmet! Och efter att ret-instruktionen har använts i det här programmet (för att återgå till anropskoden), behöver vi helt enkelt återgå till slingan för bearbetning av användarinmatning. Sålunda skapade vi ett operativsystem: det består av de enklaste kommandotolknings- och programladdningsmekanismerna, implementerade i cirka 40 rader assemblerkod, om än med mycket hjälp från rutiner från lib.asm-biblioteket.

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

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

Efter detta 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 skivavbildningen med floppy.img, men det finns ett enklare sätt att använda GNU Mtools (www.gnu.org/software /mtools). Detta är en uppsättning program för att arbeta med disketter som använder MS-DOS/FAT12 filsystem, tillgängliga från paketförråd programvara alla populära Linux-distributioner, så allt du behöver göra är att använda apt-get, yum, pacman eller vilket verktyg du än använder för att installera mjukvarupaket på din distribution.

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

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

Notera de roliga symbolerna i slutet av kommandot: kolon, kolon och snedstreck. Nu är vi nästan redo att lansera vårt operativsystem, men vad är poängen om det inte finns några appar 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 bara hur mycket din auktoritet kommer att stiga bland nördarna. Spara följande kod i en fil som heter test.asm:

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

Den här koden använder helt enkelt BIOS-funktionen för att skriva ut ett "X" på skärmen och återställer sedan kontrollen till koden som kallade den - i vårt fall är den koden operativsystemets kod. Organisationsraden som börjar programmets 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, och därför måste alla förskjutningar beräknas om för att ta hänsyn till detta.

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 begrunda 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: systemstarthanteraren boot.img, som vi integrerade i den första sektorn av diskavbildningen, laddar operativsystemets kärna mykernel.bin, som visar ett välkomstmeddelande. Ange kommandot ls för att få namnen på två filer som finns på disken (mykernel.bin och test.bin), och ange sedan namnet på den sista filen för att köra den och visa ett X på skärmen.

Det är coolt, eller hur? Nu kan du börja slutföra kommandoskal ditt operativsystem, lägg till implementeringar av nya kommandon och lägg även 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 introducera ett koddelningskoncept för att minska kodduplicering. Mycket av arbetet ligger kvar.

lib.asm biblioteksrutiner

Som nämnts tidigare, tillhandahåller biblioteket lib.asm en stor uppsättning användbara rutiner för användning inom dina kärnor operativsystem och individuella program. Vissa av dem använder instruktioner och begrepp som ännu inte har berörts i artiklar i denna serie, andra (som diskrutiner) är nära besläktade med utformningen av filsystem, men om du anser dig vara kompetent i dessa frågor kan du läsa dem själv med deras implementeringar och förstå principen om drift. Det är dock viktigare att ta reda på hur man ringer dem från din egen kod:

  • lib_print_string - accepterar en pekare till en nollterminerad sträng via SI-registret och skriver ut strängen till skärmen.
  • lib_input_string - accepterar en pekare till en buffert via SI-registret och fyller denna buffert med tecken som angetts av användaren med hjälp av tangentbordet. Efter att användaren tryckt på Enter-tangenten avslutas raden i bufferten null och kontrollen återgår till den anropande programkoden.
  • lib_move_cursor - flyttar markören på skärmen till en position med koordinater som överförs genom DH (radnummer) och DL (kolumnnummer) registren.
  • lib_get_cursor_pos - denna subrutin bör anropas för att erhålla de aktuella rad- och kolumnnumren 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 strängens tecken till versaler.
  • lib_string_length - tar en pekare till början av en nollterminerad sträng via AX-registret och returnerar dess längd via AX-registret.
  • lib_string_compare - accepterar pekare till början av två nollterminerade strängar med hjälp av SI- och DI-registren och jämför dessa strängar. Ställer in bärflaggan om raderna är identiska (för att använda en hoppinstruktion beroende på jc carry-flaggan) eller rensar denna flagga om raderna är olika (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 i den bufferten en nollterminerad sträng som innehåller en kommaseparerad lista med filnamn från disken.
  • lib_load_file - tar en pekare till början av en rad som innehåller filnamnet med hjälp av AX-registret och laddar innehållet i filen med den offset som passerar genom CX-registret. Returnerar antalet byte som kopierats till minnet (det vill säga filstorleken) med hjälp av BX-registret, eller ställer in bärflaggan om en fil med det angivna namnet inte hittas.

Jag säger genast, stäng inte artikeln med tankarna "Fan, ännu en Popov." Han har bara en polerad Ubuntu, medan 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 låt oss nu prata 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 känslan, varför inte förverkliga min dröm?" Jag googlade på webbplatser om detta ämne och hittade en artikel från Habr: "Hur man börjar och inte slutar skriva ett OS." Tack till dess författare för länken till OSDev Wiki nedan. Jag gick dit och började jobba. Det fanns all information om det minimala operativsystemet i en artikel. 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ärld!" Jag hoppade direkt ur stolen och insåg att jag inte skulle ge upp. Jag skrev "konsol" (inom citattecken; jag hade inte tillgång till ett tangentbord), men bestämde mig sedan för att skriva ett fönstersystem. Till slut 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. Jag googlade på Y Window System - det finns. Som ett resultat döpte jag Z Window System 0.1, inkluderat i OS365 pre-alpha 0.1. Och ja, ingen såg henne förutom jag själv. 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 ännu, inte ens ett fönstersystem:

Textmarkören rörde sig inte ens, som du kan se. Sedan skrev jag ett par enkla applikationer baserad på Z. Och här är release 1.0.0 alpha. Det fanns många saker där, även systemmenyer. A filhanterare och kalkylatorn fungerade helt enkelt inte.

Jag blev direkt terroriserad av en vän som bara bryr sig om skönhet (Mitrofan, förlåt). Han sa: "Tvätta ner VBE-läget 1024*768*32, tvätta ner det, tvätta ner det! Nåväl, låt oss dricka upp det!" Tja, jag var redan trött på att lyssna på honom och klippte fortfarande ner honom. Om implementeringen nedan.

Jag gjorde allt med min bootloader, nämligen GRUB. Med dess hjälp kan du ställa in det grafiska lä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 sedan från Multiboot-informationsstrukturen tar jag framebuffer-adressen och skärmupplösningen och skriver pixlar där. VESA gjorde allt väldigt förvirrande - RGB-färger måste anges i omvänd ordning (inte R G B, utan B G R). I flera dagar förstod jag inte varför pixlarna inte visades!? 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 och klippte samtidigt 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 - assemble) - en kompilator från assemblerspråk till maskinspråkkommandon.
Det finns en assembler för varje processorarkitektur och för varje OS- eller OS-familj. Det finns också så kallade "cross-assemblers" som låter dig sätta ihop program för en annan målarkitektur eller ett annat OS på maskiner med en arkitektur (eller i miljön för ett OS), och få körbar kod i ett format som lämpar sig för exekvering på målarkitekturen eller i målmiljöns 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). Den enkla montören A86 var också populär vid en tidpunkt.
Till en början stödde de bara 16-bitars instruktioner (tills 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 har utvecklingen av programmet helt stoppats.
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 assemblerprojekt med öppen källkod, versioner av vilka är 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 under BSD-licensen (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
För närvarande finns det 2 kompilatorer producerade av Atmel (AVRStudio 3 och AVRStudio4). Den andra versionen är ett försök att korrigera den inte särskilt framgångsrika första. Samlaren ingår också i WinAVR.
ÄRM
AVR32
MSP430
PowerPC

Montering och sammanställning

Processen att översätta ett program på assemblerspråk till objektkod brukar 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 varje uttryck dölja ett stort antal olika instruktioner. I princip är denna uppdelning ganska godtycklig, så ibland kallas översättningen av monteringsprogram också kompilering.

assembleringsspråk

assembleringsspråk- en typ av programmeringsspråk på låg nivå, vilket är ett format för inspelning av maskinkommandon 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 inspelning (mnemonisk kod) av kommandon och deras argument. Assembly language 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 ett program; upprepa ett visst fragment ett visst antal gånger; kompilera fragmentet enligt tillståndet; ställ in fragmentexekveringsadressen annorlunda ä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 ett motsvarande assemblerspråk (eller dialekt).

Fördelar och nackdelar

Fördelar med assemblerspråk

Minimal redundant kod, det vill säga användningen av färre instruktioner och minnesåtkomster, möjliggör ökad hastighet och minskad programstorlek.
Säkerställande av full kompatibilitet och maximal användning av den önskade plattformens kapacitet: användning av speciella instruktioner och tekniska funktioner för denna plattform.
Vid programmering i assemblerspråk blir speciella funktioner tillgängliga: direkt åtkomst till hårdvara, in-/utgångsportar och speciella processorregister, samt möjligheten att skriva självmodifierande kod (det vill säga metaprogrammering, utan behov av en mjukvarutolk) .
De senaste säkerhetsteknikerna som introducerats i operativsystem tillåter inte skapandet av självmodifierande kod, eftersom de utesluter möjligheten att samtidigt utföra instruktioner och skriva i samma minnesområde (W^X-teknik i BSD-system, DEP i Windows).

Nackdelar med Assembly Language

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 felsökning och modifiering av programmet svårare, liksom svårigheten att implementera programmeringsparadigm och andra konventioner. vilket leder till komplexiteten i gemensam utveckling.
Färre antal tillgängliga bibliotek, deras låga kompatibilitet med varandra.
Inte portabel till andra plattformar (förutom binärt kompatibla).

Ansökan

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

Länkar sammansättningskod till andra språk

Eftersom endast fragment av ett program oftast är skrivna på assemblerspråk behöver de länkas till andra delar på andra språk. Detta uppnås på två huvudsakliga sätt:
På sammanställningsstadiet— Införande av assemblerfragment (inline assembler) i ett program med hjälp av särskilda språkdirektiv, inklusive skrivprocedurer på assemblerspråk. Metoden är bekväm för enkla datatransformationer, men fullfjädrad assemblykod 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 skapas med den.
På layoutstadiet, eller separat sammanställning. För att de sammansatta modulerna ska interagera räcker det att de anslutande funktionerna stöder de erforderliga anropskonventionerna och datatyperna. Individuella moduler kan skrivas på vilket språk som helst, 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 inspelningsinstruktioner är detsamma för båda standarderna:

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

där opcode är den direkta mnemoniken för instruktioner till processorn. Prefix kan läggas till (upprepningar, ändringar i adresseringstyp, etc.).
Operanderna kan vara konstanter, registernamn, adresser i RAM, 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 är mnemonics för Motorola, ARM, x86-processorer och kontroller). De beskrivs i processorspecifikationerna. 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 inspelning av minnesminnen (kommandosystemet ärvdes från en processor från en annan tillverkare).
Till exempel ärvde Zilog Z80-processorn Intel i8080-instruktionssystemet, utökade det och ändrade mnemonics (och registerbeteckningar) på sitt eget sätt. Till exempel ändrade jag Intels mov till ld. Motorola Fireball-processorer ärvde Z80-instruktionssystemet, vilket minskade det något. Samtidigt har Motorola officiellt återvänt till Intel mnemonics. Och för tillfället arbetar hälften av montörerna för Fireball med Intel-mnemonics och hälften med Zilog-mnemonics.

direktiv

Förutom instruktioner kan ett program innehålla direktiv: kommandon som inte översätts direkt till maskininstruktioner, men som styr kompilatorns funktion. Deras uppsättning och syntax varierar avsevärt och beror inte på hårdvaruplattformen, utan på kompilatorn som används (genererar dialekter av språk inom samma familj av arkitekturer). Som en "gentleman's set" av direktiv kan vi lyfta fram:
definition av data (konstanter och variabler)
hantera programorganisation i minne och utdatafilsparametrar
ställa in kompilatorns driftläge
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 konstruktioner och loopar (för det strukturerade programmeringsparadigmet)
makron

Exempel på program

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

.MODEL TINY 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 får sitt namn från namnet på översättaren (kompilatorn) från dessa språk - assembler (engelsk assembler). Namnet på den senare beror på det faktum att det inte fanns några språk på högre nivå på de första datorerna, och det enda alternativet till att skapa program med hjälp av assembler var att programmera 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 språket som ett specifikt program är skrivet på, är det lämpligt att klargöra vilken arkitektur det är avsett för och vilken dialekt av språket det är skrivet på.

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

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

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

Modern "Hummingbird" är en regelbundet uppdaterad "nattlig version" 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.

Förändringarna i nattliga byggnader, även om de är små, har ackumulerats ganska mycket under å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, och lade till en TCP/IP-stack och ljudcodec. 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 lanseringshastigheten.



Liksom tidigare versioner är den senaste "Hummingbird" skriven i flat assembler (FASM) och upptar en diskett - 1,44 MB. Tack vare detta kan den placeras helt och hållet 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 dig om faran. Anledningen är tydligen assemblerprogrammen i distributionen. VirusTotal definierar nu webbplatsen som helt säker.

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


Ett problem är att HTTPS-protokollet inte stöds av webbläsaren som är inbyggd i Kolibri. Därför var det inte möjligt att titta på webbplatsen i den, precis som att öppna sidorna på Google, Yandex, Wikipedia, Sberbank... faktiskt, ingen vanlig adress. Alla har sedan länge gått över till ett säkert protokoll. Den enda webbplatsen med gammaldags ren HTTP som jag stötte på var "Russian Government Portal", men den såg inte den bästa ut i en textwebbläsare heller.



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å Hummingbird-laddningsskärmen när du trycker på a-tangenten.



Listan över tillgängliga alternativ är liten och den nödvändiga upplösningen kanske inte finns där. Om du har ett grafikkort med en AMD (ATI) GPU kan du omedelbart lägga till anpassade inställningar. För att göra detta måste du skicka parametern -m till ATIKMS-lastaren 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 kan KolibriOS rymma massor av spel. Bland dem finns logik- och arkadspel, tagg, orm, tankar (nej, inte WoT) - ett helt "Game Center"! Till och med Doom och Quake portades till Kolibri.



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å en flash-enhet, men den måste vara ansluten 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 identifierades omedelbart. Om du behöver skriva filer, bör du ansluta en flash-enhet med en FAT32-partition.



Kolibri distributionssats innehåller tre filhanterare, verktyg för att visa bilder och dokument, ljud- och videospelare och andra användarapplikationer. Dess huvudsakliga fokus ligger dock på utveckling av assemblerspråk.



Den inbyggda textredigeraren har ASM-syntaxmarkering och låter dig till och med omedelbart starta maskinskrivna program.



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 specialporterade till Kolibri.

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

Fortsättning är endast tillgänglig för medlemmar

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

Medlemskap i communityn inom 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!


Topp