2006 07 Jądro systemu operacyjnego [Inzynieria Oprogramowania]

background image

48

Inżynieria

oprogramowania

www.sdjournal.org

Software Developer’s Journal 7/2006

Jądro systemu operacyjnego

P

rojektowanie oraz programowanie syste-

mów operacyjnych stanowi z całą pewno-

ścią jedną z bardziej rozwojowych dziedzin

informatyki. Na rynku pojawiają się nowe typy pro-

cesorów, które oferują coraz to bardziej zaawanso-

wane możliwości. Obecnie największą popularno-

ścią wśród zastosowań domowych cieszą się pro-

cesory z rodziny x86. Ich rozwój jest ściśle powią-

zany z rosnącymi wymaganiami użytkowników.

Pierwsze procesory 80186 działały jedynie w try-

bie rzeczywistym (ang. real mode/real address mo-
de
), który ograniczał się do możliwości adresowa-

nia megabajta pamięci operacyjnej (20 bitowa szy-

na adresowa). Począwszy od procesora 80286 te

restrykcyjne ograniczenia powoli zmniejszały się,

i tak w procesorze 80286 po raz pierwszy wprowa-

dzono tryb chroniony (ang. protected mode) oraz

umożliwiono adresowanie 16 megabajtów pamięci

(24 bitowa szyna adresowa, ale procesor pozostał

nadal 16 bitowy). Jednak prawdziwe zmiany wniósł

procesor 386DX, który w pewnym sensie zrewo-

lucjonizował rynek komputerowy, a wykorzystane

w nim rozwiązania są powszechnie stosowane do

dziś. Główną zaletą tego procesora było wprowa-

dzenie możliwości 32-bitowego, chronionego trybu

pracy. Umożliwiało to adresowanie do 4GB pamię-

ci. 32-bitowy tryb chroniony wniósł szereg innych

udogodnień. Stary sposób zarządzania pamięcią

poprzez segmentację został wyparty przez stron-

nicowanie (ang. paging), które jest bardziej dosto-

sowane do wymagań programisty oraz nie zawie-

ra tylu ograniczeń co segmentacja (chodzi głównie

o limit wpisów w lokalnej tablicy deskryptorów).

Jednym z ważniejszych udogodnień, jakie wpro-

wadził tryb chroniony jest możliwość tworzenia wie-

lu procesów, z których każdy wykonuje pewien pro-

gram. W architekturze x86 do tych celów stworzono

specjalne segmenty w globalnej tablicy deskrypto-

rów, które określają stan każdego procesu (ang. Task
State Segment
). Wszystkie te rozwiązania są jednak

bezużyteczne bez odpowiedniego programu, któ-

ry mógłby je rozsądnie wykorzystać, przez co praca

z komputerem stała by się prostsza i mniej zawod-

na. W tym miejscu pojawia się miejsce na system

operacyjny, który jest programem mającym za za-

danie zarządzanie sprzętem i udostępnianie w pro-

stej formie zestawu funkcji, dzięki którym przeciętny

człowiek odnajdzie się binarnym świecie.

Zarządzanie pamięcią

w trybie rzeczywistym

Tak jak już wspomniałem, w trybie rzeczywistym mo-

żemy zaadresować jedynie 1MB pamięci, a całe za-

rządzanie tym obszarem sprowadza się do odgórne-

go podzielenia go na segmenty. Do dyspozycji pro-

gramisty oddano 64K segmentów, każdy segment

zaczyna się co 16-ty bajt. Jako że limit segmentu wy-

nosi 64KB, muszą one nachodzić na siebie. Takie

rozwiązanie umożliwia adresowanie każdej komórki

pamięci na wiele sposobów. W trybie rzeczywistym

adresowanie odbywa się zawsze poprzez podanie

dwóch 16bitowych wartości: numeru segmentu oraz

przesunięcia w nim. Aby obliczyć adres liniowy nale-

ży użyć wzoru: segment*16+przesunięcie.

Przerwania w trybie rzeczywistym

W trybie rzeczywistym mamy do dyspozycji 256 prze-

rwań. Wliczamy w to przerwania programowe oraz

sprzętowe. Przerwania programowe, jak sama na-

zwa wskazuje dają nam możliwość zaprogramowania

się, czyli ustalenia gdzie procesor ma przeskoczyć po

wywołaniu instrukcji INT. Przerwania sprzętowe – IRQ

