Многопоточное программирование в Linux
- Системный вызов __clone. В линуксе ( точнее в pthreads) используется одноуровневая система потоков, так называемая N-to-N ( или 1-1 ) реализация, которая мултеплексирует созданные пользователем потоки в такое же количество выполняемых потоков ядра. Однако, на самом деле, потоки в линуксе являются обыкновенными процессами, имеющими свой уникальный pid_t, стек, но разделяющие между собой определенные составляющие контекста процесса — его память , таблицу файловых дескрипторов, таблицу обработчиков сигналов. Такого рода процессы именуются «облегченными» (LWP — Light Weight Processes), и в линуксе для их создания ( как и для создания обыкновенных процессов ) используется системный вызов __clone(2).
Сигнатура этого вызова такова: int __clone(int (*fn) (void *arg), void *child_stack, int flags, void *arg) Младший байт праметра flags содержит номер сигнала, посылаемого родительскому прцессу, когда завершается соданный им потомок. Параметр также может быть установлен с помощью операции побитового или ( | ) со следующими костантами,чтобы указать что именно разделятся между потомком и родителем:
CLONE_VM | Если CLONE_VM установлен, родитель и потомок выполняются в одном адресном пространтсве. В частности, запись в память выполненная в потомке или родителе, также видна в другом процессе. Общей является и работа с отображаемой памятью, выполняемая с помощью системных вызов mmap(2) и munmap(2). Если CLONE_VM не установлен, то потомок будет выполняться в своей отдельной копии адресного пространтсва родителя на время вызова __clone. Запись в память, а также отображение файлов в память теперь выполняется процессами независимо, как в случае с fork(2) |
CLONE_FS | Если CLONE_FS установлен, родитель и потомок используют общую информацию о файловой системе. Сюда входит корневой каталог, текущий каталог и параметр umask процесса. Любой вызов chroot(2), chdir(2), or umask(2), выполненный либо потомком, либо родителем влияет также и на другой поцесс. Если CLONE_FS не установлен, для потомка создается копия информации о файловой системе родителя но момент вызова __clone. Вызовы chroot(2), chdir(2), umask(2) выполненные процессами не влияют на другой процесс. |
CLONE_FILES | Если CLONE_FILES установлен, родитель и потомок будут использовать общую таблицу файловых дескриторов. Эти дескрипторы будут всегда ссылаться на одни и те же файлы и в родительском процессе и в потомке. Любой файловый дескриптор открытый в одном процессе может быть использован в другом. То же самое относится и к закрытию дескриптора с помощью close(2) или изменению его флпгов с помощью fcntl(2). Еслт CLONE_FILES не установлен, потомок получает копию файловых дескритпоров родителя на момент выполнения __clone. Все операции над фйловыми дескрипторами проводятся процессами независимо друг от друга. |
CLONE_SIGHAND | Если CLONE_SIGHAND установлен, родитель и потомок используют общую таблицу обработчиков сигналов. Если потомок или родитель вызывают sigaction(2) чтобы изменить реакцию на сигнал, то эти изменения происходят и в другом процессе. Тем не менее, оба процесса имеют раздельные сигнальные маски. Таким образом, каждый из них может блокировать или разблокировать сигнал используя sigprocmask(2) не влияя на другой процесс. Если CLONE_SIGHAND не установлен, потомок получает копию таблицы обработчиков синалов на момент вызова __clone. Вызовы sigaction(2) выполненные позже в одном из процессов не влияют на другой. |
CLONE_PID | Если CLONE_PID установлен, потомок получает такой же идентификатор процесса (process ID), что и у родителя. Но использование этого флага не рекомендуется, так как большая часть ПО все еще расчитывает на уникальность идентификаторов процессов. Если CLONE_PID не установлен, потомок получает свой уникальный идентификатор. |
Таким образом, для создания потока нужно задать flags как
CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND.
Например:
#include #include #include #include #include int thread1(void * thread_arg) < printf("thread1 started\n"); for(int i=0;i<10;i++)< sleep(1); printf("thread1: i=%d\n",i); >printf("thread1 finished\n"); > char stack[10000]; int main() < printf("main thread started\n"); if(clone(thread1,(void*)(stack+10000-1), CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND,NULL) == -1) < perror("clone failed"); exit(1); >for(int i=0;i <12;i++)< sleep(1); printf("main thread: i=%d\n",i); >printf("main thread finished\n"); return 0; >
void inchild_term_handler(int signum) < switch (signum) < case SIGSEGV: fprintf(stderr,"Seg fault. Core dumped to /tmp/core."); chdir("/tmp"); signal(signum, SIG_DFL); pthread_kill_other_threads_np(); kill(getpid(),signum); break; >>
Имеет смысл в работающей версии запретить создание core файла с помощью установок setrlimit(2) во избежание возможности получения доступа к какой-нибудь закрытой информации, находившейся в памяти процесса.
#include #include #include #include #include class A < public: A()< printf("A::A()\n"); >~A() < printf("A::~A()\n"); >>; void * thread1(void * thread_arg) < A a; printf("thread1 started\n"); //for(int i=0;i<10;i++)< for(;;)< sleep(1); pthread_testcancel(); >> int main() < pthread_t th1; if(pthread_create(&th1,NULL,thread1,NULL) != 0)< perror("pthread_create failed"); exit(1); >sleep(2); printf("Sending cancelation request to th1\n"); pthread_cancel(th1); printf("Cancelation request sent, making join on th1\n"); pthread_join(th1, NULL); printf("main thread finished\n"); return0; >