int21h

Vytvoření vlastního "operačního systému"

Pokud se na to cítíte, můžete si vyzkoušet, jaké to je, dělat vlastní OS. Hlavně podotýkám, zapomeňte na jazyk C, Pascal, atd. Budeme pracovat pouze v assembleru. Uděláme si totiž OS zcela od základů, tj. (skoro) nic z toho, co znáte, nemůžete použít. INT 21h neexistuje (mám na mysli přerušení DOSu, nikoliv náš časopis).

Existuje pouze to, co nám poskytuje BIOS. Tedy, pro práci s klávesnicí máme sice nějaké to INT 16h, ale už minule jsme se naučili obsloužit si ji rychleji. Operace s diskem jsou přístupné přes INT 13h (ovšem na práci se soubory zapomeňte, IO.SYS neexistuje). COM a LPT už také umíme ovládat, video stále sídlí na INT 10h v plné síle i s VESA BIOSem. Toto jsou prostředky, se kterými si musíte pro začátek vystačit (pokud nechcete skočit přímo na práci s porty - ano, i s HDD jde komunikovat rychleji přes porty než přes INT 13h, ale je to pro začátek dost těžké). A myš? Neexistuje. INT 33h je totiž softwarové přerušení, a Vy nemáte ovladač. Co s tím? Napište si vlastní, jak už jsme probírali v minulých seriálech. Pro programování jádra našeho OS budeme používat libovolný assembler, který podporuje vytváření čistých COM souborů. Můžete použít sice TASM, ale ten je pouze 16 bitový. Já jsem sáhl pro FASM (Flat Assembler, můžete si ho stáhnout na http://flatassembler.net/), neboť ten je nejen 32 bitový, ale umí i instrukce MMX, SSE, 3DNow, SSE2, SSE3, atd.. :-O. Veškeré kódy budeme psát v něm. Aplikace generované Pascalem atd. Vám nepoběží, protože neexistuje žádný kód (na int 21h), který by je zavedl do paměti, spustil a umožnil jim pracovat se soubory, atd. Počítač umí ale vykonávat COM soubory, protože jsou v nich jen čisté instrukce. Pokud Vás to ještě neodradilo, půjdeme dál.

Nejprve trocha teorie. Jak se pracuje s disky. Zapomeňte na FAT12 nebo FAT16, protože pro nás v tuto chvíli neexistují. Mají význam jen s IO.SYS (nebo s čímkoliv dalším, co je umí číst, a proč bychom emulovali něco tak předpotopního?). Jak zajistit, aby náš operační systém běžel. Takže, BIOS po stisknutí hlavního vypínače provede nějaké ty testy, inicializuje zařízení a pak provede instrukci INT 19h. Ta načte do paměti (v případě diskety) 1. sektor stopy 0 hlavy 0. Jedná se o 512 bytů, které umístí na adresu $7c0:0. Pokud poslední dva byty, tj. word, jsou rovny $aa55, BIOS předá řízení na tuto adresu (tj. tam musí být první instrukce). Tady se jedná o tzv. zavaděč, který budeme muset napsat a umístit nějak do daného sektoru (u diskety to je jednoduché). Tento zavaděč je sice malý (ale divili byste se, co se dá napsat v 0.5 kB, viz. např. grafické boot demo CoolBOOT.ASM od Grevena I z Suicide Software), ale slouží hlavně jako předmostí. Co bude dělat je už jen na nás. Od této chvíle běžíme jen MY. Tj. žádný Windows, který nám diktuje co můžeme a co nemůžeme. Ale také žádná správa souborů, paměti, XMS, EMS, zvuk, nic. Ale to nás zatím netrápí. Zavaděč udělá většinou pouze 1 věc: nahraje do paměti jiný "soubor", který může být třeba IO.SYS, nebo už např. jádro systému. To záleží jen na Vás. Jádro by mělo umět zajistit alespoň správu paměti, souborů na disku a spouštění programů, aby si mohlo případně nahrávat své další moduly. Protože máte pro sebe celý disk (pro nás, jako nový OS, na dané disketě/oddílu není NIC), rezervujte si např. dalších 10 sektorů (5 kB), nebo kolik chcete a nahrajte do něj jádro. To bude defakto čistý COM (hlavně pozor, ať ho nespustíte omylem v DOSu nebo někde :-D). Protože adresa $7c0:0 už nebude důležitá, můžete na ní zapomenout a později ji přepsat. Vaše jádro si můžete nahrát kam chcete. Buď na konec prvních 640 kB, nebo na začátek paměti. Jen pozor, že první 1 kB je vyhrazen tabulce vektorů, a další paměť až do adresy $4f:$f ($4ff:0) má vyhrazen BIOS pro svá data. Můžete se tedy směle nastrkat např. na $500:0, kde normálně sídlí DOS :-). To provedeme také my. Když umíte udělat bootovací disketu, umíte i CD (jen pozor, aby Vám tam NERO nedal spíše DOSový zavaděč :-D). U HDD je situace poněkud odlišná. Sice se také nahráváte do boot sektoru, ale protože disk může být rozdělený na různé oddíly, měli byste brát odhled na uživatele, kteří chtějí mít např. více operačních systémů současně. Na začátku disku (první sektor) je tzv. MBR, tedy záznam rozdělení disku (a zároveň bootovací rutina - z pohledu BIOSu totiž není mezi MBR a boot sektorem žádný rozdíl). Tam je seznam nejen všech oddílů, ale také, který z nich je bootovací. Vy byste se měli nainstalovat tam, kam Vám řekne uživatel. I když daný disk není tzv. primární. V MBR pevného disku je většinou menu (Grub, Lilo, Windows boot-loader), které umožňuje výběr OS (nahraje boot sektor z primárního, nebo jiného oddílu), a ten byste neměli zlikvidovat (pokud by chtěl mít uživatel Váš OS jako primární, nastaví si to sám).

