Un sistema operativo inserisce il proprio codice macchina quando si apre un programma?


32

Sto studiando CPU e so come legge un programma dalla memoria ed eseguo le sue istruzioni. Capisco anche che un sistema operativo separa i programmi nei processi, e quindi si alternano tra loro così velocemente che pensi che siano in esecuzione contemporaneamente, ma in realtà ogni programma funziona da solo nella CPU. Ma, se il sistema operativo è anche un gruppo di codice in esecuzione nella CPU, come può gestire i processi?

Ho pensato e l'unica spiegazione che ho potuto pensare è: quando il sistema operativo carica un programma dalla memoria esterna alla RAM, aggiunge le sue istruzioni nel mezzo delle istruzioni del programma originale, quindi il programma viene eseguito, il programma può chiamare il sistema operativo e fare alcune cose. Credo che ci sia un'istruzione che il sistema operativo aggiungerà al programma, che consentirà alla CPU di tornare al codice del sistema operativo qualche volta. Inoltre, credo che quando il sistema operativo carica un programma, controlla se ci sono alcune istruzioni proibite (che salterebbero a indirizzi proibiti nella memoria) ed elimina quindi.

Sto pensando al rigth? Non sono uno studente CS, ma in realtà uno studente di matematica. Se possibile, vorrei un buon libro su questo, perché non ho trovato nessuno che spieghi come il sistema operativo può gestire un processo se il sistema operativo è anche un gruppo di codice in esecuzione nella CPU e non può funzionare allo stesso orario del programma. I libri dicono solo che il sistema operativo può gestire le cose, ma ora come.


7
Vedi: Cambio di contesto Il SO fa un cambio di contesto all'app. L'app può quindi richiedere servizi del sistema operativo che riportano un contesto nel sistema operativo. Quando l'app termina, il contesto torna al sistema operativo.
Guy Coder

4
Vedi anche "syscall".
Raffaello


1
Se i commenti e le risposte non rispondono alla tua domanda alla tua comprensione o soddisfazione, ti preghiamo di chiedere maggiori informazioni come commento e spiegare cosa stai pensando o dove ti sei perso, o su cosa specificamente hai bisogno di maggiori dettagli.
Guy Coder

2
Penso che interruzione , aggancio (di un interrupt), timer hardware (con hook di scheduling -handling) e paging (risposta parziale alla tua osservazione sulla memoria proibita) siano le principali parole chiave di cui hai bisogno. Il sistema operativo deve collaborare strettamente con il processore per eseguire il suo codice solo quando necessario. Pertanto, la maggior parte della potenza della CPU può essere utilizzata sul calcolo effettivo, non sulla sua gestione.
Palec,

Risposte:


35

No. Il sistema operativo non scherza con il codice del programma iniettando nuovo codice in esso. Ciò avrebbe una serie di svantaggi.

  1. Sarebbe dispendioso in termini di tempo, poiché il sistema operativo dovrebbe eseguire la scansione dell'intero eseguibile apportando le sue modifiche. Normalmente, parte dell'eseguibile viene caricata solo se necessario. Inoltre, l'inserimento è costoso in quanto è necessario spostare un sacco di roba di mezzo.

  2. A causa dell'indecidibilità del problema di arresto, è impossibile sapere dove inserire le istruzioni "Torna al sistema operativo". Ad esempio, se il codice include qualcosa del genere while (true) {i++;}, devi sicuramente inserire un hook all'interno di quel loop ma la condizione sul loop ( true, qui) potrebbe essere arbitrariamente complicata, quindi non puoi decidere per quanto tempo deve passare. D'altra parte, sarebbe molto inefficiente inserire hook in ogni loop: ad esempio, tornare indietro al sistema operativo durante for (i=0; i<3; i++) {j=j+i;}rallenterebbe molto il processo. E, per lo stesso motivo, non è possibile rilevare loop brevi per lasciarli soli.

  3. A causa dell'indecidibilità del problema di arresto, è impossibile sapere se le iniezioni di codice hanno cambiato il significato del programma. Ad esempio, supponiamo di utilizzare i puntatori a funzione nel programma C. L'iniezione di un nuovo codice sposterebbe le posizioni delle funzioni, quindi, quando ne chiamavi una tramite il puntatore, saltavi nel posto sbagliato. Se il programmatore fosse abbastanza malato da usare i salti calcolati, anche quelli fallirebbero.

  4. Giocherebbe un inferno allegro con qualsiasi sistema anti-virus, poiché cambierebbe anche il codice del virus e rovinerebbe tutti i tuoi checksum.

