Użyteczne funkcyjki...

Każdy programista un*xowy zna te funkcje. Prawie każdy użytkownik un*xa piszący czasem jakieś programy zna te funkcje. Są to bardzo wygodne funkcje. Opis może się przydać nawet programistom, którzy używają Linuksa (FreeBSD, Solarisa czy co tam jeszcze) do rozwiązywania algorytmów. Do rzeczy.

Jeśli chcesz przekierować standardowe wejście do jakiegoś pliku, możesz otworzyć plik i albo zapisywać wszystko fprintfem, albo sprawić, żeby stdout było otwartym plikiem. Pierwsze wyjście nie jest zbyt wygodne w rozbudowanych programach, podczas gdy drugie pociąga za sobą dopisanie co najwyżej trzech linijek kodu. Wystarczy posłużyć się funkcjami int dup(int desk) i int dup2(int stary_desk, int nowy_desk). dup() zwraca nowy deskryptor, odnoszący się do tego samego pliku co desk. Nowy deskryptor ma najniższy możliwy numer (tzn. jest to najniższa liczba całkowita, która nie była poprawnym deskryptorem). dup2() działa ciut inaczej: nowy_desk zaczyna odnosić się do tego samego pliku, co stary_desk, przy czym jeśli poprzednio był prawidłowym deskryptorem, to zostanie zamknięty. To teraz przydałoby się wiedzieć, które deskryptory odpowiadają którym strumieniom we/wy. Deskryptor 0 odpowiada za standardowe wejście, w związku z czym jest otwarty tylko-do-odczytu. Deskryptor 1 to standardowe wyjście i jest otwarty tylko-do-zapisu. Deskryptor 2 jest standardowym błędem i jest otwarty do-odczytu-i-zapisu (tzn. można stąd czytać dane z klawiatury i tu zapisywać dane na monitor).

int main(void)
{
int plik = open("plik.txt", O_WRONLY | O_CREAT | O_EXCL, 0666);
/* kod dający praktycznie ten sam efekt: close(1); dup(plik); */
dup2(plik, 1);
close(plik); /* to nie jest potrzebne, ale w dobrym tonie */
puts("Hell oWorld!");

return 0;
}

Funkcja unsigned int sleep(int sek) usypia proces na sek sekund. Funkcja int usleep(unsigned long usek) usypia proces na usek mikrosekund (1 usekunda = 1/1000 ms = 1/1000 000 s). Funkcja usleep() na systemach z rodziny *BSD nie zwraca nic (jest typu void). W zasadzie nigdy nie korzystałem z wartości zwracanych przez te funkcje, zawsze stosowałem je najprościej możliwie:

int main(void)
{
puts("Witam na poczatku progsa");
sleep(1); /* spimy jedna sekunde */
puts("Witam po jednej sekundzie");

return 0;
}

Dalej jest funkcyjka void bzero(void *blok, size_t rozmiar), zerująca wielkosc bajtów (size_t to tak naprawdę int) zaczynając od adresu blok. Przydatne przy dynamicznej alokacji tablic albo struktur. W manualu stoi, że funkcja jest odradzana, zamiast niej należy używać funkcji void* memset(void *blok, int znak, size_t rozmiar). memset() wypełnia rozmiar bajtów spod adresu blok bajtem znak, a zwraca dokładnie to, co było w blok. Czemu więc int zamiast char? Bo procesor pojedynczą instrukcją może przetwarzać tylko 32-bitowe liczby (na procesorach 32-bitowych, czyli pochodne po x86), nie mniejsze i nie większe. Zatem obcinanie zmiennej do ośmiu bitów tylko po to, żeby zaraz z powrotem zamienić to na 32 bity nie ma sensu.

int main(void)
{
int *dynamiczna_tablica;
dynamiczna_tablica = malloc(200); /* 200 bajtow, czyli 50 intow */
memset(dynamiczna_tablica, 0, 200); /* teraz mamy tablice wypelniona zerami */

/* tu jakis uzyteczny kod */

free(dynamiczna_tablica);
return 0;
}

Niezła jest funkcja void perror(char *komunikat). Wypisuje na ekranie (dokładnie na standardowym wyjściu błędu) bzdury podane przez programistę, po czym wypisuje systemowe objaśnienie numeru błędu ze zmiennej errno (-> man errno). Nie musisz zakładać a priori żadnych możliwych wartości błędu. Jeśli jakaś funkcja w razie błędu ustawia zmienną errno, możesz łatwo dowiedzieć się, z jakiego powodu się wywaliła.

int main(void)
{
int deskryptor;
if ( ( deskryptor = open("nazwapliku.txt", O_RDONLY) ) == -1 )
  {
  /* tu bedzie wiecej informacji, np. "No such file or directory" */
  perror("Blad przy otwieraniu pliku");
  return 1;
  }

close(deskryptor);
return 0;
}

