I servizi di sistema sono un insieme di routine messe a disposizione dal SO, che sono invocabili tramite syscall.
La funzione syscall nella libreria glibc è dichiarata come segue:
long syscall(long num_del_servizion, param_del_servizio, ...)
Per convenzione ogni chiamata è associata ad un numero del servizio richiesto. In corrispondenza al numero del servizio si associa una costante simbolica dal prefisso sys seguito dal nome del servizio.
È difficile conoscere il nome della system call service routine che esegue un determinato servizio di sistema. Talvolta ha lo stesso nome del servizio, ma in molti casi ha un nome diverso, legato anche all'evoluzione del sistema.
Per semplificare noi assumiamo che il nome della system call service routine che esegue un servizio sia uguale al nome della costante simbolica utilizzata per individuare il servizio nella chiamata della funzione syscall di glibc. Pertanto per noi le system call service routine hanno un nome costituito dal prefisso sys_ seguito dal nome del servizio.
Al momento dell'avviamento (bootstrap) devono avvenire le seguenti operazioni:
idle (PID 1).Deve infatti esistere almeno un processo iniziale che viene creato direttamente dal sistema operativo. Le operazioni di avviamento successive alla creazione del processo 1 sono svolte dal programma init, cioè un normale programma non privilegiato che differisce dagli altri processi solamente per il fatto di non avere un processo padre.
Il processo 1, eseguendo init crea un processo per ogni terminale sul quale potrebbe essere eseguito un login. Questa operazione è eseguita leggendo il file di configurazione (inittab). Quando un utente si presenta al terminale ed esegue il login, se l'identificazione va a buon fine, allora il processo che eseguiva il programma di login lancia in eseuzione il programma shell, e da questo momento la situazione è quella di normale funzionamento.
Il processo idle, dopo aver concluso le operazioni di avviamento assume le seguenti caratteristiche:
wait_xxx.Prima di invocare la SYSCALL, per effettuare il passaggio di parametri dalla funzione syscall alla funzione del kernel system_call occorre passare il numero del servizio da invocare nel registro rax, e passare eventuali parametri ordinatamente nei registri rdi, rsi, rdx, r10, r8 e r9.
La funzione syscall invoca il kernel tramite l'istruzione SYSCALL che invoca la funzione del kernel system_call. L'indirizzo al quale la CPU data all'interno del kernel è quello della funzione system_call. A sua volta la system_call invoca la System Call Service Routine opportuna sys_xxx per eseguire il servizio xxx in base al numero presente nel registro rax.
Attualmente la libreria più utilizzata per i thread in Linux è la Native POSIX Thread Library.
I processi normali sono creati quando si esegue una fork, quelli leggeri quando si esegue una pthread_create, e in entrambi i casi la creazione è effettuata dal padre.
Il processo figlio potrà condividere con il padre una serie più o meno estesa di proprietà e componenti, mentre il processo leggero creato da una pthread_create condivide con il padre chiamante una serie di componenti, di cui noi consideriamo solamente la memoria e la tabella dei file aperti.
Entrambe le funzioni fork e pthread_create sono realizzate dal nucleo Linux tramite una sola e complessa system call service routine, la sys_clone.
cloneLa funzione di libreria glibc clone crea un processo con caratteristiche di condivisione definite analiticamente tramite una serie di flag.
int clone (int (*fn)(void *), void* child_stack, int flags, void* arg, ...)
int (*fn)(void *) indica che si tratta di un puntatore a una funzione che riceve un puntatore a void come argomento e restituisce un intero, void* arg è il puntatore ai parametri da passare alla funzione fn e void* child_stack è l'indirizzo della pula utente che verrà utilizzata dal processo figlio.
I flag sono piuttosto numerosi, ci limitiamo a trattare:
CLONE_VM: indica che i due processi utilizzano lo stesso spazio di memoria.CLONE_FILES: indica che i due processi devono condividere la tabella dei file aperti.CLONE_THREAD: indica che il processo viene creato per implementare un thread.La funzione clone eseguita dal processo padre crea un processo figlio che esegue la funzione fn(*arg), ha una sua pila utente dislocata all'indirizzo child_stack, condivide con il padre gli elementi indicati dai flag presenti nella chiamata. Inoltre se è specificato il flag CLONE_THREAD avrà lo stesso TGID del chiamante.
La funzione pthread_create è implementata in maniera molto diretta invocando la funzione clone nel modo seguente.
char* pila = malloc(...);
clone(fn, pila, CLONE_VM, CLONE_FILES, CLONE_THREAD, ...);
Il processo figlio condivide memoria e file con il padre, e lo spazio per la pila utente del thread viene allocato all'interno della memoria dinamica del processo stesso.
La system call service routine sys_clone è pensata per creare un processo figlio normale e quindi assomiglia alla funzione fork.
long sys_clone (unsigned long flags, void* child_stack, void* ptid, void* ctid, struct pt_regs* regs);
Se child_stack è CLONE_VM non dev'essere specificato, altrimenti non è garantita la correttezza del funzionamento. In caso contrario, il figlio lavora su una pila posta all'indirizzo child_stack e tipicamente la memoria non viene condivisa.
Esistono due system call service routine relative alla cancellazione dei processi: sys_exit e sys_exit_group.
Il servizio sys_exit_group è implementato nel modo seguente: invia a tutti i membri del gruppo il signal di terminazione ed esegue una normale sys_exit.
Il servizio sys_exit deve rilasciare le risorse utilizzate dal processo, restituire un valore di ritorno al processo padre, e invocare la funzione schedule per lanciare in esecuzione un nuovo processo.
La terminazione di un singolo thread può avvenire per esecuzione dell'istruzione return e per invocazione di pthread_exit. Viene comunque realizzata utilizzando il servizio sys_exit.
La funzione C di libreria glibc exit è usata per terminare un processo con tutti i suoi thread, ed è implementata invocando il servizio sys_exit_group.
I singoli servizi di SO devono talvolta leggere o scrivere dati nella memoria utente del processo che li ha invocati. Linux fornisce una serie di macro assemble utilizzabili per questo scopo (Linux/arch/x86/include/asm/uaccess.h).