SO2 kolokwium1 sci

Procesy i sygnały w Linuksie

Budowa procesu w Uniksie(Przestrzeń procesu użytkownika): kontekst użytkownika(tekstu, stałych, zmiennych zainicjowanych, zmiennych niezainicjowanych, sterty i stosu) oraz kontekst jądra(wyłącznie dane). Obszar tekstu(tylko do odczytu-może go współdzielić kilka procesów równocześnie) zawiera rozkazy maszynowe, które są wykonywane przez sprzęt. Obszar stałych(tylko do odczytu) we współczesnych systemach uniksowych jest łączony w jeden obszar z obszarem tekstu. Obszar zmiennych zainicjowanych zawiera zmienne, którym zostały przypisane wartości początkowe, ale proces może je dowolnie modyfikować. Obszar zmiennych niezainicjowanych(bss) zawiera zmienne, które mają wartość początkową zero, a więc nie trzeba ich wartości inicjujących przechowywać w pliku programu. Sterta(ang. heap) i stos(ang. stack) tworzą w zasadzie jeden obszar – sterta służy do dynamicznego przydzielania dodatkowego obszaru w pamięci, natomiast na stosie przechowywane są ramki stosu, czyli informacje związane z wywołaniem podprogramów. Sterta rozszerza się w stronę wyższych adresów, natomiast stos w stronę niższych adresów. Kontekst jądra(zawiera informacje o stanie tego procesu)-proces użytkownika nie ma bezpośredniego dostępu, obszar ten może być modyfikowany tylko przez jądro. Pewne wartości w tym kontekście mogą być modyfikowane z poziomu procesu użytkownika poprzez odpowiednie wywołania systemowe.

Tworzenie nowych procesów: Działający proces może stworzyć proces potomny używając funkcji fork() udostępnianej z poziomu biblioteki standardowej języka C. W systemie Linux funkcja ta jest „opakowaniem” na wywołanie clone(), które nie jest wywołaniem standardowym, tzn. nie jest dostępne w innych systemach kompatybilnych z Uniksem i nie należy go bezpośrednio stosować w programach, które mają być przenośne. W Linuksie zastosowany jest wariant tworzenia procesów określany po angielsku copy-on-write. Oznacza to, ze po stworzeniu nowego procesu współdzieli on zarówno obszar tekstu, jak i obszar danych (tj. stertę, stos, zmienne zainicjowane i niezainicjowane) z rodzicem. Dopiero, kiedy któryś z nich będzie próbował dokonać modyfikacji danych nastąpi rozdzielenie obszaru danych (proces potomny otrzyma kopię obszaru rodziciela). Aby wykonać nowy program należy w procesie potomnym użyć jednej z funkcji exec(). Sterowanie z procesu rodzicielskiego do procesu potomnego nigdy bezpośrednio nie wraca, ale proces rodzicielski może poznać status wykonania procesu potomnego wykonując jedną z funkcji wait(). Jeśli proces rodzicielski nie wykona tej funkcji, to zakończony proces potomny zostaje procesem zombie. W przypadku, gdy proces-rodziciel zakończy się wcześniej niż proces potomny, to ten ostatni jest „adoptowany” przez proces init, którego PID (identyfikator procesu) wynosi „1” lub inne procesy należące do jego grupy procesu rodzicielskiego.

Sygnały(przerwania programowe): można uznać za prostą formę komunikacji między procesami, ale przede wszystkim służą one do powiadomienia procesu, że zaszło jakieś zdarzenie. Sygnały są asynchroniczne względem wykonania procesu (nie można przewidzieć kiedy się pojawią). Mogą być wysłane z procesu do procesu lub z jądra do procesu. Programista ma do dyspozycji funkcję kill(), która umożliwia wysłanie sygnału do procesu o podanym PID. Z każdym procesem jest związana struktura, w której umieszczone są adresy procedur obsługi sygnałów. Jeśli programista nie napisze własnej funkcji obsługującej dany sygnał, to wykonywana jest procedura domyślna, która powoduje natychmiastowe zakończenie procesu lub inne, zależne od konfiguracji zachowanie. Część sygnałów można ignorować, lub zablokować je na określony czas. Niektórych sygnałów nie można samemu obsłużyć, ani zignorować, ani zablokować (np. SIGKILL).

Opis ważniejszych funkcji:

