深入探究 Linux 線程創(chuàng)建:glibc 的 pthread_create 與神秘的 clone 函數(shù)
在Linux下進(jìn)行多線程編程時(shí),我們通常會(huì)使用POSIX線程庫(kù)(pthread),它提供了一組用于線程管理的API函數(shù),其中最常用的就是pthread_create函數(shù)。不過(guò),了解pthread_create的內(nèi)部工作原理和底層系統(tǒng)調(diào)用對(duì)于深入理解Linux多線程編程非常重要。
clone與fork
pthread_create函數(shù)是glibc中實(shí)現(xiàn)的POSIX線程庫(kù)的一部分,它是基于Linux系統(tǒng)調(diào)用clone來(lái)創(chuàng)建線程的。clone函數(shù)是一個(gè)比f(wàn)ork更靈活和底層的系統(tǒng)調(diào)用,它允許我們創(chuàng)建一個(gè)新的進(jìn)程或線程,而fork只能創(chuàng)建新進(jìn)程。
clone函數(shù)和fork函數(shù)都用于創(chuàng)建新的執(zhí)行流,但它們有一些重要的區(qū)別:
(1) 創(chuàng)建級(jí)別:
- fork函數(shù)用于創(chuàng)建一個(gè)新的進(jìn)程,新進(jìn)程是調(diào)用進(jìn)程的副本。
- clone函數(shù)可以用于創(chuàng)建新的線程或新的進(jìn)程,取決于傳遞給它的標(biāo)志。這使clone比f(wàn)ork更加靈活。
(2) 共享資源:
- fork創(chuàng)建的進(jìn)程有獨(dú)立的地址空間,父子進(jìn)程之間不共享內(nèi)存。
- clone可以選擇與父進(jìn)程共享內(nèi)存、文件描述符等資源,這可以用于創(chuàng)建線程。
(3) 系統(tǒng)開(kāi)銷:
- 由于fork會(huì)復(fù)制整個(gè)地址空間,因此開(kāi)銷較大。
- clone可以選擇共享資源,因此通常比f(wàn)ork更高效。
clone函數(shù)的實(shí)現(xiàn)原理
clone函數(shù)通過(guò)傳遞不同的標(biāo)志參數(shù)來(lái)控制其行為。以下是一些常見(jiàn)的標(biāo)志參數(shù)及其作用:
- CLONE_VM:共享虛擬內(nèi)存,子進(jìn)程/線程與父進(jìn)程共享地址空間。
- CLONE_FS:共享文件系統(tǒng)信息,子進(jìn)程/線程與父進(jìn)程共享文件系統(tǒng)。
- CLONE_FILES:共享文件描述符表,子進(jìn)程/線程與父進(jìn)程共享打開(kāi)的文件。
- CLONE_SIGHAND:共享信號(hào)處理,子進(jìn)程/線程與父進(jìn)程共享信號(hào)處理器表。
- CLONE_PARENT_SETTID:設(shè)置父進(jìn)程的TID(線程ID)。
- CLONE_CHILD_SETTID:設(shè)置子進(jìn)程/線程的TID。
clone函數(shù)的核心思想是在新的執(zhí)行流中執(zhí)行一個(gè)新的函數(shù),這個(gè)函數(shù)通常是main函數(shù)的替代品。這個(gè)新函數(shù)在創(chuàng)建線程時(shí)會(huì)被調(diào)用,它可以執(zhí)行不同的任務(wù),使得多線程編程成為可能。
clone函數(shù)創(chuàng)建線程示例
要使用clone函數(shù)創(chuàng)建線程,我們需要傳遞適當(dāng)?shù)臉?biāo)志參數(shù)和一個(gè)函數(shù)指針,該函數(shù)指針指向線程要執(zhí)行的函數(shù)。以下是一個(gè)簡(jiǎn)單的示例:
#define _GNU_SOURCE
#include <stdio.h>
#include <sched.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#define STACK_SIZE (1024 * 1024)
void *child_function(void *arg) {
printf("Child thread: PID=%d, TID=%ld\n", getpid(), syscall(SYS_gettid));
return NULL;
}
int main() {
char *stack;
char *stack_top;
pid_t pid;
stack = (char *)malloc(STACK_SIZE);
if (stack == NULL) {
perror("malloc");
exit(EXIT_FAILURE);
}
stack_top = stack + STACK_SIZE;
pid = clone(child_function, stack_top, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, NULL);
if (pid == -1) {
perror("clone");
exit(EXIT_FAILURE);
}
printf("Parent thread: PID=%d, TID=%ld, Child PID=%d\n", getpid(), syscall(SYS_gettid), pid);
// Wait for the child to finish
if (waitpid(pid, NULL, 0) == -1) {
perror("waitpid");
exit(EXIT_FAILURE);
}
free(stack);
return 0;
}
這個(gè)示例中,我們使用clone函數(shù)創(chuàng)建了一個(gè)新線程,新線程將執(zhí)行child_function函數(shù)。父線程和子線程可以共享虛擬內(nèi)存、文件系統(tǒng)信息等資源,這使得它們可以方便地共享數(shù)據(jù)。