Elementy programowania systemowego w środowisku Unix
better !pout !cry
better watchout
lpr why
santa claus town
cat /etc/passwd >list
ncheck list
ncheck list
cat list | grep naughty >nogiftlist
cat list | grep nice >giftlist
santa claus town
who | grep sleeping
who | grep awake
who | egrep 'bad|good'
for (goodness sake) {
be good
}
/* Nadesłał: Jakub Pelczar, IS 2012 */
DO PRZYGOTOWANIA
-
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)
.
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 command(x) oznacza, że należy wpisać:
man x command
np.: ,,przejrzeć podręcznik do funkcji creat(2)
:
man 2 creat
Deskryptory plików
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
Proszę przejrzeć podręcznik do funkcji creat(2). Jak wywołuje się funkcję, co funkcja zwraca?
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?
Czytanie z pliku
Proszę przejrzeć podręcznik do funkcji read(2). Jak wywołuje się funkcję, co funkcja zwraca?
Zapis do pliku
Proszę przejrzeć podręcznik do funkcji write(2). Jak wywołuje się funkcję, co funkcja zwraca?
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.
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
Środowisko
Proszę przejrzeć podręcznik environ(7).
Proszę przejrzeć podręcznik do funkcji getenv(3), putenv(3), setenv(3).
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.
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
Ćwiczenie: Podstawowe operacje na plikach
W ćwiczeniu będą wykorzystywane przygotowane programy.
Proszę skopiować treść programów ze strony
Proszę oglądnąć poniższy program.
Program f1.c:
- 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:
- 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:
Ćwiczenie: Podstawowe operacje na katalogach
Proszę oglądnąć, skompilować i uruchomić poniższy program.
Program d2.c:
- 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:
precyzyjne sprawdzanie wartości zwracanych przez funkcje, a co za tym idzie podstawową diagnostykę błędów,
jak najwięcej możliwości typu ls, czyli czytanie kolejnych danych zwracanych przez funkcję stat(),
możliwość wyświetlania zawartości podkatalogów.
Ćwiczenie: Środowisko pracy procesu
Proszę oglądnąć i uruchomić poniższy program:
Program p1.c:
- 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.
Ćwiczenie: Podstawy pracy z procesami
Proszę oglądnąć i uruchomić poniższy program:
Program p2.c:
- 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, aby działał tak samo przy użyciu innych wywołań z rodziny funkcji exec().
Proszę oglądnąć i uruchomić poniższy program.
Program p3.c:
- 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:
- 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:
- 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.
Ćwiczenie: Procesy i uruchamianie programów
Proszę oglądnąć i uruchomić poniższy program.
Program p6.c:
- 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);
}
Ćwiczenie: Procesy i zrównoleglanie pracy
Proszę oglądnąć i uruchomić poniższy program.
Program p7.c:
- 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:
Brian W. Kernighan, Dennis M. Ritchie, Język ANSI C (org. The C Programming Language (Second Edition)), WNT, Warszawa, 1994 i następne.
Brian W. Kernighan, Rob Pike, The UNIX Programming Environment, Prentice Hall, 1984.
Marc J. Rochkind, Programowanie w systemie Unix dla zaawansowanych (arg. Advanced Unix Programming), WNT, Warszawa, 1997.
W. Richard Stevens, Advanced Programming in the UNIX Environment, Addison-Wesley Professional, 1992.
Eric S. Raymond, The Art of UNIX Programming, Addison-Wesley Professional, 2003.