Systémové programování pro blbečky

Část 1: Zjištění základních atributů (PID, PPID, UID)

Každý proces má své unikátní PID a identitu uživatele (UID). Než začneme procesy vytvářet, naučíme se zjistit data o procesu, ve kterém náš program právě běží.

K tomu použijeme systémová volání getpid() (vlastní ID), getppid() (ID rodičovského procesu) a getuid() (ID uživatele, pod kterým proces běží).

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    // Získáme atributy současného procesu
    pid_t my_pid = getpid();
    pid_t parent_pid = getppid();
    uid_t my_uid = getuid();

    printf("[Hlavní proces] Moje PID: %d\n", my_pid);
    printf("[Hlavní proces] PID mého rodiče (PPID): %d\n", parent_pid);
    printf("[Hlavní proces] Moje uživatelské UID: %d\n", my_uid);

    return 0;
}

Část 2: Vytvoření nového procesu pomocí fork()

Nové procesy dědí atributy ze svého rodičovského (parent) procesu. V Linuxu se nový proces vytváří tak, že se ten stávající „rozdvojí“ pomocí volání fork().

Od okamžiku zavolání fork() běží v systému dva identické procesy (rodič a dítě). Rozlišíme je podle návratové hodnoty:

  • Rodič dostane jako výsledek PID svého nového dítěte.

  • Dítě dostane hodnotu 0.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    pid_t pid;

    printf("Před rozdvojením (forkem)...\n");

    // Rozdvojení procesu
    pid = fork();

    if (pid < 0) {
        // Pokud fork selže
        perror("Fork se nepovedl :(");
        return 1;
    } 
    else if (pid == 0) {
        // Kód, který vykoná pouze DOSPĚLÉ DÍTĚ
        printf("[Dítě] Já jsem nový proces! Moje PID je %d a PID mého rodiče je %d.\n", getpid(), getppid());
    } 
    else {
        // Kód, který vykoná pouze RODIČ
        printf("[Rodič] Já jsem rodič. Moje PID je %d a vytvořil jsem dítě s PID %d.\n", getpid(), pid);
    }

    printf("[Proces %d] Tohle vypíší oba procesy, protože oba pokračují odsud dolů.\n", getpid());
    return 0;
}

Část 3: Skupiny procesů a čekání na dítě (wait)

V této finální ukázce vytvoříme proces, zjistíme jeho skupinu pomocí getpgrp() a ukážeme si, jak rodič správně počká na ukončení svého dítěte pomocí volání wait(). Čekání je důležité, aby se z dítěte po skončení nestal tzv. „zombie proces“.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t pid;

    printf("[Rodič] Moje ID skupiny (PGID) je: %d\n", getpgrp());

    pid = fork();

    if (pid == 0) {
        // Sme v dětském procesu
        printf("[Dítě] Moje PGID je: %d (zdědil jsem ho od rodiče)\n", getpgrp());
        printf("[Dítě] Teď budu 2 sekundy simulovat práci...\n");
        sleep(2);
        printf("[Dítě] Končím.\n");
        return 42; // Dítě vrátí rodiči status 42
    } 
    else {
        // Jsme v rodiči
        int status;
        printf("[Rodič] Čekám, až dítě dokončí práci...\n");
        
        // Rodič se zde zastaví a čeká na signál o konci dítěte
        pid_t child_pid = wait(&status);

        if (WIFEXITED(status)) {
            printf("[Rodič] Dítě %d úspěšně skončilo a vrátilo kód: %d\n", 
                   child_pid, WEXITSTATUS(status));
        }
    }

    return 0;
}

Co jsme se naučili?

  1. Jak zjistit identitu procesu (PID) a uživatele (UID).

  2. Jak naklonovat proces pomocí fork().

  3. Že procesy automaticky dědí skupinu (PGID) a rodič na ně musí počkat pomocí wait().