Nazov clanku moze trosku zavadzat, na vystiznejsi a pritom strucny som neprisiel. Tento serial sa bude tykat najma pouzitia GLIBC kniznic a kompilovania a linkovania programov napisanych v C na GNU/Linux systemoch. Od citatela sa ocakava minimalna znalost programovania v C. Ak vsak mate stipku logiky a ovladate zaklady programovania, serial je z istej casti urceny aj vam.
Ak sa v clankoch dostanete k niecomu, comu nebudete rozumiet, pytajte sa v komentaroch. Pre teoriu jazyka odporucam Uvod do programovania v jazyku C.
Vstupne argumenty pre program
Ked spustame program z prikazoveho riadku, zvycajne piseme nazov programu a parametre pre program (napriklad ls -al
). Aby sa k tymto argumentom dostal proces, kernel (jadro systemu) ich zapise do isteho miesta pamate procesu a funkcii main
ich priradi ako vstupne hodnoty.
Funkcia main
dostava 3 vstupne hodnoty: pocet argumentov, smernik (pointer) na argumenty a smernik na enviromentalne nastavenia (vysvetlenie nizsie). Nulty argument (prvy v poli) je nazov, ktorym bol program spusteny.
// kompilacia: gcc -o priklad1 priklad1.c
#include <stdio.h>
int main (int argc, char ** argv) {
int i;
for (i = 0; i < argc; i++) {
printf("%i : %s\n", i, argv[i]);
}
}
user@hostname:~/c$ priklad1 a b c
0 : priklad1
1 : a
2 : b
3 : c
user@hostname:~/c$ ./priklad1 ahoj
0 : ./priklad1
1 : ahoj
gunzip
je zvycajne symbolicky odkaz na gzip
, robi vsak odlisnu cinnost. Pri zistovani teda nestaci porovnat argv[0]
s retazcom "gunzip"
, je potrebne odstranit z retazca argv[0]
cestu ku suboru a zanechat iba nazov suboru (z retazca "/bin/gunzip"
treba ziskat retazec "gunzip"
). V string.h
je na tento ucel definovany prototyp funkcie basename
.
Priklad 2: Zistenie nazvu suboru, ktorym bol program spusteny
// kompilacia: gcc -o priklad2 priklad2.c
#include <stdio.h>
#include <string.h>
int main (int argc, char ** argv) {
char * progname = basename(argv[0]);
printf("%s\n", progname);
}
user@hostname:~/c$ priklad2
priklad2
user@hostname:~/c$ ./priklad2
priklad2
Vyssie spomenute enviromentalne nastavenia su take nastavenia, ktore su narozdiel od argumentov globalnejsie - su rovnake pre viac procesov. Obsahuju informacie napriklad o domovskom adresari uzivatela, type terminalu, nastavenej lokale (jazyku)... Ich vypis mozno ziskat prikazom
export
v prikazovom riadku.Enviromentalne nastavenia vo svojom nazve nemozu obsahovat znak
=
, pretoze tymto znakom sa oddeluje nazov od ich hodnoty.
Priklad 3: Vypis enviromentalnych nastaveni
// kompilacia: gcc -o priklad3 priklad3.c
#include <stdio.h>
int main (int argc, char ** argv, char ** envp) {
int i = 0;
while (1) {
if (envp[i] == NULL)
break;
printf("%s\n", envp[i]);
i++;
}
}
envp
je zakoncene NULL
bunkou (jadro nam neposkytne pocet tychto nastaveni, na ich konci vsak prida hodnotu NULL
a preto pri ich vypise neostaneme v nekonecnej slucke).
Na pracu s enviromentalnymi nastaveniami GLIBC ponuka prototypy funkcii v stdlib.h
- ak ich raz mienite pouzit, odporucam tuto stranku manualu (anglicky).
V unistd.h
je definovana premenna char ** environ
, ktora obsahuje environmentalne nastavenia bez toho aby ich tam daval uzivatel. Nemusite v deklaracii funkcie main pouzit tretiu hodnotu, v nastaveni environ tieto nastavenia budu definovane. GLIBC musi mat environmentalne nastavenia uloznene napriklad pri volani execv
(sekcia Procesy).
Normalne ukoncenie programu
V doterajsich prikladoch sa nase programy koncili jednoduchym ukoncenim funkcie main
. Niekedy je vsak potrebne program ukoncit vo vetve (napriklad pri zisteni chyby). Nato je urcena funkcia exit(int status)
, ktora je definovana v stdlib.h
. Funkcia exit
ziada parameter status - vysledok programu. Definovane su makra EXIT_SUCCESS
a EXIT_FAILURE
s hodnotamy 0 a 1. Vase programy vsak mozu vracat aj ine cisla. Vysledna hodnota statusu sa pouziva najma pri programovani v shell jazykoch.
Priklad 4: Normalne ukoncenie programu
// kompilacia: gcc -o priklad4 priklad4.c
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char ** argv, char ** envp) {
if (argc < 2) {
printf("nezadal si argument\n");
exit(EXIT_FAILURE);
}
printf("%s\n", argv[1]);
exit(EXIT_SUCCESS);
}
EXIT_SUCCESS
), ak bol zadany minimalne jeden argument okrem argv[0]
. V opacnom pripade - ak argv[1]
nie je definovane - vypise sa chybove hlasenie a program vrati hodnotu 1 (EXIT_FAILURE
).user@hostname:~/c$ priklad4
nezadal si argument
user@hostname:~/c$ echo $?
1
user@hostname:~/c$ priklad4 ahoj
ahoj
user@hostname:~/c$ echo $?
0
$?
v bashi oznacuje status naposledy vokonaneho programu.)
Vacsie cisla ako 1 vracia napriklad zip
- programy pouzivaju zip
na zbalenie, respektive rozbalenie archivu. Pocet moznych chyb je vacsi ako 1, preto sa pouzivaju vyssie cisla (pozri napriklad man zip
, sekcia DIAGNOSTICS).
IO - input/output
Dalsia vec, ktorej sa budeme venovat, je IO - API pre citanie a zapisovanie. V GNU systeme existuju 2 koncepty pre IO - stream (prud) a file descriptor (popisovac suboru).
Pre system je jednoduchsi file descriptor, ten je vlastne jedinym popisovacom IO entity z hladiska kernelu. Je to cele cislo, ktoremu je v kerneli pre dany proces priradena IO entita.
Stream je popisovac IO entity iba z hladiska procesu - funkcie pri praci s entitou v konecnom dosledku pouziju skryty file descriptor - ten je ulozeny v strukture streamu.
Priklad 5: Zapisanie retazca do suboru
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
int main () {
int fd; // pamat pre file descriptor
FILE * fp; // pamat pre stream
fd = open("subor1", O_WRONLY | O_CREAT, 0644);
if (fd < 0) {
perror("subor1");
exit(EXIT_FAILURE);
}
write(fd, "nieco 1", 7);
close(fd);
fp = fopen("subor2", "w");
if (!fp) {
perror("subor2");
exit(EXIT_FAILURE);
}
fprintf(fp, "nieco 2");
fclose(fp);
exit(EXIT_SUCCESS);
}
- Najprv sme funkciou open otvorili subor
"subor1"
, do premennejfd
sa ulozil file descriptor otvorenej entity. Ak je hodnotafd
mensia ako 0, pri otvarani nastala chyba (subor neexistuje alebo k nemu nie su prava). Pouzitim funkciewrite
, ktora ziada 3 argumenty (file descriptor, retazec a dlzku retazca) sme zapisali data do suboru a nasledne sme subor zatvorili (close
).
Funkciaopen
ziada 2, respektive 3 argumenty. Prvy argument je vzdy nazov suboru ktory otvarame, druhy argument su nastavenia pre otvorenie (mozu byt kombinovane logickymOR
- operator|
), treti argument je povinny pri pouzity nastaveniaO_CREAT
a obsahuje pristupove prava, ktore maju byt pridelene vytvaranemu suboru.
Nastavenia pre otvorenie su definovane vofcntl.h
:
O_RDOLNY
- otvorenie iba na citanie,
O_WRONLY
- otvorenie iba na zapis,
O_RDWR
- citanie aj zapis,
O_CREAT
- ak ziadany subor neexistuje a proces ma pravo ho v danom adresari vytvorit, subor bude vytvoreny,
O_APPEND
- zapis zacina na konci suboru.
Existuju aj ine nastavenia - definicie mozte najst konkretne v/usr/include/bits/fcntl.h
, dokumentaciu na tychto strankach manualu.
Niektore budu zmienene v dalsich castiach serialu. - Na otvorenie suboru cez stream pouzijeme funkciu
fopen
, ktorej prvy argument je nazov suboru, druhy typ otvorenia.
Typ otvorenia je jeden alebo dvojznakovy retazec. Dostupne su tieto:
"r"
- otvorenie na citanie,
"w"
- otvorenie na zapis. Ak subor existuje, jeho obsah je vyprazdneny,
"a"
- otvorenie na zapis zacatim na konci suboru (ukazovatel je posunuty na koniec suboru),
"r+"
- otvorenie na citanie aj zapis,
"w+"
- otvorenie na citanie aj zapis s vyprazdnenim suboru na zaciatku,
"a+"
- otvorenie na citanie a zapis zacatim na konci suboru.
Ak funkcia fopen zlyha, do pointrafp
ulozi nullovu hodnotu (NULL
). Funkciafprintf
je funkcia formatovaneho vystupu (akoprintf
).fclose
subor zatvori.
Na prevadzanie file descriptoru na stream a spat sa pouzivaju funkcie fdopen
a fileno
.
Priklad 6: Pouzitie fdopen
a fileno
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
int main () {
int fd;
FILE * fp;
fd = open("foo", O_WRONLY | O_CREAT, 0644);
if (fd < 0) {
perror("foo");
exit(EXIT_FAILURE);
}
write(fd, "prvy riadok\n", 12);
fp = fdopen(fd, "w");
fd = -1000; // teraz uz mozme zabudnut hodnotu fd
if (!fp) {
perror("foo");
exit(EXIT_FAILURE);
}
fprintf(fp, "druhy riadok\n");
fflush(fp);
fd = fileno(fp); // naspat ziskame hodnotu fd
if (fd < 0) {
perror("fileno");
exit(EXIT_FAILURE);
}
write(fd, "treti riadok\n", 13);
close(fd);
exit(EXIT_SUCCESS);
}
foo
cez file descriptor, zapise donho retazec, potom s pomocou funkcie fdopen
ziska stream na tento subor, zapise don retazec cez stream, potom znovu zo streamu pomocou funkcie fileno
ziska file descriptor, zapise treti retazec a subor zatvori. Je potrebne si vsimnut funkciu fflush
, ktora je pouzita po zapisani do suboru cez stream. Jej funkcia bude popisana nizsie v stati o bufferingu streamov.Zmena pozicie v subore
Pri citani dat zo suboru alebo pri zapisovani don casto potrebujeme zacat citat/pisat nie od zaciatku, ale od nejakej pozicie v subore. Niekedy je tiez potrebne viackrat menit tuto poziciu. Na zmenu pozicie pri file descriptoroch sluzi funkcia lseek
, pri streamoch fseek
.
Priklad 7: Pouzitie lseek
a fseek
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
int main () {
// najprv do suboru zapiseme 30 znakov X
int fd;
fd = open("priklad7_test", O_RDWR | O_CREAT, 0644);
write(fd, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 30);
// teraz sa presunime na poziciu 10 od zaciatku
// suboru a prepiseme 10 znakov medzetami
lseek(fd, 10, SEEK_SET);
write(fd, " ", 10);
close(fd);
// a+ otvori subor na citanie aj zapis,
// ukazovatel je na konci suboru (30)
FILE * fp;
fp = fopen("priklad7_test", "a+");
// presunieme sa na poziciu 15,
// precitame 10 znakov a vypiseme ich
fseek(fp, 15, SEEK_SET);
char buffer[11];
fread(buffer, 1, 10, fp);
buffer[10] = '\0';
printf("%s\n", buffer);
fclose(fp);
exit(EXIT_SUCCESS);
}
lseek
a fseek
potrebuju 3 hodnoty: file descriptor alebo stream, pozicia v subore, typ zmeny pozicie.Pre typ zmeny pozicie su definovane 3 makra:
SEEK_SET
- zmena pozicie absolutne - od zaciatku suboru,SEEK_CUR
- zmena pozicie relativne - od aktualnej pozicie,SEEK_END
- zmena pozicie relativne - od konca suboru.Buffering streamov
Pri pouziti funkcie write
s file descriptorom je ziadost okamzite poslana kernelu a zapis je prevedeny pri volani. Pri streamoch je vsak defaultne nastavene, ze data ktore sa zapisu cez fwrite
, fprintf
a ine, su najprv ulozene do bufferu v pamati a zapisane do suboru su az ked velkost dat v bufferi prekroci iste cislo (fully buffered) alebo ked sa dosiahne znak \n
(line buffered). Data su teda pri streamoch zapisovane v blokoch. Ak chceme vyprazdnit buffer a data v nom zapisat do suboru, pouzijeme funkciu fflush(FILE * stream)
. Ak stream je NULL
, vyprazdnene budu vsetky otvorene streamy.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
int main () {
printf("text");
write(1, "abc", 3);
fflush(stdout);
write(1, "def\n", 4);
exit(EXIT_SUCCESS);
}
Keby sme nevedeli o bufferingu streamov, predpokladali by sme ze vystup prikladu bude
textabcdef
. Kedze stream stdout
ma defaultne zapnuty line buffering, pri pouziti printf
su data najprv zapisane do bufferutohto streamu. Pouzitie funkcie
write(1, "abc", 3)
ihned vypise retazec "abc"
, fflush(stdout)
nasledne vyprazdni buffer (vypise sa "text"
) a potom write
vypise "def\n"
.
Ak chcete vypnut stream buffering pre dany stream, pouzite setbuf(FILE * stream, NULL).
Pre kompletne vysvetlenie kontrolovania bufferingu pozri tieto stranky manualu.
Zhrnutie funkcii
int open(const char * filename, int flags[, mode_t mode])
otvorenie suboru, vrati file descriptor
int close(int filedes)
zatvorenie suboru identifikovaneho file descriptorom
ssize_t read(int filedes, void * buffer, size_t size)
citanie size
bytov z filedes
do buffer
, vrati pocet precitanych bytov (ak nebolo mozne ziskat size
bajtov), -1 v pripade chyby
ssize_t write(int filedes, void * buffer, size_t size)
zapisanie size
bytov z buffer
do filedes
, vrati pocet zapisanych bytov, -1 v pripade chyby
int lseek(int filedes, long int offset, int whence)
zmeni poziciu vo filedes
na offset
typom whence
FILE * fopen(char * filename, char * opentype)
otvorenie suboru, vrati stream
int fclose(FILE * stream)
zatvori stream
int fcloseall()
zatvori vsekty otvorene streamy, vrati 0 pri uspechu, EOF
pri chybe
FILE * freopen(char * filename, char * opentype, FILE * stream)
kombinacia fclose
a nasledne fopen
int getc(FILE * stream)
precita 1 znak zo streamu ako unsigned char
a ulozi ho ako int
. Ak bol dosiahnuty koniec suboru, vrati EOF
int getchar()
getc
zo standardneho vstupu - getc(stdin)
char * fgets(char * s, int count, FILE * stream)
cita zo stream
az kym nedosiahne znak noveho riadku \n
, alebo ak dosiahne count-1
precitanych znakov. s
musi mat velkost count
bytov, vysledok ulozeny don obsahuje aj nulovaci znak \0
char * gets(char * s)
tato funkcia cita zo standardneho vstupu az kym nedosiahne znak noveho riadku. Vysledok ulozi do znakoveho pola s
. POZOR: nakolko tato funkcia nerobi ziadnu kontrolu o velkosti pola s
, je mozny BUFFER OVERFLOW UTOK na toto pole. POUZITIE TEJTO FUNKCIE JE VELMI NEBEZPECNE A NEODPORUCA SA!
size_t fread (void * data, size_t size, size_t count, FILE * stream)
precita count
objektov velkosti size
do pola data
zo stream
. Vrati pocet precitanych objektov. Ak sa chyba alebo EOF
vyskytne v strede nejakeho objektu, vrati pocet kompletnych objektov a necely objekt zahodi.
Priklad: fread(buffer, 1, 10, stdin)
- precita 10 jednobytovych objektov (10 znakov) zo stdin
.
site_t fwrite (void * data, size_t size, size_t count, FILE * stream)
zapise count
objektov velkosti size
z pola data
do stream
. Vrati pocet uspesne zapisanych objektov.
int fflush(FILE * stream)
vyprazdni buffer streamu. Ak stream je NULL
, vyprazdni bufferi vsetkych streamov.
Pre subory vacsie ako 232 bytov (4 GB) sa pouzivaju funkcie so suffixom 64: open64, lseek64, fopen64, freopen64
.
Funkcie read64/write64
neexistuju - nie su potrebne, nakolko je malo pravdepodobne ze sa jednym volanim takejto funkcie bude citat/zapisovat viac ako 4 GB dat.
Procesy
Vytvaranie procesov je zaklad dnesnych operacnych systemov. Kazdy proces ma svoj vlastny adresovaci priestor. Proces vykonava program - mozte mat viac procesov vykonavajucich ten isty program, kazdy proces ma svoju vlastnu kopiu tohto programu a vykonava ho nezavisle od ostatnych kopii.
Procesy su zorganizovane hierarchicky. Kazdy proces ma svoj rodicovsky proces (parent process) - proces, ktory ho vytvoril alebo z ktoreho vznikol. Proces vytvoreny z nejakeho procesu sa nazyva dcersky proces (child process). Dcersky proces dedi mnoho vlastnosti po svojom rodicovksom procese.
int system(char * command)
tato funkcia spusti prikaz cez shell /bin/sh
. Vrati -1 ak nebolo mozne prikaz vykonat, v opacnom pripade vrati navratovu hodnotu vykonavaneho programu.
Priklad 9: Pouzitie system
na vykonanie ls -al
a ulozenie vypisu adresara do priklad9out
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main () {
int status;
status = system("ls -al >priklad9out");
printf("status = %i\n", status);
exit(EXIT_SUCCESS);
}
Kazdy proces ma svoje identifikacne cislo - process id, resp. PID. Zivotnost procesu pominie ked je jeho terminacia oznamena jeho rodicovskemu procesu. Vtedy su vsetky informacie o procese uvolnene. Procesy sa vytvaraju systemovym volanim fork
. Dcersky proces vytvoreny s fork
je kopia svojho rodicovkeho procesu, az nato, ze ma svoje vlastne PID. Novy forkovany dcersky proces pokracuje s vykonavanim toho isteho programu ako rodicovksy proces na mieste, kde sa vracia volanie fork
. Navratova hodnota fork
sa pouziva na rozoznaie dcerskeho od rodicovskeho procesu.
Dcersky proces moze potom zmenit program ktory vykonava niektorou s exec
funkcii. Program, ktory proces vykonava, nazyva sa image procesu. Zmena imagu procesu sposobi absolutne zabudnutie na predchadzajuci image procesu. Ak novy program exituje, ukonci sa cely program, namiesto vratenia sa k predchadzajucemu procesu.
pid_t getpid()
- vrati PID procesu
pid_t getppid()
- vrati PID rodicovskeho procesu
pid_t fork()
funkcia vytvory novy proces. Pre dcersky proces vrati hodnotu 0, pre rodicovksy vrati PID dcerskeho procesu. Ak sa nepodari vytvorit novy proces, vrati -1.
Priklad 10: Jednoduche pouzitie fork
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main () {
int pid;
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid) {
printf("rodicovksy proces ; PID dcerskeho procesu je %i\n", pid);
} else {
printf("dcersky proces\n");
}
exit(EXIT_SUCCESS);
}
int execv (char * filename, char * argv[])
funkcia sa pokusi zmenit image procesu na program filename
- vykona filename
. Argument argv
je NULL
terminovane pole retazcov, ktore sa pouziju ako argv
argument pre main
funkciu vykonavaneho programu. Posledny prvok tohto pola musi byt NULL
. Prvy prvok tohto pola by mal byt nazov spustacieho suboru programu. Environmentalne nastavenia pre novy image su take iste, ako environmentalne nastavenia sucasneho imagu.
int execve (char * filename, char * argv[], char * env[])
funkcia je podobna funkcii execv
, ibaze environmentalne nastavenia si programator urci sam.
int execvp (char * filename, char * argv[])
ak filename
neobsahuje znak /
(absolutnu cestu), program je hladany v adresaroch, ktorych zoznam je v environmentalnom nastaveni PATH
. Uzitocna je na spustanie systemovych utilit.
Na kompletny funkcii zoznam sa mozte pozriet tu.
Priklad 11: Spustenie ls -al
s execv
.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main () {
char * argumenty[] = {"ls", "-al", NULL};
execv("/bin/ls", argumenty);
exit(EXIT_SUCCESS);
}
pid_t waitpid(pid_t pid, int * status, int options)
funkcia ziska status jedneho zo svojich dcerskych procesov a ulozi ho do miesta, na ktore ukazuje smernik status
. Vrati PID procesu ktoreho status zistovala. Ak je argument pid
nastaveny na konkretne PID, waitpid
bude ignorovat ostatne svoje dcerske procesy.
Hodnota WAIT_ANY
(-1) v pid
hovori, ze waitpid
ma zistit status hociakeho zo svojich dcerskych procesov a WAIT_MYPGRP
(0) hovori, ze ma zistit status dcerskych procesov ktore su v tej istej procesnej skupine (bude vysvetlene v niektorej z buducich casti) ako volajuci proces.
Argument options
je logickym OR
scitane bitove pole - vacsinou 0. Prepinac WHOHANG
zaisti, ze ak nie je dostupny ziadny dcersky proces, waitpid
nebude cakat na ukoncenie jedneho z nich, miesto toho vrati 0. Prepinac WUNTRACED
zabezpeci, ze waitpid
vrati informaciu o hociakom dcerskom procese ktory bol bud ukonceny alebo stopnuty.
pid_t wait(int * status)
wait(&status)
je ekvivalent s waitpid(-1, &status, 0)
Status ktory ulozi waitpid je mozne vyparsovat s tymyto makrami:
int WIFEXITED(status)
vrati nenulovu hodnotu ak bol program normalne ukonceny (s exit
)
int WEXITSTATUS(status)
ak WIFEXITED(status)
je nenulova hodnota, toto makro vrati dolnych 8 bitov statusu - navratovu hodnotu procesu
int WIFSIGNALED(status)
vrati nenulovu hodnotu ak bol proces terminovany signalom, ktory nebolo mozne vybavit (signaly budu vysvetlene v jednej z buducich casti)
int WTERMSIG(status)
ak WIFSIGNALED(status)
je nenulova hodnota, toto makro vrati cislo signalu, ktory proces terminoval
int WCOREDUMP(status)
vrati nenulovu hodnotu ak bol proces terminovany a vyprodukoval core dump
int WIFSTOPPED(status)
vrati nenulovu hodnotu ak bol dcersky proces stopnuty
int WSTOPSIG(status)
ak WIFSTOPPED(status)
je nenulova hodnota, toto makro vrati cislo signalu, ktory proces stopol
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/wait.h>
#include <errno.h>
int main () {
int pid;
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// dcersky proces - pockame 5 sekund
sleep(5);
// a spustime "ping neexistujucihost"
char * args[] = {"ping", "neexistujucihost", NULL};
execvp("ping", args);
// ak sa execvp nepodarilo, vratime hodnotu 50
exit(50);
}
// rodicovsky proces
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("dcersky proces skoncil s navratovou hodnotou %i\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("dcersky proces bol terminovany signalom cislo %i\n", WTERMSIG(status));
} else if (WCOREDUMP(status)) {
printf("dcersky proces vyprodukoval core dump\n");
} else if (WIFSTOPPED(status)) {
printf("dcersky proces bol stopnuty signalom cislo %i\n", WSTOPSIG(status));
}
exit(EXIT_SUCCESS);
}
Na zaver nieco k dalsej casti - bude o problematike signalov medzi procesmi, cakania na IO, termio - terminalove IO a socketom.
Vyborne! Davno som nevidel clanok co by to pekne zhrnul. 5/5
Par detailov:
- uviedol by som tam priklad pouzitia "errno". To je myslim dost zaklad, aj ked uznavam ze casto nie potrebny.
- velky odkaz na glibc manual na zaciatku alebo na konci, nielen v texte
Este mozno niekomu nemusi byt z textu jasne na co su dve sady funkcii na pristup k suborom, cez systemove handle, a cez streamy. Spomina sa len ze streamy su buffrovane, ale myslim ze nebude odveci trochu viac to rozviest. Tie streamy maju zmysel hlavne pri spracovani textu, tj. textove streamy. Pri binarnych streamoch je asi naozaj jedinym rozdielom len to buffrovanie. Pri texte vsak toho treba robit viac.
http://www.gnu.org/software/libc/manual/html_node/Binary-Streams.html
http://www.gnu.org/software/libc/manual/html_node/Opening-Streams.html
Na niektorych non-POSIX systemoch sa napriklad pri citani musia 2-bytove konce riadkov CRLF prelozit na LF (v Ccku '\n'), a pri zapise naopak. Preto na non-POSIX systemoch sa ani neda spolahlivo vyuzivat fseek() so SEEK_REL. Na POSIX systemoch to neplati, ale je to jeden z dovodov preco su veci riesene tak ako su, a je to dolezite vediet pri portabilnom kode.
Dalsia vec je, ze pri citani je buffrovanie aspon jedneho znaku nevyhnutne pre funkcie typu scanf(). Napriklad ked nacitavame cislo, musime spracuvat znaky az kym neprijde nejaky znak ktory nieje cislica. Tento znak je teda uz z handle precitany, ale scanf() ktory cita cislo ho nesmie spracovat. Tento znak sa teda musi niekam ulozit, a pouzit pri nasledujucom citani. Preto nieje mozne urobit funkciu typu scanf() bez pouzitia buffra, a preto neexistuje varianta tejto funkcie priamo pre systemove handle.
http://www.gnu.org/software/libc/manual/html_node/Unreading.html
Plus mala poznamka pre kolegov detailistov: glibc implementacia nacitania cisla z textu pomocou scanf() sa neda pouzit pokial chceme poriadnu kontrolu vstupu. Je tam totizto spravena taka dost somarina, ze ak je cislo na vstupe vecsie ako velkost premennej do ktorej ho zapisujeme, tak si veselo pretecie. Napriklad ked do 32bit premennej skusite pomocou scanf nacitat 4294967297, tak v tej premennej budete mat 1. Standard jasne nedefinuje co urobit v tomto pripade, takze logicke by bolo vratit ERANGE, lenze vela ludi so mnou ocividne nesuhlasi. Preto ten scanf() treba brat trosku s rezervou.
Chcelo by to nejak zhrnut tie datove typy, ono sa sice da vyhrabat zo zdrojakov akeho typu je to size_t, pid_t a vsetky tieto ostatne typy(v zdrojakoch ich je nehorazne vela a casto po dlhom hrabani zistim ze ukazuju na obycajny int), ale neustale to hladat nie je zrovna efektivne.
Ucel tych typov je praveze NEVEDIET, resp. NESPOLIEHAT SA na to aka je to velkost. Akonahle sa raz spolahnes na to aky je nieco typ, tak prestavas byt portabilny. Napriklad na 32bit masine pozries ze size_t je 32bitov, napchas to do int ktory je tiez 32bitov. Ale potom prejdes na 64bit masinu, kde size_t je 64bit, ale int je stale 32bit. A si vieskde....
Velice dlouhy a velice pekny clanek davam 5/5
Jeste by si mohl zminit funkci:
getenv()
jinak dobra praca ;-)
-_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_-
Linux akbar! BHL
Na Blackhole chodim uz par mesiacov ale az Tvoj clanok ma 'prinutil' zaregistrovat sa. Sice neprogramujem v C a v blizkej dobe sa do toho asi ani pustat nebudem, ale myslim, ze nepoznat C a pracovat s linuxom je dost opovazlive. Urcite budem sledovat Tvoj serial aby som pochytil aspon to najnevyhnutnejsie .. Dobra praca.
Duri
Nesuhlasim. To ze ovladas C neznamena ze ti to nejako pomoze pri praci s linuxom (nejakym linuxovym distrom). Samozrejme ak mas na mysli linux ako kernel tak moze byt.
Osobne si mylim, ze pre linuxakov je skor potrebny bash ako C.
A k clanku: je super, ale ja ako clovek ktory sa nedavno zacal ucit C++ (a C nevie ani zdaleka), vobec nechapem veciam ako:
FILE * fp; // pamat pre stream
int main (int argc, char ** argv, char ** envp)
Konkretne tym hviezdickam, ale beriem na vedomie tuto vetu: "Od citatela sa ocakava minimalna znalost programovania v C".
----------
tommyhot@hackingmachine:~$ microsoft &> /dev/null
http://en.wikipedia.org/wiki/Pointer
http://people.tuke.sk/igor.podlubny/C/Kap9.htm
Tym c-ckom som mal na mysli okrem ineho aj kompilaciu zdrojovych kodov pod linuxom. Ak mas znalost o cecku (a o tom, co su to 'header' subory, co su to 'objekt' subory), skompilovat program pomocou gcc bude urcite lahsie, resp. bude davat vacsi zmysel. Pre mna je './configure', 'make' a 'make install' spanielska dedina. Napriek tomu, ze to viem do konzoly nabuchat ako opica a dufat, ze sa to skompiluje, radsej by som bol, keby som vedel, co sa v skutocnosti pod pokrievkou deje. Potom sa aj troubleshooting robi jednoduhsie, ked nieco nefunguje .. Znalost c-cka to sice nevyzaduje, ale ludia ktori s nim robia to maju jednoduhsie ..
D.
Ano, do serialu bude zahrnute aj vytvaranie Makefile suborov a tiez vyssi level pre vytvaranie konfiguracnych skriptov configure pomocou autoconf a nasledne Makefile.
toto by som ja osobne velmi privital :) dokumentacia k niektorym veciam je niekedy dost naprd. ja som sa mekfile ucil hackovanim makefileov od kernelu.
---
Cuchat s nadchou, to je ako sniffovat bez promiscu.
Ja to takisto velmi privitam. Ja na makefajly pouzivam scons (make system postaveny na pythone). Ale ked mam nieco robit s programami co pouzivaju ten GNU build mechanizmus kde sa generuje subor podla ktoreho sa generuje subor podla ktoreho sa generuje makefile... radsej sa rovno vopred vzdam.
tak zase taky nemas uplne pravdu Linux je o moznosti volby a existuji aj C shelly ;-)
-_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_-
Linux akbar! BHL
Mno ako clovek, ktory sa ucil C a potom C++ ti naozaj odporucam najskor sa naucit Ccko, lebo bez jeho znalosti sa v ani v C++ daleko nedostanes... A ukazovatele resp. polia su veeelmi dolezite :), daju sa pouzivat jednoducho ale vie to byt aj pekny "fet", ked si predstavis napr. ukazovatel na funkciu, ktora ma argumenty abc, ktorej navratovou hodnotou je ukazovatel na funkciu s parametrami def, ktorej navratovou hodnotou je dalsi ukazovatel na trojrozmerne pole typu void :))) (viac sa mi uz pisat nechcelo)
m_ax
Po tom ako mi arkas hodil link na pointre (za co mu dakujem), som prisiel na to, ze je to to iste ako nepriame adresovanie v assembleri, takze uz viem o com je rec :)
----------
tommyhot@hackingmachine:~$ microsoft &> /dev/null
No neviem ci je to zrovna najlepsi napad zacat zhurta C++ bez toho aby si vedel C. Potom sa musis ucit vela veci naraz, napriklad aj tie pointre, ktore by si inak uz ovladal. Ja by som doporucoval najprv prekusat Ccko aspon trochu.
Podla mojho osobneho nazoru je pre poriadne pochopenie C++ dost dolezity aj assembler, resp. vediet ako pracuje CPU. Take veci ze chapat co sa da vratit na zasobniku, co sa kam alokuje, odkial sa kedy da brat pamat, preco velke veci neodovzdavat hodnotou, atd. Bez toho niektore veci nedavauju zmysel, preco sa urcite veci mozu a ine nie.