Qual è la exec()
funzione e la sua famiglia? Perché viene utilizzata questa funzione e come funziona?
Per favore, qualcuno spieghi queste funzioni.
Qual è la exec()
funzione e la sua famiglia? Perché viene utilizzata questa funzione e come funziona?
Per favore, qualcuno spieghi queste funzioni.
Risposte:
Semplicisticamente, in UNIX, hai il concetto di processi e programmi. Un processo è un ambiente in cui viene eseguito un programma.
La semplice idea alla base del "modello di esecuzione" UNIX è che ci sono due operazioni che puoi fare.
Il primo è a fork()
, che crea un processo nuovo di zecca contenente un duplicato (principalmente) del programma corrente, incluso il suo stato. Ci sono alcune differenze tra i due processi che consentono loro di capire quale è il genitore e quale è il bambino.
Il secondo è quello di exec()
, che sostituisce il programma nel processo corrente con un programma nuovo di zecca.
Da queste due semplici operazioni, è possibile costruire l'intero modello di esecuzione UNIX.
Per aggiungere qualche dettaglio in più a quanto sopra:
L'uso di fork()
ed exec()
esemplifica lo spirito di UNIX in quanto fornisce un modo molto semplice per avviare nuovi processi.
La fork()
chiamata è quasi un duplicato del processo corrente, identico in quasi tutti i modi (non tutto viene copiato, ad esempio, i limiti delle risorse in alcune implementazioni, ma l'idea è di creare una copia più vicina possibile). Solo un processo chiama fork()
ma due processi ritornano da quella chiamata: sembra strano ma è davvero piuttosto elegante
Il nuovo processo (chiamato figlio) ottiene un diverso ID processo (PID) e ha il PID del vecchio processo (genitore) come suo padre PID (PPID).
Poiché i due processi ora eseguono esattamente lo stesso codice, devono essere in grado di dire quale è quale - il codice di ritorno di fork()
fornisce queste informazioni - il bambino ottiene 0, il genitore ottiene il PID del bambino (se fork()
fallisce, no figlio viene creato e il genitore riceve un codice di errore).
In questo modo, il genitore conosce il PID del bambino e può comunicare con esso, ucciderlo, aspettarlo e così via (il bambino può sempre trovare il suo processo genitore con una chiamata a getppid()
).
La exec()
chiamata sostituisce l'intero contenuto corrente del processo con un nuovo programma. Carica il programma nello spazio di elaborazione corrente e lo esegue dal punto di ingresso.
Quindi, fork()
e exec()
sono spesso usati in sequenza per ottenere un nuovo programma in esecuzione come figlio di un processo corrente. Le shell in genere lo fanno ogni volta che si tenta di eseguire un programma come find
- la shell fork, quindi il bambino carica il find
programma in memoria, impostando tutti gli argomenti della riga di comando, l'I / O standard e così via.
Ma non devono essere usati insieme. È perfettamente accettabile che un programma chiami fork()
senza un seguito exec()
se, ad esempio, il programma contiene sia codice padre che codice figlio (è necessario fare attenzione a ciò che si fa, ogni implementazione potrebbe avere limitazioni).
Questo è stato usato abbastanza (e lo è ancora) per i demoni che semplicemente ascoltano su una porta TCP e fanno il fork di una copia di se stessi per elaborare una richiesta specifica mentre il genitore torna in ascolto. Per questa situazione, il programma contiene sia il codice padre che il codice figlio.
Allo stesso modo, i programmi che sanno di essere finiti e vogliono solo eseguire un altro programma non ne hanno bisogno fork()
, exec()
e quindi wait()/waitpid()
per il bambino. Possono semplicemente caricare il bambino direttamente nel loro spazio di elaborazione corrente con exec()
.
Alcune implementazioni UNIX hanno un ottimizzato fork()
che utilizza ciò che chiamano copy-on-write. Questo è un trucco per ritardare la copia dello spazio di elaborazione fork()
fino a quando il programma non tenta di modificare qualcosa in quello spazio. Ciò è utile per quei programmi che utilizzano solo fork()
e non exec()
in quanto non devono copiare un intero spazio di elaborazione. Sotto Linux, fork()
fa solo una copia delle tabelle delle pagine e una nuova struttura dei compiti, exec()
farà il grosso lavoro di "separare" la memoria dei due processi.
Se exec
viene chiamato following fork
(e questo è ciò che accade principalmente), ciò causa una scrittura nello spazio del processo e viene quindi copiato per il processo figlio, prima che siano consentite le modifiche.
Linux ha anche un vfork()
, ancora più ottimizzato, che condivide praticamente tutto tra i due processi. Per questo motivo, ci sono alcune restrizioni in ciò che il bambino può fare e il genitore si ferma finché il bambino non chiama exec()
o _exit()
.
Il genitore deve essere fermato (e al figlio non è permesso tornare dalla funzione corrente) poiché i due processi condividono anche lo stesso stack. Questo è leggermente più efficiente per il classico caso d'uso di fork()
seguito immediatamente da exec()
.
Si noti che non v'è una intera famiglia di exec
chiamate ( execl
, execle
, execve
e così via), ma exec
in un contesto qui significa nessuna di esse.
Il diagramma seguente illustra l' fork/exec
operazione tipica in cui la bash
shell viene utilizzata per elencare una directory con il ls
comando:
+--------+
| pid=7 |
| ppid=4 |
| bash |
+--------+
|
| calls fork
V
+--------+ +--------+
| pid=7 | forks | pid=22 |
| ppid=4 | ----------> | ppid=7 |
| bash | | bash |
+--------+ +--------+
| |
| waits for pid 22 | calls exec to run ls
| V
| +--------+
| | pid=22 |
| | ppid=7 |
| | ls |
V +--------+
+--------+ |
| pid=7 | | exits
| ppid=4 | <---------------+
| bash |
+--------+
|
| continues
V
exec
l'utilità viene utilizzata per reindirizzare l'IO del processo corrente? In che modo il caso "null", che esegue exec senza argomenti, è stato utilizzato per questa convenzione?
exec
il mezzo per sostituire il programma corrente (la shell) in questo processo con un altro, non specificare quell'altro programma con cui sostituirlo può semplicemente significare che non vuoi sostituirlo.
exec
essere chiamato senza un programma. Ma è un po 'strano in questo scenario poiché l'utilità originale del reindirizzamento per un nuovo programma - un programma che verrebbe effettivamente utilizzato exec
- scompare e si dispone di un utile artefatto, reindirizzando il programma corrente - che non viene utilizzato exec
o avviato in alcun modo - invece.
Le funzioni nella famiglia exec () hanno comportamenti diversi:
Puoi mescolarli, quindi hai:
Per tutti loro l'argomento iniziale è il nome di un file che deve essere eseguito.
Per maggiori informazioni leggere la pagina man di exec (3) :
man 3 exec # if you are running a UNIX system
execve()
dalla tua lista, che è definita da POSIX, e hai aggiunto execvpe()
, che non è definita da POSIX (principalmente per ragioni di precedenti storici; completa l'insieme di funzioni). Altrimenti, un'utile spiegazione della convenzione di denominazione per la famiglia - un'utile aggiunta a paxdiablo ', una risposta che spiega di più sul funzionamento delle funzioni.
execvpe()
(et al) non elenca execve()
; ha la sua pagina man separata (almeno su Ubuntu 16.04 LTS) - con la differenza che le altre exec()
funzioni della famiglia sono elencate nella sezione 3 (funzioni) mentre execve()
sono elencate nella sezione 2 (chiamate di sistema). In sostanza, tutte le altre funzioni della famiglia sono implementate in termini di chiamata a execve()
.
La exec
famiglia di funzioni fa in modo che il processo esegua un programma diverso, sostituendo il vecchio programma che era in esecuzione. Cioè, se chiami
execl("/bin/ls", "ls", NULL);
quindi il ls
programma viene eseguito con l'id del processo, la directory di lavoro corrente e l'utente / gruppo (diritti di accesso) del processo che ha chiamato execl
. Successivamente, il programma originale non è più in esecuzione.
Per avviare un nuovo processo, fork
viene utilizzata la chiamata di sistema. Per eseguire un programma senza sostituire l'originale, è necessario fork
, quindi exec
.
qual è la funzione exec e la sua famiglia.
La exec
famiglia funzione è tutte le funzioni utilizzate per eseguire un file, ad esempio execl
, execlp
, execle
, execv
, e execvp
.They sono tutti i frontend per execve
e fornire diversi metodi di chiamarla.
perché viene utilizzata questa funzione
Le funzioni Exec vengono utilizzate quando si desidera eseguire (avviare) un file (programma).
E come funziona.
Funzionano sovrascrivendo l'immagine del processo corrente con quella avviata. Sostituiscono (terminando) il processo attualmente in esecuzione (quello che ha chiamato il comando exec) con il nuovo processo che è stato avviato.
Per maggiori dettagli: vedere questo collegamento .
exec
è spesso usato insieme a fork
, di cui ho visto che hai chiesto anche tu, quindi ne discuterò tenendo presente questo.
exec
trasforma il processo corrente in un altro programma. Se hai mai guardato Doctor Who, allora è come quando si rigenera: il suo vecchio corpo viene sostituito con un nuovo corpo.
Il modo in cui ciò accade con il tuo programma ed exec
è che molte delle risorse che il kernel del sistema operativo controlla per vedere se il file che stai passando exec
come argomento del programma (primo argomento) è eseguibile dall'utente corrente (id utente del processo effettuando la exec
chiamata) e in tal caso sostituisce la mappatura della memoria virtuale del processo corrente con una memoria virtuale del nuovo processo e copia i dati argv
e envp
che sono stati passati nella exec
chiamata in un'area di questa nuova mappa della memoria virtuale. Molte altre cose possono anche accadere qui, ma i file che erano aperti per il programma che ha chiamato exec
saranno ancora aperti per il nuovo programma e condivideranno lo stesso ID di processo, ma il programma che ha chiamato exec
cesserà (a meno che exec non abbia fallito).
Il motivo per cui questo viene fatto in questo modo è che separando l' esecuzione di un nuovo programma in due passaggi come questo è possibile eseguire alcune operazioni tra i due passaggi. La cosa più comune da fare è assicurarsi che il nuovo programma abbia determinati file aperti come determinati descrittori di file. (ricorda qui che i descrittori di file non sono gli stessi di FILE *
, ma sono int
valori che il kernel conosce). In questo modo puoi:
int X = open("./output_file.txt", O_WRONLY);
pid_t fk = fork();
if (!fk) { /* in child */
dup2(X, 1); /* fd 1 is standard output,
so this makes standard out refer to the same file as X */
close(X);
/* I'm using execl here rather than exec because
it's easier to type the arguments. */
execl("/bin/echo", "/bin/echo", "hello world");
_exit(127); /* should not get here */
} else if (fk == -1) {
/* An error happened and you should do something about it. */
perror("fork"); /* print an error message */
}
close(X); /* The parent doesn't need this anymore */
Ciò consente di eseguire:
/bin/echo "hello world" > ./output_file.txt
dalla shell dei comandi.
Quando un processo usa fork (), crea una copia duplicata di se stesso e questo duplicato diventa il figlio del processo. Il fork () è implementato usando la chiamata di sistema clone () in linux che ritorna due volte dal kernel.
Capiamolo con un esempio:
pid = fork();
// Both child and parent will now start execution from here.
if(pid < 0) {
//child was not created successfully
return 1;
}
else if(pid == 0) {
// This is the child process
// Child process code goes here
}
else {
// Parent process code goes here
}
printf("This is code common to parent and child");
Nell'esempio, abbiamo assunto che exec () non viene utilizzato all'interno del processo figlio.
Ma un genitore e un figlio differiscono in alcuni degli attributi PCB (blocco di controllo del processo). Questi sono:
Ma per quanto riguarda la memoria del bambino? È stato creato un nuovo spazio indirizzi per un bambino?
Le risposte in no. Dopo il fork (), sia il genitore che il figlio condividono lo spazio degli indirizzi di memoria del genitore. In Linux, questi spazi degli indirizzi sono suddivisi in più pagine. Solo quando il bambino scrive su una delle pagine di memoria padre, viene creato un duplicato di quella pagina per il bambino. Questo è anche noto come copia in scrittura (copia le pagine genitore solo quando il bambino scrive su di esso).
Comprendiamo la copia su scrittura con un esempio.
int x = 2;
pid = fork();
if(pid == 0) {
x = 10;
// child is changing the value of x or writing to a page
// One of the parent stack page will contain this local variable. That page will be duplicated for child and it will store the value 10 in x in duplicated page.
}
else {
x = 4;
}
Ma perché è necessaria la copia su scrittura?
Una tipica creazione di un processo avviene tramite la combinazione fork () - exec (). Per prima cosa capiamo cosa fa exec ().
Il gruppo di funzioni Exec () sostituisce lo spazio degli indirizzi del bambino con un nuovo programma. Una volta che exec () viene chiamato all'interno di un figlio, verrà creato uno spazio di indirizzi separato per il figlio che è completamente diverso da quello del genitore.
Se non ci fosse un meccanismo di copia su scrittura associato a fork (), le pagine duplicate sarebbero state create per il bambino e tutti i dati sarebbero stati copiati nelle pagine del bambino. Allocare nuova memoria e copiare i dati è un processo molto costoso (richiede tempo al processore e altre risorse di sistema). Sappiamo anche che nella maggior parte dei casi, il bambino chiamerà exec () e questo sostituirà la memoria del bambino con un nuovo programma. Quindi la prima copia che abbiamo fatto sarebbe stata uno spreco se la copia su scrittura non fosse presente.
pid = fork();
if(pid == 0) {
execlp("/bin/ls","ls",NULL);
printf("will this line be printed"); // Think about it
// A new memory space will be created for the child and that memory will contain the "/bin/ls" program(text section), it's stack, data section and heap section
else {
wait(NULL);
// parent is waiting for the child. Once child terminates, parent will get its exit status and can then continue
}
return 1; // Both child and parent will exit with status code 1.
Perché il genitore attende un processo figlio?
Perché è necessaria la chiamata di sistema exec ()?
Non è necessario usare exec () con fork (). Se il codice che il bambino eseguirà è all'interno del programma associato a parent, exec () non è necessario.
Ma pensa ai casi in cui il bambino deve eseguire più programmi. Prendiamo l'esempio del programma shell. Supporta più comandi come find, mv, cp, date ecc. Sarà giusto includere il codice del programma associato a questi comandi in un programma o fare in modo che il bambino carichi questi programmi nella memoria quando richiesto?
Tutto dipende dal tuo caso d'uso. Hai un server web che ha dato un input x che restituisce 2 ^ x ai client. Per ogni richiesta, il server web crea un nuovo figlio e gli chiede di eseguire il calcolo. Scriverai un programma separato per calcolarlo e userai exec ()? O scriverai semplicemente il codice di calcolo all'interno del programma genitore?
Di solito, la creazione di un processo implica una combinazione di chiamate fork (), exec (), wait () ed exit ().
Le exec(3,3p)
funzioni sostituiscono il processo corrente con un altro. Cioè, il processo corrente si interrompe e un altro viene eseguito al suo posto, assumendo alcune delle risorse del programma originale.