różnią się tylko tym, że oprócz możliwości wywołania

ich bezpośrednio z kodu programu, mogą być również

wywołane przez fizyczne urządzenie. Każda linia IRQ

jest przypisana do innego wektora przerwań w IVT

(ang. Interrupt Vectors Table).

IVT jest tablicą, która zawiera 256 wpisów typu

segment:offset. Każdy taki wpis jest nazywany wek-

torem przerwania. Kiedy procesor otrzymuje polece-

nie wykonania przerwania, musi znać nowy CS i IP

punktu wejścia programu obsługi.

Wartości te pobiera z IVT wykonując następują-

ce obliczenia:

Segment=IVT[int*4]

Offset=IVT[int*4+2]

Tak więc jeden wektor zajmuje w IVT dokładnie 4baj-

ty. Przed przejściem do programu obsługi przerwa-

nia, procesor odkłada na stos następujące rejestry:

SS

- segment stosu,

SP

– obecne przesunięcie w segmencie stosu,

Grzegorz Pełechaty

Autor jest od 7 lat programistą języka C. Interesuje się
zagadnieniami systemów operacyjnych, elektroniką i sie-
ciami neuronowymi. Obecnie pracuje nad projektem dar-
mowego systemu sieciowego, opartego o jądro monoli-
tyczne, oraz w pełni zgodnego ze standardami POSIX
(http://www.netcorelabs.org). System jest rozpowszech-
niany na warunkach licencji General Public License v2.
Kontakt z autorem: grzegorz.pelechaty@areoos.com

background image

Programowanie systemów operacyjnych

49

www.sdjournal.org

Software Developer’s Journal 7/2006

FLAGS

– flagi procesora,

CS

- segment kodu,

IP

– obecne przesunięcie w segmencie kodu (licznik in-

strukcji).

Rejestry są odkładane po to, aby po powrocie z przerwa-

nia, program mógł dalej kontynuować swoje działanie. Stos

wraca do poprzedniej wartości, ponieważ dane odłożone

na nim przez program obsługi przerwania są teraz bezu-

żyteczne.

Tryb chroniony i pierścienie ochrony

Pierścienie ochrony (ang. Protection rings) są to pozio-

my uprzywilejowania, jakie zastosowano w procesorach IA-

286p+. System uprawnień jest dosyć rozbudowany i opiera

się o czteropoziomowy układ zabezpieczeń, w którym pier-

ścień zerowy jest najbardziej uprzywilejowany, a trzeci posia-

da znaczne ograniczenia. Uprawnienia te obowiązują w pra-

wie wszystkich elementach trybu chronionego. Jedynym wy-

jątkiem jest stronicowanie, które będzie dokładniej omówione

w kolejnej części cyklu.

Zarządzanie pamięcią

w trybie chronionym

W trybie chronionym istnieją dwa mechanizmy zarządzania

pamięcią. Poprzez segmentację oraz stronicowanie. Naj-

pierw postaram się przybliżyć pojęcie segmentacji, ponie-

waż wygląda ona trochę inaczej, niż miało to miejsce w try-

bie rzeczywistym.

Znana z trybu rzeczywistego zamiana zawartości reje-

strów segmentowych i przesunięcia na adres fizyczny tra-

ci sens w trybie chronionym. Tutaj segmenty są od sie-

bie odseparowane i chociaż nadal są dostępne programo-

wo, interpretacja ich zawartości jest zupełnie inna. Rejestr

segmentowy przechowuje teraz selektor segmentu, a nie

wprost jego adres. 13 najstarszych bitów tego rejestru sta-

nowi adres 8bajtowej struktury opisującej dany segment

(ang. Segment Descriptor). Z pozostałych trzech bitów dwa

poświęcone zostały na implementację czteropoziomowe-

go systemu praw dostępu do segmentu, a jeden określa

czy wspomniany powyżej adres odnosi się do tzw. tablicy
lokalnej
czy globalnej. Rekordami w tych tablicach są wła-

śnie deskryptory segmentów. Każdy z nich zawiera jedno-

znaczną informację o lokalizacji segmentu w pamięci i jego

rozmiarach. W ten sposób zdefiniowany jest spójny obszar

o adresie początkowym wyznaczonym przez liczbę 32-bi-

tową. Na liczbę określającą rozmiar takiego bloku przezna-

czone zostało pole 20-bitowe. Istnieją dwie możliwości in-

Mapa pierwszego megabajtu pamięci

00000000 – 000003FF

Tablica wektorów przerwań

00000400 – 000004FF

Obszar danych biosu

00000500 – 0009FBFF

Pamięć konwencjonalna (640KB)

00007C00 – 00007DFF

Program rozruchowy

0009FC00 – 0009FFFF

Rozszerzony obszar danych biosu

(EBDA)

000A0000 – 000BFFFF

Pamięć VGA (128KB)

000A0000 – 000AFFFF

Bufor ramki VGA(64KB)

000B0000 – 000B7FFF

Pamięć dla kart monochromatycznych

(32KB)

000B8000 – 000BFFFF

Pamięć dla kart kolorowych (32KB)

000C0000 – 000C7FFF

BIOS karty graficznej (32KB – ROM)

000F0000 – 000FFFFF

BIOS płyty głównej (64KB – ROM)

Spis wektorów linii IRQ w trybie

rzeczywistym

• Linia IRQ Wektor

Urządzenie generujące syngnał IRQ

0 08h

Zegar systemowy

1 09h

Klawiatura

2 0Ah

Wyjście kaskadowe do układu Slave

3 0Bh

Port COM2

4 0Ch

Port COM1

5 0Dh

Port LPT2

6 0Eh

Kontroler napędu dysków elastycznych

7 0Fh

Port LPT1

8 70h

Zegar czasu rzeczywistego (RTC)

9 71h

Wywołuje przerwanie IRQ2

10 72h

Zarezerwowane

11 73h

Zarezerwowane

12 74h

Zarezerwowane

13 75h

Koprocesor arytmetyczny

14 76h

Kontroler dysku twardego

15 77h

Zarezerwowane

Listing 1.

Struktura deskryptora segmentu w Globalnej

Tablicy Deskryptorów

struct

gdt_seg_desc

{

unsigned

short

len15_0

;

unsigned

short

base15_0

;

unsgined

char

base23_16

;

unsigned

char

flags1

;

unsigned

char

flags2

;

unsigned

char

base31_24

;

}

;

Listing 2.

Funkcja tworząca nowy segment w Globalnej

Tablicy Deskryptorów

struct

gdt_seg_desc

*

gdt_table

=

(

struct

gdt_seg_desc

)

GDT_ADDRESS

;

void

createSegment

(

int

pos

,

unsigned

long

base

,

unsigned

long

len

,

unigned

char

flags1

,

unsigned

char

flags2

)

{

gdt_table

[

pos

]

.

len15_0

=

(

unsigned

short

)(

len

&

0xFFFF

);

gdt_table

[

pos

]

.

base15_0

=

(

unssigned

short

)(

base

&

0xFFFF

);

gdt_table

[

pos

]

.

base23_16

=

(

unsigned

char

)(

(

base

>>

16

)

&

0xFF

);

gdt_table

[

pos

]

.

flags1

=

flags1

;

gdt_table

[

pos

]

.

flags2

=

flags2

|

((

len

>>

16

)

&

0xf

);

gdt_table

[

pos

]

.

base31_24

=

(

unsigned

char

)(

(

base

&

0xF000

)

>>

24

);

}

background image

50

Inżynieria

oprogramowania

www.sdjournal.org

Software Developer’s Journal 7/2006

bitowy, budowany jest ze złożenia zawartości 16bitowe-

go rejestru segmentowego i 32bitowego rejestru przesu-

nięcia. W przypadku ziarnistości 4KB maksymalny roz-

miar segmentu wynosi 4GB. Liczba możliwych segmen-

tów to 2^14(2^13 deskryptorów lokalnych i tyle samo glo-

balnych), co daje w sumie astronomiczną objętość 64TB

(2^14*2^32). Właściwie już jeden taki segment stanowi

wielkość optymalną – 4GB przestrzeni adresowej zaspo-

kaja przy obecnym rozwoju techniki PC dość wygórowa-

ne wymagania. Rozwiązanie takie, określane jako “płaski

model pamięci” (ang. flat memory model), stosowane jest

w systemie Windows NT.

Zarządzanie

Globalną Tablicą Deskryptorów

Listing 2 zawiera funkcję, która tworzy nowy segment w

GDT. Struktura opisująca pojedynczy deskryptor segmen-

tu znajduje się na Listingu 1. Ustalmy jeszcze raz jak obli-

czyć selektor danego segmentu. Na selektor składa się ad-

res deskryptora względem początku Globalnej Tablicy De-

skryptorów oraz poziom uprzywilejowania i informacja o

tym czy segment znajduje się w tablicy lokalnej, czy global-
nej
.

selector=numer_segmentu*sizeof(struct gdt_desc)+DPL+ (
4 –- jeżeli deskryptor jest w LDT)

Sama tablica GDT jest opisana specjalnym, 48bitowym de-

skryptorem. Struktura opisująca ten deskryptor wygląda na-

stępująco:

struct gdt_desc {
unsigned short gdt_size;
unsigned long gdt_address;
} __atribute__((packed));

Pierwsze 16 bajtów powinno zawierać rozmiar tablicy GDT mi-

nus 1bajt, czyli 8192*8-1.

Tablicę ładujemy poleceniem lgdt, które przyjmuje fizycz-

ny adres deskryptora w pamięci (w postaci bezpośredniego

adresu, bądź też rejestru). Musimy pamiętać, że pierwszy de-

skryptor jest zawsze pusty. Nie ma możliwości, aby został on

wykorzystany w jakikolwiek sposób przez system.

Jądro systemu

Teraz spróbujemy zebrać te wszystkie informacje w spójną

całość i napiszemy proste jądro systemu operacyjnego. Nie

będzie to oczywiście jądro, które byłoby w stanie zrobić co-

kolwiek, ale po uruchomieniu zobaczymy napis hello world i to

powinno nam na razie wystarczyć.

Listing 3.

Definicje poszczególnych bitów we flagach

deskryptora segmentu

Opis

definicji

poszczeg

ó

lnych

flag

segmentu

:

// FLAGS1 (P + DPL + SYS/APP + TYPE)

#define GDT_PRESENT 0x80
#define GDT_DPL3 0x60
#define GDT_DPL1 0x20
#define GDT_DPL2 0x40
#define GDT_DPL0 0x00

// GDT_SYS będzie poruszone podczas omawiania
// wielozadaniowości. Obecnie interesuje nas
// tylko GDT_APP, które przeznaczone jest dla segmentu
// danych lub kodu.

#define GDT_SYS 0x00
#define GDT_APP 0x10

// Dodatkowe flagi dla segmentów innych niż data lub
// code (GDT_SYS)

#define GDT_RESERVED 0x0
#

define

GDT_TSS16

0x1

// 0001 16 bitowy TSS (dostępny)

#

define

GDT_LDT

0x2

// 0010 LDT

#

define

GDT_TSS16_BUSY

0x3

// 0011 16 bitowy TSS (zajęty)

#

define

GDT_CALL16

0x4

// 0100 16 bitowa bramka wywołań

#

define

GDT_TASK

0x5

// 0101 Bramka zadania

#

define

GDT_INT16

0x6

// 0110 16 bitowa bramka

// przerwania

#

define

GDT_TRAP16

0x7

// 0111 16 bitowa bramka pułapki

#

define

GDT_TSS32

0x9

// 1001 32 bitowy TSS (dostępny)

#

define

GDT_TSS32_BUSY

0xB

// 1011 32 bitowy TSS (zajęty)

#

define

GDT_CALL32

0xC

// 1100 32 bitowa bramka wywołań

#

define

GDT_INT32

0xE

// 1110 32 bitowa bramka

// przerwania

#

define

GDT_TASK_GATE

0xF

// 1111 32 bitowa bramka pułapki

// Dla GDT_APP

#define GDT_DATA 0x00
#define GDT_WRITE 0x02
#define GDT_EXP_DOWN 0x04
#define GDT_CODE 0x08
#define GDT_READ 0x02
#define GDT_CONF 0x04

// FLAGS2 (G + D/B + 0 + AVL)
// Ziarnistość danego segmentu

#define GDT_GRANULARITY 0x80

// Rodzaj segmentu – 32 lub 16 bitowy

#define GDT_USE32 0x40
#define GDT_USE16 0x00

// Segment do dowolnego użytku przez system

#

define

GDT_AVAIL

0x00

Listing 4.

Nagłówek multiboot dla wykonywalnych plików

ELF

struct

multiboot_header

{

unsigned

long

magic

;

unsigned

long

flags

;

unsigned

long

checksum

;

}

;

terpretowania liczby w tym polu. W trybie 1:1 (ziarnistość

1B) rozmiar maksymalny wynosi po prostu 2^20 = 1MB.

Gdyby jednak przyjąć jednostkę 4KB (ziarnistość 4KB), roz-

miar segmentu może sięgać do 2^20*2^12 = 2^32 = 4GB.

Informacja o tym, która z konwencji aktualnie obowiązuje,

zawarta jest w deskryptorze.

Adres logiczny, do którego odwołuje się procesor 32-

background image

Programowanie systemów operacyjnych

Listing 5.

Kod inicjalizacyjny jądra

Zestaw Narzędzi

Do rozpoczęcia prac nad pisaniem własnego systemu ope-

racyjnego niezbędny będzie nam pewien zestaw narzędzi,

który w znacznej mierze ułatwi nam to zadanie:

• edytor tekstu

• GCC & GAS

• GNU binutils (ld, make)

• opcjonalnie emulator (qemu/vmware)

• program rozruchowy zgodny ze standardem multiboot

Edytor tekstu będzie nam oczywiście potrzebny do pisania ko-

du. Kompilatory gcc i gas posłużą do jego skompilowania. Po-

nieważ nie jest możliwe napisanie jądra jedynie przy użyciu

języka wysokiego poziomu, musimy również posiadać kompi-

lator asemblera. Szereg programów z pakietu binutils pomo-

że nam w skonsolidowaniu całego obrazu. Użycie emulatora

PC jest wysoce wskazane, ponieważ dzięki niemu nie będzie-

my zmuszeni za każdym razem restartować komputera w ce-

lu sprawdzenia poprawności naszego kodu. Ostatnim progra-

mem jaki musimy posiadać jest program rozruchowy zgodny

ze standardem multiboot. Przykładem takiego programu jest

GRUB, który staje się coraz bardziej powszechny. Oczywiście

możemy napisać własny program rozruchowy, jednak mija się

to z celem. GRUB zagwarantuje nam zgodność z większością

sprzętu oraz przejdzie za nas w tryb chroniony tym samym

oddając w nasze ręce w pełni 32-bitowe środowisko pracy.

Wszystkie wymienione powyżej narzędzia są rozpowszech-

.text
.globl

_start

_start

:

jmp

multiboot_entry

.align 4

multiboot_header

:

.

long

0x1BADB002

.

long

0x00000003

.

long

-

(

0x1BADB002 + 0x00000003

)

multiboot_entry

:

movl

$

(

stack

+100

)

,%

esp

call

setup_gdt

call

__main

mbi

:

.

long

0x0

setup_gdt

:

movl

$

gdt_table

, %

esi

movl

$0xA000, %

edi

movl

$8, %

ecx

rep

movsl

movl

$0xA000+8*8, %

edi

movl

$0x2000-8, %

ecx

fill_gdt

:

movl

$0,

(

%

edi

)

movl

$0, 4

(

%

edi

)

addl

$8, %

edi

dec

%

ecx

jne

fill_gdt

1:

lgdt

gdt

ljmp

$

(

0x10

)

, $

go

go

:

movl

$

(

0x18

)

, %

eax

movl

%

eax

, %

ds

movl

%

eax

, %

es

movl

%

eax

, %

fs

movl

%

eax

, %

gs

movl

%

eax

, %

ss

ret

.data

gdt

: .

word

0x2000*8-1

.

long

0xA000

gdt_table

:

.

quad

0x0000000000000000 #

pusty

deskryptor

.

quad

0x0000000000000000 #

nie

u

ż

ywamy

.

quad

0x00cf9a000000ffff # 0x10

kernel

4

GB

code

at

0x00000000

.

quad

0x00cf92000000ffff # 0x18

kernel

4

GB

data

at

0x00000000

.comm

stack

, 0x500

R

E

K

L

A

M

A

background image

52

Inżynieria

oprogramowania

www.sdjournal.org

Software Developer’s Journal 7/2006

niane na warunkach General Public License, więc są całkowi-

cie darmowe.

Kod inicjacyjny jądra

Aby GRUB mógł załadować jądro, musi ono posiadać specjalny

nagłówek informacyjny. Adres tego nagłówek musi być wyrów-

nany do czterech bajtów. Struktura opisująca nagłówek multi-

boot dla wykonywalnych plików ELF znajduje się na Listingu 4.

Standardowe wartości jakie powinny być użyte w naszym

wypadku to:

magic=0x1BADB002
flags=0x00000003
checksum=-(0x1BADB002 + 0x00000003)

Na Listingu 5 wypełniamy GDT czterema deskryptorami. De-

skryptor numer 2 wskazuje na segment kodu, którego limit jest

ustawiony na 4GB, a co za tym idzie ziarnistości segmentu mu-

si wynosić 4KB. Segment ten ma uprawnienia pierścienia 0. Ko-

lejny segment jest przeznaczony na dane. Ma on taki sam adres

bazowy i limit jak segment kodu, jednak różni się typem. Po wy-

pełnieniu GDT, wywołujemy funkcję

_ main()

, która będzie po-

czątkiem naszego właściwego jądra.

Kernel

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

rogu ekranu

Jak widać na Listingu 6, po wypisaniu komunikatu koń-

czymy funkcję nieskończoną pętlą. Jest to jedyne wyjście po-

nieważ nie mamy systemu do którego moglibyśmy powrócić.

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-

cesor próbując wywołać przerwanie nr 6 natrafiłby na kolejny

problem, ponieważ nie mamy załadowanej tablicy IDT (ang.
Interrupt Descriptors Table). Wtedy wystąpiłby potrójny błąd

(ang. Triple fault), który wiąże się z natychmiastowym restar-

tem procesora.

Kompilacja

Przy kompilacji tak dużych projektów, jakimi są systemy ope-

racyjne bardzo dobrym rozwiązaniem jest zastosowanie pli-

ków make. N a Listingu 7 przedstawiono sposób użycia tych

plików, w oparciu o źródłowe pliki, które stworzyliśmy wcze-

śniej. Mowa o kodzie inicjacyjnym, który powinniśmy zapi-

sać w pliku init.S oraz źródle jądra, które powinno nosić na-

zwę main.c

Listing 7 zapisujemy pod nazwą makefile w katalogu ze

źródłami.

Rysunek 1.

Jądro systemu uruchomione pod emulatorem

Vmware

Listing 7.

Plik makefile dla jądra

CC=gcc
LD=ld
OBJS=init.o main.o

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

Listing 6.

Przykładowe jądro systemu, wypisujące napis

“hello world” w lewym górnym rogu ekranu

static

char

*

video

=(

char

*)

0xB8000

;

void

clrscr

(

void

)

{

int

i

;

for

(

i

=

0

;

i

<

80

*

50

;

i

+=

2

)

{

video

[

i

]=

32

;

video

[

i

+

1

]=

0x7

;

}

}

void

puts

(

char

*

msg

)

{

char

*

ptr

=

video

;

while

(*

msg

)

{

*

ptr

++=*

msg

++;

*

ptr

++=

0x7

;

}

}

void

__main

(

void

)

{

clrscr

();

puts

(

"hello world!"

);

for

(;;);

}

background image

53

Programowanie systemów operacyjnych

www.sdjournal.org

Software Developer’s Journal 7/2006

Konsolidacja

Jak zapewne zauważyliście na Listingu 7 jednym z argumen-

tów programu LD był plik kernel.lds.

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-

mat wyjściowy i offset, pod który zostanie skompilowane

nasze jądro. Przykład takiego skryptu znajduje się na Li-

stingu 8.

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

pamięci (pomijając 64KB tablicę GDT, która znajduje się

poniżej pierwszego megabajata). Ponadto obraz wyjściowy

będzie w formacie pliku wykonywalnego ELF, dzięki czemu

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)

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