È possibile aggirare il problema del problema di arresto simulando il codice e inserendo hook in qualsiasi ciclo che viene eseguito più di un determinato numero di volte. Tuttavia, ciò richiederebbe una simulazione estremamente costosa dell'intero programma prima che gli fosse permesso di eseguire.

In realtà, se si desidera iniettare codice, il compilatore sarebbe il posto naturale per farlo. In questo modo, dovresti farlo una sola volta, ma non funzionerebbe ancora per il secondo e il terzo motivo indicati sopra. (E qualcuno potrebbe scrivere un compilatore che non ha funzionato insieme.)

Esistono tre modi principali in cui il sistema operativo riprende il controllo dai processi.

  1. Nei sistemi cooperativi (o non preventivi), esiste una yieldfunzione che un processo può chiamare per restituire il controllo al sistema operativo. Naturalmente, se questo è il tuo unico meccanismo, fai affidamento sui processi che si comportano bene e un processo che non produce cederà la CPU fino a quando non termina.

  2. Per evitare questo problema, viene utilizzato un interrupt del timer. Le CPU consentono al sistema operativo di registrare i callback per tutti i diversi tipi di interruzioni implementate dalla CPU. Il sistema operativo utilizza questo meccanismo per registrare un callback per un interrupt timer che viene attivato periodicamente, il che gli consente di eseguire il proprio codice.

  3. Ogni volta che un processo tenta di leggere da un file o interagire con l'hardware in qualsiasi altro modo, chiede al sistema operativo di fare un lavoro per esso. Quando viene richiesto al sistema operativo di eseguire qualcosa da un processo, può decidere di mettere in attesa quel processo e iniziare a eseguirne uno diverso. Questo potrebbe sembrare un po 'machiavellico, ma è la cosa giusta da fare: l'I / O del disco è lento, quindi puoi anche far funzionare il processo B mentre il processo A è in attesa che i grumi rotanti di metallo si spostino nel posto giusto. L'I / O di rete è ancora più lento. L'I / O della tastiera è glaciale perché le persone non sono esseri gigahertz.


5
Puoi sviluppare di più sul tuo ultimo punto 2.? Sono curioso di questa domanda e sento che la spiegazione è saltata qui. Mi sembra che la domanda sia "come il sistema operativo riprende la CPU dal processo" e la tua risposta dice "Il sistema operativo lo gestisce". ma come? Prendi il ciclo infinito nel tuo primo esempio: in che modo non congela il computer?
BiAiB

3
Alcuni sistemi operativi lo fanno, la maggior parte dei sistemi operativi almeno scherza con il codice per fare "collegamento", quindi il programma può essere caricato a qualsiasi indirizzo
Ian Ringrose,

1
@BiAiB La parola chiave qui è "interrompere". La CPU non è solo qualcosa che elabora un determinato flusso di istruzioni, ma può anche essere interrotta in modo asincrono da una fonte separata - soprattutto per noi, I / O e interruzioni di clock. Poiché solo il codice dello spazio kernel può gestire gli interrupt, Windows può essere sicuro di essere in grado di "rubare" il lavoro da qualsiasi processo in esecuzione ogni volta che lo desidera. I gestori di interrupt possono eseguire qualsiasi codice vogliano, incluso "memorizzare i registri della CPU da qualche parte e ripristinarli da qui (un altro thread)". Estremamente semplificato, ma questo è il cambio di contesto.
Luaan,

1
Aggiungendo a questa risposta; lo stile del multitasking di cui ai punti 2 e 3 è chiamato "multitasking preventivo", il nome si riferisce alla capacità del sistema operativo di anticipare un processo in esecuzione. Il multitasking cooperativo veniva usato frequentemente su sistemi operativi meno recenti; su Windows almeno il multitasking preventivo non è stato introdotto fino a Windows 95. Ho letto di almeno un sistema di controllo industriale in uso oggi che utilizza ancora Windows 3.1 esclusivamente per il suo comportamento multitasking cooperativo in tempo reale.
Jason C,

