The Definitive Guide to Linux System Calls | Cerca per titolo, autore, parola chiave | ||||||||
The Definitive Guide to Linux System Calls Questo post spiega come, in Linux, un programma possa invocare una funzione del kernel (system call): open, fork, read, write (e molte altre). Le System call sono il mezzo attraverso il quale un programma utente può eseguire una qualche operazione all'interno dello spazio del kernel: creare processi, eseguire operazioni di rete, eseguire operazioni di Input/Output. Ci sono diverse modalità di invocazione delle system call, da parte dei programmi utente, e, a livello assembly (low level instruction), le istruzioni sono differenziate a seconda dell'architettura hardware su cui si opera. La prima domanda da porsi è: perchè un programma utente non è in grado di eseguire quelle funzioni senza l'intervento del kernel? La risposta è nell'architettura stessa delle CPU x86-64, che suddivide i circuiti in tre differenti livelli di privilegi: il privilegio assegnato al richiedente determina le aree alle quali il richiedente può accedere. Il livello di privilegio detenuto dal richiedente, quindi, determina quali istruzioni CPU il richiedente può eseguire. Il livello più elevato è il livello 0 (Ring 0), che è il livello in cui viene eseguito il kernel. Un qualsiasi programma utente viene eseguito al livello più basso, normalmente il livello 3 (Ring 3). Quindi, affinché un programma utente possa eseguire una qualche istruzione privilegiata, si dovrà poter assegnare a quel programma utente il livello necessario, almeno momentaneamente. Per modificare il livello di privilegi di un programma utente e permettergli di chiedere al kernel di eseguire una qualche istruzione, esistono vari modi, ma il più utilizzato è quello degli interrupt. Un interrupt è un evento (un segnale), generato dall'hardware, ma anche via software, che avvisa il kernel che qualcosa di inatteso nel sistema è successo. un esempio di interrupt hardware è l'interrupt inviato, dalla NIC o scheda di rete, alla CPU all'arrivo di un pacchetto di dati. La CPU dovrà in qualche modo interrompere il processo in esecuzione per poter ricevere ed elaborare il pacchetto di dati in arrivo. Un interrupt software, invece, viene inviato attraverso l'istruzione INT (x86-64 system). Come risponde la CPU ad un interrupt? Ad ogni interrupt inviato corrisponde un numero che determina il pezzo di codice che la CPU dovrà eseguire in risposta all'interrupt ricevuto. Nella CPU viene mantenuto un array di numeri di interrupt, a ciascuno dei quali viene associato l'indirizzo di memoria della funzione da eseguire e le eventuali opzioni da elaborare, quali, per esempio, il livello di privilegio richiesto dalla funzione stessa. Il problema è che, a livello software, il kernel riserva un solo interrupt da inviare, da parte dei programmi utente, al kernel stesso: INT 0x80, al quale è associata la funzione (interrupt handler o gestore di interrupt) chiamata ia32_syscall (nei sistemi a 32 bit). Quindi, per sapere esattamente quale system call invocare, in caso di ricezione di un interrupt software, il kernel deve poter cercare in alcuni registri dedicati i dati che gli servono: * Emulated IA32 system calls via int 0x80. * %eax System call number * %ebx Arg1 * %ecx Arg2 * %edx Arg3 * %esi Arg4 * %edi Arg5 * %ebp Arg6 Quindi, quando il kernel riceve un interrupt software 0x80, cerca nel registro EAX (per i sistemi a 32 bit) o RAX (per i sistemi a 64 bit) il numero della system call da eseguire e cerca nei successivi registri le eventuali opzioni da esegure con l'invocazione della system call. Se volessimo, per esempio, invocare la system call EXIT, dovremmo sapere a quale numero è associata e quanti e quali argomenti esige. Attenzione, perchè il numero associato a ciascuna delle system call varia a seconda che si tratti di un sistema a 32 bit o a 64 bit. Inoltre, sono state inserite due nuove istruzioni, molto più veloci, per sostituire l'istruzione INT 0x80: sysenter per i sistemi a 32 bit e: syscall per i sistemi a 64 bit. Quindi, per eseguire la system call EXIT, in un sistema a 64 bit, corrispondente al numero 60 e richiedente un solo argomento, il codice di uscita della funzione, sarà sufficiente scrivere le seguenti istruzioni assembly: mov rax, 60 mov rbx, 0 syscall La documentazione relativa alla Intel Instruction Set Reference spiega come funziona l'istruzione syscall: SYSCALL invoca un system-call handler (un gestore di system call) al livello privilegi zero, prelevando il valore del nuovo puntatore RIP (Register Instuction Pointer) dal registro IA32_LSTAR MSR, dopo aver salvato l'indirizzo dell'istruzione subito successiva all'istruzione SYSCALL nel registro RCX. In altre parole, il kernel trova l'indirizzo di memoria dell'istruzione da eseguire all'arrivo di un SYSCALL nel registro IA32_LSTAR MSR. I Model Specific Registers (MSR) sono registri di controllo dedicati a specifiche funzioni della CPU. La documentazione della CPU riporta gli indirizzi di ciascuno di questi registri speciali.Gli MSR sono stati introdotti con i Pentium, anche se il loro numero ed i servizi ad essi legati sono dipendenti dal modello e dalla famiglia del processore (P6, Core Duo, Core 2 Duo, etc...). La principale differenza tra un registro normale ed un registro MSR è la modalità di accesso. Contrariamente a quanto accade con i registri normali, non è possibile eseguire un'istruzione MOV su un registro MSR. Come si può fare, quindi? Ciascun registro MSR è identificato da un numero a 32 bit. Scrivendo il valore N nel registro ECX, è possibile leggere o scrivere il contenuto del N-th registro MSR, usando le istruzioni RDMSR e WRMSR. Il valore letto, dall'istruzione RDMSR, verrà memorizzato nei registri EDX:EAX, oppure, in caso di un'istruzione WRMSR, il valore verrà comunque prelevato dalla stessa coppia di registri. Naturalmente, restano in essere le convenzioni da rispettare, in caso di chiamata di SYSCALL: il programma utente deve inserire il numero della SYSCALL da invocare nel registro RAX e gli eventuali argomenti in una porzione di registri general purpose, come documentato nell'x86-64 ABI, sezione A.2.1:
|
|||||||||
The Definitive Guide to Linux System Calls | Disclaimer: questo è un link a contenuti ospitati su server esterni. |