MBR (na disketě chybí, proto na ní má byty $aa55 boot sektor, a nikoliv MBR) vypadá takto (jedná se čistě o SW záležitost, BIOS neví nic o členění disku!):


type	TZ = record
		Boot : byte;	{0 = neaktivní, $80 = z tohoto oddílu zavést OS}
		Hlava : byte;	{na které hlavě začíná}
		SektCyl : word;	{na kterém sektoru a cylindru začíná}
		Kod : byte;	{jaký OS je zde: 4 = DOS, atd.}
		HlavaK : byte;	{na které hlavě, atd. končí}
		SektCylK : word;
		RelSec : longint;	{číslo prvního relativního sektoru}
		Vel : longint;		{velikost oddílu v sektorech}
	     end;

var	MBR : record
		Rutina : array[0..$1be-1] of byte;	{spuštění bootovacího programu}
		Z1,Z2,Z3,Z4 : array[0..15] of TZ;	{informace o oddílu}
		Oznac : word;				{byty $55 a $aa}
	      end;

Údaje SEKTCYL mají strukturu viz. ATHelp (přerušení BIOSu, 19h, Boot, struktura MBR), aby se daly použít ve funkci v INT 13h. Relativní sektor udává první sektor na daném oddílu, který se dá vypočítat jako:

	rel_sek=(č.cyl*sektorů_na_cyl*hlav)+(č.hlavy*sektorů_na_cyl)+(č.sek-1)
Tento sektor budete potřebovat pro zápis boot sektoru na disk pomocí služby INT 26h. Pokud použijete INT 13h, musíte si najít dané údaje v MBR, ale jinak je to stejné jako u diskety (jen jsou tam jiná čísla). Boot sektor by měl vypadat následovně:

