Subsections

   
11 LAB: Elementy programowania systemowego w środowisku Unix

WPROWADZENIE

Temat: Podstawowe operacje na plikach

Uwaga! przed korzystaniem z podręcznika można przełączyć go na język polski:

export LANG=pl_PL
lub angielski
export LANG=en_EN

Uwaga! sformułowanie typu ,,przeczytać w manualu opis cos(x)'' oznacza, że należy wpisać:

man 2 cos
np.: ,,przejrzeć podręcznik do funkcji creat(2)'':
man 2 creat

   
11.0.0.1 Deskryptory plików

W systemie Unix dostęp do danych realizowany jest przez pliku. 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.

   
11.0.0.2 Tworzenie pliku

Proszę przejrzeć podręcznik do funkcji creat(2). Jak wywołuje się funkcję, co funkcja zwraca?

   
11.0.0.3 Otwarcie pliku

Funkcja creat() jest szczególnym przypadkiem open().

Proszę przejrzeć podręcznik do funkcji open(2). Jak wywołuje się funkcję, co funkcja zwraca?

   
11.0.0.4 Czytanie z pliku

Proszę przejrzeć podręcznik do funkcji read(2). Jak wywołuje się funkcję, co funkcja zwraca?

   
11.0.0.5 Zapis do pliku

Proszę przejrzeć podręcznik do funkcji write(2). Jak wywołuje się funkcję, co funkcja zwraca?

   
11.0.0.6 Zamknięcie pliku

Proszę przejrzeć podręcznik do funkcji close(2). Jak wywołuje się funkcję, co funkcja zwraca?

Temat: Zaawansowane operacje na plikach

System udostępnia 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.

   
11.0.0.7 Informacje o pliku

Proszę przejrzeć podręcznik do funkcji stat(2). Jak wywołuje się funkcję, co funkcja zwraca?

Temat: Podstawowe operacje na katalogach

Katalogi implementowane są przez zwykłe pliki. Tym niemniej w systemie Unix występuje szereg funkcji upraszczających pracę z katalogami.

Proszę przejrzeć podręcznik do funkcji opendir(3), closedir(3), scandir(3). Jak wywołuje się te funkcje?

Temat: Podstawy pracy z procesami

   
11.0.0.8 Środowisko

Proszę przejrzeć podręcznik environ(7).

Proszę przejrzeć podręcznik do funkcji getenv(3), putenv(3), setenv(3).

   
11.0.0.9 Uruchamianie programu

Proszę przejrzeć podręcznik do funkcji execve(2). Proszę przejrzeć podręcznik do grupy funkcji exec(3). Funkcje z tej grupy pozwalają na uruchamianie kodu nowego programu, w obrębie już istniejącego, bieżącego procesu. Jak wywołuje się funkcje? czym się różnią?

Nie należy mylić działania tych funkcji z działaniem funkcji system(3), która uruchamia zewnętrzne polecenie.

   
11.0.0.10 Tworzenie nowego procesu

Do tworzenia nowego procesu służy funkcja fork(). Tworzy ona proces potomny będący kopią procesu macierzystego, która dziedziczy jego środowisko pracy.

Proszę przejrzeć podręcznik do funkcji fork(2). Jak wywołuje się funkcję, co funkcja zwraca?

ĆWICZENIA

11.1 Ćwiczenie: Podstawowe operacje na plikach

W ćwiczeniu będą wykorzystywane przygotowane programy.

  1. Proszę pobrać plik archiwum projektu lab-unixsys.tar.gz
  2. Archiwum należy skopiować do swojego katalogu domowego i rozpakować:
    $ tar xvzf lab-unixsys.tar.gz
    

Proszę oglądnąć poniższy program.

Program 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);
}

skompilować go:

gcc -Wall -ansi -pedantic f1.c -o f1
i uruchomić, podając jako argument stworzony wcześniej plik tekstowy.

Należy rozbudować program o:

Proszę oglądnąć poniższy program.

Program 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);
}
skompilować go:
gcc -Wall -ansi -pedantic f2.c -o f2
i uruchomić, podając jako argument stworzony wcześniej plik tekstowy i drugi plik, do którego zostaną przepisane dane.

Należy rozbudować program o:

11.2 Ćwiczenie: Podstawowe operacje na katalogach

Proszę oglądnąć, skompilować i uruchomić poniższy program.

Program 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;
}
Jest to prymitywny program typu ls.

Należy rozbudować program o:

11.3 Ćwiczenie: Środowisko pracy procesu

Proszę oglądnąć i uruchomić poniższy program:

Program p1.c:

#include <stdio.h>
#include <unistd.h>

extern char **environ;

int main (int argc, char **argv, char **envp) {
    int i;

    printf("Srodowisko procesu:\n");
    for (i = 0; envp[i] != NULL;i++)
	printf("%s\n", envp[i]);

    return(0);
}
Proszę zmodyfikować program, aby pozwalał na wypisywanie i zmianę wartości wybranej zmiennej środowiskowej.

11.4 Ćwiczenie: Podstawy pracy z procesami

Proszę oglądnąć i uruchomić poniższy program:

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() a exec()?

Proszę zmodyfikować program tak, używają innych wywołań exec().

Proszę oglądnąć i uruchomić poniższy program.

Program p3.c:

#include <stdio.h>
#include <unistd.h>

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ę oglądnąć i uruchomić kolejny program.

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());
    exit (0);
    return(0);
}
Proszę otoczyć komentarzem wywołanie funkcji sleep(), jak to wpłynie na działanie procesów?

Proszę oglądnąć i uruchomić poniższy program.

Program 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());
    exit (0);
    return(0);
}
Procesy macierzyste mogą czekać na zakończenie potomnych.

Proszę rozbudować powyższy program, wg. własnej inwencji.

11.5 Ćwiczenie: Procesy i uruchamianie programów

Proszę oglądnąć i uruchomić poniższy program.

Program 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());
    exit (0);
    return(0);
}

11.6 Ćwiczenie: Procesy i zrównoleglanie pracy

Proszę oglądnąć i uruchomić poniższy program.

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());
    exit (0);
    return(0);
}
Ważna obserwacja: proces potomny dziedziczy środowisko, wraz z kopiami deskryptorów plików.

Proszę przeanalizować i zmodyfikować powyższy program, np. tak, aby czytał inne fragmenty pliku, lub wykonywał równolegle inne operacje.

BIBLIOGRAFIA

Ważne, przydatne książki:



Grzegorz J. Nalepa 2007-04-19