Understand the Linux Process:
Deep Analysis 4 Linux Sysadmin (DA4LS)
First Article
Photo on sxc.hu
Alessio Porcacchia
L’Autore
Alessio Porcacchia East Point Bussiness Park (Irlanda)
Alessio Porcacchia Nasce a Roma nel 1972, e’ consulente da moltissimi anni dei sistemi Unix/Linux (tutti i derivati del SYS4/5) Lavora Attualmente all’estero Presso una azienda di software molto conosciuta. Ha lavorato su praticamente qualsiasi derivato unix. E’ orgogliosamente un consulente Debian, ed e’ uno dei vecchi membri del Lugroma. E’ un accanito fan di Douglas Adams. Ha pubblicati diversi manuali scaricabili dal suo sito www.porcacchia.altervista.org ed e’ contattabile
Tramite l’indirizzo boxlinux[at]gmail.com
Cominciamo con il dire che per poter avere un quadro cristallino della situazione, e comprendere in modo chiaro e preciso cosa l'amministratore di sistema o l'analista debba fare davanti ad una shell o ad un compilatore, bisogna porsi una domanda fondamentale: Cosa conosco in modo approfondito del sistema? Che cosa devo Ottimizzare?
Per fare cio' si deve comprendere approfonditamente cosa accade dal punto di vista approfondito dentro al nostro GNU/LINUX. Molte volte ci si trova davanti, ricercando per la rete a documenti o troppo semplificati e superficiali oppure a documenti tecnici che vanno troppo nel profondo perdendosi in qualche meandro dove il lettore si chiede: Si ok ma non mi hai spiegato ancora perché ho questo problema. Succede sempre. Cerchero' in questo primo articolo di spiegare come funziona il processo linux. Se i Lettori troveranno tale Articolo di interesse, affronteremo negli Articoli successivi, concetti che riguardano la VM , L'analisi dei Vari Filesystems e il meccanismi del Kernel.
I processi linux:
Cercheremo di analizzare la “la vita la morte e i miracoli” del processi linux e come vengono gestiti da Linux. Cominciamo Con il dire cosa sia un processo linux. Il modo migliore per definire un processo linux Un Processo e' una qualsiasi istanza che e' in corso (running || executing) di un qualsiasi programma. Il programma non e’ nient'altro che una serie di istruzioni (codice macchina) immagazzinato in un binario o eseguibile.
Tale programma a sua volta e' immagazinato da qualsiasi device che e' stato costruito per questa funzione (hard disk, DVD etc etc) .
All'interno del nostro sistema e' L'entita' dinamica per eccellenza, pensate al progamma molto semplicemente ad un oggetto in continuo cambiamento, nella sua continua esecuzione di codice macchina immagazzinato che viene eseguito. Nel programma lanciato avvengono molte operazioni di cui abbiamo solo una visibilità parziale comprese le operazioni sui registri della CPU , ogni singolo parametro delle varie routines, includendo tutte le attivita' del microprocessore inerenti al programma
Linux i processi vengono rappresentati da una Task_Struct e' array di puntatori che richiama continuamente se stesso che ogni processo venga ripetuto più volte. Per intenderci la possiamo chiamare come una circular doubly-linked list che immagazzina i descrittore dei vari processi
la struttura e spiegata basta usare il man (man sched.h) l'init_task e' variabile per la generazione di tutti i processi (task e' usato per definire processo)
#define for_each_process(p) \
for (p = &init_task ; (p = next_task(p)) != &init_task ; )
Potremmo con uno schema l'init_task in questo modo :

La struttura del processo come una task_struck (nel kernel) e grande 1024 bytes per poter sapere
l’estatta grandezza potete compilare il seguente codice e lanciarlo:
#define __KERNEL__
#include <linux/sched.h>
main()
{
printf("sizeof(struct task_struct) - %d\n",
sizeof(struct task_struct));
}
La cosa che troviamo assai interessante che il numero massimo dei processi per default sono limitati a 512(limitato dalla grandezza del vettore task). Quando un nuovo processo viene creato un nuovo task_struct viene allocato nella memoria ed aggiunto alla task_struct.
Detto Questo possiamo definire gli stati di un processo. Quando un processo cambia e rigorosamente legato a questo tipo di stati:
Processo in Running
Il processo e attualmente up and running e un processo che sta girando sul sistema o e pronto per poter essere eseguito dal sistema
Processo in Waiting (sleep)
Il processo e’ in attesa di un evento di sistema o che venga rilasciata una risorsa
Processo in Uninterrutible Sleep
Il processo è in attesa di un risposta dal sistema (I/O)e non può essere interrotto in nessuna circostanza cercheremo di spiegare le differenze tra interrompibile e non interrompibile:
a) interrompibile che può essere interrotto da un segnale o da un evento (anche a livello utente)
b)non interrompibile che non può essere interrotto in nessun caso (dipendente dall’hardware o dai devices)
processo in Stopped
un processo che e’ stato fermato da un segnale o da un evento (anche a livello utente).
processo Zombie
e un processo che dovrebbe essere in stato di Stopped e invece per qualche ragione continua a rimanere nella task_ structure e nel vettore di task i Processi zombie sono noti nel mondo unix. Normalmente il Processo Zombie avviene per due motivi: Il Programma compilato pur funzionando non e’ esente da minori errori nel codice (codice non pulito o dirty code) e invece di portare il processo ad uno stato di stop lo rendono ancora effettivamente presente, Dei processi figli che non hanno ricevuto il segnale di stop, e pur morendo il processo principale
rimangono in un auto-engaged state
Cosa assai importante da dire (che molti testi hanno tralasciato) e che Tutti i processi non sono realmente indipendenti uno dall’altro, sono tutti correlati tra di loro e legati. Il Processo di Init iniziale e’ l’unico ad avere nessuna interdipendenza con i successivi, ma se ci riflettiamo possiamo comprendere che anche esso e’ indirettamente correlato agli altri, nel senso che ovviamente il processo di Init senza i successivi processi figli, essendo quello dove tutti i processi derivano, senza tali processi non avrebbe senso. Indi Indirettamente proprio per la sua natura anche esso e correlato ad il resto.
LA VFS E IL FILESYSTEM
Il Processo ovviamente apre e chiude per sua natura dei files e tale aperture o chiusura di file si riflette sul filesystem ovviamente questo si riflette sulla aperture dei puntatori degli inodes all’interno del Virtual File System (attenzione che si chiama Virtual File System ma e’ tutt’altro che virtuale!) ed e’ quello che gestisce il filesystem in Kernel Space. Si parla anche in questo caso di Superblocchi o inodes ma non vanno scambiati tra i superblocks e gli Inode del Filesystem che conosciamo normalmente (EXT2 EXT3 ReiserFS etc etc) anche se il tipo di funzionamento e’ il medesimo. La VFS gestisce in modo ordinato e uniforme l’accesso da parte degli applicativi a FileSystem.
Gerarchicamente Il filesystem e’ ovviamente influenzato l’inode e i mattone fondamentale di immagazzinamento dei dati di un file ogni blocco di inode definisce la ownership, la timestamps (creazione modifica e accsso modification, access), grandezza, numero di hard links e la esatta posizione del blocco dei dati di tale file.
Ecco uno schema seplificato di come la VFS interagisce:
I processi inoltre tendono ad interagire con altri “oggetti” dell’ambiente linux. Uno dei principali e sicuramente la importantissima Virtual Memory.
LA VIRTUAL MEMORY
Molto semplicemente la memoria Virtuale e la somma della RAM+la swap (swap spazio del disco visto come estensione fisica della memoria volatile o RAM). Il ruolo che gioca la Virtual Memory e’ fondamentale all’interno dei sistemi unix/linux i programmi si riferiscono esclusivamente ad esso per lo spazio che deve essere allocato in memoria e accedono alla memoria fisica tramite la MMU. La MMU e’ un oggetto hardware che traduce gli indirizzi virtuali in indirizzi fisici (ovvero l’indirizzo reale sulla memoria) controlla che effettivamente l’indirizzo di memoria allocato fisico corrisponda effettivamente ad uno spazio libero nella memoria e gestisce la page fault. Detto questo, che relazione ha la memoria virtuale per il nostro processo. Innanzitutto possiamo dire che per il processo come viene allocata e gestita la memoria. La Segmentazione di memoria (si gestisce con segmenti di grandezza diversa di memoria) mentre la paged memory invece divide in segmenti esattamente uguali) La scelta del primo, del secondo o la mescolanza di entrambi (ibridazione) puo’ ovviamente effetti sul sistema se viene moltiplicato per tutte le operazioni in memoria che i processi producono.
Infine abbiamo i timers. Il processore tiene traccia del tempo di creazione di ogni singolo processo ogni tick il processore aggiorna l’ammontare del tempo complessivo utilizzato dal processo nel nel sistema.
Altro importante “oggetto” nel nostro ambiente “vitale” del processo sono gli Identificatori
IDENTIFICATORI
Essendo un sistema unix-like come tutti i sistemi unix like anche linux basa tutto, (compreso i processi) con i permessi. Questo oltre a riflettersi su file e directory si riflette anche ai processi. Detto questo possiamo dire che un processo per default puo’ appartenere fino a 32 gruppi (default) Ovviamente anche il file se deve accedere a uno piu’ file se non ha i permessi di lettura o scrittura questo ovviamente non potrà svolgere quei compiti che gli sono stati affidati. Esisterà quindi un vettore groups nella nostra ormai famigerata task_struct.
Ma vediamo nel dettaglio che tipo identificatori di gruppo influenzano la task_struct
UID E GUID
IDENTIFICANO GRUPPO E UTENTE che ha lanciato tale processo
Vi sono altre tre sottocategorie:
Effettivo:
Succede che alcuni programmi debbano cambiare uid e guid, affinché possano continuare i propri tasks. Succede ad esempio che per poter accedere a dei servizi con diverse uid e guid. In questo caso potrebbero sorgere effettivi problemi se tali servizi non hanno li stessi privilegi di gruppo e utente. Un guid/uid effettivo e quello che il processo aveva inizialmente e il kernel lo utilizza per controllare effettivamente i privilegi effettivi di tale programma.
Di filesystem:
Questo tipo di sotto categoria riguarda tutti I permessi inerenti ad un file che risiede per l’appunto sul filesystem. Sia per motivi di sicurezza, sia per motivi di praticità quando un processo deve accedere alla lettura o alla scrittura di un file. Ovviamente in questo caso sono proprio a cambiare e non quello Effettivo. E utile capire che il mantenimento di questa sotto categoria e’ assai utile quando riguarda un Kill Signal. Se per esempio avessimo un samba filesystem ovviamente il programma in user mode dovesse accedere a quel filesystem con e a dei suoi files
Ovviamente il quel cas saranno solamente i uid/guid di filesystem a cambiare
Saved:
Ovviamente questo uid/guid e’ usato per memorizzare sia ‘uid/guid orginale nel momento in cui cambia.
LO SCHEDULING E LO SCHEDULER
Altro meccanismo fondamentale per comprendere come “vive” un processo all’interno del nostro sistema linux e’ lo scheduling. Il processo parzialmente viene eseguito in parte nella user space e in parte in kernel space. Per farvi un esempio il processo tende a swappare grazie alle system call (si veda mio articolo su il problem solving). Ora tutti i processi devono attendere che la CPU ceda a loro il tempo per la elaborazione delle loro operazioni. Linux usa la prepre-emptive scheduling. Questo tipo di schedulazione ogni processo ha 200 millisecondi per eseguire le operazioni. Quando il tempo scade, un nuovo processo prende il suo posto e il processo precendente si mette in coda, finche’ non ripotra’ ripetere tale processo. Tale processo e’ chiamato time slice.
Il valore della time-slice è controllato dalla nice del processo, ovviamente come abbiamo visto il vettore struct e’ anche esso definito nella task_struct.
Lo scheduler si occupa di tale processo interrompendone temporaneamente un altro. Generalmente un sistema monoprocessore sono in grado di eseguire un solo programma per volta. Lo scheduler per l’appunto viene utilizzato per far convivere piu’ tasks contemporaneamente. Uno schema assai preciso e’ il seguente:
Come Possiamo visualizzare nello schema dello scheduler pubblicato da Pellizzari su wikipedia quando il processo parte dallo stato di Ready passa in modo unidirezionale allo stato di running. Lo stato di running invece interagisce in modo bidirezionale sia con lo stato di stop (interazione I/O e infine con lo stato end) lo stato di interazione I/O e assai lenta rispetto alle normali operazioni comuni.
Le informazioni principali che lo scheduler mantiene nella task_struct:
La Policy
Ci sono due tipi processi in linux quelli normali e quelli realtime ogni processo real time ha una priorita maggiore rispetto ad un qualsiasi processo non real time comunque lo scheduler dara’ la priorita’ al processo real time (real time indica sempre un processo che la sua correttezza di esecuzione dipende dal tempo di risposta). I processi in real time hanno due tipi di policy la round robin e la FIFO, il primo tutti i processi in ordine di arrivo mentre la FIFO invece e’ un algoritmo viene immessi dal sistema e il suo ordine non cambia piu’
This is the scheduling policy that will be applied to this process. There are two types of Linux
Priorita’
E la priorita che lo scheduler da ai processi. Ed e’ basato anche sul totale del tempo che ci vorra’ per essere eseguito. Questa priorita’ e basata anche sul renice (man renice)
Priorita’ Real time
Tutti i processi realtime che vengono schedulati anche loro hanno di perse’ una priorita’ tra loro come le priorita’ relative ad realtime e processo normale. Lo scheduler anche qui decide se si hanno dei processi realtime quale di questi deve avere priorita’ maggiore rispetto agli altri processi real time
Il tempo (counter)
l’ammontare totale del tempo che deve essere usato dal processo per runnare. Quando il processo la priorita’ decrementa per ogni clock tick
il processo attuale
ovviamente il processo che in quel momento e’ stato processato deve essere obbligatoriamente portato a compimento affinche’ un altro processo possa essere runnato.
La selezione dei processi
Lo scheduler controlla I processi in coda e cerca quello che merita la priorita’ per essere runnato Se ci sono real time process applica la rt_policy il counter per i processi normali e un couter+1000 questo significa che i processi rt vengono prima eseguiti con altissima priorita’ e poi vengono applicati le altre varie priorita’
. Processi in Swap
Come viene eseguito un binario: Gli ELF
Tutti i programmi di linux sono normalmente eseguiti da gli interprete dei comandi ovviamente tale interprete e’ la shell . Un considerazione: per comprendere ovviamente cosa avviene al programma la cosa migliore e’ che venga lanciato tramite la shell (l’utilizzo tramite interfacce come KDE o Gnome) pur essendo frendly non potranno dare la stesse informazioni quando vengono lanciate da shell (provate voi stessi a lanciare un programma da interfaccia grafica e da shell e noterete che la shell riportera’ delle informazioni che l’interfaccia grafica per ovvi motivi non puo’ dare in output) Escludendo CD e pwd (per motivi architetturali del sistema unix sys5) tutti i comandi unix sono dei binari eseguibili tramite shell. Cosa avviene quando lanciamo un comando:
la shell cerca prima di tutto in base alla env PATH (echo $PATH) cerca il binario
se il tool e’ stato trovato viene eseguitoe la shell clona se stesso usando il meccanismo di fork Il nuovo processo figlio sostituise il binario che e’ stato eseguito.
La shell aspetta che il commando sia completato, o aspetta un segnale di uscita control-d. o di sospensione control-z (SIGSTOP) che puo’ essere lanciato dall’utente, o che venga messo ad esempio in background (binario &) SIGCONT.
Un binario eseguibile ha molti formati ed essere utilizzato ad esempio usato da diversi interpetri (perl python etc etc) o puo essere uno script. Gli script di shell vengono riconosciuti e possono essere lanciati ovviamente per li gli shell script l’interprete e’ /bin/shell o più comunemente /bin/bash. Per poter comprendere il tipo di file che si ha davanti semplicemente si puo’ usare il tool file (man file).
Qualsiasi binario eseguibile contene il codice che deve essere eseguito con i dati che viene caricato nella memoria ed eseguito il piu’ comune tra questi e’ il formato ELF.
IL FORMATO ELF
Il formato ELF Executable and Linkable Format e il formato standard piu utilizzato per la creazione di un binario eseguibile. Ovviamente e’ lo standard piu’ utilizzato ma ovviamente ce ne sono altri come il (ECOFF .out) L’ELF fu progettato dalla UNIX SYSTEM LABS Possiamo definire quatro diversi tipi di ELF files
1)Riallocabili creati da compilatori e assembler hanno bisogno di essere precedentemente processati dal linker prima di essere eseguiti
2) Eseguibili: sono gia’ riallocabili eccetto per le collegamenti nella shared library (che dovrebbe essere risolto nel momento in cui vengono lanciati)
3) Shared Objects
4) core file o core dump file
L’header ELF e’ una preziosa informazione per comprendere il tipo di ELF con ci troviamo di fronte per poter leggere un header di un elf basta usare il tool objdump (man objdump).
(miglior diagramma rappresentativo dell’header by docs.sun.com)
Ecco la rappresentazione schematica di un ELF
(da tldp.org)
Dal punto di vista dell’header ad esempio il noto helloworld.o descrive Con due intestazione (phnum and phoff viene poi dopo il caricamento delle informazioni il programma viene eseguito caricando le dovute informazioni printf() viene dato l’ouput notorio “hello world”
Ma vediamo schematicamente cosa accade a livello alto del nostro progamma hello world:
section .data (caricamento dati)
Msg db ‘hello world’, 0x0a ( l’output)
len equ $ - msg (lunghezza della stringa hello world)
section .text (sezione testo)
global _start ( dichiarazione obbligatorie per il linker (ld)
_start: (dice al linker dove si trova la entry point)
mov eax, 4 (qui comincia la parte assembler una sys_write)
mov ebx, 1 ( il file descriptor che conosciamo stdout)
mov edx, msg (lungezza del messaggio
Int 0x80 (la nostra classica chiamata syscall)
mov eax, 1 ( avviene qui una sys exit numero di chiamata della system call sys_exit)
Int 0x80 (la nostra classica chiamata syscall)
A questo punto quando il nostro procresso parte viene settata nella virtual memory la struttura dati del nostro programma. Quando viene eseguito viene poi caricato nella nostra memoria fisica
Detto questo dovremmo aver concluso il nostro articolo su come I processi e i binari funzionino su linux