var	BootSekt : record
			JMPboot : array[0..2] of byte;		{skok na 1. instrukci}
			Nazev : array[0..7] of char;		{napr. 'NAS OS'}
			{teď jde tzv. blok BPB}
			VelSekt : word;			{počet bytů na sektor, 512B}
			VelClus : byte;			{počet sektorů na cluster}
							{min.vel.souboru}
			Rezerv : word;		{volné sektory před první FAT}
			PocetF : byte;		{počet FAT, disketa=1, HDD=2+}
			VelRoot : word;		{max.počet položek hl.adresáře po 32B}
			CelkSekt : word;	{počet sektorů na oddílu DOSu}
			Popis : byte;		{deskriptor, totéž jako první byte FAT}
			VelFAT : word;		{počet sektorů na 1 FAT}
			{konec BPB}
			Sektoru : word;		{počet sektorů na stopu}
			Hlav : word;		{počet povrchů}
			Skytych : word;		{počet skrytých sektorů}
			{od MS-DOS verze 4.0}
			SpecSkr : longint;	{speciální skryté sektory}
			CelkemSekt : longint;	{celkový počet sektorů pro CELKSEKT=0}
			FyzickeC : word;	{fyzické číslo disku, 128 pro disk 0}
			RozBoot : byte;		{signatura rozšířeného boot sektoru}
			Serial : longint;	{sériové číslo, byty jdou opačně jako v RAM}
			Nazev : array[0..10] of char;	{jméno disku, na konci #32}
			SFAT : array[0..7] of char;	{jméno FAT, doplněno mezerami}
		   end;

Absolutní číslo sektoru pro INT 25/26h vypočítáte jako (viz. ATHelp):

 kořen_sekt = (VelKmen*32+VelSekt-1)/VelSekt
 první_data = RezSekt+(VelFat*PočFat)+kořen_sekt
 abs_sektor = první_data+((číslo_clust-2)*VelClust)

Pokud jste si všimli toho MS-DOSu, tam Vám možná něco seplo. Ano, boot sektor je nezávislý na BIOSu, proto je tam také ta první instrukce, která předává řízení až za tyto data. Jinými slovy, pokud nebudeme potřebovat číst FAT oddíly DOSu, můžeme toho zahodit a udělat celý boot sektor bez dat. Proč se držet konvencí Microsoftu. Tento sektor ale budete potřebovat znát, pokud Váš OS bude muset být schopen číst DOSové disky a diskety (jinak byste totiž do něj moc dat nedostali). Jak zjistit, kde leží daný soubor, jak se jmenuje a jak přečíst jeho data, se můžete dočíst v ATHelpu (Rejstřík, FAT).

My si teď nejprve uděláme pro zjednodušení program, který zapíše na disketu boot sektor a jádro našeho OS. Obojí budou dva soubory typu COM, jeden s max. velikostí 512 bytů a druhý s maximální velikostí 5 kB (budete se divit, co všechno se do toho vejde). Soubory vytvoříme později, abychom se nyní zbavili posledního Pascalu. Program bez chybové kontroly (!) vypadá takto:


program InstOS;

var	Boot : array[0..511] of byte;
	Jadro : array[0..5119] of byte;
	VelBoot,VelJadra : word;
	Soub : file;
	RW : word;
begin
{nejprve nahrajeme oba soubory do paměti}
 Assign(Soub,'bootsec.bin');
 Reset(Soub,1);
 if IOResult = 0 then
 begin
  if FileSize(Soub) <= 512 then
  begin
   VelBoot := FileSize(Soub);
   BlockRead(Soub,Boot,VelBoot,RW);
  end;
  Close(Soub);
  if VelBoot <> RW then Exit;
  Boot[510] := $55;		{aby se z kódu dalo nabootovat}
  Boot[511] := $aa;
 end;
 Assign(Soub,'jadroos.bin');
 Reset(Soub,1);
 if IOResult = 0 then
 begin
  if FileSize(Soub) < 5120 then
  begin
   VelJadra := FileSize(Soub);
   BlockRead(Soub,Jadro,VelJadra,RW);
  end;
  Close(Soub);
  if VelJadra <> RW then Exit;
 end;
{zapíšeme boot sektor do diskety A:}
 asm
	mov	ah,3	{zápis, 2=čtení}
	mov	al,1	{počet sektorů = 1}
	mov	dl,0	{první disketa, 1=B:, $80=první disk, D:=$81}
	xor	dh,dh	{hlava 0}
	xor	ch,ch	{stopa 0; dolních 8 bitů z 10 bitové hodnoty}
	mov	cl,1	{stopa 0 v bitech 6-7, sektor 1 v bitech 0-5}
	{mov	cx,1}
	mov	ax,seg boot
	mov	es,ax	{data pro zápis}
	mov	bx,offset boot
	int	13h	{v AH je poté případný kód chyby}
 end;
{zapíšeme jádro OS}
 asm
	mov	ah,3
	mov	al,10	{nic se nestane, když bude zbytek dat nějaký "svinčík"}
	xor	dx,dx
	mov	cx,2
	mov	ax,seg jadro
	mov	es,ax
	mov	bx,offset jadro
	int	13h
 end;
end;

Pokud si vytvoříte tento program v assembleru, měli byste dostat něco takového (tento je myslím určen pro FASM; zároveň je to docela dobrá ukázka, jak číst soubory pod DOSem):


	org     100h
; prohledáme příkazovou řádku pro název souboru
	mov	ch,1
	mov	di,$81
	mov	al,' '
	repe	scasb
	lea	dx,[di-1]
	dec	di
	mov	al,13
	repne	scasb
	mov	byte [di-1],0
; otevřeme soubor
	mov	ax,$3d00
	int     $21
	jc	quit
	xchg	bx,ax
; přečteme z něj boot sektor
	mov	ah,3Fh
	mov	cx,512
	mov	dx,sektor
	int     21h
	jc	quit
; přečteme originální boot sektor z diskety
	mov	bp,3
	xor	dx,dx			{pro disketu B: :-) dejte do DX 1}
	mov	bx,original
read_retry:
	mov	ax,$201
	int     $13
	jnc	read_ok
	xor	ah,ah
	int     $13
	dec	bp
	jnz	read_retry
	retn
read_ok:
	mov	si,original+$b
	mov	di,sektor+$b
	mov	cl,28
	rep	movsb
	mov	bp,3
	mov	cx,0001h
	mov	bx,sektor
