===== 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