fork() - stwórz proces potomny. Zwraca dwie wartości: dla procesu macierzystego - PID potomka, dla procesu potomnego „0”. Jeśli jej wywołanie się nie powiedzie, to zwraca wartość „-1”.

clone() - funkcja specyficzna dla Linuksa, służy do tworzenia nowego procesu.

getpid() i getppid() - funkcje zwracają odpowiednio: PID procesu bieżącego i PID jego rodzica.

sleep() - służy do „uśpienia” procesu na określoną liczbę sekund.

wait - nie jest to jedna funkcja, ale rodzina funkcji (wait(), waitpid(), wait3(), wait4()). Powodują one, że proces macierzysty czeka na zakończenie procesu potomnego. Status zakończenia procesu możemy poznać korzystając z odpowiednich makr.

exit() - funkcja kończąca wykonanie procesu. Istnieje kilka innych podobnych funkcji

exec – rodzina funkcji (execl(), execlp(), execle(), execv(), execv()), które zastępują obraz w pamięci aktualnie wykonywanego procesu obrazem nowego procesu odczytanym z pliku.

kill() – funkcja powodująca wysłanie sygnału o określonym numerze do procesu o określonym PID.

signal() – funkcja pozwala określić zachowanie procesu, po otrzymaniu odpowiedniego sygnału. Z tą funkcją powiązane są funkcje sigblock() i sigsetmask(). Współcześnie zalecane jest stosowanie sigaction() i sigprocmask () zamiast signal().

pause() – funkcja powoduje, że proces czeka na otrzymanie sygnału.

alarm() - pozwala ustawić czas, po którym proces otrzyma sygnał SIGALRM


Potoki i łącza nazwane w Linuksie

Komunikacja z wykorzystaniem strumieniistnieje możliwość utworzenia nowego procesu realizującego wybrane polecenie powłoki i stworzenia dla niego łącza komunikacyjnego za pomocą tyko jednego wywołania funkcji. Tą funkcją jest funkcja popen(). Łącze utworzone przy jej pomocy jest jednokierunkowe(proces wywołujący może do niego pisać lub z niego czytać, nigdy jednocześni). Łącze należy zamykać za pomocą funkcji pclose(). Jest ono wiązane ze standardowym wejściem lub wyjściem polecenia, w zależności od rodzaju operacji, czyli domyślnie „zastępuje” klawiaturę lub ekran.

Potoki(łącza nienazwane)- komunikacja jednokierunkowa, która zwykle służy do wymiany informacji między dwoma spokrewnionymi procesami. Istnieje możliwość używania potoku tylko w obrębie jednego procesu lecz stosuje się tego w praktyce. Potok tworzony jest w przestrzeni jądra, ale poprzez wywołanie funkcji pipe() w przestrzeni użytkownika, a dostęp do niego odbywa się przy pomocy funkcji umożliwiających niskopoziomowe operacje na plikach – read() i write(). Ma on skończoną pojemność, której wielość zależy od konfiguracji systemu(w nowszych wersjach 64KiB). Dla utworzonego potoku można ustawić znacznik O_NONBLOCK, który powoduje, że program nie będzie przechodził w stan oczekiwania w określonych sytuacjach, związanych z obsługą potoku (szczegóły w następnym punkcie).

Kolejki FIFO(Łącza nazwane)-podobna w działaniu do potoku, ale w przeciwieństwie do niego ma nazwę, a więc mogą z niej korzystać procesy niespokrewnione. FIFO pojawiły się w System V (Istniały od System III). Pierwotnie tworzono je za pomocą funkcji mknod(), obecnie funkcją mkfifo(). Aby skorzystać z tak utworzonego łącza nazwanego należy go otworzyć albo do zapisu, albo do odczytu (to samo ograniczenie co w przypadku potoku). Dodatkowo można zastosować flagę O_NONBLOCK. Powoduje to, że proces nie będzie czekał na zakończenie pewnych operacji związanych z obsługą kolejki. Dokładniej objaśnia to poniższa tabela:

Dwa ostatnie wiersze powyżej tabeli odnoszą się także do potoków.Można również wyodrębnić kilka reguł, którym podlega pisanie i czytanie do kolejek i potoków:-Jeśli proces żąda przeczytania mniejszej porcji danych niż wynosi bieżąca zawartość medium, to będzie pobrane dokładnie tyle danych, ile zażądano. Reszta danych nie ulega zniszczeniu i może być odczytana przy następnej operacji czytania.