write_retry:
	mov	ax,$301
	int     $13
	jnc	quit
	xor	ah,ah
	int     $13
	dec	bp
	jnz	write_retry
quit:
	retn
original	rb 512
sektor		rb 512

Tento program (není můj) funguje sice trochu jinak, než ten náš v Pascalu, a zapisuje také pouze boot sektor, ale jak říkám, pokud Vás těší se hrabat v ASM, můžete si ho přepsat a doplnit. Nám ostatním zatím poslouží "instalační program" psaný a kompilovaný v TP7 (nebo po úpravách v FP). Zároveň by pro Vás neměl být problém upravit si kód tak, aby se OS dal instalovat na HDD. Prozatím Vás to ale učit nebudu, protože na disketě se nedá nic zkazit :-) Zatím to bylo celkem snadné. Tak, jdeme si udělat zavaděč. Nažhavte Váš FASM (příklady můžete najít např. od Alexander Popov Emulation Soft):


; boot sektor je nahrán na $7c0:0
	ORG 7C00h
; přeskočíme případnou sekci pro data
	jmp	start
; zpráva pro uživatele
zprava	DB	'Operacni system MY_OS v0.1',13,10
	DB	'Nahravam...'
ne386	DB	13,10,'Vas CPU neni 386 a vyssi!'
start:
; někde se píše, že se musí udělat zásobník
; pokud je to pravda, tak ho uděláme za náš program o velikosti 256W
; jestli je pouze pro těch pár instrukcí, tak stačí 16B :-)
	mov	ax,$7c0
	mov	ss,ax
	mov	sp,$3fe		; jeho vrchol
; jediný registr, který máme, je CS
; tam máme také data, takže si inicializujeme i DS
	push	cs
	pop	ds
; přepneme se do textového módu
; (pokud chcete grafický, tak snad víte, jak na to)
	mov	ax,3
	int	$10
; zobrazíme uvítací zprávu
	mov	bp,zprava
	push	ds
	pop	es
	mov	cx,36	; délka řetězce
	call	vypis
; otestujeme, zda je počítač 386 nebo lepší
; (existuje spousta typů testů, viz. ATHelp, Procesory)
	push	sp
	pop	ax
	cmp	ax,sp
	jnz	chybaCPU
	smsw	ax
	shr	ax,1
	jc	nahraj
	mov	ax,$ffff
	mov	cx,$20
	shl	ax,cl
	or	ax,ax
	je	chybaCPU
	jmp	nahraj
chybaCPU:
	mov	cx,27
	mov	bp,non386
	call	vypis
	hlt
nahraj:
; nahrajeme do paměti jádro našeho OS
; (někde se doporučuje adresa $800:0)
	mov	ah,2
	mov	al,10	; mov	ax,$2a
	mov	cx,2
	xor	dh,dh	; DL neměníme, BIOS nám v něm posílá číslo mechaniky
			; ze které nabootoval, tj. abychom četli NAŠE jádro
	mov	bx,$500
	mov	es,bx
	xor	bx,bx
	int	$13
; předáme řízení jádru
	jmp	$500:0
; procedura pro výpis řetězce (použijeme EGA/VGA BIOS)
vypis:	mov	ah,$13
	mov	al,1	; mov	ax,$1301
	mov	dh,1
	mov	dl,1	; mov	dx,$101
	;xor	bh,bh
	mov	bx,7
	int	$10
	ret

Soubor zkompilujte jako BOOTSEC.BIN (když zadáte jen "FASM.EXE BOOTSEC.ASM", je to pojistka proti vytvoření "klikacího" COM, ikdyž obsah bude stejný). Tak ze 1619 bytového ASM nám překladač vyprodukoval 168 bytový soubor. Takže zavaděč je hotový. Jakmile ho počítač načte a předá řízení na adresu $500:0, už nebude potřeba ani jeho kód, ani zásobník. Zavaděč si můžete upravit, např. vykreslovat během zavádění nějaké ty grafické efekty (pokud se přepnete do grafického režimu), které by v jádru jen zbytečně zvětšovaly jeho velikost. Teď si tedy budeme muset udělat jádro.