na końcu następujące linie:

title MyOS
root (hdD,P)
kernel /boot/kernel

W miejsce D i P wstawiamy odpowiednio numer dysku i party-

cji, na której mamy nasze jądro (jest ta sama partycja z której

został uruchomiony obecnie działający system).

Testowanie

Teraz przyszła kolej na przetestowanie naszego syste-

mu, jednak przed tym musimy go skompilować i skopio-

wać tak powstały obraz do katalogu /boot. W celu skompi-

lowania naszego jądra wpisujemy polecenie make, w kata-

logu gdzie znajdują się źródła wraz z plikami makefile i ker-

nel.lds. Po skopiowaniu obrazu do katalogu /boot restartu-

jemy komputer i w menu wyboru GRUB'a wybieramy pozy-

cję MyOS. Jeżeli wszystko poszło pomyślnie powinniśmy

zobaczyć identyczny efekt jak na Rysunku 1.

Podsumowanie

Tak jak się przekonaliście, pisanie systemu operacyjnego to

dość trudne i czasochłonne zajęcie,

które zabierze cenne chwile z waszego życia, ale da rów-

nież niesamowitą satysfakcję z tego, że stworzyliśmy coś wy-

jątkowego. Sami wytyczyliśmy standardy i nie musieliśmy być

podporządkowani innym. I chyba to w tym wszystkim jest naj-