3
@BiAiB In realtà, ti sbagli. Le CPU desktop non eseguono il codice in sequenza e in modo sincrono da circa i486. Tuttavia, anche le CPU più vecchie avevano ancora input asincroni - interrupt. Immagina una richiesta di interruzione hardware (IRQ) proprio come un pin sulla CPU stessa - quando arriva 1, la CPU interrompe qualsiasi cosa stia facendo e inizia l'elaborazione dell'interruzione (che sostanzialmente significa "preserva lo stato e passa a un indirizzo in memoria"). La gestione degli interrupt in sé non è x86o qualunque codice, è letteralmente cablata. Dopo aver saltato, esegue nuovamente (qualsiasi) x86codice. Le discussioni sono un'astrazione molto più alta.
Luaan,

12

Mentre la risposta di David Richerby è buona, fa una specie di smalto su come i moderni sistemi operativi fermano i programmi esistenti. La mia risposta dovrebbe essere accurata per l'architettura x86 o x86_64, che è l'unica comunemente utilizzata per desktop e laptop. Altre architetture dovrebbero avere metodi simili per raggiungere questo obiettivo.

All'avvio del sistema operativo, imposta una tabella di interrupt. Ogni voce della tabella punta a un po 'di codice all'interno del sistema operativo. Quando si verificano interruzioni, che è controllato dalla CPU, esamina questa tabella e chiama il codice. Esistono vari interrupt, come la divisione per zero, il codice non valido e alcuni definiti dal sistema operativo.

Questo è il modo in cui il processo utente parla al kernel, come se volesse leggere / scrivere sul disco o qualcos'altro che il kernel del sistema operativo controlla. Un sistema operativo imposterà anche un timer che chiama un interruzione al termine, quindi il codice in esecuzione viene forzatamente modificato dal programma utente al kernel del sistema operativo e il kernel può fare altre cose come mettere in coda altri programmi per l'esecuzione.

Dalla memoria, quando ciò accade, il kernel del sistema operativo deve salvare dove si trovava il codice e quando il kernel ha finito di fare ciò che deve fare ripristina lo stato precedente del programma. Quindi il programma non sa nemmeno che è stato interrotto.

Il processo non può modificare la tabella degli interrupt per due motivi, il primo è che è in esecuzione in un ambiente protetto, quindi se tenta di chiamare un determinato codice assembly protetto, la CPU attiverà un altro interrupt. Il secondo motivo è la memoria virtuale. La posizione della tabella degli interrupt è compresa tra 0x0 e 0x3FF nella memoria reale, ma con i processi utente tale posizione di solito non è mappata e il tentativo di leggere la memoria non mappata attiverà un altro interrupt, quindi senza la funzione protetta e la possibilità di scrivere nella RAM reale , il processo utente non può modificarlo.


4
Gli interrupt non sono definiti dal sistema operativo, sono dati dall'hardware. E la maggior parte delle architetture attuali hanno istruzioni speciali per chiamare il sistema operativo. i386 ha usato un interrupt (generato dal software) per questo, ma non è più così per i suoi successori.
vonbrand,

2
So che gli interrupt sono definiti dalla CPU, ma il kernel imposta i puntatori. Forse l'ho spiegato male. Ho anche pensato che Linux usasse ancora int 9 per parlare con il kernel, ma forse ora ci sono modi migliori.
Programmdude,

Questa è una risposta abbastanza fuorviante sebbene l'idea che gli scheduler preventivi siano guidati da interruzioni del timer sia corretta. Innanzitutto vale la pena notare che il timer è in hardware. Anche per chiarire che il processo "salva ... ripristina" è chiamato interruttore di contesto e coinvolge principalmente il salvataggio di tutti i registri della CPU (che include il puntatore alle istruzioni), tra le altre cose. Inoltre, i processi possono effettivamente modificare le tabelle di interrupt, questo si chiama "modalità protetta", che definisce anche la memoria virtuale, ed è in circolazione dal 286 - un puntatore alla tabella di interrupt è memorizzato in un registro scrivibile.
Jason C,

