1 / 38

UNIX jesień/zima 2013

UNIX jesień/zima 2013. Wykład 4 Programowanie systemowe w Linux: System plików i procesy. dr inż. Wojciech Bieniecki Instytut Nauk Ekonomicznych i Informatyki http://wbieniec.kis.p.lodz.pl/pwsz. Pliki zwykłe w UNIX.

emilia
Download Presentation

UNIX jesień/zima 2013

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. UNIXjesień/zima 2013 Wykład 4 Programowanie systemowe w Linux: System plików i procesy dr inż. Wojciech Bieniecki Instytut Nauk Ekonomicznych i Informatyki http://wbieniec.kis.p.lodz.pl/pwsz

  2. Pliki zwykłe w UNIX Jądro systemu operacyjnego UNIX udostępnia dwie operacje na plikach realizowane odpowiednio przez funkcje systemowe Odczyt – funkcja read Zapis – funkcja write Z punktu widzenia jądra w systemie UNIX plik nie ma żadnej struktury – jest traktowany jako tablica bajtów. Operacje odczytu lub zapisu mogą dotyczyć dowolnego fragmentu pliku, określonego z dokładnością do bajtu. Plik w UNIX identyfikowany jest przez nazwę. Podawanie nazwy pliku przy każdym odwołaniu do niego wymagałoby przeszukiwania katalogów w celu jego odnalezienia. Dlatego wprowadzono została funkcję systemową open, której zadaniem jest zaalokowanie niezbędnych zasobów w jądrze, umożliwiających wykonywanie dalszych operacji na pliku bez potrzeby przeszukiwania katalogów.

  3. Pliki zwykłe w UNIX Funkcja open zwraca deskryptor (liczba), który będzie przekazywany jako parametrem dla funkcji systemowych związanych z operacjami na otwartych plikach. Standardowo zajęte są deskryptory: 0 – standardowe wejście, 1 – standardowe wyjście 2 –standardowe wyjście diagnostyczne Przy otwieraniu pliku przekazywany jest tryb otwarcia, określający dopuszczalne operacje, jakie można wykonać w związku z tym otwarciem, np. tylko zapis, tylko odczyt lub zapis i odczyt. Tryb otwarcia może mieć również wpływ na sposób wykonania tych operacji, np. każda operacja zapisu Jądro systemu operacyjnego dostarcza też mechanizm tworzenia plików – dostępny przez funkcję systemową creat, która tworzy plik i otwiera go w trybie do zapisu, zwracając odpowiedni deskryptor.

  4. Pliki zwykłe w UNIX Tworzenie i otwieranie plików realizowane jest za pomocą funkcji: open - otwarcie pliku (uogólniona funkcja open umożliwia również utworzenie pliku) creat - utworzenie pliku i otwarcie do zapisu dup - utworzenie kopii deskryptora i nadanie jej pierwszego wolnego numeru z tablicy otwartych plików dup2 - utworzenie kopii deskryptora, umożliwiające określenie jej identyfikatora przez użytkownika close - zamknięcie deskryptora otwartego pliku, unlink - usunięcie dowiązania do pliku Operacje na plikach realizowane są za pomocą funkcji: read - odczyt fragmentu pliku, write - zapis fragmentu pliku, lseek - przesunięcie wskaźnika bieżącej pozycji Powyższe funkcje zdefiniowane są w pliku nagłówkowym fcntl.h.

  5. Struktury danych w jądrze systemu związane z otwartymi plikami

  6. Mechanizm errno errno - uniksowy mechanizm zgłaszania błędów przez funkcje libc w szczególności dla funkcji obsługi plików. Jeśli funkcja zakończy się błędem, sygnalizuje to zwracając zwykle -1 lub, w przypadku funkcji zwracających wskaźnik, NULL. Program powinien wtedy zajrzeć do zmiennej globalnej errno, żeby dowiedzieć się jaki dokładnie błąd wystąpił. Jeśli funkcja zakończy się pomyślnie zawartość errno nie jest zdefiniowana. Opis błędu w odpowiednim dla danego locale języku można otrzymać za pomocą funkcji char *strerror(interrnum); lub intstrerror_r(interrnum, char *buf, size_t n); Parametrem errnum jest najczęściej wartość zmiennej globalnej errno

  7. Opis plikowych funkcji systemowych intcreat(const char *pathname, mode_t mode) Funkcja tworzy plik, którego lokalizację wskazuje parametr pathname i otwiera go do zapisu. Prawa dostępu do utworzonego pliku ustawiane są zgodnie z parametrem mode (zgodnie z UNIX-owymi prawami np 0640). Jeśli plik o takiej nazwie już istnieje a proces wywołujący funkcję creat ma prawo do zapisu tego pliku, to jego zawartość jest usuwana. Funkcja zwraca deskryptor pliku. W przypadku wystąpienia błędów, ich kody dostępne są poprzez errno. EEXIST – plik o podanej nazwie już istnieje, a użyto flag O_CREAT i O_EXCL EFAULT – nazwa pathname wskazuje poza dostępną przestrzeń adresową EACCES – żądany dostęp do pliku nie jest dozwolony ENFILE – osiągnięto limit otwartych plików w systemie EMFILE – proces już otworzył dozwoloną maksymalną liczbę plików EROFS – żądane jest otwarcia w trybie zapisu pliku będącego plikiem tylko do odczytu

  8. Opis plikowych funkcji systemowych int open(const char *pathname, int flags[, mode_t mode]) Funkcja otwiera plik o podanej nazwie zgodnie z odpowiednimi uprawnieniami (parametr mode) i flagami: O_RDONLY – otwarcie w trybie tylko do odczytu O_WRONLY – otwarcie w trybie tylko do zapisu O_RDWR – otwarcie w trybie do odczytu i do zapisu Argument flags może być połączony bitowym OR z jedną (lub więcej) z następujących wartości: O_CREAT – utworzenie pliku, jeśli plik jeszcze nie istnieje, O_TRUNC – obcięcie pliku, jeśli plik istnieje i otwierany jest w trybie O_WRONLY lub O_RDWR, O_EXCL – powoduje zgłoszenie błędu jeśli plik już istnieje i otwierany jestz flagą O_CREAT O_APPEND – operacje pisania odbywają się na końcu pliku. Możliwe kody błędów errno w przypadku błędnego zakończenie funkcji – analogicznie do funkcjicreat

  9. Opis plikowych funkcji systemowych intclose(intfd) Funkcja zamyka deskryptor pliku przekazany przez parametr fd. Po zamknięciu pliku zwalniana jest pozycja w tablicy deskryptorów i może ona zostać ponownie wykorzystana przy otwarciu kolejnego pliku, czyli nowo otwarty plik może otrzymać ten sam deskryptor, który miał plik wcześniej zamknięty. Ponadto zmniejszany jest o 1 licznik deskryptorów w tablicy otwartych plików. Jeśli fd jest ostatnią kopią deskryptora pliku, to zasoby z nim związane zostają zwolnione, natomiast jeśli deskryptor był ostatnia referencją do pliku, który usunięto komendą unlink, plik jest kasowany. Poprawne wykonanie funkcji zwraca 0, błędne -1. W tym przypadku errno przyjmuje wartość: EBADF – wartość fd nie jest prawidłowym deskryptorem otwartego pliku intunlink(const char *pathname) Funkcja powoduje usunięcie dowiązania do pliku. Wskazana przez parametr pathname nazwa pliku jest usuwana, a dodatkowo - jeśli było to jedyne dowiązanie tego pliku następuje usunięcie pliku z systemu.

  10. Opis plikowych funkcji systemowych int dup(intoldfd) Funkcja daje nam drugi uchwyt do tego samego pliku. Funkcja tworzy kopię pozycji w tablicy deskryptorów na innej, wolnej pozycji o najniższym indeksie. Stary i nowy deskryptor mogą być używane zamiennie. Deskryptory współdzielą pozycję pliku i flagi, np. jeśli pozycja pliku zmieniła się po użyciu funkcji lseek na jednym z deskryptorów, zmieniła się ona także na drugim. W przypadku błędu funkcja zwraca -1 Możliwe kody błędów to: EBADF – oldfd nie jest deskryptorem otwartego pliku EMFILE – proces już osiągnął maksymalną liczbę otwartych deskryptorów plików

  11. Opis funkcji systemowych int dup2(intoldfd, intnewfd) Funkcja daje nam drugi uchwyt do tego samego pliku o wartości określonej przez newfd. Gdyby newfd wskazywał na poprzednio otwarty plik, dodatkowo nastąpi jego zamknięcie

  12. Opis plikowych funkcji systemowych int read(inffd, void *buf, size_t count) Funkcja powoduje odczyt count bajtów z otwartego pliku, identyfikowanego przez fd, począwszy od bieżącej pozycji wskaźnika do pliku i umieszczenie ich pod adresem buf w przestrzeni adresowej procesu. Funkcja zwraca liczbę bajtów na której udało się wykonać operację. Zero oznacza koniec pliku, -1 błąd. Odczyt powoduje zmianę wskaźnika bieżącej pozycji w pliku. Po otwarciu pliku wskaźnik ten ustawiony jest na 0, czyli na początek pliku, a po kolejnych operacjach przesuwa się w kierunku końca pliku o tyle bajtów ile udało się odczytać. Możliwe kody błędów errno: EINTR – wywołanie zostało przerwane sygnałem przed odczytaniem danych EAGAIN – przy użyciu O_NONBLOCK wybrano nieblokujące I/O, a nie ma akurat danych dostępnych do odczytania natychmiast EIO – błąd I/O. Zdarza się to np. jeśli proces jest w grupie procesów tła próbuje czytać z kontrolującego tty, lub ignoruje sygnał SIGTIN, lub jego grupa procesów jest osierocona. EISDIR – fd odnosi się do katalogu EBADF – fd nie jest prawidłowym deskryptorem pliku, lub nie jest otwarty dla odczytu EINVAL – fd wskazuje na obiekt nieodpowiedni do odczytu EFAULT – buf wskazuje poza dostępną przestrzeń adresową

  13. Opis plikowych funkcji systemowych int write(inffd, void *buf, size_t count Funkcja powoduje zapis count bajtów do otwartego pliku, wskaznego przez fd, począwszy od bieżącej pozycji wskaźnika do pliku. Bajty są pobierane z adresu buf w segmencie danych przestrzeni adresowej procesu. Funkcja zwraca liczbę bajtów na której udało się wykonać operację. Podobnie jak dla funkcji read zapis powoduje zmianę wskaźnika bieżącej pozycji w pliku. EBADF – deskryptor fd nie jest prawidłowym deskryptorem pliku, lub nie jest otwarty dla odczytu EINVAL – deskryptor fd wskazuje na obiekt nieodpowiedni do zapisu EFAULT – deskryptor buf jest poza dostępną przestrzenią adresową EPIPE – fd jest podłączony do potoku, lub gniazda, którego drugi koniec jest zamknięty. Gdy zdarzy się taka sytuacja, proces otrzyma sygnał SIGPIPE; jeśli jednak go przechwytuje, blokuje lub ignoruje, zwrócony zostanie błąd EPIPE EAGAIN – wybrano nieblokujący I/O (przy użyciu O_NONBLOCK) a do potoku lub gniazda o deskryptorze fd nie można natychmiast zapisać danych EINTR – wywołanie zostało przerwane sygnałem przed zapisaniem danych ENOSPC – urządzenie, zawierające plik o deskryptorze fd nie ma miejsca na dane

  14. Opis plikowych funkcji systemowych long lseek(inffd, off_t offset, int whence) Funkcja powoduje zmianę wskaźnika bieżącej pozycji w otwartym pliku o deskryptorze fd. Nowa pozycja jest bajtem o numerze offset liczonym odpowiednio względem : początku pliku, gdy whence == SEEK_SET końca pliku , gdy whence == SEEK_END bieżącej pozycji, gdy whence == SEEK_CUR Dla offset < 0 następuje przesunięcie w kierunku początku pliku a dla offset >0 przesunięcie w kierunku końca pliku. Funkcja zwraca wartość wskaźnika po przesunięciu liczoną względem początku pliku. Możliwe kody błędów (errno) w przypadku błędnego zakończenie funkcji: EBADF – deskryptor fd nie jest prawidłowym deskryptorem pliku ESPIPE – fd jest związany z potokiem, gniazdem, lub FIFO EINVAL – parametr whence ma nieprawidłową wartość

  15. Przykład 1 – kopiowanie pliku Sprawdzamy poprawność wywołania #include <fcntl.h> #include <stdio.h> #define MAX 512 intmain(intargc, char* argv[]){ char buf[MAX]; intdesc_zrod, desc_cel; intlbajt; if (argc<3){ fprintf(stderr, "Za maloargumentow. Uzyj:\n"); fprintf(stderr, "%s <plik zr> <plik doc>\n", argv[0]); exit(1); } Otwieramy do odczytu plik źródłowy desc_zrod = open(argv[1], O_RDONLY); if (desc_zrod == -1){ perror("Blad otwarcia pliku zrodlowego"); exit(1); }

  16. Przykład 1 – kopiowanie pliku Tworzymy nowy plik desc_cel = creat(argv[2], 0640); if (desc_cel == -1){ perror("Blad utworzenia pliku docelowego"); exit(1); } W pętli kopiujemy bajt po bajcie while((lbajt = read(desc_zrod, buf, MAX)) > 0){ if (write(desc_cel, buf, lbajt) == -1){ perror("Blad zapisu pliku docelowego"); exit(1); } } if (lbajt == -1){ perror("Blad odczytu pliku zrodlowego"); exit(1); } if (close(desc_zrod) == -1 || close(desc_cel) == -1){ perror("Bladzamkniecia pliku"); exit(1);} exit(0);}

  17. Przykład 1 – wyświetl rozmiar pliku Sprawdzamy poprawność wywołania #include <fcntl.h> #include <stdio.h> intmain(intargc, char* argv[]){ intdesc; long rozm; if (argc < 2){ fprintf(stderr, "Za maloargumentow. Uzyj:\n"); fprintf(stderr, "%s <nazwa pliku>\n", argv[0]); exit(1); } Otwieramy do odczytu plik źródłowy desc_zrod = open(argv[1], O_RDONLY); if (desc_zrod == -1){ perror("Blad otwarcia pliku zrodlowego"); exit(1); }

  18. Przykład 2 – wyświetl rozmiar pliku Przesuwamy wskaźnik na koniec pliku rozm = lseek(desc, 0, SEEK_END); if (rozm == -1){ perror("Blad w pozycjonowaniu"); exit(1); } Wyświetlamy rozmiar i zamykamy plik. printf("Rozmiar pliku %s: %ld\n", argv[1], rozm); if (close(desc) == -1){ perror("Bladzamkniecia pliku"); exit(1); } exit(0); }

  19. Zadania laboratoryjne 1. Napisz program do rozpoznawania czy plik o podanej nazwie jest plikiem tekstowym (plik tekstowy zawiera znaki o kodach 0-127 – można w tym celu użyć funkcji isascii) • 2. Napisz program do porównywania plików o nazwach przekazanych jako argumenty. • Wynikiem działania programu ma być komunikat że • pliki są identyczne, • pliki różnią się od znaku nr<nrznaku> w linii<nr znaku linii> • gdy jeden z plików zawiera treść drugiego uzupełnioną o jakieś dodatkowe znaki – plik <nazwa2> zawiera<liczba>znaków więcej niż zawartość pliku • <nazwa1>

  20. Proces – przypomnienie Proces (zadanie) to jednostka, kontrolowana przez system operacyjny i związana z wykonywanym programem Proces ma przydzielone zasoby typu pamięć, procesor i urządzenia zewnętrzne itp. Część przydzielonych zasobów jest do wyłącznej dyspozycji procesu (np. segment danych, segment stosu), część jest współdzielona z innymi procesami (np. procesor, segment kodu). W zależności od aktualnie posiadanych zasobów wyróżnia się stany procesu (np. wykonywany, uśpiony, gotowy), które zmieniają się cyklicznie w związku z wykonywanym programem lub ze zdarzeniami zachodzącymi w systemie. Stan procesu, umożliwiający kontynuację jego wykonywania po przerwaniu nazywany jest kontekstem procesu. • System UNIX udostępnia mechanizm: • tworzenia nowych procesów, • usuwania procesów • uruchamiania programów.

  21. Proces – przypomnienie Procesy w systemie UNIX tworzą zatem drzewiastą strukturę hierarchiczną, podobnie jak katalogi. Każdy proces, z wyjątkiem procesu systemowego o identyfikatorze 1, tworzony jest przez inny proces, który staje się jego przodkiem zwanym też rodzicem. Nowo utworzony proces nazywany jest procesem potomnym. Nowo utworzony proces nazywany jest procesem potomnym. Potomek tworzony jest w wyniku wywołania przez przodka funkcji systemowej fork. Po utworzeniu potomek kontynuuje wykonywanie programu swojego przodka od miejsca wywołania funkcji fork. Proces może się zakończyć dwojako: w sposób naturalny przez wywołanie funkcji systemowej exit. Funkcja systemowa exit wywoływana jest niejawnie na końcu wykonywania programu przez proces lub może być wywołana jawnie w każdym innym miejscu programu. w wyniku reakcji na sygnał. Zakończenie procesu w wyniku otrzymania sygnału nazywane jest zabiciem. Proces może otrzymać sygnał wysłany przez jakiś inny proces (również przez samego siebie) za pomocą funkcji systemowej kill lub wysłany przez jądro systemu operacyjnego.

  22. Proces – przypomnienie Proces macierzysty może się dowiedzieć o sposobie zakończenia swojego potomka przez wywołanie funkcji systemowej wait. Jeśli wywołanie funkcji wait nastąpi przed zakończeniem potomka, przodek zostaje zawieszony w oczekiwaniu na to zakończenie. Jeżeli proces macierzysty zakończy działanie przed potomnym, to proces potomny nazywany jest sierotą (ang. orphan) i jest „adoptowany” przez proces systemowy init, który staję się w ten sposób jego przodkiem. Jeżeli proces potomny zakończył działanie przed wywołaniem funkcji wait w procesie macierzystym, potomek pozostanie w stanie zombi (inaczej upiór, duch, mumia). Zombi jest procesem, który zwalnia wszystkie zasoby (nie zajmuje pamięci, nie jest mu przydzielany procesor), zajmuje jedynie miejsce w tablicy procesów w jądrze systemu operacyjnego i zwalnia je dopiero w momencie wywołania funkcji wait przez proces macierzysty, lub w momencie zakończenia procesu macierzystego.

  23. Proces – przypomnienie W ramach istniejącego procesu może nastąpić uruchomienie innego programu w wyniku wywołania jednej z funkcji systemowych exec. Uruchomienie nowego programu oznacza zmianę programu wykonywanego dotychczas przez proces, czyli zastąpienie wykonywanego programu innym programem. Bezbłędne wykonanie funkcji exec oznacza bezpowrotne zaprzestanie wykonywania bieżącego programu i rozpoczęcie wykonywania nowego programu Jeżeli wykonanie exec zakończy się błędem, program wykonujemy dalej.

  24. Funkcje systemowe obsługi procesów intfork(void) W momencie wywołania funkcji tworzony jest proces potomny, który wykonuje współbieżnie ze swoim przodkiem ten sam program. Potomek rozpoczyna wykonywanie programu od wyjścia z funkcji fork i kontynuuje wykonując kolejną instrukcję, znajdującą się w programie. Do funkcji fork wchodzi proces macierzysty, a wychodzą z niej dwa procesy: macierzysty i potomny, przy czym każdy z nich otrzymuję inną wartość zwrotną funkcji fork. Wartością zwrotną funkcji fork w procesie macierzystym jest PID potomka, a w procesie potomnym wartość 0. W przypadku błędnego wykonania funkcji fork potomek nie zostanie utworzony, a proces wywołujący otrzyma wartość -1. Wówczas możemy odczytać kody błędów z errno. EAGAIN – błąd alokacji wystarczającej ilości pamięci na skopiowanie stron rodzica i zaalokowanie struktury zadań ENOMEM – nie można zaalokować niezbędnych struktur jądra z powodu braku pamięci

  25. Funkcje systemowe obsługi procesów intgetpid(void) intgetppid(void) Przy poprawnym wykonaniu funkcja getpid zwraca własny identyfikator a funkcja getppid - identyfikator procesu macierzystego. Zakończenie błędne: -1 voidexit(int status) Funkcja kończy działanie procesu, który ją wykonał i powoduje przekazanie w odpowiednie miejsce tablicy procesów wartość status, która może zostać odebrana i zinterpretowana przez proces macierzysty. Jeśli proces macierzysty został zakończony, a istnieją procesy potomne – to wykonanie ich nie jest zakłócone, ale ich identyfikator procesu macierzystego wszystkich procesów potomnych otrzyma wartość 1 będącą identyfikatorem procesu init (proces potomny staje się sierotą). exit(0) – oznacza poprawne zakończenie wykonanie procesu exit(dowolna wartość różna od 0 ) – oznacza wystąpienie błędu

  26. Funkcje systemowe obsługi procesów intwait(int *status) Przypadek 1: funkcja zawiesza w tym miejscu wykonywanie programu, dopóki nie zakończy się jeden z procesów potomnych. Wtedy zwraca PID zakończonego procesu. W pole status wstawiany jest status, z jakim zakończył się ten proces. Przypadek 2: Proces potomny zakończył się zanim wywołano wait. Funkcja zwraca wartość tak, jak w przypadku 1 ale od razu. Przypadek 3: Nie istnieją żadne procesy potomne. Funkcja od razu kończy się i zwraca -1. Wartość status==NULL. intwaitpid(intpid, int *status, intoptions) Ta funkcja pozwala poczekać na zamknięcie tylko niektórych potomków pid==-1 – działanie takie jak funkcji wait pid==-GPID – czekamy na zakończenie dowolnego z potomków z grupy oznaczonej prze GPID) pid==PID – czekamy na zakończenie potomków oznaczonego przez PID

  27. Przykład 1 – użycie fork //fork_ex1.c #include <stdio.h> main() { printf("Poczatek\n"); fork(); printf("Koniec\n"); } $ ./fork_ex1 Poczatek Koniec Koniec $ Program jest początkowo wykonywany przez jeden proces. W wyniku wywołania funkcji systemowej fork następuje rozwidlenie i tworzony jest proces potomny, który kontynuuje wykonywanie programu swojego przodka od miejsca utworzenia.

  28. Przykład 2 – użycie exec //test_exec.c #include <stdio.h> main() { printf("Poczatek\n"); execlp("ls", "ls", "-a", NULL); printf("Koniec\n"); } W wyniku wywołania funkcji systemowej execlp następuje zmiana wykonywanego programu, zanim sterowanie dojdzie do instrukcji wyprowadzenia napisu Koniec na standardowe wyjście. Zmiana wykonywanego programu powoduje, że sterowanie nie wraca już do poprzedniego programu i napis Koniec nie pojawia się na standardowym wyjściu w ogóle.

  29. Przykład 3 – użycie fork, exec i wait Zmiana wykonywanego programu przez wywołanie funkcji execlp odbywa się tylko w procesie potomnym (gdy wywołana funkcja fork zwróci 0). include <stdio.h> main(){ printf("Poczatek\n"); switch (fork()) { case -1: perror("Blad utworzenia. potomka"); break; case 0: /* proces potomny */ execlp("ls","ls","-a", NULL); perror("Bladuruch. programu"); exit(1); default: /* proces macierz.*/ if (wait(NULL) == -1) perror("Blad w oczek. na zak. potomka"); } printf("Koniec\n"); } Gdyby execlp nie udała się, to wyświetli się komunikat o błędzie i proces potomny zakończy się dzięki exit. Aby uniknąć sytuacji, że proces macierzysty wyświetli napis Koniec zanim nastąpi wyświetlenie listy plików, stosujemy funkcję wait, która zawiesza proces macierzysty do momentu zakończenia potomka.

  30. Przykład 4 – obserwacja statusu zakończonego procesu #include <stdio.h> main(){ int pid1, pid2, status; pid1 = fork(); /* tu następuje rozwidlenie */ if (pid1 == 0) /* procespotomny */ exit(7); /* proces macierzysty */ printf("Mam przodka o identyfikatorze %d\n", pid1); pid2 = wait(&status); printf("Status zakoncz. procesu %d: %x\n", pid2, status); }

  31. Przykład 5 – obserwacja statusuzabitego procesu #include <stdio.h> main(){ int pid1, pid2, status; pid1 = fork(); if (pid1 == 0) { /* proces potomny */ sleep(10); /*usypiamy proces potomny na 10 sekund */ exit(7); } /* proces macierzysty */ printf("Mam przodka o identyfikatorze %d\n", pid1); kill(pid1, 9); pid2 = wait(&status); printf("Stat. zak. procesu %d: %x\n", pid2, status); }

  32. Procesy i standardowe wejście / wyjście Programy systemowe UNIX oraz pewne funkcje biblioteczne przekazują wyniki swojego działania na standardowe wyjście, czyli do jakiegoś pliku, którego deskryptor ma ustaloną wartość. Podobnie komunikaty o błędach przekazywane są na standardowe wyjście diagnostyczne. Funkcja biblioteczna printf – pobiera parametry i przekazuje wynik na STDOUT. Funkcja biblioteczna perror – pobiera parametry i przekazuje wynik na STDERR. Program ls – pobiera parametry i przekazuje wynik na STDOUT. Pewne programy takie jak more, grep, sort, tr, cutprzekazują wyniki przetwarzania danych wejściowych na standardowe wyjście. Plik z danymi wejściowymi dla tych programów może być przekazany przez podanie jego nazwy jako jednego z argumentów w linii poleceń. Jeśli plik nie zostanie podany, to program zakłada, że dane należy czytać ze standardowego wejścia, czyli otwartego pliku o ustalonym deskryptorze.

  33. Procesy i standardowe wejście / wyjście Przyporządkowanie deskryptorów: 0 — deskryptor STDIN, na którym jest wykonywana funkcja read w programach systemowych w celu pobrania danych do przetwarzania; 1 — deskryptor STDOUT, na którym wykonywana jest funkcja write w programach systemowych w celu przekazania wyników; 2 — deskryptor STDERR, na którym wykonywana jest funkcja write w celu przekazania komunikatów o błędach. Z punktu widzenie programu nie jest istotne, jaki plik jest podłączony do danego deskryptora - ważne jest, jakie operacje można na takim pliku wykonać. W ten sposób przejawia się niezależność plików od urządzeń. Operacje wykonywane na plikach identyfikowanych przez deskryptory 0–2 to najczęściej read i write. Funkcja systemowa lseek może być wykonywana tylko na pliku o dostępie swobodnym, nie może być wykonana na pliku o dostępie sekwencyjnym (łącze komunikacyjne) Za pomocą more można wiec cofać się w przeglądanym plik tylko wówczas, gdy jego nazwa jest przekazana jako parametr w linii poleceń.

  34. Procesy i standardowe wejście / wyjście Proces dziedziczy tablicę deskryptorów od swojego przodka. Jeśli nie nastąpi jawne wskazanie zmiany, STDIN, STDOUT i STDERR procesu uruchamianego przez powłokę w wyniku interpretacji polecenia użytkownika jest terminal, gdyż terminal jest też standardowym wejściem, wyjściem i wyjściem diagnostycznym powłoki. Funkcja systemowa exec nie zmienia stanu tablicy deskryptorów. Jednak możliwa jest podmiana deskryptorów w procesie przed wywołaniem funkcji exec, a następnie zmiana wykonywanego programu. Jednym ze sposobów zmiany STDIN, STDOUT i STDERR jest wykorzystanie faktu, że funkcje alokujące deskryptory (między innymi creat, open) przydzielają zawsze deskryptor o najniższym wolnym numerze. Zamykamy deskryptor STDOUT Zakładając, że STDIN jest otwarte (deskryptor 0), deskryptor numer 1 jest najniższym wolnym deskryptorem o najmniejszej wartości. Funkcja creat przydzieli 1 do pliku ls.txt i plik ten będzie standardowym wyjściem procesu i pozostanie takim po uruchomieniu innego programu Przykład: przeadresowanie STDOUT na plik ls.txt #include <stdio.h> main(intargc, char* argv[]){ close(1); creat("ls.txt", 0600); execvp("ls", argv); }

  35. Sieroty i zombi Program tworzy proces-sierotę, który będzie istniał przez około 30 sekund. Metodą fork tworzymy proces potomny, który wykonuje warunek if (idzie spać na 30 sekund a następnie kończy się dzięki funkcji exit). #include <stdio.h> main(){ if (fork() == 0){ sleep(30); exit(0); } exit(0); } Współbieżnie działający proces macierzysty kończy swoje działanie zaraz po utworzeniu potomka, osierocając go w ten sposób. Po zakończeniu działania proces potomny kończy się i przekazuje status zakończenia. Status ten może zostać pobrany przez jego przodka w wyniku wywołania funkcji systemowej wait.

  36. Sieroty i zombi Do czasu wykonania funkcji wait przez przodka status potomka przechowywany jest w tablicy procesów na pozycji odpowiadającej zakończonemu procesowi. Proces taki istnieje zatem w tablicy procesów pomimo, że zakończył już wykonywanie programu i zwolnił wszystkie pozostałe zasoby systemu (pamięć, procesor, pliki). Proces potomny, który zakończył swoje działanie i czeka na przekazanie statusu zakończenia przodkowi, określany jest terminem zombi. Program tworzy proces-zombi, który będzie istniał przez około 30 sekund. #include <stdio.h> main(){ if (fork() == 0) exit(0); sleep(30); wait(NULL); } Metodą fork tworzymy proces potomny, który natychmiast kończy swoje działanie przez wywołanie funkcji exit przekazując przy tym status zakończenia. Proces macierzysty zwleka z odebraniem tego statusu śpiąc przez 30 sekund, a dopiero później wywołuje funkcję wait, co usuwa proces-zombi.

  37. Zadania laboratoryjne 1. Napisz program tworzący dwa procesy. Każdy ze stworzonych procesów powinien utworzyć proces – potomka. Należy wyświetlać identyfikatory procesów rodziców i potomków po każdym wywołaniu funkcji fork. 2. Napisz program którego rezultatem będzie wydruk zawartości bieżącego katalogu poprzedzony napisem „Poczatek” a zakończony napisem „Koniec” 3. Napisz program tworzący równocześnie trzy procesy zombi.

  38. Literatura Robert Love: Linux. Programowanie systemowe. Helion 2008 Stevens R.W.: Programowanie w środowisku systemu UNIX. WNT, 2002 Havilland K., Gray D., Salama B.: Unix - programowanie systemowe. ReadMe, 1999

More Related