Menu

Budowa systemu

Budowa komputera

Przykładowe systemy

Programowanie

Linki



Wstęp

Programowanie systemów operacyjnych nie jest rzeczą łatwą. Wymaga spełnienia pewnych warunków. Niestety, będąc kompletnym laikiem, systemu operacyjnego nie stworzysz.

Do warunków tych należą

1. Znajomość pewnego języka wysokiego poziomu, nadającego się do programowania systemów operacyjnych (zalecany ANSI C)

2. Znajomość języka angielskiego w stopniu na tyle dobrym, aby posłużyć się dokumentacją sprzętu (dokumentacja Intela jest bardzo ważnym źródłem informacji) oraz obsłużyć środowisko programistyczne

3. Przygotowane odpowiednie środowisko programistyczne (zwykle wiąże się to także z umiejętnością obsługi Linuksa, gdyż o dobre środowisko programowania systemu operacyjnego pod system Windows jest na prawdę ciężko)

    

Niestety, w spełnieniu dwóch pierwszych warunków moja strona nie jest w stanie pomóc (no może w pewnej części warunku drugiego, ponieważ zawiera część dokumentacji, ale to nie to samo). W trzecim z nich pomogę właśnie tym artykułem, który poświęcę głównie przygotowaniu środowiska programistycznego.

Środowisko programistyczne

Dobre środowisko programistyczne to podstawa wygodnej, szybkiej i efektywnej pracy nad systemem operacyjnym. Dlatego powinno ono zostać dobrze skomponowane i dopracowane, aby później nie zaistniała konieczność wprowadzania w nim poprawek i uzupełniania go.

Części środowiska programistycznego systemów operacyjnych

0. System operacyjny

Jest podstawą środowiska programistycznego(i dlatego nadałem mu cyfrę 0), ponieważ na nim jest ono właśnie uruchomione. Powinien to być system stabilny i łatwy w użytkowaniu, aby nie tracić czasu na problemy z systemem operacyjnym. Osobiście polecam Linuksa, ponieważ powstało na prawdę wiele narzędzi programistycznych na ten system i chociaż wymaga on sporo konfiguracji ze strony użytkownika to ostatecznie się opłaci, zyskując stabilny, szybki i wygodny system operacyjny oraz dostęp do wielu przydatnych narzędzi.

1. Kompilator

Bez niego ani rusz. Prawidłowy kompilator do zastosowania przy programowaniu systemów operacyjnych musi obsługiwać wiele formatów wyjściowych (preferowany ELF, ponieważ jest to format zalecany przez standard Multiboot). Musi także posiadać możliwość wyłączenia wszystkich zewnętrznych bibliotek standardowych, aby nie nadpisać żadnych funkcji systemowych. Dobrym przykładem jest kompilator GNU GCC, jednak jedynie w wersji pod system operacyjny Linux, gdyż wiele portów (w tym Cygwin i Mingw32 pod Windows) nie obsługuje formatu ELF.

2. Linker

On również musi obsługiwać wiele formatów wyjścia, ale także wejścia, a zwłaszcza ELF. Powinien umożliwić pominięcie wszystkich wewnętrznych bibliotek i funkcji. Dobrym przykładem jest LD z pakietu GNU Binutils (z tą samą uwagą co do GNU GCC).

3. Assembler

Assemblerów powstało wiele i wybór tego właściwego należy do Ciebie. Jedyną cechą, którą powinien posiadać jest obsługa formatu wynikowego ELF. Assemblery różnią się głównie składnią - dostępne są AT&T lub Intel, oraz obsługą różnego rodzaju makr, skryptów i specjalnych pseudopoleceń. Polecam NASM oraz jego następcę YASM, ze względu na łatwość użytkowania i bogate możliwości.

4. Edytor

Wygodny edytor jest pojęciem względnym. Niektórzy wolą, aby był konsolowy, inni wolą graficzny. Ja ze swojej strony polecam graficzny edytor z obsługą kolorowania składni i automatycznego wcinania kodu, ponieważ znacznie poprawia to czytelność kodu i wygodę użytkowania. Osobiście używam jEdit, edytora napisane w Javie, dzięki temu jest przenośny i działa zarówno pod Linuksem, jak i Windowsem.

5. Maszyna wirtualna (emulator)

