Systemy operacyjne

Opis zajęć i zasad

Zajęcia 8

Procesy

Proces (zwany też zadaniem) jest jednostką aktywną, kontrolowaną przez system operacyjny i związaną z wykonywanym programem. Proces ma przydzielone zasoby typu pamięć (segment kodu, segment danych, segment stosu, segment danych systemowych), procesor, 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 przypadku współbieżnego wykonywania tego samego programu w ramach kilku procesów).

W zależności od aktualnie posiadanych zasobów wyróżnia się stany procesu (np. wykonywany, uśpiony, gotowy).

Każdy proces, z wyjątkiem procesu systemowego o identyfikatorze 1, tworzony jest przez inny proces, który staje się jego przodkiem zwanym też procesem rodzicielskim lub krótko rodzicem. Nowo utworzony proces nazywany jest potomkiem lub procesem potomnym. Procesy w systemie UNIX tworzą zatem drzewiastą strukturę hierarchiczną, podobnie jak katalogi.

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 lub w wyniku reakcji na sygnał (kill).

Jeżeli proces macierzysty zakończy działanie przed procesem potomnym, to proces potomny nazywany jest sierotą (ang. orphant) 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 (proces taki nazywany jest zombi, upiorem, duchem lub mumią). 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.

Funkcje służące do obsługi procesów w większości zdefiniowane są w plikach sys/types.h oraz unistd.h.

Funkcje systemowe w C

fork

Służy do tworzenia procesów potomnych.

int fork ( void );

Wartości zwracane:

W procesie macierzystym funkcja zwraca identyfikator (pid) procesu potomnego (wartość większą od 1), a w procesie potomnym wartość 0. Zakończenie błędne zwraca -1.

W momencie wywołania funkcji (przez proces który właśnie staje się przodkiem) 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 po wywołaniu funkcji fork. Do funkcji fork wchodzi zatem tylko 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 identyfikator (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.

Przykład:

#include <stdio.h>

int main(int argc, char *argv[]){
        int pid;
        int num = 10;
        pid = fork();
        printf("%d\n", num);
        if(pid == 0){       /*child*/
                num = 1;
        } else if(pid > 0){  /*parent*/
                num = 2;
        }
        printf("%d\n", num);
}

getpid & getppid

Zwraca własny identyfikator (w przypadku funkcji getpid) lub identyfikator procesu macierzystego (dla funkcji getppid) oraz -1 w przypadku błędu.

int getpid ( void ) oraz int getppid ( void )

Przykład:

#include <stdio.h>
# include <sys/types.h>

int main(){

        int id ;
        id = fork() ;
        printf("id value : %d\n",id);
        if ( id == 0 )
        {
                printf ( "Child : Hello I am the child process\n");
                printf ( "Child : Child is PID: %d\n", getpid());
                printf ( "Child : Parent is PID: %d\n", getppid());
        }
        else
        {
                printf ( "Parent : Hello I am the parent process\n" ) ;
                printf ( "Parent : Parent is PID: %d\n", getpid());
                printf ( "Parent : Child is PID: %d\n", id);
        }

}

Uruchom program kilka razy, czy widzisz różnice w wyniku jego wykonanania?

exit

void exit ( int status )

Funkcja kończy działanie procesu, który ją wykonał i powoduje przekazanie w odpowiednie miejsce tablicy procesów wartości 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ą (ang. orphant) i jest „adoptowany" przez proces systemowy init). Funkcja exit zdefiniowana jest w pliku stdlib.h.

exit(0) - oznacza poprawne zakończenie wykonanie procesu

exit(dowolna wartość różna od 0 ) - oznacza wystąpienie błędu

wait

Oczekiwanie na zakończenie procesu potomnego. Funkcja zwraca identyfikator (pid) procesu, który się zakończył. Gdy działa parę procesów potomnych zakończenie jednego z nich powoduje powrót z funkcji wait.

int wait ( int *status )

int waitpid ( int pid, int *status, int options )

Poprawne wykonanie funkcji zwraca identyfikator procesu potomnego, który się zakończył, zakończenie błędne -1. Argumenty:

status - status zakończenia procesu (w przypadku zakończenia w sposób normalny) lub numer sygnału w przypadku zabicia potomka lub wartość NULL, w przypadku gdy informacja o stanie zakończenia procesu nie jest istotna

pid - identyfikator potomka, na którego zakończenie czeka proces macierzysty

pid < -1 oznacza oczekiwanie na dowolny proces potomny, którego identyfikator grupy procesów jest równy modułowi wartości pid.

pid = -1 oznacza oczekiwanie na dowolny proces potomny; jest to takie samo zachowanie, jakie stosuje funkcja wait.

pid = 0 oznacza oczekiwanie na każdy proces potomny, którego identyfikator grupy procesu jest równe identyfikatorowi wołającego procesu.

pid > 0 oznacza oczekiwanie na potomka, którego identyfikator procesu jest równy wartości pid.

options - jest sumą OR zera lub następujących stałych:

WNOHANG oznacza natychmiastowe zakończenie jeśli potomek się nie zakończył.

WUNTRACED oznacza zakończenie także dla procesów potomnych, które się zatrzymały, a których status jeszcze nie został zgłoszony.

Przykład:

#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <wait.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
        if (fork()==0) {
                /*proces potomny*/
                exit(5);
        } else {
                /*proces macierzysty*/
                int stat;
                wait(&stat);
                printf("status=%d\n", stat>>8);
        }
        exit(0);
}

