   #Start Contents English version

                Pisanie programow wielowatkowych pod Linuksem

   Asembler, jak wszystkie inne strukturalne jezyki programowania pozwala
   pisac programy, w ktorych sciezka wykonywanych instrukcji jest tylko
   jedna. Moga byc rozwidlenia i petle, ale zawsze wykonuje sie tylko
   jedna rzecz na raz.

   Watki pozwalaja na uruchomienie wielu niezaleznych sciezek, ktore beda
   wykonywane rownolegle. Daje to duze mozliwosci programom, ktore
   wykonuja kilka czynnosci na raz (na przyklad czytanie z jednego pliku
   i zapisywanie przetworzonych danych do drugiego). Zysk jest tez w
   programach sieciowych, a zwlaszcza serwerach. Po dodaniu obslugi
   watkow mozliwe jest polaczenie wiecej niz jednego klienta w danej
   chwili. Ale przejdzmy wreszcie do szczegolow.

   Najpierw omowie trzy funkcje z biblioteki jezyka C (scisle mowiac, z
   biblioteki pthreads), ktore pozwola nam zarzadzac watkami.
    1. pthread_create - tworzenie nowego watku.
       Funkcja ta przyjmuje 4 argumenty. Od lewej (ostatni wkladany na
       stos) sa to:
          + adres zmiennej typu DWORD, ktora otrzyma identyfikator nowego
            watku.
          + atrybuty nowego watku, jesli chcemy cos specjalnego. Zero
            oznacza domyslne argumenty.
          + adres funkcji watku. Funkcja ta otrzyma na stosie adres
            dodatkowych danych, ktore mozna przekazac do watku.
          + adres dodatkowych danych, ktore chcemy przekazac do watku.
    2. pthread_exit - zakonczenie biezacego watku
       Funkcja ta konczy biezacy watek. Wartosc podana jako jedyny jej
       argument (adres danych) moze byc wykorzystana przez watki
       podlaczone (pthread_join) do tego watku. Po zakonczeniu wszystkich
       watkow, program konczy dzialanie z kodem 0.
    3. pthread_yield - oddanie czasu procesora innym watkom lub procesom
       Oczywiscie, system operacyjny sam tez przydziela czas procesora
       poszczegolnym watkom, ale wywolujac te funkcje mozemy powiedziec,
       by skrocil czas przeznaczony dla tego watku i dal go innym.
       Przydaje sie, gdy biezacy watek chwilowo skonczyl prace (na
       przyklad zabraklo danych itp.). Funkcja nie przyjmuje zadnych
       argumentow.

   Ponizej przedstawiam krociutki program, ktory pokaze, jak to wszystko
   dziala. Program ma jeden raz wyswietlic napis pierwszy w funkcji
   glownej i 5 razy napis drugi w funkcji watku.
   (przeskocz program)
; Przykladowy program wielowatkowy w asemblerze
;
; Autor: Bogdan D., bogdandr (at) op.pl
;
; kompilacja:
; nasm -O999 -f elf -o watki.o watki.asm
; gcc -o watki watki.o -lpthread

section .text
global  main

; deklaracje funkcji zewnetrznych
extern  pthread_create
extern  pthread_exit

main:

        mov     eax, 4
        mov     ebx, 1
        mov     ecx, napis1
        mov     edx, napis1_dl
        int     80h                     ; wyswietlamy napis pierwszy

        push    dword 0                 ; dodatkowe dane
        push    dword watek             ; adres funkcji do uruchomienia
        push    dword 0                 ; atrybuty
        push    dword id_watku          ; gdzie zapisac ID
        call    pthread_create          ; utworzenie nowego watku

; Nie nalezy wychodzic z programu funkcja sys_exit (EAX=1), gdyz
; zakonczyloby to wszystkie watki programu. Zamiast tego, zamykamy tylko
; watek glowny.
        push    dword 0
        call    pthread_exit            ; zakonczenie biezacego watku

watek:

        mov     dword [t1+timespec.tv_nsec], 0
        mov     dword [t1+timespec.tv_sec], 5           ; 5 sekund

        mov     esi, 5                  ; napis drugi wyswietlimy 5 razy
.petla:
        mov     eax, 162                ; sys_nanosleep
        mov     ebx, t1                 ; adres struktury mowiacej,
                                        ; ile chcemy czekac
        mov     ecx, 0
        int     80h                     ; robimy przerwe...

        mov     eax, 4
        mov     ebx, 1
        mov     ecx, napis2
        mov     edx, napis2_dl
        int     80h                     ; wyswietl napis drugi

        dec     esi
        jnz     .petla                  ; wykonuj petle, jesli ESI != 0

        push    dword 0
        call    pthread_exit            ; zakonczenie biezacego watku

section .data

napis1          db      "Funkcja glowna.", 10
napis1_dl       equ     $ - napis1

napis2          db      "Watek.", 10
napis2_dl       equ     $ - napis2

struc timespec
        .tv_sec:        resd 1
        .tv_nsec:       resd 1
endstruc

t1              istruc timespec

