C na GNU/Linux

13.07.2008 12:00 | kabel

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.

Priklad 1: Vypis argumentov

// 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]);
        }
}
Spustenie a priklad vystupu:
user@hostname:~/c$ priklad1 a b c
0 : priklad1
1 : a
2 : b
3 : c
user@hostname:~/c$ ./priklad1 ahoj
0 : ./priklad1
1 : ahoj
Ako vidiet, nulty argument (spustaci nazov) nie je konecny nazov suboru, ale obsahuje aj cestu k tomuto suboru. Toto je dolezity fakt pri programoch, ktorych cinnost zavisi aj od nazvu, akym boli spustene - napriklad 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);
}
Spustenie a priklad vystupu:
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++;
        }
}
Z prikladu mozno dedukovat ze pole 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);
}
Tento priklad vypise prvy argument a vrati status 0 (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
(Premenna $? 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);
}
Ako vidime, v priklade sme pouzili obidva koncepty:

  • Najprv sme funkciou open otvorili subor "subor1", do premennej fd sa ulozil file descriptor otvorenej entity. Ak je hodnota fd mensia ako 0, pri otvarani nastala chyba (subor neexistuje alebo k nemu nie su prava). Pouzitim funkcie write, ktora ziada 3 argumenty (file descriptor, retazec a dlzku retazca) sme zapisali data do suboru a nasledne sme subor zatvorili (close).
    Funkcia open ziada 2, respektive 3 argumenty. Prvy argument je vzdy nazov suboru ktory otvarame, druhy argument su nastavenia pre otvorenie (mozu byt kombinovane logickym OR - operator |), treti argument je povinny pri pouzity nastavenia O_CREAT a obsahuje pristupove prava, ktore maju byt pridelene vytvaranemu suboru.
    Nastavenia pre otvorenie su definovane vo fcntl.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 pointra fp ulozi nullovu hodnotu (NULL). Funkcia fprintf je funkcia formatovaneho vystupu (ako printf). 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);
}
Priklad otvori subor 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);
}
Funkcie 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.

Priklad 8: Pouzitie fflush

#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 bufferu
tohto 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

Priklad 12: Vytvorenie dcerskeho procesu, spustenie programu ping v dcerskom procese, zistenie statusu dcerskeho procesu.

#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.

    • Re: C na GNU/Linux 14.07.2008 | 04:47
      Avatar vid   Používateľ

      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.

    • Re: C na GNU/Linux 14.07.2008 | 12:32
      Avatar blackhole   Návštevník

      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.

      • Re: C na GNU/Linux 14.07.2008 | 17:04
        Avatar vid   Používateľ

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

    • Re: C na GNU/Linux 14.07.2008 | 13:35
      Avatar Alexej   Používateľ

      Velice dlouhy a velice pekny clanek davam 5/5
      Jeste by si mohl zminit funkci:
      getenv()

      jinak dobra praca ;-)

      -_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_-
      Linux akbar! BHL

      ---- -_ _-_- _-_ _ ---- - -__- -_-- -_ _- - _
    • Re: C na GNU/Linux 14.07.2008 | 14:39
      Avatar blackhole   Návštevník

      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

      • Re: C na GNU/Linux 14.07.2008 | 15:42
        Avatar blackhole_tommyhot   Používateľ

        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

        • Re: C na GNU/Linux 14.07.2008 | 16:16
          Avatar blackhole   Návštevník

          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.

          • Re: C na GNU/Linux 14.07.2008 | 16:35
            Avatar kabel   Používateľ

            Ano, do serialu bude zahrnute aj vytvaranie Makefile suborov a tiez vyssi level pre vytvaranie konfiguracnych skriptov configure pomocou autoconf a nasledne Makefile.

            • Re: C na GNU/Linux 17.07.2008 | 17:43
              Avatar blackhole_ventYl   Používateľ

              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.

              --- Cuchat s nadchou, to je ako sniffovat bez promiscu.
            • Re: C na GNU/Linux 17.07.2008 | 21:00
              Avatar vid   Používateľ

              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.

        • Re: C na GNU/Linux 14.07.2008 | 16:37
          Avatar Alexej   Používateľ

          tak zase taky nemas uplne pravdu Linux je o moznosti volby a existuji aj C shelly ;-)

          -_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_-
          Linux akbar! BHL

          ---- -_ _-_- _-_ _ ---- - -__- -_-- -_ _- - _
        • Re: C na GNU/Linux 14.07.2008 | 17:07
          Avatar blackhole   Návštevník

          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

          • Re: C na GNU/Linux 14.07.2008 | 17:10
            Avatar blackhole_tommyhot   Používateľ

            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

        • Re: C na GNU/Linux 14.07.2008 | 17:10
          Avatar vid   Používateľ

          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.