|
|||||||
|
|
|
![]() |
|
|
Strumenti |
|
|
#81 | |
|
Senior Member
Iscritto dal: Apr 2000
Città: Roma
Messaggi: 15625
|
Quote:
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...
__________________
0: or %edi, %ecx; adc %eax, (%edx); popf; je 0b-22; pop %ebx; fadds 0x56(%ecx); lds 0x56(%ebx), %esp; mov %al, %al andeqs pc, r1, #147456; blpl 0xff8dd280; ldrgtb r4, [r6, #-472]; addgt r5, r8, r3, ror #12 |
|
|
|
|
|
|
#82 | |
|
Senior Member
Iscritto dal: Oct 2002
Città: San Jose, California
Messaggi: 11794
|
Quote:
__________________
"We in the game industry are lucky enough to be able to create our visions" @ NVIDIA |
|
|
|
|
|
|
#83 | |
|
Bannato
Iscritto dal: Feb 2005
Città: Roma
Messaggi: 7029
|
Quote:
|
|
|
|
|
|
|
#84 | |
|
Senior Member
Iscritto dal: Apr 2000
Città: Roma
Messaggi: 15625
|
Quote:
__________________
0: or %edi, %ecx; adc %eax, (%edx); popf; je 0b-22; pop %ebx; fadds 0x56(%ecx); lds 0x56(%ebx), %esp; mov %al, %al andeqs pc, r1, #147456; blpl 0xff8dd280; ldrgtb r4, [r6, #-472]; addgt r5, r8, r3, ror #12 |
|
|
|
|
|
|
#85 | |
|
Senior Member
Iscritto dal: Apr 2000
Città: Roma
Messaggi: 15625
|
Quote:
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: Codice:
#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;
}
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) Codice:
#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;
}
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ò: Codice:
#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;
}
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
__________________
0: or %edi, %ecx; adc %eax, (%edx); popf; je 0b-22; pop %ebx; fadds 0x56(%ecx); lds 0x56(%ebx), %esp; mov %al, %al andeqs pc, r1, #147456; blpl 0xff8dd280; ldrgtb r4, [r6, #-472]; addgt r5, r8, r3, ror #12 Ultima modifica di ilsensine : 25-07-2005 alle 09:35. |
|
|
|
|
|
|
#86 | |
|
Bannato
Iscritto dal: Feb 2005
Città: Roma
Messaggi: 7029
|
Quote:
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"!! 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. |
|
|
|
|
|
|
#87 | |
|
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
Quote:
ci ho preso ? ciao |
|
|
|
|
|
|
#88 | |
|
Senior Member
Iscritto dal: Apr 2000
Città: Roma
Messaggi: 15625
|
Quote:
Comunque a naso non ci hai preso
__________________
0: or %edi, %ecx; adc %eax, (%edx); popf; je 0b-22; pop %ebx; fadds 0x56(%ecx); lds 0x56(%ebx), %esp; mov %al, %al andeqs pc, r1, #147456; blpl 0xff8dd280; ldrgtb r4, [r6, #-472]; addgt r5, r8, r3, ror #12 |
|
|
|
|
|
|
#89 | |
|
Senior Member
Iscritto dal: Apr 2000
Città: Roma
Messaggi: 15625
|
Quote:
__________________
0: or %edi, %ecx; adc %eax, (%edx); popf; je 0b-22; pop %ebx; fadds 0x56(%ecx); lds 0x56(%ebx), %esp; mov %al, %al andeqs pc, r1, #147456; blpl 0xff8dd280; ldrgtb r4, [r6, #-472]; addgt r5, r8, r3, ror #12 |
|
|
|
|
|
|
#90 | ||
|
Senior Member
Iscritto dal: Oct 2002
Città: San Jose, California
Messaggi: 11794
|
Quote:
Quote:
(Prima pero' devo installare Linux, approposito, ho ancora quel problema col mio Bimbo embedded e sono disperato)
__________________
"We in the game industry are lucky enough to be able to create our visions" @ NVIDIA |
||
|
|
|
|
|
#91 | |
|
Senior Member
Iscritto dal: Apr 2000
Città: Roma
Messaggi: 15625
|
Quote:
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.
__________________
0: or %edi, %ecx; adc %eax, (%edx); popf; je 0b-22; pop %ebx; fadds 0x56(%ecx); lds 0x56(%ebx), %esp; mov %al, %al andeqs pc, r1, #147456; blpl 0xff8dd280; ldrgtb r4, [r6, #-472]; addgt r5, r8, r3, ror #12 |
|
|
|
|
|
|
#92 | |
|
Senior Member
Iscritto dal: Apr 2000
Città: Roma
Messaggi: 15625
|
Quote:
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...
__________________
0: or %edi, %ecx; adc %eax, (%edx); popf; je 0b-22; pop %ebx; fadds 0x56(%ecx); lds 0x56(%ebx), %esp; mov %al, %al andeqs pc, r1, #147456; blpl 0xff8dd280; ldrgtb r4, [r6, #-472]; addgt r5, r8, r3, ror #12 |
|
|
|
|
|
|
#93 | |
|
Senior Member
Iscritto dal: Oct 2002
Città: San Jose, California
Messaggi: 11794
|
Quote:
__________________
"We in the game industry are lucky enough to be able to create our visions" @ NVIDIA |
|
|
|
|
|
|
#94 | |
|
Senior Member
Iscritto dal: Oct 2001
Messaggi: 11471
|
Quote:
Non ho piu idee. O forse è ancora colpa delle "mucche" malefiche ? ciao |
|
|
|
|
|
|
#95 | |
|
Senior Member
Iscritto dal: Sep 2004
Messaggi: 3967
|
Quote:
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: Codice:
off = lseek(fd, 10, SEEK_SET); Sono desolato, ma ho un ragionamento in testa e non riesco ad esprimermi bene.
__________________
Dai wafer di silicio nasce: LoHacker... il primo biscotto Geek
|
|
|
|
|
|
|
#96 | ||
|
Senior Member
Iscritto dal: Apr 2000
Città: Roma
Messaggi: 15625
|
Quote:
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> Quote:
Tradotto, aspetta almeno tot microsecondi prima di ritornare (a meno di qualche segnale rompiscatole). E' comunemente implementata tramite nanosleep (v. relativa pagina man).
__________________
0: or %edi, %ecx; adc %eax, (%edx); popf; je 0b-22; pop %ebx; fadds 0x56(%ecx); lds 0x56(%ebx), %esp; mov %al, %al andeqs pc, r1, #147456; blpl 0xff8dd280; ldrgtb r4, [r6, #-472]; addgt r5, r8, r3, ror #12 |
||
|
|
|
|
|
#97 | ||
|
Senior Member
Iscritto dal: Sep 2004
Messaggi: 3967
|
Quote:
Quote:
__________________
Dai wafer di silicio nasce: LoHacker... il primo biscotto Geek
|
||
|
|
|
|
|
#98 | |
|
Senior Member
Iscritto dal: Apr 2000
Città: Roma
Messaggi: 15625
|
Quote:
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...
__________________
0: or %edi, %ecx; adc %eax, (%edx); popf; je 0b-22; pop %ebx; fadds 0x56(%ecx); lds 0x56(%ebx), %esp; mov %al, %al andeqs pc, r1, #147456; blpl 0xff8dd280; ldrgtb r4, [r6, #-472]; addgt r5, r8, r3, ror #12 |
|
|
|
|
|
|
#99 | |
|
Senior Member
Iscritto dal: Sep 2004
Messaggi: 3967
|
Quote:
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.
__________________
Dai wafer di silicio nasce: LoHacker... il primo biscotto Geek
|
|
|
|
|
|
|
#100 | |
|
Senior Member
Iscritto dal: Apr 2000
Città: Roma
Messaggi: 15625
|
Quote:
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'è?
__________________
0: or %edi, %ecx; adc %eax, (%edx); popf; je 0b-22; pop %ebx; fadds 0x56(%ecx); lds 0x56(%ebx), %esp; mov %al, %al andeqs pc, r1, #147456; blpl 0xff8dd280; ldrgtb r4, [r6, #-472]; addgt r5, r8, r3, ror #12 |
|
|
|
|
|
| Strumenti | |
|
|
Tutti gli orari sono GMT +1. Ora sono le: 16:06.



















