2006 07 Jądro systemu operacyjnego [Inzynieria Oprogramowania]


Inżynieria
oprogramowania
Jądro systemu operacyjnego
Grzegorz Pełechaty
rojektowanie oraz programowanie syste- z komputerem stała by się prostsza i mniej zawod-
mów operacyjnych stanowi z całą pewno- na. W tym miejscu pojawia się miejsce na system
Pścią jedną z bardziej rozwojowych dziedzin operacyjny, który jest programem mającym za za-
informatyki. Na rynku pojawiają się nowe typy pro- danie zarządzanie sprzętem i udostępnianie w pro-
cesorów, które oferują coraz to bardziej zaawanso- stej formie zestawu funkcji, dzięki którym przeciętny
wane możliwości. Obecnie największą popularno- człowiek odnajdzie się binarnym świecie.
ścią wśród zastosowań domowych cieszą się pro-
cesory z rodziny x86. Ich rozwój jest ściśle powią- Zarządzanie pamięcią
zany z rosnącymi wymaganiami użytkowników. w trybie rzeczywistym
Pierwsze procesory 80186 działały jedynie w try- Tak jak już wspomniałem, w trybie rzeczywistym mo-
bie rzeczywistym (ang. real mode/real address mo- żemy zaadresować jedynie 1MB pamięci, a całe za-
de), który ograniczał się do możliwości adresowa- rządzanie tym obszarem sprowadza się do odgórne-
nia megabajta pamięci operacyjnej (20 bitowa szy- go podzielenia go na segmenty. Do dyspozycji pro-
na adresowa). Począwszy od procesora 80286 te gramisty oddano 64K segmentów, każdy segment
restrykcyjne ograniczenia powoli zmniejszały się, zaczyna się co 16-ty bajt. Jako że limit segmentu wy-
i tak w procesorze 80286 po raz pierwszy wprowa- nosi 64KB, muszą one nachodzić na siebie. Takie
dzono tryb chroniony (ang. protected mode) oraz rozwiązanie umożliwia adresowanie każdej komórki
umożliwiono adresowanie 16 megabajtów pamięci pamięci na wiele sposobów. W trybie rzeczywistym
(24 bitowa szyna adresowa, ale procesor pozostał adresowanie odbywa się zawsze poprzez podanie
nadal 16 bitowy). Jednak prawdziwe zmiany wniósł dwóch 16bitowych wartości: numeru segmentu oraz
procesor 386DX, który w pewnym sensie zrewo- przesunięcia w nim. Aby obliczyć adres liniowy nale-
lucjonizował rynek komputerowy, a wykorzystane ży użyć wzoru: segment*16+przesunięcie.
w nim rozwiązania są powszechnie stosowane do
dziś. Główną zaletą tego procesora było wprowa- Przerwania w trybie rzeczywistym
dzenie możliwości 32-bitowego, chronionego trybu W trybie rzeczywistym mamy do dyspozycji 256 prze-
pracy. Umożliwiało to adresowanie do 4GB pamię- rwań. Wliczamy w to przerwania programowe oraz
ci. 32-bitowy tryb chroniony wniósł szereg innych sprzętowe. Przerwania programowe, jak sama na-
udogodnień. Stary sposób zarządzania pamięcią zwa wskazuje dają nam możliwość zaprogramowania
poprzez segmentację został wyparty przez stron- się, czyli ustalenia gdzie procesor ma przeskoczyć po
nicowanie (ang. paging), które jest bardziej dosto- wywołaniu instrukcji INT. Przerwania sprzętowe  IRQ
sowane do wymagań programisty oraz nie zawie- różnią się tylko tym, że oprócz możliwości wywołania
ra tylu ograniczeń co segmentacja (chodzi głównie ich bezpośrednio z kodu programu, mogą być również
o limit wpisów w lokalnej tablicy deskryptorów). wywołane przez fizyczne urządzenie. Każda linia IRQ
Jednym z ważniejszych udogodnień, jakie wpro- jest przypisana do innego wektora przerwań w IVT
wadził tryb chroniony jest możliwość tworzenia wie- (ang. Interrupt Vectors Table).
lu procesów, z których każdy wykonuje pewien pro- IVT jest tablicą, która zawiera 256 wpisów typu
gram. W architekturze x86 do tych celów stworzono segment:offset. Każdy taki wpis jest nazywany wek-
specjalne segmenty w globalnej tablicy deskrypto- torem przerwania. Kiedy procesor otrzymuje polece-
rów, które określają stan każdego procesu (ang. Task nie wykonania przerwania, musi znać nowy CS i IP
State Segment). Wszystkie te rozwiązania są jednak punktu wejścia programu obsługi.
bezużyteczne bez odpowiedniego programu, któ- Wartości te pobiera z IVT wykonując następują-
ry mógłby je rozsądnie wykorzystać, przez co praca ce obliczenia:
" Segment=IVT[int*4]
Autor jest od 7 lat programistą języka C. Interesuje się
" Offset=IVT[int*4+2]
zagadnieniami systemów operacyjnych, elektroniką i sie-
ciami neuronowymi. Obecnie pracuje nad projektem dar-
Tak więc jeden wektor zajmuje w IVT dokładnie 4baj-
mowego systemu sieciowego, opartego o jądro monoli-
ty. Przed przejściem do programu obsługi przerwa-
tyczne, oraz w pełni zgodnego ze standardami POSIX
(http://www.netcorelabs.org). System jest rozpowszech- nia, procesor odkłada na stos następujące rejestry:
niany na warunkach licencji General Public License v2.
Kontakt z autorem: grzegorz.pelechaty@areoos.com
" SS - segment stosu,
" SP  obecne przesunięcie w segmencie stosu,
48
www.sdjournal.org
Software Developer s Journal 7/2006
Programowanie systemów operacyjnych
Listing 1. Struktura deskryptora segmentu w Globalnej
Tablicy Deskryptorów
Mapa pierwszego megabajtu pamięci
struct gdt_seg_desc {
" 00000000  000003FF Tablica wektorów przerwań
unsigned short len15_0;
" 00000400  000004FF Obszar danych biosu
unsigned short base15_0;
" 00000500  0009FBFF Pamięć konwencjonalna (640KB)
unsgined char base23_16;
" 00007C00  00007DFF Program rozruchowy
unsigned char flags1;
" 0009FC00  0009FFFF Rozszerzony obszar danych biosu
(EBDA) unsigned char flags2;
" 000A0000  000BFFFF Pamięć VGA (128KB)
unsigned char base31_24;
" 000A0000  000AFFFF Bufor ramki VGA(64KB)
};
" 000B0000  000B7FFF Pamięć dla kart monochromatycznych
(32KB)
" 000B8000  000BFFFF Pamięć dla kart kolorowych (32KB)
Zarządzanie pamięcią
" 000C0000  000C7FFF BIOS karty graficznej (32KB  ROM)
w trybie chronionym
" 000F0000  000FFFFF BIOS płyty głównej (64KB  ROM)
W trybie chronionym istnieją dwa mechanizmy zarządzania
pamięcią. Poprzez segmentację oraz stronicowanie. Naj-
pierw postaram się przybliżyć pojęcie segmentacji, ponie-
" FLAGS  flagi procesora, waż wygląda ona trochę inaczej, niż miało to miejsce w try-
" CS - segment kodu, bie rzeczywistym.
" IP  obecne przesunięcie w segmencie kodu (licznik in- Znana z trybu rzeczywistego zamiana zawartości reje-
strukcji). strów segmentowych i przesunięcia na adres fizyczny tra-
ci sens w trybie chronionym. Tutaj segmenty są od sie-
Rejestry są odkładane po to, aby po powrocie z przerwa- bie odseparowane i chociaż nadal są dostępne programo-
nia, program mógł dalej kontynuować swoje działanie. Stos wo, interpretacja ich zawartości jest zupełnie inna. Rejestr
wraca do poprzedniej wartości, ponieważ dane odłożone segmentowy przechowuje teraz selektor segmentu, a nie
na nim przez program obsługi przerwania są teraz bezu- wprost jego adres. 13 najstarszych bitów tego rejestru sta-
żyteczne. nowi adres 8bajtowej struktury opisującej dany segment
(ang. Segment Descriptor). Z pozostałych trzech bitów dwa
Tryb chroniony i pierścienie ochrony poświęcone zostały na implementację czteropoziomowe-
Pierścienie ochrony (ang. Protection rings) są to pozio- go systemu praw dostępu do segmentu, a jeden określa
my uprzywilejowania, jakie zastosowano w procesorach IA- czy wspomniany powyżej adres odnosi się do tzw. tablicy
286p+. System uprawnień jest dosyć rozbudowany i opiera lokalnej czy globalnej. Rekordami w tych tablicach są wła-
się o czteropoziomowy układ zabezpieczeń, w którym pier- śnie deskryptory segmentów. Każdy z nich zawiera jedno-
ścień zerowy jest najbardziej uprzywilejowany, a trzeci posia- znaczną informację o lokalizacji segmentu w pamięci i jego
da znaczne ograniczenia. Uprawnienia te obowiązują w pra- rozmiarach. W ten sposób zdefiniowany jest spójny obszar
wie wszystkich elementach trybu chronionego. Jedynym wy- o adresie początkowym wyznaczonym przez liczbę 32-bi-
jątkiem jest stronicowanie, które będzie dokładniej omówione tową. Na liczbę określającą rozmiar takiego bloku przezna-
w kolejnej części cyklu. czone zostało pole 20-bitowe. Istnieją dwie możliwości in-
Listing 2. Funkcja tworząca nowy segment w Globalnej
Spis wektorów linii IRQ w trybie
Tablicy Deskryptorów
rzeczywistym
struct gdt_seg_desc *gdt_table=
" Linia IRQ Wektor Urządzenie generujące syngnał IRQ
(struct gdt_seg_desc)GDT_ADDRESS;
" 0 08h Zegar systemowy
void createSegment(
" 1 09h Klawiatura
int pos, unsigned long base, unsigned long len,
" 2 0Ah Wyjście kaskadowe do układu Slave
unigned char flags1, unsigned char flags2 ) {
" 3 0Bh Port COM2
gdt_table[pos].len15_0 = (unsigned short)(
" 4 0Ch Port COM1
len & 0xFFFF);
" 5 0Dh Port LPT2
" 6 0Eh Kontroler napędu dysków elastycznych gdt_table[pos].base15_0 = (unssigned short)(
" 7 0Fh Port LPT1
base & 0xFFFF);
" 8 70h Zegar czasu rzeczywistego (RTC)
gdt_table[pos].base23_16 = (unsigned char)(
" 9 71h Wywołuje przerwanie IRQ2
(base >> 16) & 0xFF);
" 10 72h Zarezerwowane
gdt_table[pos].flags1 = flags1;
" 11 73h Zarezerwowane
gdt_table[pos].flags2 = flags2 | ((len >> 16) & 0xf);
" 12 74h Zarezerwowane
gdt_table[pos].base31_24 = (unsigned char)(
" 13 75h Koprocesor arytmetyczny
(base & 0xF000) >> 24);
" 14 76h Kontroler dysku twardego
}
" 15 77h Zarezerwowane
Software Developer s Journal 7/2006 www.sdjournal.org
49
Inżynieria
oprogramowania
bitowy, budowany jest ze złożenia zawartości 16bitowe-
Listing 3. Definicje poszczególnych bitów we flagach
go rejestru segmentowego i 32bitowego rejestru przesu-
deskryptora segmentu
nięcia. W przypadku ziarnistości 4KB maksymalny roz-
Opis definicji poszczególnych flag segmentu: miar segmentu wynosi 4GB. Liczba możliwych segmen-
// FLAGS1 (P + DPL + SYS/APP + TYPE) tów to 2^14(2^13 deskryptorów lokalnych i tyle samo glo-
#define GDT_PRESENT 0x80 balnych), co daje w sumie astronomiczną objętość 64TB
#define GDT_DPL3 0x60 (2^14*2^32). Właściwie już jeden taki segment stanowi
#define GDT_DPL1 0x20 wielkość optymalną  4GB przestrzeni adresowej zaspo-
#define GDT_DPL2 0x40 kaja przy obecnym rozwoju techniki PC dość wygórowa-
#define GDT_DPL0 0x00 ne wymagania. Rozwiązanie takie, określane jako  płaski
model pamięci (ang. flat memory model), stosowane jest
// GDT_SYS będzie poruszone podczas omawiania w systemie Windows NT.
// wielozadaniowości. Obecnie interesuje nas
// tylko GDT_APP, które przeznaczone jest dla segmentu Zarządzanie
// danych lub kodu. Globalną Tablicą Deskryptorów
#define GDT_SYS 0x00 Listing 2 zawiera funkcję, która tworzy nowy segment w
#define GDT_APP 0x10 GDT. Struktura opisująca pojedynczy deskryptor segmen-
tu znajduje się na Listingu 1. Ustalmy jeszcze raz jak obli-
// Dodatkowe flagi dla segmentów innych niż data lub czyć selektor danego segmentu. Na selektor składa się ad-
// code (GDT_SYS) res deskryptora względem początku Globalnej Tablicy De-
#define GDT_RESERVED 0x0 skryptorów oraz poziom uprzywilejowania i informacja o
#define GDT_TSS16 0x1 // 0001 16 bitowy TSS (dostępny) tym czy segment znajduje się w tablicy lokalnej, czy global-
#define GDT_LDT 0x2 // 0010 LDT nej.
#define GDT_TSS16_BUSY 0x3 // 0011 16 bitowy TSS (zajęty)
#define GDT_CALL16 0x4 // 0100 16 bitowa bramka wywołań selector=numer_segmentu*sizeof(struct gdt_desc)+DPL+ (
#define GDT_TASK 0x5 // 0101 Bramka zadania 4  - jeżeli deskryptor jest w LDT)
#define GDT_INT16 0x6 // 0110 16 bitowa bramka
// przerwania Sama tablica GDT jest opisana specjalnym, 48bitowym de-
#define GDT_TRAP16 0x7 // 0111 16 bitowa bramka pułapki skryptorem. Struktura opisująca ten deskryptor wygląda na-
#define GDT_TSS32 0x9 // 1001 32 bitowy TSS (dostępny) stępująco:
#define GDT_TSS32_BUSY 0xB // 1011 32 bitowy TSS (zajęty)
#define GDT_CALL32 0xC // 1100 32 bitowa bramka wywołań struct gdt_desc {
#define GDT_INT32 0xE // 1110 32 bitowa bramka unsigned short gdt_size;
// przerwania unsigned long gdt_address;
#define GDT_TASK_GATE 0xF // 1111 32 bitowa bramka pułapki } __atribute__((packed));
// Dla GDT_APP
#define GDT_DATA 0x00 Pierwsze 16 bajtów powinno zawierać rozmiar tablicy GDT mi-
#define GDT_WRITE 0x02 nus 1bajt, czyli 8192*8-1.
#define GDT_EXP_DOWN 0x04 Tablicę ładujemy poleceniem lgdt, które przyjmuje fizycz-
#define GDT_CODE 0x08 ny adres deskryptora w pamięci (w postaci bezpośredniego
#define GDT_READ 0x02 adresu, bądz też rejestru). Musimy pamiętać, że pierwszy de-
#define GDT_CONF 0x04 skryptor jest zawsze pusty. Nie ma możliwości, aby został on
wykorzystany w jakikolwiek sposób przez system.
// FLAGS2 (G + D/B + 0 + AVL)
// Ziarnistość danego segmentu Jądro systemu
#define GDT_GRANULARITY 0x80 Teraz spróbujemy zebrać te wszystkie informacje w spójną
// Rodzaj segmentu  32 lub 16 bitowy całość i napiszemy proste jądro systemu operacyjnego. Nie
#define GDT_USE32 0x40 będzie to oczywiście jądro, które byłoby w stanie zrobić co-
#define GDT_USE16 0x00 kolwiek, ale po uruchomieniu zobaczymy napis hello world i to
// Segment do dowolnego użytku przez system powinno nam na razie wystarczyć.
#define GDT_AVAIL 0x00
Listing 4. Nagłówek multiboot dla wykonywalnych plików
ELF
terpretowania liczby w tym polu. W trybie 1:1 (ziarnistość
1B) rozmiar maksymalny wynosi po prostu 2^20 = 1MB. struct multiboot_header {
Gdyby jednak przyjąć jednostkę 4KB (ziarnistość 4KB), roz- unsigned long magic;
miar segmentu może sięgać do 2^20*2^12 = 2^32 = 4GB. unsigned long flags;
Informacja o tym, która z konwencji aktualnie obowiązuje, unsigned long checksum;
zawarta jest w deskryptorze. };
Adres logiczny, do którego odwołuje się procesor 32-
50
www.sdjournal.org
Software Developer s Journal 7/2006
Programowanie systemów operacyjnych
Listing 5. Kod inicjalizacyjny jądra
.text addl $8, %edi
.globl _start dec %ecx
_start: jne fill_gdt
jmp multiboot_entry 1:
.align 4 lgdt gdt
multiboot_header: ljmp $(0x10), $go
.long 0x1BADB002 go:
.long 0x00000003 movl $(0x18), %eax
.long -(0x1BADB002 + 0x00000003) movl %eax, %ds
multiboot_entry: movl %eax, %es
movl $(stack+100) ,%esp movl %eax, %fs
call setup_gdt movl %eax, %gs
call __main movl %eax, %ss
mbi: ret
.long 0x0 .data
setup_gdt: gdt: .word 0x2000*8-1
movl $gdt_table, %esi .long 0xA000
movl $0xA000, %edi gdt_table:
movl $8, %ecx .quad 0x0000000000000000 # pusty deskryptor
rep .quad 0x0000000000000000 # nie używamy
movsl .quad 0x00cf9a000000ffff # 0x10 kernel 4GB code at
movl $0xA000+8*8, %edi 0x00000000
movl $0x2000-8, %ecx .quad 0x00cf92000000ffff # 0x18 kernel 4GB data at
fill_gdt: 0x00000000
movl $0, (%edi) .comm stack, 0x500
movl $0, 4(%edi)
Zestaw Narzędzi języka wysokiego poziomu, musimy również posiadać kompi-
Do rozpoczęcia prac nad pisaniem własnego systemu ope- lator asemblera. Szereg programów z pakietu binutils pomo-
racyjnego niezbędny będzie nam pewien zestaw narzędzi, że nam w skonsolidowaniu całego obrazu. Użycie emulatora
który w znacznej mierze ułatwi nam to zadanie: PC jest wysoce wskazane, ponieważ dzięki niemu nie będzie-
my zmuszeni za każdym razem restartować komputera w ce-
" edytor tekstu lu sprawdzenia poprawności naszego kodu. Ostatnim progra-
" GCC & GAS mem jaki musimy posiadać jest program rozruchowy zgodny
" GNU binutils (ld, make) ze standardem multiboot. Przykładem takiego programu jest
" opcjonalnie emulator (qemu/vmware) GRUB, który staje się coraz bardziej powszechny. Oczywiście
" program rozruchowy zgodny ze standardem multiboot możemy napisać własny program rozruchowy, jednak mija się
to z celem. GRUB zagwarantuje nam zgodność z większością
Edytor tekstu będzie nam oczywiście potrzebny do pisania ko- sprzętu oraz przejdzie za nas w tryb chroniony tym samym
du. Kompilatory gcc i gas posłużą do jego skompilowania. Po- oddając w nasze ręce w pełni 32-bitowe środowisko pracy.
nieważ nie jest możliwe napisanie jądra jedynie przy użyciu Wszystkie wymienione powyżej narzędzia są rozpowszech-
R E K L A M A
Inżynieria
oprogramowania
ustawiony na 4GB, a co za tym idzie ziarnistości segmentu mu-
Listing 6. Przykładowe jądro systemu, wypisujące napis
si wynosić 4KB. Segment ten ma uprawnienia pierścienia 0. Ko-
 hello world w lewym górnym rogu ekranu
