2010-04-01 5 views
12

So che fork() restituisce in modo diverso per i processi figlio e padre, ma non riesco a trovare informazioni su come ciò accade. In che modo il processo figlio riceve il valore di ritorno 0 dalla forcella? E qual è la differenza rispetto allo stack delle chiamate? Come capisco, per il genitore va qualcosa del genere:Come restituisce fork() per il processo figlio

processo genitore - richiama fork -> system_call - richiama fork -> fork executes - ritorna a -> system_call - ritorna a- -> processo genitore.

Cosa succede nel processo figlio?

+7

è più comunemente chiamato "padre" e il processo di "figlio". Le persone potrebbero accusarti di sessismo. – Thomas

risposta

21

% man forcella

VALORI DI RITORNO

Upon successful completion, fork() returns a value of 0 to the child 
process and returns the process ID of the child process to the parent 
process. Otherwise, a value of -1 is returned to the parent process, no 
child process is created, and the global variable [errno][1] is set to indi- 
cate the error. 

Cosa accade è che all'interno della chiamata di sistema fork, l'intero processo viene ripetuto. Quindi, la chiamata a forcella in ogni ritorno. Questi sono ora contesti diversi, quindi possono restituire diversi codici di ritorno.

Se vuoi veramente sapere come funziona a un livello basso, puoi sempre check the source! Il codice è un po 'confuso se non si è abituati a leggere il codice del kernel, ma i commenti in linea danno una buona idea di cosa sta succedendo.

La parte più interessante della sorgente con una risposta esplicita alla tua domanda è proprio alla fine della definizione fork() in sé -

if (error == 0) { 
    td->td_retval[0] = p2->p_pid; 
    td->td_retval[1] = 0; 
} 

"td" apparentemente tiene un elenco dei valori di ritorno per fili diversi Non sono sicuro di come funzioni questo meccanismo (perché non ci sono due strutture "thread" separate). Se l'errore (restituito da fork1, la funzione di "foratura" reale) è 0 (nessun errore), quindi prendere il thread "primo" (padre) e impostarne il valore di ritorno sul PID di p2 (il nuovo processo). Se è il "secondo" filo (in p2), quindi impostare il valore di ritorno a 0.

+0

Questo è sys/kern/kern_fork.c - sorgente Linux? – osgx

+0

@osgx: No, la fonte citata proviene da FreeBSD. – jxh

0

Il processo appare identico da entrambi i lati, ad eccezione del diverso valore di ritorno (ecco perché il valore di ritorno è lì, in modo che i due processi possano distinguere la differenza!). Per quanto riguarda il processo figlio, sarà appena stato restituito da system_call nello stesso modo in cui è stato restituito il processo genitore.

+0

Sì, ma come funzionano i diversi valori restituiti? Come può una funzione restituire due valori diversi? – EpsilonVector

+0

Poiché il codice del kernel core che supporta 'fork' si prende cura di esso - ci sono due ritorni separati in corso, e quindi ognuno può avere un valore diverso. – Amber

7

I fork() chiamata restituisce sistema due volte (a meno che non fallisce).

  • Uno dei rendimenti è nel processo figlio, e c'è il valore di ritorno è 0.

  • L'altro ritorno è nel processo padre, e lì il valore di ritorno è diverso da zero (sia negativo se la forcella ha avuto esito negativo o un valore diverso da zero che indica il PID del figlio).

Le principali differenze tra il genitore e il bambino sono:

  • Sono processi separati
  • Il valore del PID è diverso
  • Il valore PPID (genitore PID) è diverso

Altre differenze più oscure sono elencate nello standard POSIX.

In un certo senso, il Come non è il tuo problema. Il sistema operativo è necessario per ottenere il risultato. Tuttavia, l'o/s clona il processo genitore, creando un secondo processo figlio che è una replica quasi esatta del genitore, impostando gli attributi che devono essere diversi dai nuovi valori corretti e di solito contrassegnando le pagine di dati come CoW (copia su scrivere) o equivalente in modo che quando un processo modifica un valore, ottiene una copia separata della pagina in modo da non interferire con l'altro. Non è come la chiamata di sistema deprecata (da me almeno - non standard per POSIX) vfork() che sarebbe opportuno evitare, anche se è disponibile sul proprio sistema. Ogni processo continua dopo lo fork() come se la funzione restituisse - così (come ho detto in alto), la chiamata di sistema fork() restituisce due volte, una volta in ciascuno dei due processi che sono quasi identici cloni l'uno dell'altro.

