===== Elementy programowania systemowego ===== ==== Przygotuj się do laboratorium ==== * Proszę przypomnieć sobie, w jaki sposób kompiluje się programy w języku C w środowisku Unix (np. [[https://www.cyberciti.biz/faq/howto-compile-and-run-c-cplusplus-code-in-linux/|zajrzeć tutaj]]). * Proszę przejrzeć manual do funkcji systemowych: **''open(2)''**, **''creat(2)''**, **''read(2)''**, **''write(2)''**, **''stat(2)''**, **''close(2)''**, \\ jak również manuale do funkcji: **''getenv(3)''**, **''putenv(3)''**, **''setenv(3)''** \\ oraz do zmiennych: **''errno(3)''**, **''environ(7)''**. ==== Wiedza ==== === 1. Operacje na plikach === * W systemie Unix dostęp do danych realizowany jest przez pliki. Dostęp procesów do samych plików jest realizowany przez __**deskryptory plików**__. Każdy proces ma pulę 20 deskryptorów (0-19), które mogą być przypisane do plików, potoków, itp. Deskryptory są używane we //wszystkich// funkcjach operujących na plikach. Deskryptor jest reprezentowany przez typ ''int''. * **Tworzenie pliku**: zob. manual do funkcji ''creat(2)'' * **Otwarcie pliku**: zob. manual do funkcji ''open(2)'' * Funkcja ''creat()'' jest szczególnym przypadkiem ''open()'' - na serwerze SPK mają dokładnie tę samą stronę w manualu. * **Czytanie z pliku**: zob. manual do funkcji ''read(2)'' * **Zapis do pliku**: zob. manual do funkcji ''write(2)'' * **Zamknięcie pliku**: zob. manual do funkcji ''close(2)'' System udostępnia również kilka funkcji oferujących zaawansowane operacje na plikach: * do zarządzania prawami dostępu służą np.: ''chmod(2)'', ''chown(2)'', * funkcje ''access(2)'', ''lseek(2)'', czy ''link(2)'' zwiększają możliwości operowania na plikach, * ''stat(2)'' zwraca **informacje o pliku**. === 2. Podstawowe operacje na katalogach === * Katalogi implementowane są przez zwykłe pliki (co oznacza, że można na nich używać funkcji dla plików). * W systemie Unix występuje również szereg funkcji upraszczających pracę z katalogami, m.in.: * ''opendir(3)'', ''closedir(3)'', * ''scandir(3)''. === 3. Praca z procesami === * Środowisko pracy: * ogólny opis w ''environ(7)'', * funkcje operujące na środowisku: ''getenv(3)'', ''putenv(3)'', ''setenv(3)''. * Uruchamianie nowych programów w obrębie bieżącego procesu: * odpowiedzialna funkcja: ''execve(2)'' * "wrapperami" dla użytkownika jest grupa funkcji ''exec(3)'' [czym się różnią między sobą?] * Nie należy ich mylić z funkcją ''system(3)'', która uruchamia zewnętrzne polecenie w osobnym procesie (i zbiera wyjście) * Tworzenie nowych procesów: * Funkcja ''fork(2)'' * Tworzy ona proces potomny będący kopią procesu macierzystego, który dziedziczy jego środowisko pracy. ==== Ćwiczenia ==== === I. Operacje na plikach === - Proszę ściągnąć i obejrzeć poniższy program, a następnie zrealizować polecenia umieszczone w kolejnych podpunktach: #include #include #include #define BUFSIZE 1024 int main (int argc, char **argv) { int f1, c; char b[BUFSIZE], *n1; c = 10; n1 = argv[1]; f1 = open (n1, O_RDONLY); read (f1, b, c); printf("%s: Przeczytano %d znaków z pliku %s: \"%s\"\n", argv[0], c, n1, b); close(f1); return(0); } - skompiluj program: gcc -Wall -ansi -pedantic f1.c -o f1 - uruchom program podając jako argument stworzony wcześniej plik tekstowy - rozbuduj program o sprawdzanie liczby argumentów wywołania - rozbuduj program o sprawdzanie rezultatu funkcji ''open()'' i ''read()'' * wskazówka 1: co powinien wypisywać ''printf()'' jako liczbę przeczytanych znaków? * wskazówka 2: dlaczego przy wypisywaniu znaków, czasami na końcu wyświetlane są "śmieci"? - Proszę ściągnąć i obejrzeć poniższy program, a następnie zrealizować polecenia umieszczone w kolejnych podpunktach: #include #include #include #include #define BUFSIZE 1024 int main (int argc, char **argv) { int f1, f2, c; char b[BUFSIZE], *n1, *n2; c = 10; n1 = argv[1]; n2 = argv[2]; f1 = open (n1, O_RDONLY); read (f1, b, c); printf("%s: Przeczytano %d znaków z pliku %s: \"%s\"\n", argv[0], c, n1, b); f2 = open (n2, O_WRONLY | O_CREAT | O_TRUNC, 0600); write (f2, b, c); printf("%s: Zapisano %d znaków do pliku %s: \"%s\"\n", argv[0], c, n2, b); close(f1); close(f2); return(0); } - skompiluj program: gcc -Wall -ansi -pedantic f2.c -o f2 - uruchom program [ile i jakich argumentów należy podać? co ma robić program?] - rozbuduj program o funkcje z 1. programu - rozbuduj program o możliwość kopiowania pliku dowolnej długości === II. Podstawowe operacje na katalogach === - Proszę ściągnąć, obejrzeć, skompilować i uruchomić poniższy program (jest to prymitywny program typu //ls//), a następnie zrealizować polecenia umieszczone w kolejnych podpunktach: #include #include #include #include #include #define MAX_CHAR 200 int main(int argc, char **argv) { int t; struct direct *e; DIR *d; struct stat s; char p[MAX_CHAR]; d = opendir(argv[1]); while ((e = readdir(d)) != 0) { printf("%d %s", (int)e->d_ino, e->d_name); if (strcmp(e->d_name, ".") != 0 && strcmp(e->d_name, "..") != 0) printf("\n"); else { p[0] = 0; strcat(p, argv[1]); strcat(p, "/"); strcat(p, e->d_name); t = stat(p, &s); if (S_ISDIR(s.st_mode)) printf("/"); printf("\n"); } } closedir(d); return 0; } - rozbuduj program o precyzyjne sprawdzanie wartości zwracanych przez funkcje, a co za tym idzie podstawową diagnostykę błędów - rozbuduj program o jak najwięcej funkcjonalności polecenia ''ls'', czyli czytanie kolejnych danych zwracanych przez funkcję ''stat()'' - rozbuduj program o możliwość wyświetlania zawartości podkatalogów === III. Praca z procesami === - Proszę ściągnąć, obejrzeć, skompilować i uruchomić poniższy program: #include #include extern char **environ; /* można skorzystać ze zmiennej environ z unistd.h ... */ int main (int argc, char **argv, char **envp) { /* ... albo z trzeciego argumentu main() */ int i; printf("Srodowisko procesu:\n"); for (i = 0; envp[i] != NULL;i++) printf("%s\n", envp[i]); return 0; } - zmodyfikuj program, aby pozwalał na wypisanie i zmianę wartości wybranej zmiennej środowiskowej - Proszę ściągnąć, obejrzeć, skompilować i uruchomić poniższy program: #include #include #include extern char **environ; int main (int argc, char **argv, char **envp) { printf("Poczatek procesu.\n"); system("echo ala ma kota"); printf("Dalszy ciag kodu...\n"); execl("/bin/echo", "echo", "jakis napis", NULL); printf("Koniec kodu...\n"); return 0; } - jaka jest różnica pomiędzy funkcjami ''system()'' a ''exec()''? - zmodyfikuj program tak, aby działał tak samo przy użyciu innych wywołań z rodziny funkcji ''exec()'' - Proszę ściągnąć, obejrzeć, skompilować i uruchomić poniższy program: #include #include extern char **environ; int main (int argc, char **argv, char **envp) { int p=0; printf("Poczatek procesu...\n"); p = fork(); printf("Tu jestem: %d\n", p); return 0; } - jak działa program? Dlaczego? - Dla lepszego zrozumienia proszę obejrzeć i uruchomić kolejny program: #include #include #include extern char **environ; int main (int argc, char **argv, char **envp) { int p=0; printf("%s[%d]: Poczatek procesu glownego...\n", *argv, getpid()); p = fork(); if (p == -1) printf("%s[%d]: BLAD! Nie moge stworzyc procesu!\n", *argv, getpid()); else if (p > 0) { printf("%s[%d]: To dalej ja, proces glowny...\n", *argv, getpid()); sleep(5); } else if (p == 0) { printf("%s[%d]: Jestem procesem potomnym, moj rodzic to: [%d]...\n", *argv, getpid(), getppid()); exit(0); } printf("%s[%d]: Koniec procesu glownego...\n", *argv, getpid()); return 0; } - proszę otoczyć komentarzem wywołanie funkcji ''sleep()'', jak to wpłynie na działanie procesów? - Proszę ściągnąć, obejrzeć, skompilować i uruchomić poniższy program (i zobaczyć w jaki sposób proces macierzysty może czekać na procesy potomne): #include #include #include #include extern char **environ; int main (int argc, char **argv, char **envp) { int p=0, p1=0; printf("%s[%d]: Poczatek procesu glownego...\n", *argv, getpid()); p = fork(); if (p == -1) printf("%s[%d]: BLAD! Nie moge stworzyc procesu!\n", *argv, getpid()); else if (p > 0) { printf("%s[%d]: To dalej ja, proces glowny...\n", *argv, getpid()); } else if (p == 0) { printf("%s[%d]: Jestem procesem potomnym, moj rodzic to: [%d]...\n", *argv, getpid(), getppid()); sleep(5); printf("%s[%d]: Koncze ze soba!\n", *argv, getpid()); exit(0); } p1=wait(NULL); printf("%s[%d]: Jestem bezdzietny, nie ma juz: %d :(\n", *argv, getpid(), p1); printf("%s[%d]: Koniec procesu glownego.\n", *argv, getpid()); return 0; } - tutaj również otocz komentarzem wywołanie funkcji ''sleep()'' - co się zmieniło? czym różni się ten program od poprzedniego? - Proszę ściągnąć, obejrzeć, skompilować i uruchomić poniższy program (i zobaczyć w jaki sposób można zmienić program dla wybranych procesów): #include #include #include #include extern char **environ; int main (int argc, char **argv, char **envp) { int p=0, p1=0; printf("%s[%d]: Poczatek procesu glownego...\n", *argv, getpid()); p = fork(); if (p == -1) printf("%s[%d]: BLAD! Nie moge stworzyc procesu!\n", *argv, getpid()); else if (p > 0) { printf("%s[%d]: To dalej ja, proces glowny...\n", *argv, getpid()); } else if (p == 0) { printf("%s[%d]: Jestem procesem potomnym, moj rodzic to: [%d]...\n", *argv, getpid(), getppid()); printf("%s[%d]: Moge byc kims innym!\n", *argv, getpid()); execl("/bin/echo", "echo", "moge stac sie programem ktory cos pisze!", NULL); } p1=wait(NULL); printf("%s[%d]: Jestem bezdzietny, nie ma juz: %d :(\n", *argv, getpid(), p1); printf("%s[%d]: Koniec procesu glownego.\n", *argv, getpid()); return 0; } - Proszę ściągnąć, obejrzeć, skompilować i uruchomić poniższy program: #include #include #include #include #include #include #define BUFSIZE 1024 #define CPC 5 #define NC 5 extern char **environ; int main (int argc, char **argv, char **envp) { int p=0, p1=0, f, n=5, c, i, j; char *b, *n1; c = NC; n1 = argv[1]; printf("%s[%d]: Poczatek procesu glownego...\n", *argv, getpid()); f = open(n1, O_RDONLY); for (i=0; i 0) { printf("%s[%d]: To dalej ja, proces glowny...\n", *argv, getpid()); } else if (p == 0) { printf("%s[%d]: Jestem procesem potomnym, moj rodzic to: [%d]...\n", *argv, getpid(), getppid()); sleep(1); lseek(f, i*CPC, SEEK_SET); b = malloc(sizeof(char)*c+1); j = read (f, b, c); b[c+1]='\n'; printf("%s: Przeczytano %d znaków, poczynajac od: %d, z pliku %s: \"%s\"\n", argv[0], j, i*CPC, n1, b); free(b); exit(0); } } p1=wait(NULL); printf("%s[%d]: Jestem bezdzietny, ostatnie dziecko to: %d :(\n", *argv, getpid(), p1); close(f); printf("%s[%d]: Koniec procesu glownego.\n", *argv, getpid()); return 0; } - ważna obserwacja: proces potomny dziedziczy środowisko, wraz z kopiami deskryptorów plików - ten program tak naprawdę nie czeka na zakończenie wszystkich swoich procesów potomnych - popraw go odpowiednio - proszę przeanalizować i zmodyfikować powyższy program, np. tak, aby czytał inne fragmenty pliku, lub wykonywał równolegle inne operacje