Przykład użycia funkcji kill() do usunięcia procesu potomka

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
int main()
{
        pid_t child;
        int status, retval;
        if((child = fork()) < 0) {
                perror("fork");
                exit(EXIT_FAILURE);
        }
        if(child == 0) {
                sleep(1000);
                exit(EXIT_SUCCESS);
        }
        else {
                if((waitpid(child, &status, WNOHANG)) == 0) {
                        retval = kill(child, SIGKILL);
                        if(retval) {
                                printf("usuniecie nie powiodlo sie\n");
                                perror("kill");
                                waitpid(child, &status, 0);
                        }
                        else
                                printf("%d usunalem\n", child);
                }
        }
        exit(EXIT_SUCCESS);
}

execl

Funckje z rodziny exec podmieniają obraz bieżącego procesu (stos, sterta, instrukcje) obrazem nowego procesu. W ramach istniejącego procesu może nastąpić uruchomienie innego programu w wyniku wywołania funkcji systemowych execl. Bezbłędne wykonanie funkcji execl oznacza bezpowrotne zaprzestanie wykonywania bieżącego programu i rozpoczęcie wykonywania programu, którego nazwa jest przekazana przez argument.

int execl ( char *path, char *arg0, ..., char *argn, char *null

int execlp ( char *file, char *arg0, ..., char *argn, char *null )

Zwraca błąd lub nic.

Pierwszym argumentem jest ścieżka bezwzględna do pliku wykonywalnego, kolejne zaś określają argumenty (uwaga: razem z nazwą polecenia). Ze względu na to, że liczba argumentów nie jest z góry ustalona, ostatnim argumentem funkcji execl musi być zawsze 0 (lub stała NULL).

Uwaga! Funkcje exec nie tworzą nowego procesu, tak jak w przypadku funkcji fork!

Przykłady:

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

main(){
        printf("Poczatek\n");

        execlp("ls", "ls", "-a", NULL);

        printf("Koniec\n");
}

Zauważ, że "Koniec" się nie wypisał! Zmieńmy trochę nasze podejście:

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

main(){
        printf("Poczatek\n");
        if (fork() == 0){
                execlp("ls", "ls", "-a", NULL);
                perror("Blad uruchmienia programu");
                exit(1);
        }
        wait(NULL);
        printf("Koniec\n");
}

Przykład:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
        int i;
        int suma=0;
        int pro = fork(); /* utwA3rz nowy proces */
        if (pro==0) sleep(2);
        for (i=0; i<3; i++)
        {
                printf("pid %d ppid %d i=%d\n",getpid(),getppid(),i);
                fflush(stdout);
                suma +=i;
        }
        printf("pid %d ppid %d  suma=%d\n",getpid(),getppid(),suma);
        //wait(NULL); //spojrz na wynik dzialania z i bez tej komendy
        exit(0);
}

