Processi C. Baroglio a.a. 2002-2003 1 Processi, flussi e pipe In Unix ogni processo è identificato da un numero intero, il Process IDentifier o PID per brevità. I processi sono generati o dall esecuzione di comandi, dall esecuzione di programmi oppure per iniziativa di altri processi che invocano la system call fork. Ogni processo comunica con il proprio ambiente attraverso tre flussi di dati: standard input, standard output e standard error. Il primo corrisponde di default alla tastiera ed è la sorgente dalla quale il processo preleva i dati da elaborare. Il secondo e il terzo corrispondono di default al monitor e sono utilizzati rispettivamente per comunicare il risultato dell elaborazione e per effettuare comunicazioni di servizio (verificarsi di errori, stampe di utilità). Il valore dei flussi standard può essere modificato. Consideriamo per esempio il comando ls, che sappiamo listare il contenuto di una directory. Se richiamo l esecuzione di ls senza ulteriori specifiche, il contenuto della working directory verrà inviato allo standard output, che corrisponde al monitor, e quindi verrà visualizzato all utente. Supponiamo di voler memorizzare il risultato del comando ls per un qualche uso futuro. A tal fine possiamo scrivere: ls > f ile risultato il simbolo di maggiore indica alla shell che lo standard output del processo corrispondente all esecuzione del comando deve essere ridirezionato sul file avente nome file risultato. Se tale file non esiste viene creato e il suo contenuto sarà il risultato di ls. Se invece esiste, il contenuto precedente verrà perso in quanto sarà sovrascritto dal risultato di ls. Per far sì che il risultato di ls venga appeso in fondo al file indicato e non sovrapposto al contenuto precedente occorre utilizzare >> anziché >. Ci sono situazioni in cui invece un processo produce molte stampe a video nelle quali l utente non è interessato. Per evitare di vederle scorrere nella finestra di lavoro l utente può ridirigere lo standard output del processo su di un file speciale, /dev/null, nel seguente modo: comando > /dev/null In questo caso l output non viene salvato, al contrario viene perso come se non fosse stato prodotto. 1
In maniera analoga è possibile ridirigere lo standard input di un processo. Alcuni processi di default elaborano dati digitati a tastiera da un utente. Qualora sia a disposizione un file di dati che potrebbe essere utile elaborare con il comando in questione. In questo caso è possibile utilizzare lo stesso programma ridirigendone lo standard input in questo modo: comando < f ile dati Questa soluzione è piuttosto frequente nel caso di programmi scritti in C: si scrive un programma che utilizza standard input e standard output e poi lo si utilizza anche su file di dati (e magari salvando i risultati su altri file), ridirigendo i flussi standard del processo. Non tutte le versioni di Unix consentono di ridirigere con un simbolo apposito il terzo ed ultimo flusso standard, lo standard error. In genere è possibile ridirigere sia lo standard output che lo standard error utilizzando > & anziché solo > (>> & anziché >>). In quei casi in cui si voglia separare i due flussi in uscita è possibile utilizzare il poco agevole (comando > output file) > &error f ile. Come abbiamo visto nell introduzione a Unix, il sistema operativo fornisce all utente un gran numero di piccoli programmi, ciascuno avente una funzionalità precisa, che possono essere combinati per realizzare compiti diversi. Il meccanismo che consente tale combinazione è la ridirezione dei flussi standard attraverso un oggetto chiamato pipe e simboleggiato dal carattere. In generale è possibile concatenare una sequenza qualsivoglia lunga di comandi nel seguente modo: comando 1 comando 2... comando N L effetto di pipe è di ridirigere lo standard output del comando (i-1)-mo nello standard input del comando i-mo. I comandi così composti costituiscono una pipeline di processi. Solo l output del comando N-mo verrà inviato al terminale. 2 Utenti e gruppi reali ed effettivi Ogni processo ha associati quattro identificatori: real user id, real group id, effective user id, effective group id. Questi identificatori hanno un ruolo fondamentale per tutti quei processi che per operare debbono accedere a dei file. Consideriamo, come esempio introduttivo, il comando passwd, che consente a ciascun utente di modificare la propria password. Questo programma utilizza due file, passwd (omonimo del comando) e shadow, che hanno associati i seguenti diritti di accesso: rw- r- - r- - root root /etc/passwd r- - - - - - - - root root /etc/shadow 2
Il file passwd può essere modificato solo dall utente root (amministratore di sistema) mentre il file shadow è un file di sola lettura, abilitata al solo utente root. Eppure gli utenti (che non sono root) riescono ad accedere a questi file quando utilizzando il comando passwd. Questo stato di cose apparentemente è in conflitto con quanto abbiamo visto riguardo i diritti di accesso a file e directory: nessun utente oltre a root dovrebbe poter modificare passwd e leggere shadow e, in effetti, se l utente provasse a modificare con un editor passwd o ad aprire shadow otterrebbe senza dubbio la segnalazione che l operazione non è consentita. Nel caso del comando passwd, invece, è come se le restrizioni fossero in qualche modo allentate specificamente per gli accessi necessari all attività di modifica della propria password. Il mistero comincia a svelarsi se eseguiamo ls -l /usr/bin/passwd (questa volta passwd è il nome del comando): r-s - - x - - x root root /usr/bin/passwd Possiamo osservare che i diritti di accesso associati all eseguibile in questione sono diversi da quelli a noi finora noti: compare una s della quale ancora non conosciamo il significato, che rende questo comando speciale. A parole questa configurazione specifica che quando un utente esegue il comando in questione acquisisce i diritti di accesso di root per il solo tempo necessario all esecuzione medesima. Volendo fare un discorso più preciso e generale, occorre tornare ai quattro identificatori associati a ciascun processo e capire che cosa sono e quali valori assumono i real/effective user/group id. Sia exe il nome di un file eseguibile. Siano utente1 e group1 i valori di UID e GID dell utente che lancia exe, producendo un processo P, che nel corso del proprio operato cercherà di accedere ad un file che chiameremo info. Il real user id di P è lo UID dell utente che lo ha generato (ovvero utente1). Analogamente il real group id di P è il GID dell utente che lo ha generato (group1, nel nostro esempio). L effective user id e l effective group id di P dipendono rispettivamente da due diritti di accesso speciali, che si chiamano set user id e set group id, associati all eseguibile exe. Se set user id è attivo, l effective user id di P sarà uguale all UID del proprietario del file eseguibile (nell esempio il proprietario di /usr/bin/passwd, cioè root); in caso contrario l effective user id di P sarà uguale al suo real user id (quindi utente1). In maniera del tutto analoga, se set group id è attivo, l effective group id del processo P sarà uguale al GID del proprietario di exe; in caso contrario sarà identico al real group id del processo (group1). La decisione di attivare o meno set user (group) id dipende dal proprietario di exe (di default sono disattivi). In entrambi i casi si utilizza chmod. L attivazione di set user id viene fatta con il comando chmod u+s exe, la sua disattivazione con chmod u-s exe, l attivazione di set group id con chmod g+s exe, la sua disattivazione con chmod g-s exe. Vediamo ora come questi quattro identificatori vengono utilizzati nel momento in cui il processo P cerca di accedere al file info. Come premessa ricor- 3
diamo che info, come tutti i file, avrà associato un proprietario e un gruppo di utenti. Valgono le seguenti regole, applicate nell ordine con cui sono elencate: 1. se l effective user id di P coincide con il proprietario di info, il processo acquisisce i diritti di accesso del proprietario di info (nel nostro esempio il processo originato dall esecuzione di /usr/bin/passwd acquisisce i diritti di accesso di root per i file passwd e shadow); 2. altrimenti se l effective group id di P e il gruppo di info concidono, P acquisisce i diritti di accesso del gruppo di utenti associato a info; 3. se nessuna delle due precedenti condizioni è valida, valgono le normali triple di diritti di accesso studiate nella sezione dedicata a file e directory; l accesso sarà consentito o meno a seconda della categoria di utenti nella quale ricadono real user id e real group id del processo P. 3 Processi in background e in foreground In questa sezione studieremo un meccanismo che consente di sfruttare la caratteristica di Unix di essere un sistema operativo multi-tasking, producendo più processi che eseguono in parallelo all interno dello stesso terminale. Fino ad ora abbiamo visto in modo implicito una sola modalità di avvio dei processi: l utente digita un comando, che viene letto dalla shell nel momento in cui viene premuto il tasto enter e da questa eseguito, al termine dell esecuzione (evidenziata dal riapparire del prompt sul terminale di lavoro) la shell si mette in ascolto di nuovi comandi. Questa modalità di esecuzione è detta di foreground. L esecuzione in foreground è caratterizzata dal fatto che il processo mantiene il controllo del terminale fino al suo termine. In altre parole, qualsiasi comando venga digitato prima del riapparire del prompt viene ignorato fino a quando il processo in esecuzione non termina, rilasciando il terminale. Esiste una seconda modalità di esecuzione in Unix: l esecuzione in background. Un processo eseguito in background non ha il controllo del terminale, quindi non impedisce l avvio di altri processi derivanti dall esecuzione di altri comandi. Tutti i tipi di shell forniscono degli strumenti per avviare processi in background, per controllarne l esecuzione e per modificare il tipo di esecuzione dei processi (passand da fore- a back-ground o viceversa). Vediamo gli strumenti offerti dalle shell di tipo csh. Le unità esecutive che le shell csh consentono di gestire (nel contesto di foree back-ground) sono dette job. Il concetto di job è un po diverso dal concetto di processo, corrisponde infatti a un gruppo di processi connessi da pipe: > cmd 1 cmd 2... cmd N Il job identifica l insieme degli N processi avviati (corrispondenti ciascuno ad un comando), ciascun processo avrà comunque un proprio PID. L avvio da linea 4
di comando di un singolo processo è un caso particolare, quello in cui l insieme contiene un solo elemento. Per avviare un job in background occorre terminare la linea di comando con il carattere & (e commerciale): > cmd 1 cmd 2... cmd N & Questa procedura produce come output immediato la stampa a video di un numero fra parentesi quadre che identifica il job avviato in background, dopodiché ricompare il prompt: la shell è pronta a eseguire in parallelo altri comandi. Su ogni terminale è possibile avviare molti job in background ma uno solo può eseguire in foreground. Le informazioni relative ai job in background sono mantenute in uno stack, che può essere visualizzato a video utilizzando il comando jobs: [1] Stopped (tty input) comando1 [2] - Stopped (tty input) comando2 comando3 [3] + Stopped (tty input) comando1 La cima dello stack corrisponde all elemento indicato con +. Un job in background non riceve input dalla tastiera però riversa su monitor le stampe inviate su stdout ed stderr, a meno di ridirezioni. Per passare in foreground un job attualmente in background si utilizza il comando fg in uno dei due seguenti modi: > fg > fg %n Il comando fg senza argomenti porta in foreground il job in cima allo stack (nell esempio il job numero 3), mentre utilizzando come argomento il simbolo % seguito da un numero si porta in foreground il job contraddistinto da quel numero. Per portare un job in background occorre eseguire due operazioni: innanzi tutto è necessario sospendere il processo attualmente in foreground utilizzando la combinazione di tasti control-z (equivale ad inviare un segnale di sospensione), in secondo luogo occorre eseguire il comando bg. 4 Uccisione dei processi Per uccidere un processo in foreground è sufficiente eseguire la combinazione di tasti control-c. In generale per uccidere un processo occorre conoscere il suo PID. È possibile uccidere qualsiasi processo eseguendo il comando: > kill 9 pid Il comando kill consente di inviare un segnale a un processo; -9 indica che si intende inviare un segnale SIGKILL il cui effetto è la terminazione del processo 5
del quale è stato indicato il PID. Per visualizzare l elenco dei processi e dei loro PID si utilizza il comando ps (vedi slide e manuale on-line). 6