Sistemi Operativi Esercitazione 2 Compilazione, Makefile e Processi
Percorso Compilazione programmi in c Makefiles Processi definizione gestione creazione
Compilazione di programmi in C
Compilazione e Linking Codice sorgente Librerie (file oggetto) COMPILATORE LINKER File oggetto Eseguibile
Compilatore e linker gcc Open-Source GNU project http://www.gnu.org Compilatore e linker gcc Supporta C e C++ Comando generico gcc <opzioni> <argomenti> Opzioni: elenco di flag che controllano il compilatore e il linker; Argomenti: elenco di file che gcc legge e trasforma in maniera dipendente dalle opzioni
Opzioni gcc (1) Opzioni più comuni -c file -o file -g -Wall Esegue la compilazione non il linker Specifica il nome di output; in genere indica il nome dell eseguibile finale (linkando) Indica a gcc di non ottimizzare il codice e di inserire informazioni extra per poter effettuare il debugging (i.e., vedere gdb) Stampa warning per tutti i possibili errori nel codice
Opzioni gcc (2) -Idir -lm -Ldir Specifica ulteriori direttori in cui cercare gli header file Specifica utilizzo libreria matematica Specifica direttori per ricercare librerie preesistenti
Processo di creazione dell'eseguibile Il compilatore prende in ingresso i file sorgenti e fornisce in uscita i file oggetto (object files -.o files) gcc -c main.c -c(compile) con l'opzione -c vengono creati solo I file oggetto e non l'eseguibile Il linker prende in ingresso i file oggetto e crea l'eseguibile gcc main.o -o nome_eseguibile Compilazione e linking in una sola istruzione gcc main.c -o nome_eseguibile
Makefiles
Cos'è un Makefile? I Makefies sono dei file che permettono, insieme al comando make di compilare e gestire i propri progetti (Programmi scritti in C) man make Sono utili per compilare i file.c soprattutto nel caso si abbiano tanti file da compilare con tanti headers da includere
Come sono organizzati? La versione base di un Makefile è composta da: target: dipendenze [tab] comando unix Esempio Makefile: all: target Shell : make all gcc Wall main.c o mywc comando unix di compilazione
Usare le dipendenze A volte può essere utile usare target differenti Example: compile: echo compiling my application gcc -Wall main.c -o mywc install : compile esegue prima il target compile e poi il target install echo copying executable in the deploy directory... clean : echo clean object files...
Esercizio 1 Creare un make che permetta di: creare l'eseguibile del sorgente mysource.c creare una directory install e copiarne l'eseguibile creato prima cancellare la directory install
Usare le variabili Le variabili sono utili per cambiare compilatore o le opzioni di compilazione. Example CC=gcc dichiarazione e inizializzazione variabili CFLAGS=-Wall ONAME=myWC compile: echo compiling my application $(CC) $(CFLAGS) main.c -o $(ONAME) uso delle variabili...
Comando make Lanciando make da shell: Cercherà un file di nome makefile nella directory corrente, e lo eseguirà. Se si hanno diversi, si può usare l'opzione -f per speificare il nome del makefile : make -f MyMakefile
Processi
Cos'è un processo É un istanza di un programma in memoria! Ogni programma che viene eseguito crea un processo. Un programma può essere un comando unix, uno script di shell, o qualsiasi eseguibile
Attributi di un processo PID (Process-Id) Il PID è usato dal Kernel per identificare il processo Simile all'utilizzo dell'inode per l'identficazione dei file PPID (Parent Process Id) Il processo che crea un'altro processo si chiama Padre (o Parent), il processo creato si chiama Figlio (o child) Il PID del processo Parent è chiamato PPID TTY Il terminale al quale il processo è associato Ci sono processi, chiamati Daemons, che non appartengono a nessun terminale UID L'ID dell'utente al quale appartiene il processo File Descriptors I Descrittori dei file collegati al processo (nput, output e descrittori di errore)
Come creare un processo(fork) System call fork() usata per creare un processo (un nuovo flusso di esecuzione) Non riceve argomenti e ritorna: 0 al processo Figlio (Child Process) PID del figlio al processo Padre (Parent process) -1 in caso di errore
Fork system call Process Fork() execution flow 1 Parallel execution Child Process execution flow 2 parent Process execution flow 1 exit(0) exit(0) Quale processo termina prima?
Fork: esempio 1 Creazione di un figlio #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { pid_t pid; fprintf(stdout, "Parent: my PID is %d: my parent is process %d\n", getpid(), getppid()); pid = fork(); if (pid == 0){ // Child fprintf(stdout, "ID Returned to Child: %d\n", pid); } else { //Parent fprintf(stdout, "ID Returned to Parent: %d\n", pid); } } exit (0);
Esercizio 2 Scrivere un programma in C che permette di creare due processi figli
Esercizio 3 Scrivere un programma in C per creare N processi figli
Fork: esempio 2 Spazio di indirizzamento dei processi /* * Illustrates the use of separate process address spaces * with common inital values */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { int x, y; x = 4; if (fork()) { // parent!= 0 y = 5; x = 15; sleep (5); } else { // child y= -5; } // recall 0 in C is FALSE fprintf(stdout, " x = %d y = %d process %d, PPID %d\n", x, y, getpid(), getppid()); exit(0); }
sleep unsigned sleep(unsigned seconds); Sospende l'esecuzione di un processo per un determinato tempo Il processo potrebbe essere sospeso più a lungo, a causa della schedulazione delle altre attività del sistema Valori di ritorno 0 tempo scaduto Unslept il tempo che resta da dormire se viene ricevuto un segnale
Sleep: esempio #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { pid_t pid1; pid_t pid2; fprintf(stdout, "Parent: my PID is %d: my parent is process %d\n", getpid(), getppid()); pid1 = fork(); if (pid1 == 0){ // Child1 fprintf(stdout, "ID Returned to First Child: %d\n", pid1); sleep(5); fprintf(stdout, "First Child end!"); } } else { //Parent fprintf(stdout, "First Child ID Returned to Parent : %d\n", pid1); pid2 = fork(); if (pid2 == 0){ // Child2 fprintf(stdout, "ID Returned to Second Child: %d\n", pid2); } else { //Parent fprintf(stdout, "Second Child ID Returned to Parent : %d\n", pid2); fprintf(stdout, "Second Child end!\n"); } } exit (0);
Esercizio 4 Qual'è l'ordine in cui compaiono i messaggi di output del programma precedente?
Gestione Processi (1) Un processo può essere esguito in due modi Foreground Di default ogni processo gira in foreground Prende gli input da tastiera e manda gli output a video Mentre un programma sta girando in foreground, non possiamo eseguire nessun altro processo Esempio foreground : $ ls Background Il vantaggio è che in questo caso si possono lanciare altri processi Non bisogna aspettare che termini I processi si lanciano in background usanto il carattere & sul terminale esempio: $ ls &
Gestione Processi(2) Lista dei processi in esecuzione comando ps $ps -f UID PID PPID C STIME TTY TIME CMD.................. UID User ID that this process belongs to (the person running it). PID Process ID. PPID Parent process ID (the ID of the process that started it). C CPU utilization of process. STIME Process start time. TTY Terminal type associated with the process TIME CPU time taken by the process. CMD The command that started this process.
Gestione processi(3) ps options -a Shows information about all users -x Shows information about processes without terminals. -u Shows additional information like -f option. -e Display extended information. Stopping processes CTRL + C keystroke this works when process is running in foreground mode. If a process is running in background kill command kill -9 PID use ps command to get the process PID you need to kill
Sincronizzazione dei Processi
System call wait pid_t wait(int *status) Blocca il processo che la chiama finchè non termina uno dei suoi figli oppure se riceve un segnale Può essere usata per rilasciare delle risorse allocate per un figlio Se non si chiama la wait il figlio che termina diventa Zombie
Zombie vs Orphan Un processo zombie (o defunct) è un processo che ha completato la sua esecuzione ha ancora una entry nella process table il Parent è ancora vivo Il processo Orphan è un processo ancora in esecuzione il cui Parent è terminato. Questi processi non sono Zombie ma vengono adottati dal processo init (ID=1)
System call wait: esempio int main(int argc, char *argv[]) { pid_t pid; int status; fprintf(stdout, "Parent: my PID is %d: my parent is process %d\n", getpid(), getppid()); pid = fork(); if (pid == 0){ // Child int s; fprintf(stdout, "Child: my PID is %d: my parent is process %d - Please enter a number(0-255)\n", getpid(), getppid()); scanf("%d",&s); fprintf(stdout, "Child %d - my exit status will be %d\n",getpid(),s%256); exit (s); } } else { //Parent wait(&status); fprintf(stdout, "Parent %d - my child status is %d\n",getpid(),status>>8); exit (0); } C P P Sync Point P
Esercizio 5 Scrivere un programma in C che può creare un Zombie
Esercizio 6 Scrivere un programma in C che può creare un Orphan
System call waitpid pid_t waitpid(pid_t pid, int *status, int options) Aspetta un figlio specifico definito dal pid