+4

Vorrei che il fan boy di vfork() spiegasse perché hanno votato in ribasso. –

+0

Bella spiegazione e bel collegamento all'ultimo standard POSIX (numero 7)! –

+1

+1 per compensare il fanboy di vfork. –

3

risposta di Steven Schlansker è abbastanza buona, ma solo per aggiungere un po 'più in dettaglio:

Ogni processo in esecuzione ha una rapida associato (da cui "cambio di contesto") - questo contesto comprende, tra le altre cose, segmento di codice del processo (contenente le istruzioni della macchina), la sua memoria heap, il suo stack e il suo contenuto del registro. Quando si verifica un interruttore di contesto, il contesto del vecchio processo viene salvato e viene caricato il contesto dal nuovo processo.

L'ubicazione per un valore di ritorno è definita dall'ABI, per consentire l'interoperabilità del codice. Se sto scrivendo il codice ASM per il mio processore x86-64 e chiamo nel runtime C, so che il valore restituito verrà mostrato nel registro RAX.

Mettendo queste due cose insieme, la logica conclusione è che la chiamata a int pid = fork() risultati in due contesti in cui l'istruzione successiva da eseguire in ognuno è uno che muove il valore RAX (il valore restituito dalla chiamata fork) in la variabile locale pid. Ovviamente, solo un processo può essere eseguito alla volta su una singola CPU, quindi l'ordine in cui questi "ritorni" accadono sarà determinato dallo schedulatore.

1

Proverò a rispondere dal punto di vista del layout della memoria del processo. Ragazzi, per favore correggetemi se qualcosa di sbagliato o impreciso.

fork() è l'unica chiamata di sistema per la creazione di processo (tranne processo fin dall'inizio 0), quindi la domanda è in realtà ciò che accade con processo di creazione in kernel. Esistono due strutture di dati del kernel relative a process, struct proc array (noto anche come process table) e struct user (ovvero area u).

Per creare un nuovo processo, queste due strutture di dati devono essere correttamente create o parametrizzate. Il modo diretto è quello di allinearsi con l'area proc & u del creatore. La maggior parte dei dati sono duplicati tra il genitore & figlio (ad es., il segmento di codice), ad eccezione dei valori nel registro di ritorno (ad es. EAX in 80x86), per il quale genitore è con il figlio pid e figlio è 0. Da allora, hai due processi (uno esistente & nuovo) eseguito dallo scheduler e in base alla pianificazione, ognuno restituirà i propri valori rispettivamente.

7

Sia padre che figlio restituiscono valori diversi a causa della manipolazione dei registri della CPU nel contesto del bambino.

Ogni processo nel kernel linux rappresentato da task_struct. task_struct è racchiuso (puntatore) nella struttura thread_info che si trova alla fine dello stack in modalità kernel. Il contesto CPU (registri) di Whole è memorizzato in questa struttura thread_info.

struct thread_info { 
    struct task_struct *task;  /* main task structure */ 
    struct cpu_context_save cpu_context; /* cpu context */ 
} 

Tutte le chiamate di sistema fork/clone() chiamano la funzione kernel_fork do_fork().

long do_fork(unsigned long clone_flags, 
      unsigned long stack_start, 
      struct pt_regs *regs, 
      unsigned long stack_size, 
      int __user *parent_tidptr, 
      int __user *child_tidptr) 

Qui è la sequenza di esecuzione

do_fork() -> copy_process-> copy_thread() (copy_thread è arco chiamata di funzione specifica)

copy_thread() copia i valori di registro dal genitore e cambia il valore restituito 0 (Nel caso del braccio)

struct pt_regs *childregs = task_pt_regs(p); 
*childregs = *regs; /* Copy register value from parent process*/ 
childregs->ARM_r0 = 0; /*Change the return value*/ 
thread->cpu_context.sp = (unsigned long)childregs;/*Write back the value to thread info*/ 
thread->cpu_context.pc = (unsigned long)ret_from_fork; 

Quando il bambino viene pianificato esegue una routine di assemblaggio ret_from_fork() che restituisce zero. Per il genitore ottiene il valore di ritorno dalla do_fork(), che è pid del processo

nr = task_pid_vnr(p); 
return nr;