id_watku        dd      0       ; zmienna, ktora otrzyma ID nowego watku

   Ale watki w programie to nie tylko same zyski. Najwiekszym problemem w
   programach wielowatkowych jest synchronizacja watkow.

   Po co synchronizowac? Po to, zeby program nie sprawial problemow, gdy
   dwa lub wiecej watkow odczytuje i zapisuje te sama zmienna globalna
   (na przyklad bufor danych).

   Co zrobic, by na przyklad watek czytajacy przetwarzal dane dopiero
   wtedy, gdy inny watek dostarczy te dane? Mozliwosci jest kilka:
     * flaga - zmienna globalna.
       Na przyklad ustalmy, ze jesli flaga jest rowna zero, to bufor moze
       byc dowolnie uzywany (do zapisu i odczytu). Jesli flaga jest rowna
       na przyklad jeden, to nie wolno wykonywac operacji na buforze (bo
       inny watek juz to robi) - nalezy poczekac, az flaga bedzie rowna
       zero.
       Zaleta tego rozwiazania jest prostota jego utworzenia. Popatrzcie:
        flaga   db      0
        ...
        watek:
        ...
        sprawdz_flage:
                cmp     byte [flaga], 1
                je      sprawdz_flage
                mov     byte [flaga], 1
        ...     ; tutaj nasze operacje
                mov     byte [flaga], 0
     * mutex - poczytajcie pl.wikipedia.org/wiki/Mutex
     * semafor ustawiajacy watki w kolejke do danego zasobu. Poczytajcie
       pl.wikipedia.org/wiki/Semafor_(informatyka)

   Jak widac, pisanie programow wielowatkowych nie jest takie trudne,
   warto wiec sie tego nauczyc. Tym bardziej, ze zyski sa wieksze
   (napisanie po jednej funkcji na kazde oddzielne zadanie), niz wysilek
   (synchronizacja).
     _________________________________________________________________

Wielowatkowosc z przerwaniem 80h

   Oczywiscie, aby pisac programy wielowatkowe, nie musicie korzystac z
   zadnej biblioteki. Odpowiednie mechanizmy posiada sam interfejs
   systemu - przerwanie int 80h.

   Skorzystam tutaj z funkcji sys_fork (numer 2). Jej jedynym argumentem
   jest adres struktury zawierajacej wartosci rejestrow dla nowego
   procesu, ale ten argument jest opcjonalny i moze byc zerem. Funkcja
   fork zwraca wartosc mniejsza od zera, gdy wystapil blad, zwraca zero w
   procesie potomnym, zas wartosc wieksza od zera (PID nowego procesu) -
   w procesie rodzica. Proces potomny zaczyna dzialanie tuz po wywolaniu
   funkcji fork, czyli rodzic po wykonaniu funkcji fork i potomek
   zaczynaja wykonywac dokladnie te same instrukcje. Procesy te mozna
   skierowac na rozne sciezki, sprawdzajac wartosc zwrocona przez fork w
   EAX.

   Oto krotki przyklad w skladni FASMa:
format ELF executable
entry _start
segment executable

_start:
        mov     eax, 2          ; funkcja fork
        xor     ebx, ebx
        int     80h             ; wywolanie

        cmp     eax, 0
        jl      .koniec         ; EAX < 0 oznacza blad

        ; ponizsze instrukcje wykona zarowno rodzic, jak i potomek:

        cmp     eax, 0
        jg      .rodzic         ; EAX > 0 oznacza, ze jestesmy w
                                ; procesie rodzica

        ; tutaj ani EAX < 0, ani EAX > 0, wiec EAX=0, czyli
        ; jestesmy w procesie potomka
        ; kod ponizej (wyswietlenie i czekanie) wykona tylko potomek

        mov     dword [t1.tv_nsec], 0
        mov     dword [t1.tv_sec], 5    ; tyle sekund przerwy bedziemy robic
                                        ; miedzy wyswietlaniem napisow

.petla:
        mov     eax, 4          ; funkcja zapisywania do pliku
        mov     ebx, 1          ; standardowe wyjscie
        mov     ecx, napis2     ; co wypisac
        mov     edx, napis2_dl  ; dlugosc napisu
        int     80h

        mov     eax, 162        ; funkcja sys_nanosleep
        mov     ebx, t1         ; tyle czekac
        mov     ecx, 0          ;ewentualny adres drugiej struktury timespec
        int     80h             ;  robimy przerwe...

        jmp     .petla          ; i od nowa....

        ; kod ponizej (wyswietlenie i wyjscie) wykona tylko rodzic
.rodzic:

        mov     eax, 4          ; funkcja zapisywania do pliku
        mov     ebx, 1          ; standardowe wyjscie
        mov     ecx, napis1     ; co wypisac
        mov     edx, napis1_dl  ; dlugosc napisu
        int     80h

.koniec:
        mov     eax, 1          ; funkcja wyjscia z programu
        xor     ebx, ebx
        int     80h

segment readable writeable

napis1          db      "Rodzic", 10
napis1_dl       =       $ - napis1
napis2          db      "Potomek", 10
napis2_dl       =       $ - napis1

struc timespec                  ; definicja struktury timespec
                                ; (tylko jako typ danych)
{
        .tv_sec:        rd 1
        .tv_nsec:       rd 1
}

t1 timespec             ; tworzymy zmienna t1 jako cala strukture

   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
