View Full Version : [C] Dichiarare variabili 'locali' ad un processo: corretto o no?
Ciao!
Ho un piccolo dubbio che riguarda il modo di dichiarare le variabili quando si fa un programma multiprocesso: e' corretto dichiarare delle variabili locali dopo una fork(), nel ramo del child-process, in modo che queste siano visibili solo all'interno di tale ramo, o le variabili vanno comunque dichiarate tutte all'inizio del programma (e quindi poi ciascun child-process ne fa l'uso che vuole con le sue copie locali)? Il mio scopo sarebbe quello di ridurre un po' la quantita' di memoria occupata, e quindi evitare di dichiarare un buffer di 1 KB nel parent-process (in cui non mi serve) che poi verrebbe duplicato dai suoi 20 figli...
Esempio:
pid=fork();
if (pid!=0) // parent-process
{
...
}
else if (pid==0) // child-process
{
char buffer[1024];
...
exit(0);
}
Si puo?
Grazie,
Gica :)
ilsensine
22-07-2005, 16:23
Ho un piccolo dubbio che riguarda il modo di dichiarare le variabili quando si fa un programma multiprocesso: e' corretto dichiarare delle variabili locali dopo una fork(), nel ramo del child-process, in modo che queste siano visibili solo all'interno di tale ramo, o le variabili vanno comunque dichiarate tutte all'inizio del programma (e quindi poi ciascun child-process ne fa l'uso che vuole con le sue copie locali)?
E' indifferente, Se dichiarate prima della fork, inizialmente contengono tutte gli stessi valori, ma poi su ciascun processo hanno una storia indipendente. Tutte le variabili quindi sono "locali" ai processi, dopo la fork.
Il mio scopo sarebbe quello di ridurre un po' la quantita' di memoria occupata, e quindi evitare di dichiarare un buffer di 1 KB nel parent-process (in cui non mi serve) che poi verrebbe duplicato dai suoi 20 figli...
Normalmente non devi occuparti di questo, ci pensa la COW (copy-on-write).
Nel tuo caso particolare, con un piccolo buffer allocato sullo stack, il tuo metodo potrebbe essere leggermente più efficiente (per "piccolo" intendo inferiore alla dimensione di una pagina, e per "potrebbe" intendo se quel buffer è sufficiente ad ingrandire lo stack di un'altra pagina).
Normalmente non devi occuparti di questo, ci pensa la COW (copy-on-write).
Nel tuo caso particolare, con un piccolo buffer allocato sullo stack, il tuo metodo potrebbe essere leggermente più efficiente (per "piccolo" intendo inferiore alla dimensione di una pagina, e per "potrebbe" intendo se quel buffer è sufficiente ad ingrandire lo stack di un'altra pagina).
Non avevo pensato in termini di dimensioni delle pagine di memoria... me ne scordo sempre :( Cmq la dimensione dipende sempre dalla particolare configurazione del kernel in uso, quindi, a meno di non sapere con certezza su quale sistema girera' il programma, la cosa e' un po' aleatoria :stordita:
Mi devo rivedere come funziona l'allocazione della memoria... :muro:
Grazie mille :ave:
Gica
Fenomeno85
23-07-2005, 10:12
quando te fai la fork il nuovo processo ha solo la pagina del suo stack e una pagina delle istruzioni.
Poi quando il processo dovrà utilizzare delle strutture dato allora sarà solo in quel momento che caricherà le nuove pagine se non ricordo male
~§~ Sempre E Solo Lei ~§~
ehm, vorrei approfittare del 3d per chiedere una cosa che non ho mai capito finora: che fa la fork? :mbe: :fagiano:
quando te fai la fork il nuovo processo ha solo la pagina del suo stack e una pagina delle istruzioni.
Poi quando il processo dovrà utilizzare delle strutture dato allora sarà solo in quel momento che caricherà le nuove pagine se non ricordo male eeeeeeeeeeehhhhhhhhhhhhhhh????????
ma sta fork che fa?!? :mbe:
crea un processo in cui c'è un solo stack e una sola pagina di codice? :D
ammazza, lo stack che occupa più del codice è notevole :asd:
Fenomeno85
23-07-2005, 10:16
ehm, vorrei approfittare del 3d per chiedere una cosa che non ho mai capito finora: che fa la fork? :mbe: :fagiano:
serve a creare un figlio ;)
~§~ Sempre E Solo Lei ~§~
serve a creare un figlio ;) più che altro lo abortisce se è come dici tu... :asd:
Fenomeno85
23-07-2005, 10:22
più che altro lo abortisce se è come dici tu... :asd:
?? è mattina non capisco ste battute :fagiano:
~§~ Sempre E Solo Lei ~§~
Se non ricordo male (ma e' probabile il contrario), almeno con Linux la fork() si occupa di creare un nuovo processo (figlio di quello che invoca la fork); il processo figlio e' una copia del padre, ma ad esempio il segmento testo (il codice) e' condiviso in read-only (quindi non c'e' duplicazione), mentre per stack e dati viene fatta la copia, ma solo in caso di scrittura (copy on write)...
Il mio dubbio riguarda la memoria allocata in fase di compilazione (quale e dove), ma non vi chiedo spiegazioni perche' dovrei saperlo da me... quindi corro a studiare (sono uno scandalo!!!).
Grazie...
Fenomeno85
23-07-2005, 10:40
Se non ricordo male (ma e' probabile il contrario), almeno con Linux la fork() si occupa di creare un nuovo processo (figlio di quello che invoca la fork); il processo figlio e' una copia del padre, ma ad esempio il segmento testo (il codice) e' condiviso in read-only (quindi non c'e' duplicazione), mentre per stack e dati viene fatta la copia, ma solo in caso di scrittura (copy on write)...
Il mio dubbio riguarda la memoria allocata in fase di compilazione (quale e dove), ma non vi chiedo spiegazioni perche' dovrei saperlo da me... quindi corro a studiare (sono uno scandalo!!!).
Grazie...
che cosa intendi per fase di compilazione quando compili il processo non può occupare dato che non lo stai eseguendo
~§~ Sempre E Solo Lei ~§~
che cosa intendi per fase di compilazione quando compili il processo non può occupare dato che non lo stai eseguendo
~§~ Sempre E Solo Lei ~§~
Prendi l'esempio del primo post: il buffer di caratteri viene allocato in fase di compilazione o in fase d'esecuzione?
E' una domanda idiota, ma al momento non mi ricordo come funziona :muro:
Se non ricordo male (ma e' probabile il contrario), almeno con Linux la fork() si occupa di creare un nuovo processo (figlio di quello che invoca la fork); il processo figlio e' una copia del padre, ma ad esempio il segmento testo (il codice) e' condiviso in read-only (quindi non c'e' duplicazione), mentre per stack e dati viene fatta la copia, ma solo in caso di scrittura (copy on write)... scusa, tutto sto macello per dire che la fork crea un processo figlio a partire dallo stesso file eseguibile del processo chiamante? :mbe: ho capito bene?
Il mio dubbio riguarda la memoria allocata in fase di compilazione (quale e dove), ma non vi chiedo spiegazioni perche' dovrei saperlo da me... quindi corro a studiare (sono uno scandalo!!!). avanti su, che dubbio è? :D
tanto sicuramente hai già detto di peggio :D asd
cmq la memoria "allocata in fase di compilazione" (andando ad intuito sul significato dell'espressione) non so quale sia negli ELF, ma in generale dovrebbe ammontare all'insieme di codice e dati inizializzati del programma.
Fenomeno85
23-07-2005, 10:47
Prendi l'esempio del primo post: il buffer di caratteri viene allocato in fase di compilazione o in fase d'esecuzione?
E' una domanda idiota, ma al momento non mi ricordo come funziona :muro:
in fase di esecuzione.
~§~ Sempre E Solo Lei ~§~
Fenomeno85
23-07-2005, 10:48
scusa, tutto sto macello per dire che la fork crea un processo figlio a partire dallo stesso file eseguibile del processo chiamante? :mbe: ho capito bene?
io che te ho detto il padre crea un figlio :mbe:
~§~ Sempre E Solo Lei ~§~
Prendi l'esempio del primo post: il buffer di caratteri viene allocato in fase di compilazione o in fase d'esecuzione? non si tratta di dati inizializzati ne' di codice, ergo...
io che te ho detto il padre crea un figlio :mbe: te mi hai detto che crea un figlio mezzo smandrappato con un solo stack e una sola pagina di codice... :fagiano:
Fenomeno85
23-07-2005, 10:50
te mi hai detto che crea un figlio mezzo smandrappato con un solo stack e una sola pagina di codice... :fagiano:
perchè fa così non duplica tutto all'istante ma solo il necessario.
~§~ Sempre E Solo Lei ~§~
perchè fa così non duplica tutto all'istante ma solo il necessario. ecco, ora non ti seguo più. :fagiano:
in che senso non duplica tutto all'istante? che sarebbe il necessario? :mbe:
quindi la fork crea un "processo smandrappato"? :mbe:
e che succede quando l'esecuzione supera la prima pagina? #GP? :sofico:
Fenomeno85
23-07-2005, 10:53
ecco, ora non ti seguo più. :fagiano:
in che senso non duplica tutto all'istante? che sarebbe il necessario? :mbe:
quindi la fork crea un "processo smandrappato"? :mbe:
e che succede quando l'esecuzione supera la prima pagina? #GP? :sofico:
allora è un utilizzo ponderato delle risorse ;) quando finisce la prima pagina prende e carica quella che gli serve easy :D
~§~ Sempre E Solo Lei ~§~
allora è un utilizzo ponderato delle risorse ;) quando finisce la prima pagina prende e carica quella che gli serve easy :D ma che intendi, carica da dove, dal disco? dal file originale o da quello di swap? non dovrebbero essere già caricate nel processo padre...?
l'arcano mondo di Linux... :mbe:
ma che intendi, carica da dove, dal disco? dal file originale o da quello di swap? non dovrebbero essere già caricate nel processo padre...?
l'arcano mondo di Linux... :mbe:
Quarda che tutti i sistemi operativi di un certo livello usano la COW. Mica solo linux :p
ciao ;)
Fenomeno85
23-07-2005, 10:56
ma che intendi, carica da dove, dal disco? dal file originale o da quello di swap? non dovrebbero essere già caricate nel processo padre...?
l'arcano mondo di Linux... :mbe:
il processo ha un numero massimo di pagine che può tenere in memoria tutte le altre sono in swap o manco caricate.
~§~ Sempre E Solo Lei ~§~
Quarda che tutti i sistemi operativi di un certo livello usano la COW. Mica solo linux :p anche windows se è per questo, ma mo che c'entra? :mbe:
qui si parla della fork e degli strani processi che crea... anormali... :mbe:
il processo ha un numero massimo di pagine che può tenere in memoria tutte le altre sono in swap o manco caricate. embe'? :mbe:
Fenomeno85
23-07-2005, 10:59
anche windows se è per questo, ma mo che c'entra? :mbe:
qui si parla della fork e degli strani processi che crea... anormali... :mbe:
perchè anomali carica solo quando gli serve ... un processo gira tranquillamente con la pagina dello stack e la sua prima pagina del codice.
~§~ Sempre E Solo Lei ~§~
Fenomeno85
23-07-2005, 11:00
embe'? :mbe:
cosa?
~§~ Sempre E Solo Lei ~§~
allora, 1 secondo, ricominciamo da capo: la funzione fork crea un processo figlio del processo chiamante partendo dal suo stesso file eseguibile; per farlo suppongo che (ragionando analogamente a come funzia Windows, visto che almeno a livello concettuale dovrebbero funzionare in maniera simile) il loader si vada a leggere tutto l'albero delle dipendenze dell'eseguibile e carichi uno per uno tutti i moduli richiesti; per "caricare un modulo" si intende mapparlo in memoria virtuale, ovvero creare tutte le pagine descritte nel suo file; infine ovviamente bisogna rilocarlo se necessario. dopodiché si crea il thread primario con il suo contesto, il suo stack ed il suo eventuale local storage, ed infine si chiamano gli entry point delle varie librerie caricate e successivamente quello princpale dell'eseguibile, e ha così inizio l'esecuzione del programma.
e invece no! non è così, perché i processi creati dalla fork sono strani!!! giusto? :)
EDIT: dimenticavo che se un modulo è già caricato in un altro processo e non è necessario rilocarlo, è sufficiente rimappare delle nuove pagine nel nuovo processo puntandole agli stessi indirizzi fisici dell'altro; questo vale per le pagine di codice e dati condivisi, sempre considerando però che quelle di codice sono COW, e quindi se vengono modificate vengono comunque replicate fisicamente.
perchè anomali carica solo quando gli serve ... un processo gira tranquillamente con la pagina dello stack e la sua prima pagina del codice. oddio, adesso anche lo stack ha una sola pagina... O_o'
Fenomeno85
23-07-2005, 11:07
allora, 1 secondo, ricominciamo da capo: la funzione fork crea un processo figlio del processo chiamante partendo dal suo stesso file eseguibile; per farlo suppongo che (ragionando analogamente a come funzia Windows, visto che almeno a livello concettuale dovrebbero funzionare in maniera simile) il loader si vada a leggere tutto l'albero delle dipendenze dell'eseguibile e carichi uno per uno tutti i moduli richiesti; per "caricare un modulo" si intende mapparlo in memoria virtuale, ovvero creare tutte le pagine descritte nel suo file; infine ovviamente bisogna rilocarlo se necessario. dopodiché si crea il thread primario con il suo contesto, il suo stack ed il suo eventuale local storage, ed infine si chiamano gli entry point delle varie librerie caricate e successivamente quello princpale dell'eseguibile, e ha così inizio l'esecuzione del programma.
e invece no! non è così, perché i processi creati dalla fork sono strani!!! giusto? :)
il caricamento del processo padre dovrebbe essere circa lo stesso se non erro il figlio non fa altro che copiarsi le pagine che gli servono oppure le carica se il padre non le aveva caricate ... non trovo le slide osti
~§~ Sempre E Solo Lei ~§~
cosa? cosa "cosa"? intendevo dire "embe' che c'entra"?
Fenomeno85
23-07-2005, 11:08
oddio, adesso anche lo stack ha una sola pagina... O_o'
all'inizio si ovvio ci deve caricare solo il pid ... mi sembra di ricordare che è li che veniva messo. poi ovvio dopo il processo impilerà tutto quello che gli serve
~§~ Sempre E Solo Lei ~§~
il caricamento del processo padre dovrebbe essere circa lo stesso se non erro il figlio non fa altro che copiarsi le pagine che gli servono HA!!! e qui ti sei contraddetto!!! :D
allora, lo vedi che il figlio non carica una sola pagina, ma le carica tutte??? :D :ciapet:
sto parlando però solo del codice, lo stack invece è tutta 1 altra questione...
Fenomeno85
23-07-2005, 11:11
HA!!! e qui ti sei contraddetto!!! :D
allora, lo vedi che il figlio non carica una sola pagina, ma le carica tutte??? :D :ciapet:
sto parlando però solo del codice, lo stack invece è tutta 1 altra questione...
no all'inzio carica solo la prima pagina ... in un programma il processo figlio non è che deve fare tutto quello che fa il padre e il padre non farà quasi mai le pagine del figlio.
~§~ Sempre E Solo Lei ~§~
allora, 1 secondo, ricominciamo da capo: la funzione fork crea un processo figlio del processo chiamante partendo dal suo stesso file eseguibile; per farlo suppongo che (ragionando analogamente a come funzia Windows, visto che almeno a livello concettuale dovrebbero funzionare in maniera simile) il loader si vada a leggere tutto l'albero delle dipendenze dell'eseguibile e carichi uno per uno tutti i moduli richiesti; per [...]
No il loader degli eseguibili non c'etra niente con fork :)
L'unica cosa che fa fork è creare una nuova entry nella tabella dei processi e copiarci dentro tutto quello che c'era in quella del padre. File descriptor dei files aperti compresi. A questo punto la fork ritorna pid 0 al figlio e il pid del figlio al padre e termina.
Il codice del eseguibile è gia stato caricato quando il loader ha caricato il padre ed è condiviso dai due processi. Le pagine dei dati sono condivise fino al momento in cui qualcuno non decide di scrivere in memoria. In quel momento viene presa la pagina in cui si cerca di scrivere e la si copia in un'altra per il processo figlio.
ciao ;)
Fenomeno85
23-07-2005, 11:15
No il loader degli eseguibili non c'etra niente con fork :)
L'unica cosa che fa fork è creare una nuova entry nella tabella dei processi e copiarci dentro tutto quello che c'era in quella del padre. File descriptor dei files aperti compresi. A questo punto la fork ritorna pid 0 al figlio e il pid del figlio al padre e termina.
Il codice del eseguibile è gia stato caricato quando il loader ha caricato il padre ed è condiviso dai due processi. Le pagine dei dati sono condivise fino al momento in cui qualcuno non decide di scrivere in memoria. In quel momento viene presa la pagina in cui si cerca di scrivere e la si copia in un'altra per il processo figlio.
ciao ;)
:mano:
padre fa quello che gli pare il figlio non esiste
pid = fork();
if (pid ==0){
qui il figlio fa tutto quello che deve fare,
il padre potrebbe essere già morto.
}
qui il padre continua a fare quello che deve fare,
il figlio può essere morto o meno.
~§~ Sempre E Solo Lei ~§~
No il loader degli eseguibili non c'etra niente con fork :)
L'unica cosa che fa fork è creare una nuova entry nella tabella dei processi e copiarci dentro tutto quello che c'era in quella del padre.
Sono stato precipitoso. Ovvio che per fare sti giochini con le pagine la fork deve "clonare" anche la page table del padre ed assegnarla al figlio.
ciao ;)
Fenomeno85
23-07-2005, 11:17
Sono stato precipitoso. Ovvio che per fare sti giochini con le pagine la fork deve "clonare" anche la page table del padre ed assegnarla al figlio.
ciao ;)
La chiamate fork() per la creazione di processi
condividono automaticamente tutti i file aperti;
il figlio eredita dal padre i descrittori aperti, che
restano condivisi fra padre e figlio;
i descrittori condivisi hanno ovviamente una sola
posizione corrente.
~§~ Sempre E Solo Lei ~§~
[QUOTE=Fenomeno85]
padre fa quello che gli pare il figlio non esiste
pid = fork();
if (pid ==0){
qui il figlio fa tutto quello che deve fare,
il padre potrebbe essere già morto.
}
Il mondo linux è un mondo crudele. Questo in teoria non dovrebbe mai succedere. Il padre deve essere sempre presenta alla morte del figlio per dargli il permesso di "crepare". Altrimenti poi i figli diventano processi zombie. :D
ciao ;)
no all'inzio carica solo la prima pagina ... in un programma il processo figlio non è che deve fare tutto quello che fa il padre e il padre non farà quasi mai le pagine del figlio. ammesso che si parli sempre della sezione di codice: cominciamo a distinguere tra i modi di "caricare" una pagina; "caricare" per te vuol dire crearla o scriverla fisicamente in RAM?
se un modulo è già caricato in un altro processo e deve essere ri-caricato, la tabella del suo processo viene organizzata così: le pagine di codice vengono create tali e quali (quindi COW), indipendentemente dal fatto che stiano in RAM o sul disco; il contenuto che sta sul disco viene swappato quando necessario, ma questo non c'entra nulla ne' con la creazione di processi ne' con la fork perché ci pensa a parte il file system quando viene ricevuto #PF.
dal momento che le pagine di codice sono COW, non possono essere condivise, ma devono essere replicate, nel caso in cui il modulo debba essere rilocato o nel caso in cui il programma le modifichi.
le pagine di dati shared vengono trattate alla stessa maniera, solo che non sono COW: se vengono scritte la modifica ha effetto in tutti i processi (è esattamente ciò che si vuole ottenere).
le pagine di dati inizializzate devono essere ricreate e reinizializzate.
le pagine di dati non inizializzati vengono create e in genere azzerate.
dimentico qualcosa?
tutto questo, ribadisco, indipendentemente dalle operazioni di swap!!
Fenomeno85
23-07-2005, 11:27
diciamo che un processo in generale utilizza solo pagine residenti in memoria centrale
caricare significa sia crearle che ficcarle in memoria :D
~§~ Sempre E Solo Lei ~§~
Scusate se "vado e vengo", ma 'sta connessione gprs e' un po' instabile...
Comunque mi sto un po' perdendo, quindi queste cose me le devo rivedere con calma senno' comincio (o continuo) a dire cose sulla base dei miei vaghi ricordi (e la mia memoria e' stabile quanto il gprs :) ).
Solo una cosa (prescindendo da come vengono gestiti i processi):if (a==0)
{
char buffer[10];
...
...
}
else
{
char buffer1[1024];
...
...
} lo spazio per buffer e buffer1 viene allocato in fase di esecuzione nello stack, a seconda che si esegua l'uno o l'altro ramo dell'if. Giusto? Pero' lo spazio di memoria necessario a contenere gli indirizzi di buffer e buffer1 dove e quando vengono allocati? Necessariamente in fase di compilazione, no? Dove? Nel data-segment, o no?
Perdonate le eventuali castronerie...
No il loader degli eseguibili non c'etra niente con fork :) e caççHì°, l'avevo intuito visto che sti processi creati dalla fork sono completamente sminnati... :D
L'unica cosa che fa fork è creare una nuova entry nella tabella dei processi e copiarci dentro tutto quello che c'era in quella del padre.
eeehhh??? O_______O
scusa ma questo non coincide affatto con quello che mi era stato detto finora... io avevo capito che la fork crea un nuovo processo partendo dallo stesso file eseguibile del processo chiamante...
File descriptor dei files aperti compresi. :mbe:
come dire che eredita tutti gli handle?
A questo punto la fork ritorna pid 0 al figlio e il pid del figlio al padre e termina. ma quindi il nuovo processo ha anche dei thread uguali? perché se sta fork ritorna sia dal padre che dal figlio (lo spirito santo si sente escluso :D LOL scherzo) vuol dire che il figlio sta eseguendo lo stesso thread nello stesso punto...
Il codice del eseguibile è gia stato caricato quando il loader ha caricato il padre ed è condiviso dai due processi. giustamente.
Le pagine dei dati sono condivise fino al momento in cui qualcuno non decide di scrivere in memoria. In quel momento viene presa la pagina in cui si cerca di scrivere e la si copia in un'altra per il processo figlio. capisco; quindi per un po' sono COW anche quelle, giusto?
e adesso la domanda da un milione di dollari: :sofico:
a che ç@##° serve sta fork??? :D :D :D
a creare i worm? :asd: :D
Sono stato precipitoso. Ovvio che per fare sti giochini con le pagine la fork deve "clonare" anche la page table del padre ed assegnarla al figlio. ovvio, questo l'avevo intuito.
Il mondo linux è un mondo crudele. Questo in teoria non dovrebbe mai succedere. Il padre deve essere sempre presenta alla morte del figlio per dargli il permesso di "crepare". Altrimenti poi i figli diventano processi zombie. :D ecco, ora scherzi a parte (:D) questa cosa me la sono chiesta: in Windows quando muore il padre di un processo, il processo diventa "zombie" (:D) ossia va al livello gerarchico principale; in Linux è lo stesso?
diciamo che un processo in generale utilizza solo pagine residenti in memoria centrale e vorrei vedere che usa quelle che stanno sul disco... -.-'
caricare significa sia crearle che ficcarle in memoria :D se tu intendevi questo allora non era vero che il processo figlio ne "caricava" solo una di codice e una di stack ;)
da quanto ho capito quando viene eseguita una fork, le pagine a crearle le crea tutte, e poi scrive in memoria fisica quelle necessarie (quest'ultima operazione è ovvia e non ha nulla a che vedere con la fork; si tratta semplicemente delle normali operazioni di swap).
e caççHì°, l'avevo intuito visto che sti processi creati dalla fork sono completamente sminnati... :D
:D
eeehhh??? O_______O
scusa ma questo non coincide affatto con quello che mi era stato detto finora... io avevo capito che la fork crea un nuovo processo partendo dallo stesso file eseguibile del processo chiamante...
Ottimo almeno questo lo abbiamo chiarito.
:mbe:
come dire che eredita tutti gli handle?
vedi che continui a pensare con la mentalità windows su un sistema non windows
Cmq diciamo di si :P
ma quindi il nuovo processo ha anche dei thread uguali? perché se sta fork ritorna sia dal padre che dal figlio (lo spirito santo si sente escluso :D LOL scherzo) vuol dire che il figlio sta eseguendo lo stesso thread nello stesso punto...
Attento a non fare confusione. Thread != Processi.
capisco; quindi per un po' sono COW anche quelle, giusto?
in che senso sono COW.
Non è uno "stato" della pagina. È il nome di un algoritmo Copy On Write.
e adesso la domanda da un milione di dollari: :sofico:
a che ç@##° serve sta fork??? :D :D :D
a creare i worm? :asd: :D
Mai sentito parlare di server concorrenti ? :D
ciao ;)
ecco, ora scherzi a parte (:D) questa cosa me la sono chiesta: in Windows quando muore il padre di un processo, il processo diventa "zombie" (:D) ossia va al livello gerarchico principale; in Linux è lo stesso?
Per quel poco che ne so, i figli non ancora terminati di un processo terminato vengono ereditati (o adottati) dal processo init (il processo che ha pid=1 e che genera tutti gli altri processi). Quando questi processi rimasti "orfani" terminano, riportano il loro exit_status a init anziche' al padre che li ha generati (che ormai e' morto).
Fenomeno85
23-07-2005, 11:39
e vorrei vedere che usa quelle che stanno sul disco... -.-'
se tu intendevi questo allora non era vero che il processo figlio ne "caricava" solo una di codice e una di stack ;)
da quanto ho capito quando viene eseguita una fork, le pagine a crearle le crea tutte, e poi scrive in memoria fisica quelle necessarie (quest'ultima operazione è ovvia e non ha nulla a che vedere con la fork; dipende semplicemente dalle operazioni di swap).
no :D non le crea tutte per copia dei descrittori si intendono tutti i file descriptor che il padre aveva aperto ... file, socket e compagnia bella. poi la fork crea un processo non un thread
~§~ Sempre E Solo Lei ~§~
ecco, ora scherzi a parte (:D) questa cosa me la sono chiesta: in Windows quando muore il padre di un processo, il processo diventa "zombie" (:D) ossia va al livello gerarchico principale; in Linux è lo stesso?
Si anche in linux in genere se il padre muore il nono eredita il nipotino. :D
ciao ;)
Fenomeno85
23-07-2005, 11:44
mmm non ho voglia di cercare .. cmq prova qui da qualche parte ci saranno le dispense se le ha pubblicate perchè le lezioni su sta roba le aveva fatte:
http://www.elet.polimi.it/upload/sciuto/Dipartimento/info2materiale05.htm
~§~ Sempre E Solo Lei ~§~
no :D non le crea tutte per copia dei descrittori si intendono tutti i file descriptor che il padre aveva aperto ... file, socket e compagnia bella. poi la fork crea un processo non un thread masochista... :muro: :D
allora: adesso intendiamoci bene sul significato di "creare" una pagina (io intendo semplicemente aggiungere il suo descrittore alla tabella del processo, quindi si: la fork le ricrea tutte, l'ha detto anche VICIUS); la questione dei descrittori dei file l'avevo capita, e il fatto che la fork crei un processo ormai è chiaro, ma non è vero che non crea un thread: se necessario potrebbe doverne creare anche 2, 3, 5, 10, 50 (tutti quelli che aveva il padre insomma ;)).
Fenomeno85
23-07-2005, 11:49
masochista... :muro: :D
allora: adesso intendiamoci bene sul significato di "creare" una pagina (io intendo semplicemente aggiungere il suo descrittore alla tabella del processo, quindi si: la fork le ricrea tutte, l'ha detto anche VICIUS); la questione dei descrittori dei file l'avevo capita, e il fatto che la fork crei un processo ormai è chiaro, ma non è vero che non crea un thread: se necessario potrebbe doverne creare anche 2, 3, 5, 10, 50 (tutti quelli che aveva il padre insomma ;)).
c'è differenza tra processo e thread son due cose diverse :D
carica only the page che gli servono al processo ... mettiamo che un processo ha 120mb in pagine ... di sicuro non saranno tutte in memoria e di sicuro quando viene creato il figlio non duplica tutto se no avremmo attivi solo 2 processi su un pc
~§~ Sempre E Solo Lei ~§~
c'è differenza tra processo e thread son due cose diverse :D non ho mica detto che sono due cose uguali
carica only the page che gli servono al processo ... però le deve creare tutte
mettiamo che un processo ha 120mb in pagine ... di sicuro non saranno tutte in memoria e di sicuro quando viene creato il figlio non duplica tutto se no avremmo attivi solo 2 processi su un pc ok, su questo ci siamo.
I processi vengono creati dalla funzione fork(); in molti unix questa e' una system call, Linux pero' usa un'altra nomenclatura, e la funzione fork() e' basata a sua volta sulla system call __clone, che viene usata anche per generare i thread. Il processo figlio creato dalla fork e' una copia identica del processo padre, ma ha un nuovo pid e viene eseguito in maniera indipendente.
Dopo il successo dell'esecuzione di una fork() sia il processo padre che il processo figlio continuano ad essere eseguiti normalmente a partire dall'istruzione successiva alla fork; il processo figlio e' pero' una copia del padre, e riceve una copia dei segmenti di testo, stack e dati ed esegue lo stesso codice del padre. Si tenga presente pero' che la memoria e' copiata, non condivisa, pertanto padre e figlio vedono variabili diverse.
Per quanto riguarda la gestione della memoria, in generale il segmento di testo, che e' identico per i due processi, e' condiviso e tenuto in read-only per il padre e per i figli. Per gli altri segmenti, Linux utilizza la tecnica copy-on-write; questa tecnica comporta che una pagina di memoria viene effettivamente copiata per il nuovo processo solo quando ci viene effettuata sopra una scrittura (e si ha quindi una reale differenza tra padre e figlio).
Attento a non fare confusione. Thread != Processi. lo so.
in che senso sono COW.
Non è uno "stato" della pagina. È il nome di un algoritmo Copy On Write. parlo così perché in Windows è entrambe le cose: se lo stato di protezione di una pagina è copy-on-write vuol dire che il sistema operativo ha il compito di replicarla quando intercetta una scrittura su di essa. aldilà del sistema operativo per implementare il copy-on-write di una pagina basta settarla in sola lettura e quando salta il #PF causato da una scrittura, il SO replica la pagina.
Mai sentito parlare di server concorrenti ? :D so solo che in Windows la fork non esiste e noi programmatori Win32 ce la caviamo egregiamente anche senza. :D
cmq in che modo i server concorrenti usano la fork?
lo so.
parlo così perché in Windows è entrambe le cose: se lo stato di protezione di una pagina è copy-on-write vuol dire che il sistema operativo ha il compito di replicarla quando intercetta una scrittura su di essa. aldilà del sistema operativo per implementare il copy-on-write di una pagina basta settarla in sola lettura e quando salta il #PF causato da una scrittura, il SO replica la pagina.
so solo che in Windows la fork non esiste e noi programmatori Win32 ce la caviamo egregiamente anche senza. :D
cmq in che modo i server concorrenti usano la fork?
Il processo padre rimane in ascolto sul socket in attesa di richieste quando ne riceve una la accetta si duplica con fork e poi ritorna in ascolto. Il processo figlio si occupa di parlare con il client.
ciao ;)
I processi vengono creati dalla funzione fork(); in molti unix questa e' una system call, Linux pero' usa un'altra nomenclatura, e la funzione fork() e' basata a sua volta sulla system call __clone, che viene usata anche per generare i thread. Il processo figlio creato dalla fork e' una copia identica del processo padre, ma ha un nuovo pid e viene eseguito in maniera indipendente. Dopo il successo dell'esecuzione di una fork() sia il processo padre che il processo figlio continuano ad essere eseguiti normalmente a partire dall'istruzione successiva alla fork; perfetto, questo l'ho capito.
il processo figlio e' pero' una copia del padre, e riceve una copia dei segmenti ma perché contintui a usare il termine "segmenti"? intendi dire "sezioni" (o il loro analogo Unix :p) oppure Linux usa la segmentazione?
di testo, stack e dati ma perché tutti continuate a parlare come se un processo avesse un solo stack?!? :mbe:
ed esegue lo stesso codice del padre. Si tenga presente pero' che la memoria e' copiata, non condivisa, solo i dati però...! il codice è proprio condiviso!!
pertanto padre e figlio vedono variabili diverse. tranne dove ci sono sezioni di dati condivise.
Per quanto riguarda la gestione della memoria, in generale il segmento di testo, che e' identico per i due processi, e' condiviso e tenuto in read-only per il padre e per i figli. Per gli altri segmenti, Linux utilizza la tecnica copy-on-write; scusa, non dovrebbe usarla solo per il codice? :mbe:
Il processo padre rimane in ascolto sul socket in attesa di richieste quando ne riceve una la accetta si duplica con fork e poi ritorna in ascolto. Il processo figlio si occupa di parlare con il client. ma che casino!!! così se un server riceve 100 connessioni deve creare 100 processi!!! in Win32 si usa semplicemente il multithreading... certo, se uno è fesso può anche implementarlo in multiprocessing...
ma che casino!!! così se un server riceve 100 connessioni deve creare 100 processi!!! in Win32 si usa semplicemente il multithreading... certo, se uno è fesso può anche implementarlo in multiprocessing...
Non necessariamente 100 processi, puo' fare pooling, crearne solo 10 e dividere le connessioni fra i vari processi.
Sarebbe altamente inefficiente creare un processo per ogni connessione e scalerebbe piuttosto male. Di solito, il "sweet spot", e' creare uno o due processi per CPU. Oltre al meccanismo della fork, e' possibile usare pthread per la gestione di piu' thread all'interno di ogni processo.
Win32, con le dovute differenze, funziona piu' o meno allo stesso modo: piu' processi ed ogni processo ha associati piu' thread. Win32 pero' mette a disposizione un'API piu' ampollosa e flessibile di quella di Linux, e quindi piu' complessa da usare.
maxithron
23-07-2005, 13:02
:mano:
padre fa quello che gli pare il figlio non esiste
pid = fork();
if (pid ==0){
qui il figlio fa tutto quello che deve fare,
il padre potrebbe essere già morto.
}
qui il padre continua a fare quello che deve fare,
il figlio può essere morto o meno.
~§~ Sempre E Solo Lei ~§~
uhm, no. In questo caso, se il padre muore prima di aver dato l'estrema unzione al figlio, quest'ultimo si trasforma in un processo zombie.
scusa, non dovrebbe usarla solo per il codice? :mbe:
A che serve il COW sulle pagine di codice, se queste non verranno mai modificate (e se ci provi ottieni un SEGMENTATION FAULT)? Il COW si applica a tutti i segmenti potenzialmente duplicabili dopo una fork(); se dopo una fork(), padre e figli non modificano mai nessuna variabile, nessuna pagina dei segmenti dati e stack verra' ricopiata...
Perche' uso il termine 'segmenti'? Nei processori x86 non si fa uso di segmentazione paginata?
Riguardo alla quantita' degli stack di un processo... :confused: io sapevo che ce n'e' uno solo :stordita:
Solo una cosa (prescindendo da come vengono gestiti i processi):if (a==0)
{
char buffer[10];
...
...
}
else
{
char buffer1[1024];
...
...
} lo spazio per buffer e buffer1 viene allocato in fase di esecuzione nello stack, a seconda che si esegua l'uno o l'altro ramo dell'if. Giusto? Pero' lo spazio di memoria necessario a contenere gli indirizzi di buffer e buffer1 dove e quando vengono allocati? Necessariamente in fase di compilazione, no? Dove? Nel data-segment, o no?
Qui credo di aver detto una str......
Fenomeno85
23-07-2005, 14:06
A che serve il COW sulle pagine di codice, se queste non verranno mai modificate (e se ci provi ottieni un SEGMENTATION FAULT)? Il COW si applica a tutti i segmenti potenzialmente duplicabili dopo una fork(); se dopo una fork(), padre e figli non modificano mai nessuna variabile, nessuna pagina dei segmenti dati e stack verra' ricopiata...
Perche' uso il termine 'segmenti'? Nei processori x86 non si fa uso di segmentazione paginata?
Riguardo alla quantita' degli stack di un processo... :confused: io sapevo che ce n'e' uno solo :stordita:
si appunto il segmentation fault ti dice che la pagina non è attiva che poi deriva dalla mmu
~§~ Sempre E Solo Lei ~§~
Fenomeno85
23-07-2005, 14:07
uhm, no. In questo caso, se il padre muore prima di aver dato l'estrema unzione al figlio, quest'ultimo si trasforma in un processo zombie.
si ma il processo figlio continua a fare i cacchi sui anche se entra in zombie.
~§~ Sempre E Solo Lei ~§~
A che serve il COW sulle pagine di codice, se queste non verranno mai modificate (e se ci provi ottieni un SEGMENTATION FAULT)? a parte il fatto che non dovresti ottenere segfault, bensì #GP (o #PF, nn saprei di preciso), cmq ottieni l'eccezione perché le pagine di codice sono protette; se le "sproteggi" le puoi modificare (e bada bene, la possibilità di modificare il codice deve esistere: impossibile che in un sistema operativo non si possa creare self modifying code!!).
Il COW si applica a tutti i segmenti potenzialmente duplicabili dopo una fork(); il COW si applica alle pagine che vengono scritte raramente e che possono essere condivise tra processi finché rimangono intatte, ma devono essere replicate nei processi che tentano di modificarle (poiché le modifiche non devono essere globali); questo si applica solo alle pagine di codice (e forse casomai alle pagine di dati, stack e heap subito dopo una fork).
Perche' uso il termine 'segmenti'? Nei processori x86 non si fa uso di segmentazione paginata? lo credevo anch'io fino a pochi giorni fa :D
Riguardo alla quantita' degli stack di un processo... :confused: io sapevo che ce n'e' uno solo :stordita: ce ne sono due per ogni thread. ;)
e pensa che in teoria potrebbero essercene fino a 4 per ogni thread...
Qui credo di aver detto una str...... lo spazio per contenere i due indirizzi non esiste: il compilatore genera direttamente codice che sa già l'indirizzo (che corrisponderà a EBP più qualcosa :)).
si appunto il segmentation fault ti dice che la pagina non è attiva che poi deriva dalla mmu scusa, cosa vorrebbe dire che una pagina "non è attiva"...? :mbe:
che non sta in RAM? :mbe:
in tal caso scatta #PF, non #SF...
Fenomeno85
23-07-2005, 14:35
scusa, cosa vorrebbe dire che una pagina "non è attiva"...? :mbe:
che non sta in RAM? :mbe:
in tal caso scatta #PF, non #SF...
che pirla si hai ragione page fault :stordita:
~§~ Sempre E Solo Lei ~§~
ilsensine
23-07-2005, 14:50
uhm, no. In questo caso, se il padre muore prima di aver dato l'estrema unzione al figlio, quest'ultimo si trasforma in un processo zombie.
In questo caso il figlio viene "riparentato" (termine orrendo) a init, che si occupa di svolgere il lavoro di pulizia omesso dal padre snaturato.
Se vedi zombie in giro, il padre non è ancora morto. Solo, non sta facendo bene il suo lavoro.
ilsensine
23-07-2005, 14:58
ce ne sono due per ogni thread. ;)
e pensa che in teoria potrebbero essercene fino a 4 per ogni thread...
Bè - almeno sotto linux - lo stack è uno solo in userspace, per processo/thread. Certo è una cosa che il processo, se vuole, può cambiare (nulla vieta di spostare lo stack dove vuoi durante l'esecuzione).
Poi ogni processo ha (dipende dal kernel) una o due pagine di stack in kernel space; ma è roba del kernel, non accessibile dal processo.
Bè - almeno sotto linux - lo stack è uno solo in userspace, per processo/thread. Certo è una cosa che il processo, se vuole, può cambiare (nulla vieta di spostare lo stack dove vuoi durante l'esecuzione).
Poi ogni processo ha (dipende dal kernel) una o due pagine di stack in kernel space; ma è roba del kernel, non accessibile dal processo. il manuale della IA-32 dice che ogni task può avere al max. 4 stack, uno per ogni ring; per "task" penso che quel manuale intenda un concetto generico che non è ne' processo ne' thread (non è detto che un SO debba implementare il multithreading, potrebbe anche implementare un solo thread per processo, come Windows 3.1), ma che è semplicemente un flusso di esecuzione, diciamo così. in Windows (come in Linux) penso che possiamo tradurlo semplicemente in "thread", quindi max. 4 stack per thread; dal momento che Windows usa solo 2 dei 4 ring di funzionamento delle CPU Intel, 2 stack per thread. :)
ilsensine
23-07-2005, 15:19
il manuale della IA-32 dice che ogni task può avere al max. 4 stack, uno per ogni ring
Ah ok allora i conti tornano :)
Il secondo stack è quella pagina o due in più che ti dicevo.
Il secondo stack è quella pagina o due in più che ti dicevo. si, ma per ogni thread però, non per ogni processo (come hai detto tu prima)!
ilsensine
23-07-2005, 16:33
si, ma per ogni thread però, non per ogni processo (come hai detto tu prima)!
lo stack è uno solo in userspace, per processo/thread.
:oink:
si, ma per ogni thread però, non per ogni processo (come hai detto tu prima)!
Poi ogni processo ha (dipende dal kernel) una o due pagine di stack in kernel space
:Prrr:
ilsensine
23-07-2005, 17:58
:Prrr:
Lato kernel non c'è una grande differenza tra processo e thread :cool:
Lato kernel non c'è una grande differenza tra processo e thread :cool: come no? :read:
in Linux poi più che mai, visto che non si tratta di microkernel! ;)
ilsensine
23-07-2005, 21:39
come no? :read:
in Linux poi più che mai, visto che non si tratta di microkernel! ;)
Hai una spiegazione tecnica plausibile, oltre alla "non si tratta di un microkernel"? :)
Hai una spiegazione tecnica plausibile, oltre alla "non si tratta di un microkernel"? :) ok mi arrendo: però siccome non ho Linux ti sarei grato se mi quotassi qui il codice del kernel dove il TSS di un thread che è appena entrato in kernel mode scompare magicamente.
ilsensine
24-07-2005, 00:19
ok mi arrendo: però siccome non ho Linux ti sarei grato se mi quotassi qui il codice del kernel dove il TSS di un thread che è appena entrato in kernel mode scompare magicamente.
Cosa c'entra con il discorso thread/processi? Certo che quando un task (chiamalo thread o processo o come vuoi) entra in kernel space continua a girare nel proprio contesto di esecuzione...
La mia domanda era un'altra: come fai ad essere così sicuro che su linux c'è grande differenza tra thread e processi? Se vuoi ti posto un pò di esempi per giustificare come siano gestiti in maniera praticamente uguale, non ho problemi, ma volevo sentire il tuo punto di vista...
Cosa c'entra con il discorso thread/processi? Certo che quando un task (chiamalo thread o processo o come vuoi) entra in kernel space continua a girare nel proprio contesto di esecuzione...
La mia domanda era un'altra: come fai ad essere così sicuro che su linux c'è grande differenza tra thread e processi? Se vuoi ti posto un pò di esempi per giustificare come siano gestiti in maniera praticamente uguale, non ho problemi, ma volevo sentire il tuo punto di vista...
Ti piace postare gli esempi che mi interessano. Sono curioso e non so come Linux gestisca thread/processi. PThread e' semplicemente una libreria user senza supporto diretto del kernel giusto?
Cosa c'entra con il discorso thread/processi? Certo che quando un task (chiamalo thread o processo o come vuoi) entra in kernel space continua a girare nel proprio contesto di esecuzione... allora credo di aver capito male la frase iniziale: io pensavo che tu stessi dicendo che una volta entrati in kernel mode lo stack era uno solo (solo una o due pagine), e che il motivo era che in kernel mode i task non si distinguono più.
ilsensine
24-07-2005, 23:34
allora credo di aver capito male la frase iniziale: io pensavo che tu stessi dicendo che una volta entrati in kernel mode lo stack era uno solo (solo una o due pagine), e che il motivo era che in kernel mode i task non si distinguono più.
Ah no no, ovviamente non intendevo questo :D
ilsensine
24-07-2005, 23:44
Ti piace postare gli esempi che mi interessano. Sono curioso e non so come Linux gestisca thread/processi
Bè ci provo. Castronerie e omissioni potrebbero essere presenti. In cambio potresti cercare di spiegarmi come diavolo hanno fatto quelli di Cygwin a implementare la fork sotto Windows :D
Proverò con esempi di programmi userspace, per far vedere come "ragiona" il kernel e cosa sono, per lui, "thread" e "processi".
Innanzitutto chiariamo che la chiamate userspace fork e clone utilizzano sempre la sys_clone (che utilizza la funzione interna del kernel do_fork). Non è comunque obbligatorio, linux implementa anche la sys_fork (che internamente richiama la solita do_fork). Visto che la sys_clone può fare tutto ciò che fa la sys_fork e ben altro, ormai nessuno usa più quest'ultima, che rimane solo per compatibilità con i vecchi eseguibili.
Chiarito questo, prendiamo il seguente programma di esempio:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sched.h>
#include <sys/mman.h>
#define FLAGS_THREAD >>> vedi sotto <<<
int val = 0;
int fd;
int new_thread(void *unused)
{
val = 10;
close(fd);
return 0;
}
int main(void)
{
fd = dup(0);
pid_t id;
char *stack = mmap(NULL, 65536, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_GROWSDOWN|MAP_ANONYMOUS, -1, 0);
stack += 65536;
id = clone(new_thread, stack, FLAGS_THREAD, NULL);
if(id<0) perror("clone");
else {
usleep(100000); /* aspettiamo che il child faccia danni */
if(close(fd)<0) fprintf(stderr, "parent: fd CONDIVISI con il child\n");
else fprintf(stderr, "parent: fd NON CONDIVISI con il child\n");
if(val!=0) fprintf(stderr, "parent: memoria CONDIVISA con il child\n");
else fprintf(stderr, "parent: memoria NON CONDIVISA con il child\n");
}
return 0;
}
Cosa fa il programma: serve per vedere quali effetti può avere un child, creato con diversi parametri della clone, sulla memoria e sulla tabella di file aperti del processo padre.
Innanzitutto creiamo un file descriptor (ho semplicemente "duplicato" 0, stdin, ottenendo un descrittore nuovo con cui posso giocare). Ho anche definito una variabile, val, inizialmente 0.
Subito dopo la clone, sia parent che child vedranno la memoria con gli stessi valori. Quindi val sarà 0 per entrambi, e fd sarà lo stesso per entrambi.
Il child quindi modifica il valore di val, e chiuderà il descrittore fd. Il parent, infine, controllerà se il valore in val è stato modificato, e controllerà se il descrittore fd è ancora aperto.
Esempio a)
#define FLAGS_THREAD 0
L'output del programma è il seguente:
parent: fd NON CONDIVISI con il child
parent: memoria NON CONDIVISA con il child
In effetti il comportamento è praticamente quello della fork (la sys_fork definisce anche SIGCHLD, ma è indifferente per i nostri scopi).
Ognuno dei due task ha una memoria propria (solo inizialmente identica), e degli fd autonomi (se un task chiude un fd, chiude il _suo_ fd e non quello dell'altro).
In questo caso sembra proprio che abbiamo generato due processi autonomi.
Esempio b)
#define FLAGS_THREAD CLONE_VM|CLONE_FILES
L'output del programma è il seguente:
parent: fd CONDIVISI con il child
parent: memoria CONDIVISA con il child
La memoria è condivisa per entrambi, e gli fd pure. Se un task chiude un fd, anche l'altro lo vedrà chiuso; se un task crea un nuovo fd, anche l'altro lo vedrà aperto.
Direi proprio che abbiamo creato qualcosa di simile a due "thread" classici.
Il bello viene adesso.
Esempio c)
#define FLAGS_THREAD CLONE_VM
L'output del programma è il seguente:
parent: fd NON CONDIVISI con il child
parent: memoria CONDIVISA con il child
Eh questa è bella. Sembra che abbiamo creato due thread, in quanto la memoria è tutta condivisa tra i due, ma ciascuno ha il proprio set di fd indipendente dall'altro, come in due processi distinti.
Non è finita:
Esempio d)
#define FLAGS_THREAD CLONE_FILES
L'output del programma è il seguente:
parent: fd CONDIVISI con il child
parent: memoria NON CONDIVISA con il child
Questi due "cosi" (non me la sento di definirli nè thread nè processi) hanno memoria autonoma, come nei processi distinti. Ma condividono gli stessi fd, come nei thread: anche qui se uno chiude un fd, "scomparirà" anche nell'altro. In più, se il child creasse un nuovo fd, cui viene assegnato il valore (ad esempio) 5, anche il parent scoprirà che, tentando di accedere all'fd 5 (che lui non ha mai aperto), lo troverebbe...aperto!
Morale della storia: la clone crea semplicemente un nuovo "task", generico; sempre tramite la clone, possiamo chiedere al kernel che parent e child devono condividere alcune risorse: ne abbiamo visto un paio di esempio (tabella degli fd e vma), ma ce ne sono altri (namespace, oggetti ipc come i semafori, ad esempio). Per il kernel se un oggetto "posseduto" dal task è condiviso o meno con altri task (o con altre parti dello stesso kernel!) non è di nessun disturbo.
Per questo dico che, per il kernel, processi e thread sono (quasi) la stessa cosa.
Ritornando al'esempio a) (processi distinti), faccio un altro paio di esempi per vedere come, anche tra processi apparentemente scollegati, possa essere normale condividere comunque "qualcosa". Il primo esempio è ovvio, il secondo un pò meno:
Esempio e)
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
int main(void)
{
char *page = mmap(NULL, getpagesize(), PROT_READ|PROT_WRITE,
MAP_SHARED|MAP_ANONYMOUS, -1, 0);
page[0] = 0;
fprintf(stderr, "parent: page[0] = %d\n", page[0]);
if(fork()) { /* parent */
usleep(100000); /* aspettiamo che il child faccia danni */
if(page[0]!=0)
fprintf(stderr, "parent: qualcuno ha cambiato page[0] a %d!\n", page[0]);
} else { /* child */
page[0] = 1;
fprintf(stderr, "child: imposto page[0] = %d\n", page[0]);
}
return 0;
}
Il programma stampa
parent: page[0] = 0
child: imposto page[0] = 1
parent: qualcuno ha cambiato page[0] a 1!
Abbastanza ovvio. I due processi hanno sì memoria autonoma, ma quella "shared" rimane "shared". Faccio notare che questo esempio non ha nulla in comune con la shared memory ipc, che usa tutt'altra tecnica.
Notate infine che (tornando al programma dell'esempio a), se avessi richiesto uno stack MAP_SHARED per il child, il parent avrebbe potuto accedere indisturbato allo stack del child!
Esempio f)
Negli esempi a) e c) avevo affermato che parent e child avevano tabelle di fd autonome, ed era vero.
Però:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
off_t off;
int fd = open(argv[0], O_RDONLY);
off = lseek(fd, 0, SEEK_CUR);
fprintf(stderr, "parent: Posizione iniziale: %d\n", (int)off);
if(fork()) { /* parent */
usleep(100000); /* aspettiamo che il child faccia danni */
off = lseek(fd, 0, SEEK_CUR);
if(off!=0)
fprintf(stderr, "parent: qualcuno ha cambiato l'offset a %d!\n",
(int)off);
} else { /* child */
off = lseek(fd, 10, SEEK_SET);
close(fd); /* ci permettiamo il lusso di chiuderlo anche il nostro fd! */
fprintf(stderr, "child: imposto l'offset a %d\n", (int)off);
}
return 0;
}
Questo programma stampa:
parent: Posizione iniziale: 0
child: imposto l'offset a 10
parent: qualcuno ha cambiato l'offset a 10!
Qualcuno è in grado di spiegare questa apparente assurdità? :)
Per ultimo segnalo che, nel caso uno dei due task esegua una execve per caricare l'immagine di un altro eseguibile, il kernel porrà dei limiti ad alcune delle situazioni di condivisione illustrate, per evidenti motivi di sicurezza che forse qualcuno avrà colto: scordatevi quindi di usare una delle tecniche che ho mostrato per invocare "su", e sperare di beccare il file descriptor che questo programma apre su /etc/shadow ;)
In cambio potresti cercare di spiegarmi come diavolo hanno fatto quelli di Cygwin a implementare la fork sotto Windows :D io non lo so ma vado ad intuito (cioè dico come farei io :D):
1) lanci un tuo minieseguibile di supporto, possibilmente con base address diverso da 0x400000; il suo processo sarà il nuovo processo clonato dalla fork;
2) con LoadLibrary carichi nel nuovo processo tutti moduli caricati nell'altro;
3) analizzando le immagini PE mappate in memoria dei moduli del processo originale, trovi tutte le pagine di dati e le ricopi tali e quali nel processo clone (lo fai per tutte tranne per quelle condivise, nelle quali naturalmente il contenuto è già stato aggiornato);
4) crei nel nuovo processo tanti thread quanti ce n'erano nel vecchio (anche considerando che in quello nuovo un thread ci sta già); i thread che crei sono momentaneamente sospesi;
5) ricopi nei nuovi thread i contesti di quelli vecchi così come sono;
6) riesumi i thread che nel vecchio processo non erano sospesi.
problema 1: come decidere il base address del minieseguibile di supporto? in teoria dovrebbe essere un indirizzo variabile a seconda del processo che ha chiamato la fork: l'indirizzo infatti dovrebbe essere un indirizzo che nel processo chiamante è libero e ha spazio a sufficienza per ospitare il minieseguibile; una cosa che puoi fare è riscrivere ogni volta il base address nel file del minieseguibile, ma in tal caso devi anche... "rilocare il file"!! :p
altrimenti un'altra cosa che puoi fare è usare il minieseguibile solo per creare il processo senza fargli fare nulla; poi il minieseguibile si sospende da solo il suo thread e la fork semplicemente se ne frega della sua presenza e va a sovrascrivere tutte le sue pagine (tanto poi il contesto del thread primario va aggiornato).
problema 2: il processo chiamante potrebbe aver allocato nuove pagine che non conosci con VirtualAlloc; sicuramente l'ha fatto quantomeno per l'heap; di conseguenza devi attraversare tutte le pagine del processo chiamante con VirtualQuery per controllare che non ce ne siano di "sconosciute"; se ce ne sono le devi allocare e ricopiare così come sono nel nuovo processo.
NB: questa è tutta una ipotesi: in realtà può darsi che il DDK offra qualche funzione PsXxx per gestire la struttura dei processi e aggiungere un nuovo processo senza passare per CreateProcess; in tal caso si ovvierebbe al problema 1 e si eliminerebbe la presenza dell'eseguibile di supporto. tuttavia ho dato una rapida occhiata a MSDN e non mi è sembrato che ci fossero funzioni simili.
Qualcuno è in grado di spiegare questa apparente assurdità? :)
Il sistema duplica i file descriptor ma mantiene la posizione sincronizzata tra i parenti. Questo per evitare di controllare ogni volta se qualcuno a gia chiuso il file.
ci ho preso ?
ciao ;)
ilsensine
25-07-2005, 08:45
Il sistema duplica i file descriptor ma mantiene la posizione sincronizzata tra i parenti. Questo per evitare di controllare ogni volta se qualcuno a gia chiuso il file.
ci ho preso ?
ciao ;)
Se me la "traduci" potrei dirtelo :D
Comunque a naso non ci hai preso ;)
ilsensine
25-07-2005, 08:46
4) crei nel nuovo processo tanti thread quanti ce n'erano nel vecchio
Mi sembra evidente che la fork duplica di tutto ma non i thread ;)
Morale della storia: la clone crea semplicemente un nuovo "task", generico; sempre tramite la clone, possiamo chiedere al kernel che parent e child devono condividere alcune risorse: ne abbiamo visto un paio di esempio (tabella degli fd e vma), ma ce ne sono altri (namespace, oggetti ipc come i semafori, ad esempio). Per il kernel se un oggetto "posseduto" dal task è condiviso o meno con altri task (o con altre parti dello stesso kernel!) non è di nessun disturbo.
Per questo dico che, per il kernel, processi e thread sono (quasi) la stessa cosa.
Chiarissimo, grazie. Quindi PThread non fa altro internamente che richiamare la do_clone con i parametri che hai descritto per creare thread che condividono memoria e descrittori. Dico bene?
Per ultimo segnalo che, nel caso uno dei due task esegua una execve per caricare l'immagine di un altro eseguibile, il kernel porrà dei limiti ad alcune delle situazioni di condivisione illustrate, per evidenti motivi di sicurezza che forse qualcuno avrà colto: scordatevi quindi di usare una delle tecniche che ho mostrato per invocare "su", e sperare di beccare il file descriptor che questo programma apre su /etc/shadow ;)
E io che ci stavo gia' provando :p
(Prima pero' devo installare Linux, approposito, ho ancora quel problema col mio Bimbo embedded e sono disperato)
ilsensine
25-07-2005, 10:10
Chiarissimo, grazie. Quindi PThread non fa altro internamente che richiamare la do_clone con i parametri che hai descritto per creare thread che condividono memoria e descrittori. Dico bene?
Sì infatti.
In realtà la pthread_create passa altri flag alla clone, sia per clonare tutti gli oggetti clonabili (non solo memoria e fd), sia se si intende o meno raggruppare i thread per "thread-group" (una specie dello standard unix "gruppo di processi"), sia per indicare se si intende usare o meno la TLS (thread-local storage; ne avrai sentito parlare anche sotto windws immagino).
La clone è alquanto sofisticata, negli esempi sopra ho semplificato il funzionamento sugli aspetti più importanti.
ilsensine
25-07-2005, 10:20
(Prima pero' devo installare Linux, approposito, ho ancora quel problema col mio Bimbo embedded e sono disperato)
E' difficile investigarlo; come ti ho detto, la dmesg potrebbe dare qualche indizio.
Forse il produttore può aiutarti; potrebbe trattarsi di un errata hadrware ormai noto o di un bug già risolto.
Sono oggetti molto divertenti su cui smanettare; se ha uno schermo, puoi installare le Mesa e avere le OpenGL (chissà, magari poi ti viene voglia di vederci girare B&W2... :p )
E' difficile investigarlo; come ti ho detto, la dmesg potrebbe dare qualche indizio.
Forse il produttore può aiutarti; potrebbe trattarsi di un errata hadrware ormai noto o di un bug già risolto.
Sono oggetti molto divertenti su cui smanettare; se ha uno schermo, puoi installare le Mesa e avere le OpenGL (chissà, magari poi ti viene voglia di vederci girare B&W2... :p )
Prima cerco di farlo girare decentemente su un P4 a 3Ghz e poi ti dico :D
Se me la "traduci" potrei dirtelo :D
Comunque a naso non ci hai preso ;)
Argv! :mad:
Non ho piu idee. O forse è ancora colpa delle "mucche" malefiche ? :D
ciao ;)
RaouL_BennetH
25-07-2005, 15:28
Questo programma stampa:
parent: Posizione iniziale: 0
child: imposto l'offset a 10
parent: qualcuno ha cambiato l'offset a 10!
Qualcuno è in grado di spiegare questa apparente assurdità? :)
Io assolutamente no, però stavo cercando di capire delle cose, e siccome non ho mai visto variabili del tipo "pid_t" oppure "off_t" volevo chiedere che tipo sono.
Poi, e mi scuserai di nuovo per l'ignoranza, non so cosa faccia la funzione usleep, però noto che il valore che hai attribuito nel ciclo del "parent", credo sia lo stesso che si trova nell'else relativo al child:
off = lseek(fd, 10, SEEK_SET);
e quindi penso sia quel "10" che viene fuori.
Sono desolato, ma ho un ragionamento in testa e non riesco ad esprimermi bene.
ilsensine
25-07-2005, 15:50
Io assolutamente no, però stavo cercando di capire delle cose, e siccome non ho mai visto variabili del tipo "pid_t" oppure "off_t" volevo chiedere che tipo sono.
pid_t è sostanzialmente un typedef per int.
off_t può essere un int a 32 o 64 bit, a seconda delle architetture (e sulle architetture a 32 bit può essere a 64 bit se compili definendo _FILE_OFFSET_BITS=64).
v. <sys/types.h>, <bits.types.h>, <bits/typesizes.h>
Poi, e mi scuserai di nuovo per l'ignoranza, non so cosa faccia la funzione usleep
Tecnicamente, pone il task nello stato TASK_INTERRUPTIBLE per almeno tot microsecondi.
Tradotto, aspetta almeno tot microsecondi prima di ritornare (a meno di qualche segnale rompiscatole).
E' comunemente implementata tramite nanosleep (v. relativa pagina man).
RaouL_BennetH
25-07-2005, 16:06
pid_t è sostanzialmente un typedef per int.
off_t può essere un int a 32 o 64 bit, a seconda delle architetture (e sulle architetture a 32 bit può essere a 64 bit se compili definendo _FILE_OFFSET_BITS=64).
v. <sys/types.h>, <bits.types.h>, <bits/typesizes.h>
Chiarissimo, grazie :)
Tecnicamente, pone il task nello stato TASK_INTERRUPTIBLE per almeno tot microsecondi.
Tradotto, aspetta almeno tot microsecondi prima di ritornare (a meno di qualche segnale rompiscatole).
E' comunemente implementata tramite nanosleep (v. relativa pagina man).
Secondo te, faccio bene se persevero sul ragionamento che "l'anomalia" di quel programma è legato a quella funzione?
ilsensine
25-07-2005, 16:28
Secondo te, faccio bene se persevero sul ragionamento che "l'anomalia" di quel programma è legato a quella funzione?
No, la usleep c'è solo per essere sicuri che il child abbia fatto il suo compito prima di procedere. Non è il modo corretto, ma non stavo parlando delle tecniche per sincronizzare i processi :)
L'anomalia in realtà è solo una apparenza. Nel kernel i vari task sono identificati da un oggetto "task" che contiene, tra le varie cose, un puntatore all'oggetto "files" (contenitore di file) del task in questione. Puoi pensare all'oggetto "files" come ad un array in cui files[fd] è il kernel handler "file" per l'fd in questione. (un "file" è una istanza di un inode, ovvero un "inode aperto". Un inode è un oggetto unico - ad esempio un file sul file system - , che può essere aperto - a seconda del tipo di inode - anche più volte, ottenendo più descrittori "file" distinti).
In due thread i due descrittori di task "puntano" al medesimo elenco "files" (in questo caso "files" ha un "reference count" di 2, in quanto è in uso da due entità).
In due processi creati tramite fork, ciascuno ha un _proprio_ oggetto "files", con refcount 1, ma inizialmente i vari "file" che contengono solo gli _stessi_, con refcount 2. L'apertura di nuovi fd da parte di un processo non sarà visibile all'altro processo, come anche la chiusura di un "file" condiviso (semplicemente, viene decrementato il refcount di 1 e rimosso dall'elenco "files" di chi non lo vuole più). Ma qualsiasi altra operazione sì, sarà visibile, in quanto effettuata sullo stesso oggetto.
Non potevano essere duplicati anche i singoli "file" durante una fork? No, non è tecnicamente possibile. O meglio, cercare di farlo può portare a problemi ben più gravi di quelli che si sta cercando di risolvere.
Il comportamento osservato, in effetti, è quello...corretto!
mmm credo di non essere stato molto chiaro...
RaouL_BennetH
25-07-2005, 16:33
mmm credo di non essere stato molto chiaro...
Non è che non sei stato chiaro ;)
Il problema è che forse mi sono spinto un pò troppo oltre quella che è la mia conoscenza attuale, su argomenti che di sicuro sono avanzati.
Ma che devo fare se ho sempre fame di informazioni?!?
RaouL.
ilsensine
25-07-2005, 16:40
Non è che non sei stato chiaro ;)
Il problema è che forse mi sono spinto un pò troppo oltre quella che è la mia conoscenza attuale, su argomenti che di sicuro sono avanzati.
Ma che devo fare se ho sempre fame di informazioni?!?
Aspetta, forse ho un modo più chiaro di spiegarlo...
Considera questo:
int fd1 = open("file", O_RDONLY);
int fd2 = open("file", O_RDONLY);
In questo caso i due fd sono associati a oggetti "file" differenti (anche se aperti sullo stesso inode). Se faccio lseek su uno, non la osservo sull'altro.
In questo caso:
int fd1 = open("file", O_RDONLY);
int fd2 = dup(fd1);
fd1 e fd2 sono associati allo stesso oggetto "file", che si ritrova ad avere un refcount di 2 e ad essere presente 2 volte nell'elenco "files" del processo.
Se faccio lseek su fd1, la osservo anche su fd2 (fai una prova!)
Capito il punto qual'è?
RaouL_BennetH
25-07-2005, 16:56
(fai una prova!)
appena sono a casa. Qui in azienda ci sono solo macchine windows, quindi credo che la prova non potrebbe funzionare
Capito il punto qual'è?
Non ancora al 100%, ma ovviamente non per colpa tua :)
Se non ti dispiace, vorrei tornare su quest'argomento fra qualche giorno, giusto il tempo di familiarizzare anche con termini quali "refcount", e leggere un pò meglio come funziona il kernel linux.
Non mi abbandonare! :)
Raoul, se hai tempo, dai un'occhiata ai primi capitoli di questa guida: http://gapil.truelite.it/
sempre che tu non l'abbia gia' scaricata! Credo di averla consigliata gia' a mezzo forum :)
Gica
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.