Nejprve zjistíme, jaké základní úkoly bude plnit. Potřebujeme spravovat soubory na disku. Buď se naučíme číst FAT pro jiné oddíly, a/nebo si vytvoříme svůj vlastní souborový systém (ponechme teď stranou, čemu tady říkám FAT). Uděláme ho 32 bitový, předpokládáme totiž, že náš OS poběží na 386 a lepších (s 286 se již vážně nemá cenu zabývat - dokonce je potíž sehnat fungující desku). Velikost clusteru (alokační jednotka) si nastavte tak, jak uznáte za vhodné. Musíte sladit dva parametry: čím menší cluster bude (min. 1 sektor = 512 bytů), tím méně zabere soubor o velikosti 1 byte (standardně totiž zabere v DOSu či Windows 4-32 kB!). Zároveň ale, čím více je potřeba clusterů na popis jednoho souboru, tím více místa zabírá FAT (v našem podání nepůjde o FAT12 nebo FAT16, dokonce ani o FAT32, ale prostě třeba o MOSFAT32). Celý disk musí být být totiž standardně nějak organizován a pokud nechcete prohledávat všechny odkazy na soubory pro najdutí volného místa, musíte naindexovat všechny alokační jednotky na disku. Tedy, pro 32 bitové adresování může být disk velký až 4294967296*VEL_AL_JEDN (pro 1 kB na položku to dělá disk o velikosti 4 TB). Zároveň ale platí, že na jeho adresování budete potřebovat FAT o velikosti 4294967296*VEL_POLOZKY (v našem případě 4 byty, tj. 16 GB při 1 kB na položku; pro 2 kB buď 1/2 položek (FAT=8 GB) nebo 2x větší velikost disku (8 TB), ale také 2x větší nejmenší možný soubor (2kB)). Pokud je velikost disku v dnešní době cca. 300 GB, a my si zvolíme alokační jednotku 1 kB (2 sektory), budeme muset vytvořit FAT o velikosti (300*GB div 1024)*4, což je 1.17 GB. To je daň za malé soubory! Možná existuje lepší způsob, ale toto je velice rychlé, protože máte seznam jednotek a hned víte, která je volná a která ne. Ovšem je strašně velká! Samozřejmě, náš OS nemusí pracovat s celým oddílem, o to se velikost zmenší (pro 100 GB na 1/3), ale stále je tu velká režije. Musíme si najít nějaký kompromis pro to, jak velké budou alokační jednotky (majitelům velkých disků se tedy nevyplatí dělat pidi soubory - leda byste svůj systém zdokonalili tak, že by 10% FAT byly jednotky po 1 kB pro malé soubory a zbytek by byl vyhrazen dle velikosti disku pro ten zbytek):


const	PocetJednotek = ???;
var	Seznam : array[0..PocetJednotek-1] of longint;

1073741824

	Velikost disku		Velikost jednotky	Počet		Vel.tabulky
		<2 MB			32 B		65536		256 kB
		<16 MB			128 B		131072		512 kB
		<32 MB			128 B		262144		1 MB
		<64 MB			128 B		524288		2 MB
		<128 MB			170 B		786432		3 MB
		<256 MB			256 B		1048576		4 MB
		<512 MB			256 B		2097152		8 MB
		<1 GB			341 B		3145728		12 MB
		<2 GB			512 B		4194304		16 MB
		<4 GB			683 B		6291456		24 MB
		<8 GB			1024 B		8388608		32 MB
		<16 GB			1024 B		16777216	64 MB
		<32 GB			1365 B		25165824	96 MB
		<64 GB			2048 B		33554432	128 MB
		<128 MB			2731 B		50331648	192 MB
		<256 GB			2731 B		100663296	256 MB
		<512 GB			2731 B		201326592	512 MB
		ostatní			2731 B		402653184	1 GB

Tady vidíte, kam vedla naše snaha, aby velikost FAT nezabírala příliš místa na disku. Máme menší alokační jednotku než FAT32!. Je nutné si však uvědomit, že alokační jednotky musí mít násobek 512 bytů, což nám vlastně pomůže, protože tím zmenšíme počet potřebných popisovačů na alokační jednotky:



	Velikost disku		Velikost jednotky	Počet		Vel.tabulky
		<2 MB			512 B		4096		16 kB
		<16 MB			512 B		32768		128 kB
		<32 MB			512 B		65536		256 kB
		<64 MB			512 B		131072		512 kB
		<128 MB			512 B		262144		1 MB
		<256 MB			512 B		524288		2 MB
		<512 MB			512 B		1048576		4 MB
		<1 GB			512 B		2097152		8 MB
		<2 GB			512 B		4194304		16 MB
		<4 GB			1024 B		4194304		32 MB
		<8 GB			1024 B		8388608		64 MB
		<16 GB			1024 B		16777216	128 MB
		<32 GB			2048 B		16777216	256 MB
		<64 GB			2048 B		33554432	512 MB
		<128 MB			4098 B		33554432	1 GB
		<256 GB			4096 B		67108864	2 GB
		<512 GB			4096 B		134217728	4 GB
		ostatní			4096 B		268435456	8 GB

