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. 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 przypadkiemopen()
- 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)
, czylink(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:
- f1.c
#include <stdio.h> #include <fcntl.h> #include <unistd.h> #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()
iread()
- 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:
- f2.c
#include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #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:
- d2.c
#include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/dir.h> #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:
- p1.c
#include <stdio.h> #include <unistd.h> 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:
- p2.c
#include <stdio.h> #include <unistd.h> #include <stdlib.h> 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()
aexec()
? - 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:
- jak działa program? Dlaczego?
- Dla lepszego zrozumienia proszę obejrzeć i uruchomić kolejny program:
- p4.c
#include <stdio.h> #include <unistd.h> #include <stdlib.h> 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):
- p5.c
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> 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):
- p6.c
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> 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:
- p7.c
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> #include <fcntl.h> #include <sys/stat.h> #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<n; i++) { 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(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