ważniejsze. Jeżeli zdecydujecie się na pisanie własnego sys-

temu, pamiętajcie, że najlepiej uczyć się na własnych błędach.

Starajcie się znajdować rozwiązanie na konkretny problem po-

przez testowanie własnych pomysłów, a dopiero potem sięgaj-

cie do kursów, tutoriali itp... Ta dziedzina informatyki wymaga

przede wszystkim kreatywności, a nie biernego implementowa-

nia gotowych rozwiązań.

W następnej części cyklu przedstawię najbardziej znane

metody programowego zarządzania pamięcią oraz napiszemy

prosty alokator pamięci oparty o listę dwukierunkową. Mowa

będzie również o stronicowaniu, przerwaniach i pamięci wirtu-

alnej, omówimy także sposób działania mechanizmu wymiany

pamięci pomiędzy dyskiem a pamięcią fizyczną. n

W Sieci

• Polski portal poświęcony programowaniu systemów operacyj-

nych: http://www.areoos.com/osdevpl

• Ogólnoświatowe forum programistów systemów operacyj-

nych: http://www.osdev.org

• Portal związany z programowaniem systemów, z wieloma kur-

sami oraz przykładami: http://www.osdever.net/

• Strona poświęcona głównie systemowi operacyjnemu Alt+Ctrl+