Przykład drzewo procesów:

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

#define PROC_NR 5

int main () {
        int i;
        pid_t pid;

        for (i = 1; i < PROC_NR; i++) {
                pid = fork ();
                if (pid < 0)
                perror ("Błąd w fork\n");
        else if (pid != 0) {
                printf("Jestem ojcem. Mój PID = %d. PID syna = %d\n",getpid(),pid);
                if (wait(0) < 0)
                perror ("Błąd w wait\n");
        }
        else if (pid == 0) printf("Jestem potomkiem. Mój PID = %d\n", getpid());
        }

        return 0;
}

Nowy program dziedziczy po procesie, który wywołał "exec" wiele rzeczy (np może dziedziczyć otwarte deskryptory plików).

Parametry i środowisko są dostępne w programie (napisanym w języku "C") przy pomocy parametrów "argv" i "env":

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <linux/input.h>

main(int argc, char *argv[], char *env[])
{
        int i; char **c;
        for(i=0; i<argc; i++) printf("%s\n", argv[i]);
        // wyswietlanie argumentow

        c=env; while(*c) printf("%s\n", *c++);
        // wyswietlanie A?rodowiska

        printf("\n%s\n",getenv("PWD"));
        //wyswietlenie wartośći PWD
}

Kody źródłowe poleceń basha

Program cat

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define BUFSIZ 128

int main(int argc, char *argv[]) {

        char buf[BUFSIZ];
        int f,i,n;
        if (argc <= 1)
        while((n=read(0, buf, BUFSIZ)) > 0)
                write(1, buf, n);

        for(i=1; i<argc; i++) {
                if ((f = open(argv[i], O_RDONLY)) > 0) {
                        while((n =read(f, buf, BUFSIZ)) > 0)
                        write(1, buf, n);
                        close(f);
                }
                else
                {
                        perror(argv[i]);
                }
        }
        return 0;
}

Program echo

#include <stdio.h>

int main(int argc, char *argv[]){

        int i;
        for(i=1; i<argc; i++) {
                printf("%s ", argv[i]);
        }
        printf("\n");
        return 0;
}

Program wc

#include <stdio.h>
#define IN  1 /* inside a word */
#define OUT 0 /* outside a word */
/* count lines, words, and characters in input */
main(){
        int c, nl, nw, nc, state;
        state = OUT;
        nl = nw = nc = 0;
        while((c = getchar()) != EOF) {
                ++nc;
                if(c == '\n')++nl;
                if(c == ' ' || c == '\n' || c == '\t') state = OUT;
                else
                if(state == OUT) {
                        state = IN;
                        ++nw;
                }
        }
        printf("%d %d %d\n", nl, nw, nc);
}

Program od (zapisuje plik w formie bitowej w systemie ósemkowym)

#include <stdio.h>

int main(){

        int c, n;

        n = 1;
        while ((c = getchar()) != EOF) {
                printf("%3o", c);
                if (n++ >= 10) {
                        n = 1;
                        printf("\n");
                }
                else {
                        printf(" ");
                }
        }

        if (n != 1) printf("\n");

        return(0);
}

Zadanie

  1. Napisz program którego rezultatem będzie wydruk zawartości bieżącego katalogu poprzedzony napisem „--Poczatek listy--" a zakończony napisem „--Koniec listy--".

  2. 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.

  3. Napisz program tworzący równocześnie trzy procesy zombi.

*

Wykorzystano materiały z:

https://bap.faculty.wmi.amu.edu.pl/index.php/dydaktyka/dsop/

http://wazniak.mimuw.edu.pl/index.php?title=SOP_lab_nr_7

http://korzen.org/wsisiz/programowanie%20systemowe/wyklad/6.%20Procesy%20i%20sygnaly.pdf

http://www.cs.put.poznan.pl/anstroinski/data/uploads/sop2/materials/procesy-zadania-cs.pdf

http://www.kalkowski.name/dydaktyka/2012-2013-Z/DSOP/zajecia-programowanie-2/