Maszyna wirtualna, czyli program umożliwiający uruchomienie drugiego wirtualnego komputera na komputerze jest bardzo przydatnym narzędziem przy programowaniu systemów operacyjnych. Dzięki niej nie trzeba co chwile restartować swojego komputera, aby sprawdzić działanie nowo napisanego kawałka kodu. Mile widziany w maszynie wirtualnej jest zintegrowany debugger lub możliwość współpracy z debuggerem zewnętrznym, np. GDB, co zapewni wygodne testowanie i wyszukiwanie źle działających fragmentów w systemie operacyjnym. Obecnie współpracę z debuggerem GDB oferuje emulator QEMU, natomiast BOCHS oferuje zintegrowany debugger.

6. Graficzny interfejs debuggera (opcjonalnie, polecam)

Jeżeli maszyna wirtualna potrafi współpracować z zewnętrznym debuggerem, np. GDB, przydatny okazuje się graficzny interfejs do debuggera. Takim interfejsem dla linuksowego debuggera GDB jest DDD, który umożliwia wyświetlanie aktualnie wykonywanego kodu źródłowego i inne przydatne funkcje.

Przykład kompletnego środowiska programistycznego

Oto środowisko, którego używam przy programowaniu mojego systemu Qnix:

0. System operacyjny Gentoo Linux -> kompilowany całkowicie ze źródeł, co zapewnia wydajność i stabilność
1. Kompilator GNU GCC 4.1 -> format wyjściowy ELF
2. Linker GNU Binutils LD -> format wyjściowy ELF
3. Assembler YASM -> format wyjściowy ELF, składnia Intel
4. Edytor jEdit -> graficzny, z kolorowaniem składni i automatyczną tabulacją
5. Maszyna wirtualna QEMU -> współpraca z debuggerem GDB
6. Graficzna nakładka DDD

Kompilacja i linkowanie systemu za pomocą GCC i LD

Aby poprawnie skompilować jądro systemu, należy wyłączyć wszystkie zintegrowane funkcje i biblioteki, ponieważ nie dość, że zwiększą objętość pliku wynikowego to mogą zakłócić funkcjonowanie systemu, a nawet całkowicie uniemożliwić kompilację. W przypadku GNU GCC odpowiednie parametry gwarantujace poprawną kompilację to:

gcc -o plik_wyjsciowy -c plik_wejsciowy -Wall -nostdinc -nostartfiles -nodefaultlibs -fno-builtin

Dodatkowo, aby umieścic symbole dla debuggera, można dodać opcję -g.

Aby poprawnie zlinkować wszystkie pliki, trzeba stworzyć odpowiedni skrypt linkera, który zapewni odpowiednie rozmieszczenie elementów w pliku wyjściowym i wygenerowanie odpowiednich informacji w nagłówku pliku. Dla jądra systemu, którego wykonanie rozpoczyna się procedurą _kpreboot, oraz jest ładowane przez bootloader pod adres 0x100000, przykładowy skrypt linkera może wyglądać tak:

ENTRY(_kpreboot)
OUTPUT_FORMAT(elf32-i386)
SECTIONS
{
    /* Kernel zostanie umieszczony pod adresem liniowym 0x100000, czyli na pozycji 1 megabajta */
    . = 0x100000;

    .text : AT(ADDR(.text))
    {
  _code = .;
        *(.text)
        *(.rodata*)
    }

    .data ALIGN (0x1000) : AT(ADDR(.data))
    {
        *(.data)
    }

    .bss : AT(ADDR(.bss))
    {
        _sbss = .;
        *(COMMON)
        *(.bss)
        _ebss = .;
    }
 _end = .;
}

Dodatkowo, skrypt ten mówi linkerowi, że plik wyjściowy ma mieć format 32-bitowego pliku ELF przeznaczonego na maszynę zgodną z procesorem Intel i386 lub nowszym. Skrypt tworzy także specjalny symbol _end, dzięki któremu jądro systemu będzie mogło poznać swój rozmiar. Aby zlinkować wszystkie pliki *.o z bieżącego folderu z użyciem tego skryptu, jeżeli nazywa się on linker.ld, należy użyć polecenia:

ld -o plik_wyjsciowy -Tlinker.ld *.o

Można także dopisać parametr -g, w celu dodania informacji przydatnych debuggerowi.

Dokumentacja

[1] Manual GNU GCC

[2] Manual GNU Binutils

[3] Strona internetowa na temat jEdit

[4] Strona projektu QEMU

[5] Wybierz dystrybucję Linuksa dla siebię



Valid XHTML 1.1 License Poprawny CSS!
© 2007 by Tomek Figa na licencji Creative Commons Uznanie autorstwa-Użycie niekomercyjne 2.5 Polska.