-Jeśli proces będzie usiłował odczytać więcej danych niż znajduje się w medium, to odczytanych i tak zostanie tylko tyle danych, ile jest w potoku lub kolejce.

-Jeśli proces próbuje czytać z medium, które nie zostało przez żaden inny proces otwarte do pisania, to wynikiem funkcji read() będzie zero. W przypadku kiedy ustawiony jest znacznik O_NONBLOCK zachowanie funkcji read() jest takie samo.

-Zapis danych jest operacją niepodzielną, o ile proces zapisuje dane do medium porcjami mniejszymi od jego pojemności,

-Jeśli proces próbuje zapisywać do medium, które nie zostało otwarte przez inny proces do odczytu, to otrzyma sygnał SIGPIPE, którego domyślna obsługa polega na zakończeniu procesu.

Opis ważniejszych funkcji:

popen() uruchamia polecenie powłoki podane w jej argumentach wywołania oraz tworzy strumień służący do komunikacji między procesem wywołanym, wywołującym, można obsługiwać standardowymi funkcjami fread() i fwrite().

fread() - służy do odczytu buforowanego ze strumienia.

fwrite() - służy do zapisu buforowanego do strumienia.

pclose() - służy do zamykania strumienia stworzonego przez popen().

pipe() - służy do tworzenia potoku łączącego dwa spokrewnione procesy. Jako argument wywołania przyjmuje dwuelementową tablicę, w której będą zapisane deskryptory potoku. Deskryptor zerowy jest do odczytu, a pierwszy do zapisu. Zwykle jest ona wywoływana przed fork(), co powoduje, że procesy powstałe w wyniku podziału odziedziczą tablicę deskryptorów. Każdy z tych procesów zamyka jeden z deskryptorów, np. jeśli komunikacja przebiega według schematu: rodzic -> potomek, to rodzic zamyka deskryptor do odczytu, a potomek do zapisu.

read() - czyta określoną liczbę bajtów z deskryptora bez buforowania.

write() - zapisuje określoną liczbę bajtów do deskryptora bez buforowania.

close() – zamyka deskryptor pliku. Do zamykania strumieni służy fclose() lub pclose().

mkfifo() - tworzy łącze nazwane o podanej nazwie i atrybutach, może być zastąpiona przez mknod().

open() – otwiera plik (również łącze nazwane). Możliwe jest dzięki niej ustalenie trybu otwarcia i ustawienie znaczników.

fcntl() - służy do manipulacji deskryptorem pliku. Można dzięki niej ustawić flagę O_NONBLOCK dla potoku.

error() – wypisuje wiadomość o błędzie zwróconą przez system. Przykład użycia: if(fork() < 0) perror(”fork”);

unlink() – funkcja ta usuwa z systemu plików nazwę, która może odnosić się do dowiązania lub pliku (ostatni przypadek odpowiada operacji usunięcia pliku). Można ją wykorzystać do automatycznego usuwania łącza nazwanego.

Komunikacja IPC – kolejki komunikatów

Linux udostępnia procesom użytkownika mechanizmy komunikacji, nazywane w skrócie IPC(InterprocessCommunication). W ich skład wchodzą kolejki komunikatów. Choć możliwe jest, że ten mechanizm zostanie zmieniony lub częściowo zastąpiony innym w przyszłych specyfikacjach standardu, to obecnie stanowi on dogodny sposób komunikacji między procesami. Istnieją dwa polecenia dostępne z poziomu powłoki użytkownika związane z obsługą komunikacji IPC: ipcs -wyświetla informacje na temat utworzonych kolejek, semaforów i pamięci dzielonych. ipcrm - służy do „ręcznego” usuwania z systemu zasobów IPC.

Kolejki komunikatów- są tworzone w przestrzeni jądra systemu. Proces użytkownika ma do nich dostęp poprzez wywołania systemowe(funkcje biblioteczne C). Każda kolejka ma swój id. Ich Obsługa jest mniej skomplikowana niż łączy nazwanych, a sposób przesyłania informacji jest bardziej elastyczny. Jedna kolejka może służyć kilku procesom. Procesy mogą odbierać wszystkie, lub tylko wybrane komunikaty przechodzące przez kolejkę. Komunikaty podlegają ograniczeniom pod względem wielkości jednego komunikatu, jak i sumarycznej wielkości wszystkich komunikatów. W Linux pierwszy limit wynosi 4KB (stała MSGMAX), a drugi 16KB (stała MSGMNB).