Del, zawiera również obszerną listę innych projektów: http://
www.acd.prv.pl

Literatura

• [1] Piotr Metzger, Michał Siemieniacki, Anatomia PC wydanie

IX, Helion 2004,

• [2] Uresh Vahalia, Jądro systemu Unix –- nowe horyzonty,

Wydawnictwa Naukowo-Techniczne Warszawa 2001

Listing 8.

Przykładowy skrypt dla programu

konsolidującego

OUTPUT_FORMAT

(

"elf32-i386"

)

OUTPUT_ARCH

(

i386

)

ENTRY

(

_start

)

SECTIONS

{

.

=

0x100000

+

SIZEOF_HEADERS

;

.

text

:

{

*(

.

text

)

}

.

=

ALIGN

(

16

);

.

rodata

:

{

*(

.

rodata

)

}

.

=

ALIGN

(

16

);

.

data

:

{

*(

.

data

)

CONSTRUCTORS

}

.

=

ALIGN

(

16

);

_edata

=

.

;

.

bss

:

{

*(

.

bss

)

}

.

=

ALIGN

(

16

);

_end

=

.

;

}


Wyszukiwarka

Podobne podstrony:
2006 09 Wielozadaniowość w systemach operacyjnych [Inzynieria Oprogramowania]
2006 08 Zarządzanie pamięcią w systemach operacyjnych [Inzynieria Oprogramowania]
Jądro systemu operacyjnego, java, javascript, oprogramowanie biurowe, programowanie, programowanie 2
2006 06 Wstęp do Scrum [Inzynieria Oprogramowania]
Jądro systemu operacyjnego
Projektowanie systemu, Semestr 5, Inżynieria oprogramowania
Modelowanie systemu, Semestr 5, Inżynieria oprogramowania
Jądro systemu operacyjnego
system rezerwacji, inżynieria oprogramowania
2006 04 Rozszerzenie wzorca Template [Inzynieria Oprogramowania]
2006 06 Wstęp do Scrum [Inzynieria Oprogramowania]
Rafał Polak 12k2 lab8, Inżynieria Oprogramowania - Informatyka, Semestr III, Systemy Operacyjne, Spr
Rafał Polak 12k2 lab9, Inżynieria Oprogramowania - Informatyka, Semestr III, Systemy Operacyjne, Spr
Rafał Polak 12k2 lab4a, Inżynieria Oprogramowania - Informatyka, Semestr III, Systemy Operacyjne, Sp

więcej podobnych podstron