Linux - Procesy (Processes)

Běžící instance programu se nazývá proces. Například dvě okna terminálu jsou instancemi stejného terminálového programu, ale jsou to dva různé procesy. V každém okně běží shell, který je zase jiný proces. Spuštěním programu v tomto shellu se vytvoří další nový proces.
Více spolupracujících procesů v jedné aplikaci jí umožňuje dělat více věcí najednou.

#include <unistd.h>
typedef int __pid_t;
typedef __pid_t pid_t;

getpid(2)
#include <unistd.h>
pid_t getpid(void);
  • vrací jedinečné pid aktuálního procesu

Př. Napište program, který vypíše pid svého procesu. Program nemažte, budete ho rozšiřovat o další probírané funkce!

getppid(2)
#include <unistd.h>
pid_t getppid(void);
  • vrací jedinečné pid rodiče aktuálního procesu
  • každý proces má rodiče (kromě procesu init), procesy jsou uspořádány do stromové struktury s procesem init jako vrcholem

Př. Vypište pid rodiče.

ps(1)
ps [options]
  • defaultně (bez parametrů) vypíše procesy kontrolované terminálem, ve kterém byl ps spuštěn
  • přepínači lze určit, jaké procesy a informace o nich se mají vypsat, např. -e pro všechny procesy, -u user pro procesy uživatele user, -l pro dlouhý formát výpisu, -o pro vlastní formát, atd.

Př. Vypište si všechny své procesy ve tvaru STAT USER PID PPID NICE CMD

kill(1)
kill pid ...
  • ukončí procesy pid ... (pošle jim signál SIGTERM)

Př. Spusťte si program top a ukončete ho pomocí SIGTERM.

Vytvoření procesu

system(3)
#include <stdlib.h>
int system (const char * string);
  • spustí příkaz ve string voláním /bin/sh -c string
  • její používání není bezpečné (blokuje některé signály), doporučuje se používat fork a exec
  • vrací 127, pokud shell nelze spustit, -1 při jiné chybě, jinak návratový kód příkazu

Př. Spusťte příkaz ls -la.

fork(2)
#include <unistd.h>
pid_t fork(void);
  • vytvoří stejný proces jako svého potomka, který se liší jen v pid a ppid
  • rodičovský proces i jeho potomek pokračují ve vykonávání programu od místa volání fork
  • v rodiči vrací pid potomka, v potomkovi nulu, při chybě -1 v rodiči, potomek se nevytvoří

Př. Vytvořte potomka, který vypíše svoje pid.

exec(3)
execve(2)
#include <unistd.h>
int execl( const char *path, const char *arg, ...);
int execlp( const char *file, const char *arg, ...);
int execle( const char *path, const char *arg , ..., char * const envp[]);
int execv( const char *path, char *const argv[]);
int execvp( const char *file, char *const argv[]);
int execve (const char *filename, char *const argv [], char *const envp[]);
  • tyto funkce nahradí aktuální program jiným
  • první argument je soubor (včetně cesty), který se má vykonat
  • const char *arg, ... jsou arg0, ... spuštěného programu, poslední prvek seznamu musí být NULL
  • char *const argv[] je seznam arg0, ... spuštěného programu, poslední prvek pole musí být NULL
  • char * const envp[] je seznam proměnných prostředí spuštěného programu, poslední prvek seznamu musí být NULL, prvky seznamu jsou očekávány ve tvaru "PROMENNA=hodnota"
  • funkcím execvp a execlp stačí jen jméno souboru ke spuštění, který se hledá ve vyhledávací cestě (proměnná PATH)
  • pokud se funkce ukončí (vždy -1), je to chyba

Při vytváření nového procesu se nejdříve zavolá fork, který vytvoří kopii aktuálního procesu, a pak exec, který nový proces změní na instanci programu, který chceme spustit.

Př. Spusťte příkaz date +%s pomocí fork a exec.

Linux plánuje procesy nezávisle, není zaručeno, který poběží dřív, jak dlouho poběží.

nice(1)
nice [OPTION]... [COMMAND [ARG]...]
  • spustí program s jinou prioritou (defaultně má nový proces prioritu 0)
  • rozsah priorit je -20 (nejvyšší) až 19 (nejnižší)
  • pro novou prioritu je přepínač -n
  • jenom root může spustit proces se zápornou prioritou

Př. Spusťte program yes s nice 5. Zkontrolujte si to pomocí ps. Zkuste -5.

