Krótki kurs programowania współbieżnego Procesy i sygnały w językach C oraz Python Semafory i pamięć dzielona (język C) Uproszczony semafor z Pythona Inne metody komunikowania urs systemu UNIX 1
Ale najpierw dokończenie o curses Zadanie: opracować przeglądarkę plików graficznych pracującą w trybie tekstowym. Co się przyda: Obsługa myszki (opcjonalnie) Obsługa plików graficznych (koniecznie) urs systemu UNIX 2
Myszka W trybie keypdad funkcja getch może zwrócić stałą KEY_MOUSE... wówczas możemy dowiedzieć się więcej wywołując getmouse(). zwrócony wynik to krotka (id,x,y,z,bstate). Znaczenie pól: id identyfikator urządzenia x,y,z współrzędne zdarzenia (z nie używany) bstate stan przycisków myszki i nie tylko w momencie zdarzenia urs systemu UNIX 3
Stan przycisków Stan jest wartością całkowitą, zdefinowane są stałe umożliwiające odczyt poszczególnych bitów, przykładowo: from curses import *... id,x,y,z,state = getmouse() if state BUTTON1_DOUBLECLICKED: # było podwójne kliknięcie myszki w punkcie x,y Inne stałe to (n oznacza liczbę 1,2,3 lub 4): BUTTONn_PRESSED -- wciśniety BUTTONn_RELEASED -- zwolniony BUTTONn_CLICKED -- kliknięty BUTTON_SHIFT, BUTTON_ALT, BUTTON_CTRL -- dodatkowo wciśnięte odpowiednie klawisze urs systemu UNIX 4
Obsługa plików graficznych W standardowej dystrybucji na razie ubogo: wsparcie dla plików sgi rgb w module rgbimg Dobrą opinią cieszy się natomiast Python Imaging Library (PIL), do znalezienia na stronie http://www.pythonware.com/products/index.htm urs systemu UNIX 5
Co jest niezbędne? Czytanie plików >>> import Image >>> im = Image.open("plik.jpg") >>> im.format, im.size, im.mode (JPEG, (250,250), RGB) Innnym trybem jest L dla obrazów czarno-białych Dostęp do punktu: im.getpixel(x,y). Jeżeli obiekt ma wiele warstw, to wynikiem jest krotka. Nazwy warstw to krotka zwracana przez im.getbands(), przykładowo ( R, G, B ). urs systemu UNIX 6
A teraz właściwy temat wykładu urs systemu UNIX 7
Procesy i sygnały z poziomu powłoki To był wykład bodajże czwarty. Polecenie ps informuje o działających procesach. Polecenie kill wysyła sygnał. Pamietamy, że kaźdy proces ma swój identyfikator (PID). urs systemu UNIX 8
Podstawowe funkcje dotyczące procesów Uruchamianie procesu systemowego (system) Zastępowanie innym procesem (exec) Rozdwajanie procesów (fork) Nagłówki tych funkcji znajdują się w pliku unistd.h urs systemu UNIX 9
Polecenia exec Dostępnych jest kilka poleceń o nazwach zaczynających się od exec Polecenie zastępuje bieżący proces innym, tworzonym na podstawie argumentu. Pierwszym argumentem jest zawsze ścieżka, pod którą można znaleźć program. Argumenty przekazujemy jako kolejne napisy, zakończone pustym wskaźnikiem execl, albo tablicę wskażników do znaków (execv) Przykłady: execl("/bin/ps","ps","-ax",0); char *argv[] = { "ps", "-ax", 0 }; execv("/bin/ps", argv); urs systemu UNIX 10
W funkcjach z dodatkowym e w nazwie mamy dodatkowy argument środowisko, przekazywany tak jak parametr argv. Przykładowo: char *env[] = { "PATH=/bin:/usr/bin", "TERM=vt100", 0 }; execle("/bin/ps", "/bin/ps","ps","-ax",0, env); urs systemu UNIX 11
Duplikowanie procesu Jest konieczne, jeżeli chcemy, by procesy wykonywały więcej niż jedno zadanie. Funkcja fork rozdwaja bieżący proces. Nowy proces wykonuje ten sam kod, ale w innej przestrzeni danych i z własnym środowiskiem. Ma nowy PID i jego PPID wskazuje na proces rodzica. W połączeniu z którąś funkcją exec pozwala na rodzenie procesów realizujących inne programy. urs systemu UNIX 12
Używanie funkcji fork Deklaracja funkcji fork: #include <sys/types.h> #include <unistd.h> pid_t fork(); Funkcja fork w procesie macierzystym zwraca PID nowo utworzonego procesu. Funkcja fork w procesie potomnym zwraca 0. Możliwy jest też wynik -1 (w procesie macierzystym), oznaczający, że coś poszło nie tak (na przykład przekroczona maksymalna liczba dzieci, czy też brak pamięci w tablicy procesów na nowy wpis). urs systemu UNIX 13
Python Funkcje fork oraz exec* znajdują się w module os. Oczywiście używają typów Pythona, czyli Napisów zamiast char* Listy zamiast char** Liczb całkowitych zamiast pid_t, etc Słownika jako środowiska Nie trzeba dawać 0 jak mamy nieokreśloną liczbę argumentów. urs systemu UNIX 14
Przykład #include <sys/types.h> #include <unistd.h> #include <stdio.h> int main() { pid_t pid; char *message; int n; printf("program fork rozpoczął pracę\n"); pid = fork(); switch(pid) { case -1: perror("coś nie tak."); urs systemu UNIX 15
exit(1); case 0: message = "Proces potomny."; n = 5; break; default: message = "Proces macierzysty."; n = 3; break; } } for(; n > 0; n--) { puts(message); sleep(1); } exit(0); urs systemu UNIX 16
Wynik działania programu Program fork rozpoczął pracę To proces macierzysty. To proces potomny. To proces macierzysty. To proces potomny. To proces macierzysty. To proces potomny. swiatowit:prych> To proces potomny. To proces potomny. urs systemu UNIX 17
Oczekiwanie na proces potomny Proces potomny żyje własnym życiem, czasem chcielibyśmy, by rodzic dowiedział się o losie dziecka, by przykładowo, nie działy się takie brzydkie rzeczy jak w ostatnim przykładzie Oczekiwanie rodzica na dziecko osiągamy wywołując funkcję wait, która wstrzymuje działanie procesu macierzystego do czasu, gdy jeden z procesów potomnych zakończy działanie. Deklaracja: #include <sys/types.h> #include <sys/wait.h> pid_t wait(int *stat_loc) Zwraca kod ukończonego procesu, więcej można się dowiedzieć za pomocą makr odczytujących zawartość zapisaną pod adresem stat_loc urs systemu UNIX 18
Przykładowo WEXITSTATUS(*stat_loc) zwraca kod wyjściowy procesu potomnego. urs systemu UNIX 19
Funkcje związane z sygnałami Sygnał można wysłać (kill), można również zdecydować, w jaki sposób należy sygnał obsłużyć (signal). Można ponadto wysłać sygnał do samego siebie (raise), można na sygnał poczekać (pause). Funkcja alarm umożliwia wysłanie za jakiś czas sygnału SIGALRM. Deklaracje: #include <signal.h> #include <sys/types.h> int kill(pid_t pid,int sig) int raise(int sig) #include <unistd.h> unsigned int alarm(unsigned int liczba-sekund) urs systemu UNIX 20
Deklaracja funkcji signal Deklaracja funkcji signal jest nieco skomplikowana: #include <signal.h> void (*signal(int sig, void (*func)(int)))(int) Deklaracja mówi, że funkcja signal bierze dwa parametry: sig (numer sygnału) oraz func wskaźnik na funkcję, która wymaga jednego argumentu całkowitego. Argumentem dla funkcji func jest numer sygnału, który ma obsłużyć. Funkcja signal zwraca wartość tego samego typu, co func i jest to poprzednia wartość funkcji obsługującej ten sygnał. Można używać dwóch specjalnych stałych: SIG_IGN ignoruj sygnał oraz SIG_DFL obsługa domyślna. Jeżeli coś pojdzie źle, to funkcja signal może zwrócić stałą urs systemu UNIX 21
SIG_ERR. urs systemu UNIX 22
Aplikacja budzika #include <signal.h> #include <stdio.h> #include <unistd.h> static int alarm_fired = 0; void ding(int sig) {alarm_fired = 1;} int main() { int pid; printf("budzik działa\n"); if((pid = fork()) == 0) { sleep(5); urs systemu UNIX 23
} kill(getppid(), SIGALRM); exit(0); printf("czekamy\n"); (void) signal(sigalrm, ding); pause(); if (alarm_fired) printf("dzyń, dzyń!\n"); } printf("gotowe\n"); exit(0); urs systemu UNIX 24
Sygnały w Pythonie Moduł signal. Zdefiniowanie reakcji dla sygnału to signal(s,reakcja), przy czym reakcja musi się dać wykonać dla dwóch argumentów: numeru sygnału oraz ramki definiującej zmienne (frame object). Funkcja kill(proces,syg) jest w modle os. Funkcja pause jest w module signal. urs systemu UNIX 25
Dzielenie zasobów Interfejs programistyczny Uniksa dostarcza następujących mechanizmów: 1. Blokowanie plików 2. Semafory 3. Pamięć dzielona urs systemu UNIX 26
Blokowanie plików Najprostszy schemat (blokowania kooperacyjnego) zakłada wykluczające tworzenie plików flag, mówiących o tym, że czegoś robić nie wolno. Realizuje się to za pomocą niskopoziomowej operacji open z flagami: O_RDWR O_CREAT O_EXCL Uzyskujemy w ten sposób gwarancję, że tylko jednemu procesowi uda się utworzyć blokadę. Całą resztę należy zaprogramować samodzielnie. urs systemu UNIX 27
Blokowanie obszarów W przypadku korzystania przez wiele procesów z bardzo dużych plików schemat z poprzdniego slajdu jest niewystarczający. System dostarcza mechanizmu blokowania obszarów w pliku. Relizuje się to za pomocą funkcji fcntl używanej zgodnie z następującą sygnaturą: #include <fcntl.h> int fcntl(int despliku, int polecenie, struct flock*); Struktura flock powinna zawierać między innymi pola: short l_type; short l_whence; off_t l_start; off_t l_len; pid_t l_pid; // F_RDLCK, F_UNLCK, F_WRLCK urs systemu UNIX 28
Dalsze szczegóły Stałe F_RDLCK,... oznaczają rodzaj blokady (lub informację że chcemy zdjąć blokadę) Są dwa rodzaje blokady: czytelnika (blokuje pisarzy) oraz pisarza (blokuje wsystkich). Obszar pliku definiujemy za pomocą pól l_start oraz l_len, określających początek obszaru i jego długość. Miejsce, od którego liczymy początek określone jest parametrem l_whence. Możliewe są trzy wartości: 1. SEEK_SET: od początku 2. SEEK_CUR: od bieżącej pozycji 3. SEEK_END: od końca urs systemu UNIX 29
Drugi parametr: polecenie Interesują nas trzy wartości dla polecenia: 1. F_GETLK odczytywanie blokady (na zadanym obszarze) 2. F_SETLK ustawianie blokady 3. F_SETLKW cierpliwe ustawianie blokady: ja się nie udaje to czekamy. W każdy z tych wywołań określamy jaka blokada nas interesuje. Po wywołaniu GET trzeba sprawdzić, czy coś zmieniiło się w strukturze przekazanej jako trzeci argument: jeżeli tak to pole l_pid wskazuje na blokujący proces, natomiast pozostałe pola to nowa informacja o tej blokadzie, która uniemożliwiła nasze blokowanie. Polecenie GET nie blokuje pliku. urs systemu UNIX 30
Jeszcze o blokowaniu Zaleca się używanie blokowania z operacjami read oraz write. Ich odpowiedniki z biblioteki standardowej korzystają z własnych buforów, co może spowodować nieprawdłowe działanie. Do użytkownika należy zapobieganie zakleszczeniom. Istnieje prostsza (acz mniej ogólna) funkcja blokująca: lockf. urs systemu UNIX 31