Všimněte si, že "naformátovaná" (vytvoření FAT a hlavního adresáře Root) disketa s naším formátem bude mít 2048-16 = 2032 kB volného místa pro soubory a adresáře (-položky v adresáři)!!! Samozřejmě systém je teď trochu nevýhodný pro obří disky, neboť (ač velikost tabulky není větší než 0.8% z celkové kapacity) roste velmi objem tabulky. Zkuste si zkopírovat 1 GB dat :-) Pravda, určitě nezaberete celou tabulku a také hlava nemusí projít celou tabulkou, když stačí vynásobit CISLO*4kB. Ovšem velké disky se většinou používají pro velké soubory a tedy nám tu zbytečně narůstá počet jednotek, které se musí alokovat. Kdybychom dali od 64 GB velikost jednotky 32 až 64 kB, velmi to zvýhodní velké soubory (velikost FAT nám navíc klesne na 1/8 až 1/16 pro 1 TB disky). A hlavně se zrychlí jejich čtení a zápis (pro každý blok totiž musí disk prozkoumat tabulku, aby zjistil, kde soubor pokračuje a pak se tam přesunout, a přečíst). Tuto FAT umístíte před (!) seznam adresářů a souborů, takže bude začínat hned po jádře (tj. sektor 11). Pokud nevíte, k čem slouží tato tabulka, tak si to nastudujte. Jen poznamenám, že každý Dword v ní označuje, zda daná jednotka někomu patří. Pokud ne, je v ní 0. Pokud je v ní cokoliv jiného, označuje to další alokační jednotku patřící stejnému souboru. A u nás samé "1" označují, že je to poslední blok ($ffffffff). Navíc můžete optimalizovat práci s disketou tak, že si ji celou nahrajete do paměti (nebo alespoň její FAT) a budete zapisovat do RAM. Teprve až po skončení všech zápisů (unmout :-D) zapíšete vše na disketu.

Seznam adresářů je potřeba udělat nějak dynamický, aby se mohl měnit. Pokud nevymyslíte lepší nápad, můžete udělat odkazy přímo do prostoru pro data. Tj. na konci FAT bude 1 alokační jednotka označovat kořenový adresář (čím větší, tím více položek se toho do něj vejde). V něm bude VEL_AL_JED div VEL_POLOZKY položek. Každá z nich může vypadat takto:


var	Polozka : record
		   Jmeno : array[0..31] of char;	{Také potřebujete 260 znaků?}
		   Typ : array[0..3] of char;		{4 znaková přípona}
		   AlokJednotka : longint;		{první alokační jednotka s daty}
		   Velikost : longint;			{v bytech; počet jednotek dopočítat}
		   Atributy : word;			{viz. Linux :-)}
		   Skupina : word;
		   Vlastnik : longint;
		   Datum : longint;			{čas+datum zhuštěně - změna}
		   Pristup : longint;			{kdy byl naposledy čten}
		   Rezerva : longint;
		  end;

Velikost našeho bloku je 64 bytů, tzn. že do 1 sektoru se vejdou odkazy na 4 soubory nebo adresáře. Zda je to soubor označuje např. nejvyšší bit v Atributech. Pokud je to 0, odkazuje AlokJednotka přímo na data. Pokud je tam "1", je to adresář a v dané jednotce jsou další seznamy. Velikost je významná pro oba. U souboru se dozvíte skutečnou velikost v bytech (pomocí DIV a MOD +1 vypočítáte počet jednotek), u adresáře DIV VEL_POLOZKY počet položek (a stejně jako u souboru i počet jednotek). Pokud je jméno = '', je položka nevyužita. Pokud byla poslední, tak se sníží velikost nadřazeného (položka v kořenovém adresáři zde bude mít 0, neboť už není nadřazený adresář, kam by se dalo odejít) adresáře o 64 bytů, pokud to byla první položka v alokační jednotce, tak se zároveň i ve FAT označí daná položka jako volná (tím může vznikat fragmentace, ale zase si uvolníte volné bloky blíže ke středu disku). Je potřeba nějak vyřešit odkaz na vyšší (nadřazený) adresář. Buď to bude Rezerva, kam dáte alokační jednotku, nebo např. první položka v adresáři.