(Anche la tabella degli interrupt in modalità reale è stata trasferibile - non bloccata sulla prima pagina di memoria - dall'8086.)
Jason C

1
Questa risposta manca di un dettaglio critico. Quando viene generato un interrupt, la CPU non passa direttamente al kernel. Invece, prima salva i registri esistenti, quindi passa a un altro stack e solo allora viene chiamato il kernel. Chiamare il kernel con uno stack casuale da un programma casuale sarebbe una pessima idea. Inoltre, l'ultima parte è fuorviante. Non si otterrà un interrupt "tentando" di leggere la memoria non mappata; è semplicemente impossibile. Leggi da indirizzi virtuali e la memoria non mappata semplicemente non ha un indirizzo virtuale.
MSalters,

5

Il kernel del sistema operativo ottiene il controllo dal processo in esecuzione a causa del gestore di interruzione del clock della CPU, non iniettando codice nel processo.

Dovresti leggere gli interrupt per ottenere maggiori chiarimenti su come funzionano e su come i kernel del sistema operativo li gestiscono e implementano diverse funzionalità.


Non solo l'interruzione dell'orologio: qualsiasi interruzione. E anche le istruzioni di cambio modalità.
Gilles 'SO- smetti di essere malvagio' il

3

V'è un metodo simile a ciò che si descrive: multitasking cooperativo . Il sistema operativo non inserisce istruzioni, ma ogni programma deve essere scritto per chiamare le funzioni del sistema operativo che possono scegliere di eseguire un altro dei processi cooperativi. Questo ha gli svantaggi che descrivi: un arresto anomalo del programma elimina l'intero sistema. Windows fino a 3.0 incluso funzionava così; 3.0 in "modalità protetta" e versioni successive no.

Il multitasking preventivo (il tipo normale in questi giorni) si basa su una fonte esterna di interruzioni. Gli interrupt sovrascrivono il normale flusso di controllo e di solito salvano i registri da qualche parte, quindi la CPU può fare qualcos'altro e quindi riprendere in modo trasparente il programma. Ovviamente, il sistema operativo può modificare il registro "quando lasci gli interrupt riprendi qui", quindi riprende all'interno di un processo diverso.

(Alcuni sistemi fanno istruzioni di riscrittura in modo limitato sul caricamento del programma, chiamato "thunking", e il processore Transmeta dinamicamente ricompilati per il proprio set di istruzioni)


Anche AFAICR 3.1 era cooperativo. Win95 è stato il punto in cui è entrato in gioco il multitasking preventivo. La modalità protetta ha portato principalmente all'isolamento dello spazio degli indirizzi (che migliora la stabilità, ma per ragioni in gran parte non correlate).
cHao,

Il thunking non riscrive o inietta codice nell'applicazione. Il caricatore che viene modificato è basato sul sistema operativo e non è un prodotto dell'applicazione. I linguaggi interpretativi che vengono compilati come l'uso dei compilatori JIT non modificano il codice, né iniettano nulla nel codice. Traducono il codice sorgente in un eseguibile. Ancora una volta, ciò non equivale all'iniezione di codice in un'applicazione.
Dave Gordon,

Transmeta ha preso il codice eseguibile x86 come sorgente, non un linguaggio interpretativo. E ho pensato a un caso in cui viene iniettato il codice : in esecuzione con un debugger. I sistemi X86 di solito sovrascrivono l'istruzione al punto di interruzione con "INT 03", che si inserisce nel debugger. Al ripristino, il codice operativo originale viene ripristinato.
pjc50,

Il debug è difficilmente come chiunque esegue un'applicazione; oltre quello dello sviluppatore dell'applicazione. Quindi non penso che aiuti davvero l'OP.
Dave Gordon,

3

Il multitasking non richiede nulla di simile all'iniezione di codice. In un sistema operativo come Windows, esiste un componente del codice del sistema operativo chiamato scheduler che si basa su un interrupt di processo attivato da un timer hardware. Questo viene utilizzato dal sistema operativo per passare da un programma all'altro e a se stesso, facendo sembrare che la nostra percezione umana avvenga contemporaneamente.

Fondamentalmente, il sistema operativo programma il timer hardware per spegnersi ogni tanto ... forse 100 volte al secondo. Quando il timer si spegne, genera un interrupt di processo - un segnale che dice alla CPU di interrompere ciò che sta facendo, salvare il suo stato nello stack, cambiare la sua modalità in qualcosa di più privilegiato ed eseguire il codice che troverà in un apposito design posto in memoria. Quel codice sembra essere parte dello scheduler, che decide cosa fare dopo. Potrebbe essere necessario riprendere qualche altro processo, nel qual caso dovrà eseguire ciò che è noto come "interruttore di contesto" - sostituendo l'intero stato attuale (comprese le tabelle di memoria virtuale) con quello dell'altro processo. Nel ritornare a un processo, deve ripristinare tutto il contesto di quel processo,

Il posto "appositamente designato" in memoria non deve essere conosciuto da nient'altro che dal sistema operativo. Le implementazioni variano, ma l'essenziale è che la CPU risponderà a vari interrupt eseguendo una ricerca nella tabella; la posizione della tabella si trova in un punto specifico della memoria (determinato dalla progettazione hardware della CPU), il contenuto della tabella viene impostato dal sistema operativo (generalmente all'avvio) e il "tipo" di interruzione determinerà quale voce nella tabella deve essere utilizzato come "routine di servizio di interruzione".

Niente di tutto ciò comporta "iniezione di codice" ... si basa sul codice contenuto nel sistema operativo in collaborazione con le funzionalità hardware della CPU e i suoi circuiti di supporto.


2

Penso che l'esempio del mondo reale più vicino a ciò che descrivi sia una delle tecniche utilizzate da VMware , la virtualizzazione completa mediante la traduzione binaria .

VMware agisce come un livello sotto uno o più sistemi operativi contemporaneamente eseguendo sullo stesso hardware.

La maggior parte delle istruzioni eseguite (ad es. In applicazioni ordinarie) può essere virtualizzata utilizzando l'hardware, ma un kernel del sistema operativo stesso utilizza istruzioni che non possono essere virtualizzate, poiché se il codice macchina del sistema operativo di ipotesi fosse eseguito non modificato si "spezzerebbe "del controllo dell'host VMware. Ad esempio, un SO guest dovrebbe essere eseguito nel ring di protezione più privilegiato e impostare la tabella di interrupt. Se fosse stato autorizzato a farlo, VMware avrebbe perso il controllo dell'hardware.

VMware riscrive tali istruzioni nel codice del sistema operativo prima di eseguirlo, sostituendole con salti nel codice VMware che simula l'effetto desiderato.

Quindi questa tecnica è in qualche modo analoga a ciò che descrivi.


2

Esistono vari casi in cui un sistema operativo potrebbe "iniettare codice" in un programma. Le versioni basate su 68000 del sistema Apple Macintosh creano una tabella di tutti i punti di ingresso del segmento (situati immediatamente prima delle variabili globali statiche, IIRC). All'avvio di un programma, ogni voce nella tabella è composta da un'istruzione trap seguita dal numero del segmento e spostata nel segmento. Se la trap viene eseguita, il sistema esaminerà le parole dopo l'istruzione trap per vedere quale segmento e offset sono richiesti, caricherà il segmento (se non lo è già), aggiungerà l'indirizzo iniziale del segmento all'offset e quindi sostituire la trap con un salto a quell'indirizzo appena calcolato.

Sui vecchi software per PC, sebbene ciò non sia stato tecnicamente eseguito dal "sistema operativo", era comune costruire codice con istruzioni trap anziché istruzioni matematiche del coprocessore. Se non fosse installato alcun coprocessore matematico, il gestore trap lo avrebbe emulato. Se è stato installato un coprocessore, la prima volta che viene presa una trap il gestore sostituirà l'istruzione trap con un'istruzione coprocessore; future esecuzioni dello stesso codice utilizzeranno direttamente l'istruzione coprocessore.


Il metodo FP è ancora in uso sui processori ARM, che a differenza delle CPU x86 hanno ancora varianti senza FP. Ma è raro poiché la maggior parte dell'uso di ARM è in dispositivi dedicati. In quegli ambienti è in genere noto se la CPU avrà funzionalità FP.
MSalters,

In nessuno di questi casi il sistema operativo ha inserito codice nell'applicazione. Per consentire al sistema operativo di immettere codice, è necessaria una licenza da parte del fornitore del software per "modificare" l'applicazione che non ottiene. Il sistema operativo NON inserisce il codice.
Dave Gordon,

@DaveGordon Si può ragionevolmente dire che le istruzioni bloccate sono il sistema operativo che inietta il codice nell'applicazione.
Gilles 'SO- smetti di essere malvagio' l'

@MSalters Le istruzioni bloccate si verificano comunemente nelle macchine virtuali.
Gilles 'SO- smetti di essere malvagio' l'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.