Czasem zdarza się, że chcesz zmierzyć, ile czasu będą się wykonywać pewne instrukcje. Nienajgorzej do tego się nadaje funkcja time_t time(time_t *czas). Tak na prawdę zwraca int, a dokładnie ilość sekund, jaka upłynęła od 1 stycznia 1970, od godziny 0:0:0 czasu UTC (GMT, jak kto woli, wyrażenie UTC spotyka się częściej w un*xach). Jest to tak zwany Epoch (ang. epoka, okres).
Notka: W okolicach początku roku 1970 na uniwersytecie w Berkley po raz pierwszy uruchomiono pierwszą wersję pierwszego wielozadaniowego systemu operacyjnego Unix. Stąd czas w systemach uniksowych mierzy się od tego czasu.
Notka: Wprawdzie liczba typu int jest ograniczona (32 bity), więc i czas opisywany przez nią nie może być zbyt długi, ale problematyczne stanie się to dopiero ok. 2030 roku, a do tego czasu komputery 64-bitowe będą powszechne (albo i przestarzałe), a przy 64 bitach następna problematyczna data będzie obchodzić nasze praprawnuki.
Funkcja time() przyjmuje jeden parametr, wskaźnik do zmiennej, w której będzie chciała zapisać dokładnie to samo, co zwraca. W sumie nie wiem, po co. Zamiast tego wskaźnika można władować tam NULL. Ja tego używam tak:

int main(void)
{
int poczatek, koniec;
int i;

/*
 * to mozna inaczej:
 * srand(time(&poczatek));
 * srand( (poczatek = time(NULL)) );
 */
srand(time(NULL)); /* siejemy ziarenko losowosci, bo tak trzeba */
poczatek = time(NULL);
for (i = 0; i < 200000; ++i) /* losujemy 200 tys. razy liczbe od 0 do 27 */
  rand() % 28;
koniec = time(NULL);

printf("Czas wykonania: %d");

return 0;
}

Podobne zastosowanie ma funkcja int gettimeofday(struct timeval *czas, struct timezone *strefa_czasowa). Ta pobiera adres struktury, w której zapisze aktualny czas i adres drugiej struktury, w której zapisze strefę czasową. Każdy z adresów może być równy NULL. Ze strefy czasowej jeszcze nie zdarzyło mi się skorzystać, wątpię, żeby to było potrzebne szeregowemu programiście :) Sama struktura timeval wygląda tak (podaję za odpowiadającym manualem):

struct timeval {
  long tv_sec;   /* sekundy od Epochu */
  long tv_usec;  /* mikrosekundy (1/1 000 000 sekundy) */
};

Ostatnie dwie funkcje, które na tej stronie opiszę, to int kill(pid_t pid, int sygnal) (pid_t, podobnie jak time_t i size_t, to zatypedefowany int) i sighandler_t signal(int sygnal, sighandler_t funkcja). Pierwsza, jak sama nazwa wskazuje, wysyła sygnał do procesu o odpowiednim pidzie, druga (co również nazwa wskazuje) pozwala na przechwycenie sygnału. Funkcja kill() jest użyteczna od razu, o ile się zna interesujący pid. W zasadzie użycie tego ustrojstwa jest proste:

int main(void)
{
pid_t pid; /* pamietajmy, ze pid_t to tak na prawde int */
int sygnal;

puts("Podaj pid procesu");
scanf("%d", &pid);

puts("Podaj numer sygnalu");
scanf("%d", &sygnal);

if ( kill(pid, sygnal) )
  perror("Blad wysylania sygnalu");

return 0;
}