Dobře, teď, když umíte spravovat soubory alespoň na vlastním systému (FAT16 a FAT12 je nutné se naučit také, pokud už ne rovnou FAT32, protože jinak do svého systému dostate ostatní data jedině přes sériový kabel, síť, nebo disketu, na kterou nahraje data Váš program pro DOS ve správném formátu pro Váš systém). Jak spustit program? Jednoduché: najdete na disku jeho soubor, nahrajete ho celý (nebo popř. jen část, ale zatím to nekomplikujme) do paměti, a předáme mu řízení (to je nejhodnější možný způsob, kdy program nijak neomezujete, ale také nemáte plnou kontrolu nad počítačem, tj. program Vás může přepsat, shodit systém, poškodit multitasking - pokud ho provozujete např. pomocí časovače, kdy po určité dobře předáváte řízení z programu na program pomocí změny CS a IP v zásobníku, atd.). Běžíme zatím v reálném módu (to je to nejjednodušší). Dobře, ale kam ho dáme do paměti? To je úplně jedno. Celá paměť, kterou dokážete obsáhnout pomocí segmentu a offsetu je Vaše. Pokud není nastavena brána A20, můžete alokovat 1 MB (1048592 B). Pokud je, máte k dispozici asi 1088 kB (1114112 bytů). Práce s bránou je popsána v AT Helpu (zkuste Rejstřík). Správu paměti si můžete zavést jakou chcete. Avšak, aby se předešlo plýtvání s popisovači, měli byste dovolit svým aplikacím alokovat určitou nejmenší jednotku. A protože jsme na 32 bitovém OS, měly by být údaje zadávány v Dwordech (začátek bloku paměti by také měl být zarovnán když už ne na 1 kB, tak na 16 B, či v nejhorším případě 4 byty). Pro registry typu ESI pak není problém alokovat cokoliv (pokud je ovšem máte povoleny, jinak pro Vás platí jen DI = 16 bitů na 1 segment - to ale neomezuje velikost paměti, kterou můžete aplikaci na 1 handle přidělit, jen ji nemůže adresovat celou přes 1 segment, ale musí ho posouvat). My máme ale jen 1 MB. Vaše jádro by si mělo vytvořit tabulku spuštěných aplikací, která zároveň bude udávat vlastníka paměti. Dále si vytvoříte tabulku bloků, které lze alokovat. Pokud aplikace skončí (nebo bude ukončena), můžete podle jejího čísla uvolnit veškerou paměť, kterou si nárokovala.


var	Aplikace : array[0..15] of record	{max. 16 současně běžících}
				    Cesta : string;	{cesta k souboru}
				    Nazev : array[0..31] of char;
				    DalsiInformace : xxx;
				   end;
	Bloky : array[0..127] of record		{pokud je 768 kB, tak 1 blok = 6 kB}
				  Vlastnik : word;
				  DalsiBlok : word;	{$ffff - poslední}
				 end;

Číslo bloku zároveň může určovat jeho handle. Tj. aplikace Vás požádá např. o 50 kB paměti. Vy najdete první blok, který nepatří nikomu (Vlastnik=$ffff). Toto číslo si zapamatujete a zkusíte najít ještě dalších 8 bloků (9*6 = 56 kB). Pokud je nenajdete, vrátíte chybu. Pokud ano, vrátíte číslo prvního bloku (nebo pointer, ale pak nemůžete hlídat, zda např. aplikace nezapisuje do paměti, která jí nepatří - ne že byste tomu zabránili, kdyby zapisovala přímo bez Vás), a daným blokům přiřadíte vlastníka (aplikaci). Je vhodné, aby velikost jednoho bloku byla tak 1 kB. Nezapomeňte, že ne celou paměť si můžete přivlastnit. Pod 1.MB jsou také ROM BIOSy. Ne, že by vadilo, že na dané adresy píšete (ono se to ztratí), ale k čemu by aplikacím byla paměť, do které nemohou nic zapsat? Je nutné prohledat RAM od $c800:0 výše (až do e000:0, nad touto adresou je většinou hlavní BIOS) a hledat na začátku každého 2 kB bloku značku $aa55 (uložena opět obráceně). Pokud ji tam najdete, třetí byte označuje velikost modulu v 512 bytových blocích (tj. 20 určuje 10 kB). Tuto velikost musíte označit jako nepoužitelnou (třeba $fffe) a poskočit o ní, a pokračovat v hledání, zda tam je či není další modul. Musíte také vědět, která paměť je už obsazená ještě před Vámi (VRAM, přerušení, atd.).