lejny segment jest przeznaczony na dane. Ma on taki sam adres
static char *video=(char *)0xB8000; bazowy i limit jak segment kodu, jednak różni się typem. Po wy-
void clrscr(void) { pełnieniu GDT, wywołujemy funkcję _ main(), która będzie po-
int i; czątkiem naszego właściwego jądra.
for(i=0;i<80*50;i+=2) {
video[i]=32; Kernel
video[i+1]=0x7; Na razie nasze jądro nie będzie zbytnio rozbudowane. Napi-
} szemy prostą funkcję czyszczącą ekran w tekstowym trybie
} VGA oraz funkcję wypisującą ciąg znaków w lewym górnym
void puts(char *msg) { rogu ekranu
char *ptr=video; Jak widać na Listingu 6, po wypisaniu komunikatu koń-
while(*msg) { czymy funkcję nieskończoną pętlą. Jest to jedyne wyjście po-
*ptr++=*msg++; nieważ nie mamy systemu do którego moglibyśmy powrócić.
*ptr++=0x7; Gdybyśmy jednak kontynuowali działanie, licznik instrukcji (re-
} jestr EIP) wskazywałby na pamięć, nie zawierającą instruk-
} cji procesora, co spowodowałoby wywołanie 6 wyjątku. Pro-
void __main(void) { cesor próbując wywołać przerwanie nr 6 natrafiłby na kolejny
clrscr(); problem, ponieważ nie mamy załadowanej tablicy IDT (ang.
puts("hello world!"); Interrupt Descriptors Table). Wtedy wystąpiłby potrójny błąd
for(;;); (ang. Triple fault), który wiąże się z natychmiastowym restar-
} tem procesora.
Kompilacja
niane na warunkach General Public License, więc są całkowi- Przy kompilacji tak dużych projektów, jakimi są systemy ope-
cie darmowe. racyjne bardzo dobrym rozwiązaniem jest zastosowanie pli-
ków make. N a Listingu 7 przedstawiono sposób użycia tych
Kod inicjacyjny jądra plików, w oparciu o zródłowe pliki, które stworzyliśmy wcze-
Aby GRUB mógł załadować jądro, musi ono posiadać specjalny śniej. Mowa o kodzie inicjacyjnym, który powinniśmy zapi-
nagłówek informacyjny. Adres tego nagłówek musi być wyrów- sać w pliku init.S oraz zródle jądra, które powinno nosić na-
nany do czterech bajtów. Struktura opisująca nagłówek multi- zwę main.c
boot dla wykonywalnych plików ELF znajduje się na Listingu 4. Listing 7 zapisujemy pod nazwą makefile w katalogu ze
Standardowe wartości jakie powinny być użyte w naszym zródłami.
wypadku to:
Listing 7. Plik makefile dla jądra
magic=0x1BADB002
flags=0x00000003 CC=gcc
checksum=-(0x1BADB002 + 0x00000003) LD=ld
OBJS=init.o main.o
Na Listingu 5 wypełniamy GDT czterema deskryptorami. De-
skryptor numer 2 wskazuje na segment kodu, którego limit jest CFLAGS = -fno-builtin -nostdlib -nostdinc -Wno-main -O2
all: $(OBJS)
$(LD) -Tkernel.lds -S -X -o kernel --start-group $(OBJS)
--end-group
.c.o:
$(CC) $(CFLAGS) -c $< -o $@
.S.o:
$(CC) $(CFLAGS) -traditional -c $< -o $@
CLEAN_FILES = $(OBJS)
clean:
rm -rf $(CLEAN_FILES)
dep:
find . -name '*.c' -o -name '*.S'|xargs gcc -M $(CFLAGS)
> .depend
ifeq (.depend,$(wildcard .depend))
include .depend
endif
Rysunek 1. Jądro systemu uruchomione pod emulatorem
Vmware
52
www.sdjournal.org
Software Developer s Journal 7/2006
Programowanie systemów operacyjnych
Listing 8. Przykładowy skrypt dla programu
W Sieci
konsolidującego
" Polski portal poświęcony programowaniu systemów operacyj-
OUTPUT_FORMAT("elf32-i386")
nych: http://www.areoos.com/osdevpl
OUTPUT_ARCH(i386)
" Ogólnoświatowe forum programistów systemów operacyj-
ENTRY(_start)
nych: http://www.osdev.org
SECTIONS
" Portal związany z programowaniem systemów, z wieloma kur-
{
sami oraz przykładami: http://www.osdever.net/
. = 0x100000 + SIZEOF_HEADERS;
" Strona poświęcona głównie systemowi operacyjnemu Alt+Ctrl+
.text : {
Del, zawiera również obszerną listę innych projektów: http://
*(.text)
www.acd.prv.pl
}
Konsolidacja . = ALIGN(16);
Jak zapewne zauważyliście na Listingu 7 jednym z argumen- .rodata : {
tów programu LD był plik kernel.lds. *(.rodata)
Pod tą nazwą zapisujemy skrypt dla programu konsoli- }
dującego, który przechowuje informacje o tym, gdzie umie-
ścić poszczególne segmenty w pliku oraz jaki ma być for- . = ALIGN(16);
mat wyjściowy i offset, pod który zostanie skompilowane .data : {
nasze jądro. Przykład takiego skryptu znajduje się na Li- *(.data)
stingu 8. CONSTRUCTORS
Jak widzicie jądro zostanie skompilowane z 1MB prze- }
sunięciem względem początku segmentu kodu. Tak więc
zostawiamy praktycznie nienaruszony pierwszy megabajt . = ALIGN(16);
pamięci (pomijając 64KB tablicę GDT, która znajduje się _edata = .;
poniżej pierwszego megabajata). Ponadto obraz wyjściowy .bss : {
będzie w formacie pliku wykonywalnego ELF, dzięki czemu *(.bss)
GRUB nie będzie miał problemu ze zidentyfikowaniem pliku }
(GRUB obsługuje wiele formatów plików wykonywalnych,
min. pliki executable ELF oraz pliki binarne) . = ALIGN(16);
_end = .;
Konfiguracja GRUB'a }
Ostatnią czynnością, jaką musimy wykonać jest skonfigu-
rowanie naszego programu rozruchowego, czyli GRUB'a.
W tym celu edytujemy plik  /boot/grub/grub.conf i dodajemy nel.lds. Po skopiowaniu obrazu do katalogu /boot restartu-
na końcu następujące linie: jemy komputer i w menu wyboru GRUB'a wybieramy pozy-
cję MyOS. Jeżeli wszystko poszło pomyślnie powinniśmy
title MyOS zobaczyć identyczny efekt jak na Rysunku 1.
root (hdD,P)
kernel /boot/kernel Podsumowanie
Tak jak się przekonaliście, pisanie systemu operacyjnego to
W miejsce D i P wstawiamy odpowiednio numer dysku i party- dość trudne i czasochłonne zajęcie,
cji, na której mamy nasze jądro (jest ta sama partycja z której które zabierze cenne chwile z waszego życia, ale da rów-
został uruchomiony obecnie działający system). nież niesamowitą satysfakcję z tego, że stworzyliśmy coś wy-
jątkowego. Sami wytyczyliśmy standardy i nie musieliśmy być
Testowanie podporządkowani innym. I chyba to w tym wszystkim jest naj-
Teraz przyszła kolej na przetestowanie naszego syste- ważniejsze. Jeżeli zdecydujecie się na pisanie własnego sys-
mu, jednak przed tym musimy go skompilować i skopio- temu, pamiętajcie, że najlepiej uczyć się na własnych błędach.
wać tak powstały obraz do katalogu /boot. W celu skompi- Starajcie się znajdować rozwiązanie na konkretny problem po-
lowania naszego jądra wpisujemy polecenie make, w kata- przez testowanie własnych pomysłów, a dopiero potem sięgaj-
logu gdzie znajdują się zródła wraz z plikami makefile i ker- cie do kursów, tutoriali itp... Ta dziedzina informatyki wymaga
przede wszystkim kreatywności, a nie biernego implementowa-
nia gotowych rozwiązań.
Literatura
W następnej części cyklu przedstawię najbardziej znane
metody programowego zarządzania pamięcią oraz napiszemy
" [1] Piotr Metzger, Michał Siemieniacki, Anatomia PC wydanie
prosty alokator pamięci oparty o listę dwukierunkową. Mowa
IX, Helion 2004,
będzie również o stronicowaniu, przerwaniach i pamięci wirtu-
" [2] Uresh Vahalia, Jądro systemu Unix  - nowe horyzonty,
alnej, omówimy także sposób działania mechanizmu wymiany
Wydawnictwa Naukowo-Techniczne Warszawa 2001
pamięci pomiędzy dyskiem a pamięcią fizyczną. n
Software Developer s Journal 7/2006 www.sdjournal.org
53


Wyszukiwarka

Podobne podstrony:
2006 09 Wielozadaniowość w systemach operacyjnych [Inzynieria Oprogramowania]
2006 08 Zarządzanie pamięcią w systemach operacyjnych [Inzynieria Oprogramowania]
2006 06 Wstęp do Scrum [Inzynieria Oprogramowania]
2006 04 Rozszerzenie wzorca Template [Inzynieria Oprogramowania]
2006 03 XFire w akcji [Inzynieria Oprogramowania]
2006 10 Przegląd modeli cyklu życia oprogramowania [Inzynieria Oprogramowania]
,Inżynieria oprogramowania L, operacje w bazie danych biblioteki
Wstęp do systemów operacyjnych i oprogramowania
2006 10 Łączenie kodu C z zarządzanym kodem NET [Inzynieria Oprogramowania]
System operacyjny i oprogramowanie dla fotografa
Systemy Operacyjne 07 12 2010 Kontekst i jego połączenie
2007 07 Wykorzystanie przypadków użycia do modelowania zachowania [Inzynieria Oprogramowania]
2007 07 Wykorzystanie przypadków użycia do modelowania zachowania [Inzynieria Oprogramowania]
2006 05 Antywzorce w zarządzaniu projektami informatycznymi [Inzynieria Oprogramowania]
2006 09?ta Protection API i NET Framework 2 0 [Inzynieria Oprogramowania]

więcej podobnych podstron