Pliki nagłówkowe związane z obsługą kolejek:sys/types.h – definicje typów, sys/ipc.h- funkcje i struktury wspólne dla wszystkich mechanizmów IPC, sys/msg.h – funkcje i struktury przeznaczone do obsługi kolejek komunikatów.

Najważniejsze struktury związane z kolejkamiKażdy komunikat opisany jest strukturą, w której znajduje się obowiązkowe pole określające typ komunikatu. Dalsza część definicji struktury komunikatu jest dowolna. Najczęściej podawana jest taka oto przykładowa struktura komunikatu: (structmsgbuf { longmtype; char mtext[1]; };). Jądro systemu, dla każdej kolejki utrzymuje strukturę msqid_ds, którą można częściowo modyfikować za pomocą funkcji msgctl(), głównie dotyczy to pól struktury zagnieżdżonej msg_perm: uid – identyfikatora użytkownika dla właściciela, guid – identyfikator grupy dla właściciela oraz pola mode, które obejmuje 9 najmłodszych bitów określających tryb dostępu do kolejki.

Najważniejsze funkcje obsługujące kolejki komunikatów:

ftok() –zwraca identyfikator(w praktyce zdarzają się kolizje, dla dwóch różnych par argumentów wywołania może zwrócić takie same wartości), który można użyć do tworzenia kolejki komunikatu. Aby dwa niespokrewnione procesy mogły korzystać z tego samego kanału komunikacyjnego muszą podać te same argumenty dla wywołania tej funkcji (ścieżkę dostępu) do dowolnie wybranego pliku i naturalną liczbę ośmiobitową(typem argumentu jest long, lecz branych jest pod uwagę tylko osiem najmłodszych bitów).

msgget() –tworzy kolejkę komunikatów i zwraca jej id lub zwraca id kolejki istniejącej. Kolejka jest tworzona na podstawie klucza zwracanego przez funkcję ftok(), lub dobranego przez programistę. Jeśli kolejka ma być stworzona tylko i wyłącznie na użytek jednego procesu, to jako klucz podaje się stałą IPC_PRIVATE. Drugi argument wywołania tej funkcji określa tryb dostępu (czterocyfrowa liczba ósemkowa zaczynająca się od zera, lub odpowiednia kombinacja stałych MSG_R i MSG_W) oraz może określać w jaki sposób ma być uzyskany identyfikator kolejki (IPC_CREAT i IPC_EXCL).

msgsnd() – umożliwia dodanie komunikatu do kolejki. W zależności od wartości ostatniego argumentu może, w przypadku kiedy kolejka jest zapełniona, oczekiwać na zwolnienie miejsca lub zwracać błąd (IPC_NOWAIT).

msgrcv() –umożliwia odbiór komunikatu z kolejki. Możliwe jest selektywne odbieranie komunikatów, w zależności od wartości argumentu funkcji określającego typ odbieranego komunikatu. Jeśli będzie on miał wartość zero, to będzie odebrany pierwszy komunikat znajdujący się w kolejce. Jeśli wartość dodatnią, to odebrany będzie pierwszy komunikat o takim samym typie. Jeśli natomiast argument będzie miał wartość ujemną, to odebrany będzie komunikat o typie takim samym lub mniejszym co do wartości bezwzględnej od podanego argumentu.

msgctl() –umożliwia sterowanie istniejącą kolejką komunikatów. Może wykonać trzy operacje: IPC_STAT – pobranie do struktury msqid_ds informacji o kolejce, IPC_SET – ustawienie danych kolejki na podstawie zawartości struktury msqid_ds i IPC_ RMID – usunięcie kolejki(możliwe tylko wtedy, kiedy procesy korzystające z kolejki zakończyły operacje pisania i czytania)


Wyszukiwarka

Podobne podstrony:
SO2 kolokwium1 tresci sci
SO2 kolokwium1 programy sci
do kolokwium interna
WODA PITNA kolokwium
KOLOKWIUM 2 zadanie wg Adamczewskiego na porownawczą 97
kolokwium 1
Materiały do kolokwium III
Fizjologia krążenia zagadnienia (II kolokwium)
Algebra liniowa i geometria kolokwia AGH 2012 13
analiza funkcjonalna kolokwium
kolokwiumzTMIC
kolokwium probne boleslawiec id Nieznany
Kolokwium (2)

więcej podobnych podstron