Celem zajęć było zapoznanie się z narzędziami wielowątkowości dostępnymi w systemach Linux. Należało wykorzystać funkcje fork() i clone(), zapoznać się z ich działaniem oraz różnicami między nimi.
Funkcje, o których wspomniałem we wstępie są tylko dostępne dla systemów Linux. Programy napisane z użyciem tych narzędzi są przez to nieprzenośne. Warto jednak było je poznać gdyż są interesujące. Obie funkcje reprezentują różne działanie oraz różne podejście do zagadnienia wielowątkowości.
Funkcja fork() pozwala nam utworzyć nowy proces podrzędny w naszym procesie. Jej działanie polega na skopiowaniu całego kodu i pamięci stosu dla podprocesu. Kod następnie jest wykonywany równolegle do procesu nadrzędnego. Cechą funkcji fork() jest brak wprowadzania zmian w zmiennych należących do procesu nadrzędnego. Wartości zmiennych przechowywanych na stosie podprocesu są tracone wraz z zakończeniem jego działania. Jej konstrukcja wygląda następująco:
pid_t fork();
Warty wyjaśnienia jest typ pid_t. Jest to liczba całkowita. Funkcja fork() może zwrócić nam dwa rodzaje wartości. Jeżeli funkcja zostanie wywołana w procesie nadrzędnym, funkcja fork() zwróci nam id procesu podrzędnego. Za to w podprocesie funkcja fork() zwróci nam wartość 0. Na tej podstawie możemy w łatwy sposób przygotować fragmenty kodu, które zostaną wykonane w konkretnych procesach. By tego dokonać wystarczy funkcja warunkowa.
Ciekawszą funkcją systemową udostępnianą nam w Linuxie jest funkcja clone(). Jej konstrukcja wygląda następująco:
int clone(int (*fn) (void *arg), void *child_stack, int flags, void *args)
Jak widać, pierwszym argumentem funkcji clone() jest wskaźnik na inną funkcję. W języku C wskaźnikiem na funkcję jest po prostu jej nazwa. Funkcja, którą przekażemy zostanie wykonana w procesie podrzędnym.
W odróżnieniu od fork(), clone() nie kopiuje całego kodu i stosu funkcji nadrzędnej, lecz przydzielany jest jej własny stos. Dokonuje się to w sposób dynamiczny tak, jak przydziela się miejsce zmiennym. Wykorzystuje się do tego malloc(). Po utworzeniu wskaźnika typu void o żądanym rozmiarze, przekazujemy go w drugim argumencie. Warto również pamiętać o zwolnieniu pamięci po zakończeniu pracy podprocesu, wykorzystując free().
Trzeci argument to zestaw flag, które określają zachowanie funkcji. Ich definicje zapisane są w bibliotece <sched.h>. Podczas wykonywania ćwiczenia odkryłem, że nowsze dystrybucje systemu Linux, w tym moja Fedora 20 mają tą bibliotekę przeniesioną do osobnego folderu: <linux/sched.h>. Flagi zdefiniowane w tej bibliotece pozwalają funkcji clone na zmianę i zachowanie konkretnych danych należących do procesu nadrzędnego. Podproces może dzielić:
CLONE_VM – pamięć
CLONE_FILES – otwarte pliki
CLONE_FS – system plików
CLONE_SIGHAND – sygnały
Ostatni argument funkcji clone() przyjmuje argumenty, które chcemy przekazać do funkcji z argumentu pierwszego. W naszym wypadku przekazaliśmy zero gdyż funkcja nie potrzebowała żadnych danych.
Funkcje clone i fork pokazują nam różne podejście do przetwarzania równoległego. Ich wykorzystanie jest łatwe i w prosty sposób pokazuje nam możliwości i dylematy jakie daje nam wielowątkowość. Te laboratoria nauczyły nas, że przed stworzeniem wielowątkowego programu warto się zastanowić czy chcemy by nasze wątki mogły modyfikować dane z procesu głównego. Warto też rozpatrzyć czy wykorzystana technologia nie ograniczy nas do jednego systemu, a nawet tylko do jego konkretnych wersji.