Jeśli chodzi o sygnały w un*xie, to jest parę interesujących i przydatnych rzeczy. Przede wszystkim, nie wolno ci wysłać sygnałów do nie swoich procesów. Wyjątkiem jest root, ale administrator musi mieć prawo zabijać cokolwiek. To nie Windows ;) To po pierwsze. Po drugie, sygnałów w Linuksie jest w sumie 63, ale to jest limit kernela. POSIX gwarantuje istnienie sygnałów od 1 do 31, sygnały 32 do 63 nie powinny być używane przez użytkownika. Jeśli programista chce zakillować jakiś proces (nie zabić, ale zasygnalizować mu coś), to od tego są sygnały SIGUSR1 (pod Linuksem sygnał 10) i SIGUSR2 (linuksowy numer 12). Te dwa sygnały nie są stałe, w różnych implementacjach Unixa mogą mieć różne numery. Polecam odwoływać się do ich #definicji. Domyślną akcją na te sygnały jest zakończenie procesu.
Z innych sygnałów zabójczych (kończących proces) używa się SIGTERM (zawsze 15), SIGKILL (zawsze 9), SIGINT (zawsze 2), SIGHUP (zawsze 1) oraz SIGQUIT (zawsze 3). Sygnał SIGINT jest wysyłany, gdy użytkownik programu naciśnie ^C ([Ctrl+C]). Sygnał SIGQUIT jest wysyłany, gdy użytkownik naciśnie ^\, a akcją jest zakończenie programu i - w przeciwieństwie do pozostałych wymienionych - zrzut pamięci (tzw. core dump, użyteczne, pod warunkiem, że masz sporą wprawę w debuggowaniu programów na podstawie obrazu pamięci). SIGTERM to sygnał domyślnie wysyłany przez komendę kill. SIGHUP jest z kolei wysyłany do programów, których terminal kontrolujący zniknął - dlatego podczas zdalnej pracy nie jest bezpieczne odpalać jakąś dłuższą kompilację czy coś takiego inaczej, niż poleceniem screen (zerwanie połączenia nie przerwie pracy takiej kompilacji). Na koniec zostawiłem sobie najbardziej zabójczy sygnał SIGKILL. Powoduje bezwarunkowe zakończenie programu, nie można się przed nim obronić [*].
Z ciekawszych sygnałów zostały jeszcze SIGFPE (zawsze 8), SIGSEGV (zawsze 11), SIGPIPE (zawsze 13), SIGBUS (w Linuksie 7) oraz SIGTSTP (w Linuksie 20). Ostatni sygnał jest wysyłany przy naciśnięciu ^Z i oznacza zastopowanie programu z klawiatury (tty stop). Pozostałe to kolejno: floating point exception (np. dzielenie przez 0, także przy intach), segmentation fault (naruszenie ochrony pamięci, znaczy program próbuje się odwołać do nie swojej pamięci, częste przy odczycie tablicy w pętli), oba generujące zrzut pamięci przy kończeniu procesu, broken pipe (proces próbuje pisać do potoku albo nazwanego potoku, podczas gdy nikt nie odczytuje danych), kończące proces bez zrzutu pamięci oraz bus error (błąd szyny [nie mam pomysłu na tłumaczenie], oznacza błąd adresowania, na przykład próba odczytu inta spod adresu nieparzystego, dość rzadki sygnał), generujący zrzut pamięci. Oczywiście tam, gdzie zaznaczyłem numer sygnału "w Linuksie", numer sygnału zależy od implementacji, tak więc pod Solarisem czy FreeBSD może to być inny numer.

To teraz parę słów o signal(). Funkcja pozwala na przechwycenie praktycznie dowolnego sygnału, oprócz SIGKILL i SIGSTOP, które są nieprzechwytywalne. Typ sighandler_t to tak naprawdę wskaźnik do funkcji (czyli funkcja). Ta funkcja ma przyjmować jednego inta, a zwracać nie ma nic. Przykład będzie tu chyba najbardziej obrazowy:

/* nasza funkcja obslugujaca sygnaly */
void sygnalizator(int numer_sygnalu)
{
printf("Kukuryku! (numer sygnalu: %d)\n", numer_sygnalu);
}

int main(void)
{
char znaczek;
int i;

for (i = 1; i < 32; ++i)
  signal(i, sygnalizator);

/* jakos trzeba program zakonczyc, a kill -9 jest niewygodne */
while ( (znaczek = getchar()) != 'q' ) ;

return 0;
}

Teraz po każdym naciśnięciu ^C, ^\, ^Z albo zakillowaniu wyświetli się numer sygnału. Możesz sprawdzić, że kill -STOP i kill -KILL nie zostały przechwycone.
Co ciekawe, możesz przywrócić poprzedni handler sygnału.

sighandler_t poprzedni_handler;

void handler(int sygnal)
{
puts("Wywolany handler");
signal(sygnal, poprzedni_handler);
}

int main(void)
{
poprzedni_handler = signal(SIGINT, handler);

while (1)
  sleep(1);

return 0;
}

Ten program przy pierwszym naciśnięciu ^C wyświetli Wywolany handler, a przy drugim naciśnięciu wyjdzie. Przy okazji widzisz, jak można łączyć w sekwencje handlery (wewnątrz handlera wywołanie funkcji signal()).
Na koniec, zamiast funkcji handlera możesz podać SIG_IGN, żeby sygnał zignorować (zamiast pisać pustą funkcję), albo SIG_DFL, żeby przywrócić domyślny handler sygnału (zamiast ręcznie pamiętać, jak w ostatnim przykładzie).


[*] Czasem system operacyjny potrzebuje procesu do jakiś dziwnych rzeczy, czasem musi po drodze pozwalniać zasoby procesu, więc może się zdarzyć proces-duch odporny nawet na SIGKILL, ale to są baaardzo rzadkie przypadki.