renice(1)
renice priority [[-p] pid ...] [[-g] pgrp ...] [[-u] user ...]
  • změní prioritu běžících procesů
  • procesy lze určit pomocí pid nebo všechny procesy uživatele user (defaultně pid)
  • priority může být [+-]číslo, priority = nice
  • jenom root může zvyšovat prioritu (snižovat nice)

Př. Zvyšte nice programu yes na 19. Zkontrolujte si to pomocí ps. Zkuste nice snížit.

nice(2)
#include <unistd.h>
int nice(int inc);
  • přidá inc k prioritě volájícího procesu
  • jenom root může zadat záporný inc
  • vrací 0, při chybě -1

Př. Zvyšte nice potomka.

sleep(3)
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
  • "uspí" proces na seconds sekund, nebo ho probudí neignorovaný signál
  • vrací 0, pokud čas uplynul, jinak zbývající počet sekund

Př. Pozdržte potomka 2 sekundy a vypište datum znovu.

Ukončení procesu

exit(3)
#include <stdlib.h>
void exit(int status);
  • normálně ukončí program, status je vrácen rodiči
  • funkce se neukončí

Př. Ošetřete neúspěšný fork pomocí exit.

int main(int argc, char *argv[], char *envp[]);
  • ukončení main je normální ukončení, návratová hodnota vrácena rodiči
  • návratová hodnota funkce main je sice int, ale používat by se měly jen hodnoty 0-127, kódy nad 128 včetně mají speciální význam - když je proces ukončen signálem, návratová hodnota je 128 + číslo signálu

Př. Ukončete korektně rodiče i potomka návratem z main.

abort(3)
#include <stdlib.h>
void abort(void);
  • abnormálně (s core souborem) ukončí program
  • funkce se neukončí

Př. Ošetřete neúspěšný exec pomocí abort.

Potomek může proběhnout až po ukončení předka, protože oba procesy jsou plánovány nezávisle. To je někdy nežádoucí, někdy chceme, aby rodič počkal, až se potomek ukončí.

wait(2)
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status)
  • zastaví proces, dokud se neukončí nějaký potomek, nebo pokud proces dostane ukončující signál nebo se volá nějaký signal-handler
  • pokud už potomek skončil (zombie), funkce se hned ukončí, všechny prostředky systému přidělené potomkovi jsou uvolněny
  • pokud status není NULL, na místo, kam ukazuje, se uloží informace o potomkovi
  • vrací pid potomka, při chybě -1

Př. V rodiči počkejte na potomka.

waitpid(2)
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
  • zastaví proces, dokud se neukončí potomek s ID pid, nebo pokud proces dostane ukončující signál nebo se volá nějaký signal-handler
  • pokud už potomek skončil (zombie), funkce se hned ukončí, všechny prostředky systému přidělené potomkovi jsou uvolněny
  • pokud status není NULL, na místo, kam ukazuje, se uloží informace o potomkovi
  • pokud je pid -1, funkce se chová jako wait
  • vrací pid potomka, při chybě -1, nebo 0, pokud žádný potomek neskončil a v options bylo WNOHANG

Ze status (int) lze získat různé informace o potomkovi těmito makry:

WIFEXITED(status)
  • vrací nenulové číslo, pokud potomek spončil normálně

WEXITSTATUS(status)
  • vrací návratový kód potomka
  • smí se vyhodnotit, jen pokud WIFEXITED je nenulové

Př. Vypište návratový kód potomka.

WIFSIGNALED(status)
  • vrací nenulové číslo, pokud potomka ukončil signál

WTERMSIG(status)
  • vrací číslo signálu, který ukončil potomka
  • smí se vyhodnotit, jen pokud WIFSIGNALED je nenulové

Zombie procesy

Co se stane, když potomek skončí a rodič na něj nečeká voláním wait? Informace o jeho ukončení budou dočasně ztraceny a z něj se stane zombie. Zombie je proces, který skončil, ale ještě nebyl ze systému odstraněn, to je povinnost rodiče. Potom, co se proces ukončí, stane se z něj zombie, dokud si informace o jeho ukončení nevyzvedne jeho rodič pomocí wait. Pokud si rodič tyto informace nevyzvedne nikdy (ani po svém ukončení), zombie adoptuje proces init a odstraní jej ze systému.

OTÁZKA: Co se s potomkem stane, když rodič skončí dřív než on? Bude bez rodiče?

PŘÍKLAD

Další funkce týkající se procesů jsou:



Jan Outrata
outrata@phoenix.inf.upol.cz