Pokud se Vám zdá 1 MB málo, můžete si zapnout tzv. Unreal mode. Poběžíte stále v Realu, tj. bez ochrany paměti, bez nutnosti se přepínat z chráněného režimu při obsluze přerušení nebo při práci s diskem přes INT 13h, ale máte k dispozici celou RAM (až do 4 GB). Tentokrát Vás žádný Windows nebo EMM386 neomezuje. Pokud potřebujete zároveň aplikace hlídat, musíte se ale už opravdu přepnout do chráněného režimu. Ale pozor! Žádné DPMI nebo VCPI neexistuje. Musíte procesor přepnout sami (zkuste ATHelp a "386 tam a zpátky" u PROTECTED MODE). V PM už dostanete automaticky celou přidělenou paměť. A zároveň ring 0 (nebo-li priorita 0). Tj. můžete všechno. Žádné porty pro Vás nejsou zapovězeny, taktéž žádné instrukce. Nyní máte počítač plně pod svou kontrolou. Ale také máte zároveň zodpovědnost. Přerušení BIOSu (včetně přístupu na disk) totiž nemůžete zpracovávat v PM. Musíte se pro ně vždy přepnout do RM. Nebo si napsat obsluhu disku sami pomocí portů a pak se přepínat nemusíte. Doporučuji nahlédnout do ATHelpu, nebo do článku, který sepisuje Laaca pro INT 21h. Také vřele nejprve doporučuji napsat si systém pro RM/uRM a teprve pak pro PM (až se naučíte principy). PM má tu výhodu, že můžete aplikace hlídat. Máte např. obsluhu INT = $d, které se předá kód vždy, když aplikace dělá něco, co nemá (čte jinou paměť, používá nepovolené instrukce nebo porty). CPU také poskytuje INT $e, který se vyvolá, pokud stránka, který program požaduje, není v paměti. Toto ovšem vyžaduje, aby někdo (Vy) swapoval paměť na disk a zpět (jinak se to nemůže stát). Pro Vás práce v PM hlavně znamená, že ani Vy, jakožto OS, si nemůžete dělat co chcete a i při spouštění programů se musíte držet předem daných zásad (deskriptory, atd.). Pokud si nechcete přidělávat starosti, běžte v Unrealu, a pro všechny bude život snažší (zapomeňte i na XMS a EMS, neboť tu zprostředkovává HIMEM.SYS). Pokud budou aplikace dodržovat Vaše zásady, poběží všichni rychleji a šťastněji. Dokonce i multitasking tu budete mít (pověste se např. na časovač a přepínejte postupně úlohy), dokud Vám ho nějaká jiná aplikace nepřepíše :-).

Operační systém také poskytuje nějaké služby. DOS to např. dělá (nejen) na "int $21". Pokud chcete pod Vaším OS spouštět DOSové aplikace (nebudu si namlouvat, že jste tak daleko), musíte si vzít příručku DOSu a naemulovat všechny DOSové služby (a navíc správě zavádět EXE soubory do paměti) a XMS, EMS, atd.. Váš OS ale může nabízet i vlastní služby. Prostě obsadíte nějaký vektor přerušení a na něm budete čekat. Když aplikace bude něco chtít, vloží číslo služby do nějakého registru (třeba EAX) a zavolá Vaše přerušení. Tam se spustí Vaše rutina, přečte si EAX registr a podle něj něco vykoná (obsluha grafiky, disku, CD-ROM, klávesnice, myše, atd.). Nejjednodušší je psát programy pro Váš OS jako COM (BIN) bez ORG 256, protože tak budete mít v paměti jen kód. Pravda, velikost souborů může být omezena, ale nic Vám nebrání zavést si systém typu DLL, kdy jiný COM bude jakoby knihovna a pokud aplikace zažádá, nahrajete ho do paměti a předáte řízení. V něm bude na začátku seznam procedur (JMPy na ně), které budou končit RETem. Toto je velmi otrocký systém, ale jednoduchý na správu (asi ne na naprogramování).

A jak bude vypadat Vaše jádro? To už je zcela Váš problém. Jediné, co by tam mělo být, je direktiva "ORG 0" na začátku. První instrukce, kterou tam vložíte, bude spuštěna z Vašeho zavaděče. Předně byste se měli pokusit do Vašeho jádra naimplementovat 3 základní služby: práce se soubory a adresáři na disku (opravdu jen základní čtení položky/dat, zápis), alokace paměti (a uvolňování), a spouštění programů. Pokud bude potřeba něco dalšího, udělejte si to jako další modul (program), který jádro nahraje do paměti a spustí. Včetně příkazového řádku, obsluhy klávesnice, zvuku, grafiky, textu, atd. (jakmile bude modul pro práci s klávesnicí jednou v paměti, je jedno, že ho volá Příkazový řádek - pro něj to bude totéž, jako kdyby volal svou proceduru). Jádro by mělo být co nejmenší to půjde. Moduly si hlídat nemusíte. Ty by měly být rychlé a zároveň bez chyb. Pokud budou špatné, tak je to chyba toho, kdo je napsal. Aplikace byste ale už hlídat měli. Existuje samozřejmě kontrola i v RM. Každou aplikaci budete debugovat. Jenže to je pomalé - jedná se defakto o emulaci: před každou instrukcí se vyvolá přerušení 1 a Vy zkontrolujete, co instrukce dělá a pokud usoudíte, že je to v pořádku, necháte ji vykonat, v opačném případě program sestřelíte. Je to pomalejší než Ochrana v PM, ale ne zase až tak složité (jako pochopit více méně všechny principy PM).

2006-11-30 | Martin Lux
Reklamy: