W Linuksie są co najmniej dwa sposoby na uruchomienie programu w tle:
program param1 param2 param3 &.
screen -m -d program param1 param2.
Ale nie jesteśmy programistami po to, by liczyć, że nasz program zostanie właśnie tak uruchomiony. Teraz pokażę, jak samemu zadbać o działanie swojego programu w tle. Najpierw posłuży nam do tego funkcja daemon (patrz: man 3 daemon) z biblioteki języka C. Dlatego sposób pisania programu będzie troszkę inny niż zwykle:
Aby było widać, że nasz demon (odpowiednik TSR w Linuksie) rzeczywiście działa, umieścimy w nim pętlę co jakiś czas wyświetlającą jakiś napis. W celu odmierzania przerwy skorzystamy z funkcji systemowej sys_nanosleep (numer 162).
Jak widać ze strony podręcznika, funkcja daemon przyjmuje 2 argumenty w postaci liczb całkowitych (DWORD):
Po omówieniu tego wszystkiego, przejdźmy wreszcie do przykładowego programu. Jego zadaniem jest
przejście w tryb demona i wyświetlanie co 5 sekund wskazanego tekstu w nieskończoność. Działanie będzie
więc widoczne na terminalu, z którego uruchamiacie program. Sam program można będzie oczywiście
zobaczyć na liście działających procesów (komenda ps -A
). Jedynym sposobem na zakończenie
programu jest zabicie go poleceniem kill.
A oto kod dla NASMa:
; Program przechodzący w tryb demona systemowego ; ; autor: Bogdan D., bogdandr (at) op.pl ; ; kompilacja: ; nasm -f elf -o demon.o demon.asm ; gcc -o demon demon.o extern daemon ; deklaracja funkcji zewnętrznej section .text ; początek sekcji kodu global main ;symbol "main" musi być globalny dla GCC main: push dword 1 ; drugi argument push dword 1 ; pierwszy argument call daemon ; uruchomienie funkcji daemon add esp, 8 ; usunięcie argumentów ze stosu ; przerwa między kolejnymi napisami ; będzie trwać 5 sekund i 0 nanosekund: mov dword [t1+timespec.tv_nsec], 0 mov dword [t1+timespec.tv_sec], 5 .petla: mov eax, 4 ; funkcja zapisywania do pliku mov ebx, 1 ; standardowe wyjście mov ecx, napis ; co wypisać mov edx, napis_dl ; długość napisu int 80h mov eax, 162 ; funkcja sys_nanosleep mov ebx, t1 ; tyle czekać mov ecx, 0 ; ewentualny adres drugiej struktury timespec int 80h ; robimy przerwę... jmp .petla ; i od nowa.... ; poniższy kod nie będzie wykonany mov eax, 1 xor ebx, ebx int 80h ; wyjście z programu section .data napis db "Tu Twoj demon mowi.", 10 napis_dl equ $ - napis struc timespec ; definicja struktury timespec ; (tylko jako typ danych) .tv_sec: resd 1 .tv_nsec: resd 1 endstruc t1 istruc timespec ; tworzymy zmienną t1 jako całą ; strukturę timespec
Kompilacja i łączenie odbywa się tak:
nasm -f elf -o demon.o demon.asm gcc -o demon demon.o
O jednej rzeczy należy wspomnieć: sam fakt, że program jest demonem NIE musi oznaczać, że działa na prawach administratora (i całe zabezpieczenie systemu jest do niczego).
Funkcja daemon jest co prawda z biblioteki C, ale można ją przerobić na kod korzystający wyłącznie z przerwania int 80h. Sam kod funkcji jest w pliku misc/daemon.c w źródłach biblioteki glibc. Nie jest on za długi i dość łatwo można go przerobić na takie oto makro:
%macro daemon 2 ; pierwszy parametr: nochdir - czy nie zmieniać katalogu na główny? ; drugi parametr: noclose - czy nie zamykać stdin i stdout? mov eax, 2 int 80h ; sys_fork cmp eax, 0 jl %%koniec ; EAX < 0 oznacza błąd test eax, eax jz %%dalej ; EAX = 0 w procesie potomnym ; EAX > 0 w procesie rodzica mov eax, 1 xor ebx, ebx int 80h ; sys_exit - rodzic kończy pracę. %%glowny: db "/", 0 %%devnull: db "/dev/null", 0 %%dalej: mov eax, 66 ; sys_setsid int 80h ; tworzymy nową sesję i ustawiamy GID cmp eax, 0 jl %%koniec ; EAX < 0 oznacza błąd %if %1 = 0 mov eax, 12 ; sys_chdir mov ebx, %%glowny int 80h ; zmieniamy katalog na główny %endif %if %2 = 0 ; otwieramy /dev/null: mov eax, 5 mov ebx, %%devnull mov ecx, 2 mov edx, 0 int 80h cmp eax, 0 jl %%koniec ; EAX < 0 oznacza błąd mov ebx, eax ; EBX = deskryptor /dev/null ;duplikujemy deskryptory standardowego wejścia, wyjścia i ;wyjścia błędów do deskryptora otwartego /dev/null, po czym ; ten /dev/null zamkamy mov eax, 63 mov ecx, 0 ; wejście int 80h mov eax, 63 mov ecx, 1 ; wyjście int 80h mov eax, 63 mov ecx, 2 ; wyjście błędów int 80h mov eax, 6 int 80h ; zamykamy /dev/null %endif %%koniec: %endmacro ; użycie: daemon 1, 1 ; bez żadnych PUSH ani ADD ESP
Teraz można już powrócić do starego schematu programu, gdzie symbolem startowym był _start, a łączenie odbywało się za pomocą LD, a nie GCC z biblioteką C.