用户态:从头文件pthread.h开始
pthread_create 函数的声明在 pthread.h 头文件中,在 Linux 中,C代码头文件的查找路径起点为 /usr/include 文件夹,我们相应地在其中找到了 pthread.h 文件,里面的 pthread_create 声明为:
extern int pthread_create (pthread_t *__restrict __newthread,
const pthread_attr_t *__restrict __attr,
void *(*__start_routine) (void *),
void *__restrict __arg) __THROWNL __nonnull ((1, 3));
extern 说明它是一个外部函数,通过读取程序的 ELF 信息
000000003fc8 000900000007 R_X86_64_JUMP_SLO 0000000000000000 pthread_create@GLIBC_2.34 + 0
可以知道,这个函数在 glibc 库中,查看自己 glibc 库的版本,
ldd --version
是 glibc-2.35,下载 glibc-2.35 源代码,将 glibc-2.35 文件夹导入 vs code,查找 pthread_create 函数声明,找不到,调用gdb查看程序的调用栈
#0 __pthread_create_2_1 (newthread=0x7fffffffde70, attr=0x0, start_routine=0x555555555209 , arg=0x0) at ./nptl/pthread_create.c:621
其调用的是一个叫 __pthread_create_2_1 的函数,而不是 pthread_create,并且指出了其位置在 ./nptl/pthread_create.c 文件的第 621 行,我截取下了这个片段:
__pthread_create_2_1 (pthread_t *newthread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg){/*...*/}
versioned_symbol (libc, __pthread_create_2_1, pthread_create, GLIBC_2_34);
libc_hidden_ver (__pthread_create_2_1, __pthread_create)
下面的 versioned_symbol 格外吸引我的注意,因为它使 __pthread_create_2_1 和 pthread_create 产生了联系,事实上这是一个宏,它为**__pthread_create_2_1** 函数在库中建立了一个带版本号的标志 pthread_create,所以我们才能通过 pthread_create 函数调用到 __pthread_create_2_1 函数,将它最终展开,会得到这样一个语句:
__asm__ (".symver " "__pthread_create_2_1" "," "pthread_create" "@@" "VERSION_libc_GLIBC_2_34");
下面我们通过 __pthread_create_2_1 的定义来追踪其是如何进行系统调用的:
继续找 __clone3 的C语言定义发现找不到了,通过文本搜索,在 ./sysdeps/unix/sysv/linux/x86_64/clone3.S 找到了函数入口:
ENTRY (__clone3)
/* Sanity check arguments. */
movl $-EINVAL, %eax
test %RDI_LP, %RDI_LP /* No NULL cl_args pointer. */
jz SYSCALL_ERROR_LABEL
test %RDX_LP, %RDX_LP /* No NULL function pointer. */
jz SYSCALL_ERROR_LABEL
/* Save the cl_args pointer in R8 which is preserved by the
syscall. */
mov %RCX_LP, %R8_LP
/* Do the system call. */
movl $SYS_ify(clone3), %eax
/* End FDE now, because in the child the unwind info will be
wrong. */
cfi_endproc
syscall
test %RAX_LP, %RAX_LP
jl SYSCALL_ERROR_LABEL
jz L(thread_start)
ret
可以看出,这里进行了clone3系统调用。接下来就要深入内核1了。
内核态:从clone3开始
系统调用会调用内核的函数,这就要看内核的源码了,首先查看Linux内核版本
uname -r
得到我的内核版本为 linux-5.15,下载其源代码,并将整个源码文件夹导入 vs code,直接查找函数定义同样是找不到的,通过文本搜索,找到了其通过宏间接产生定义的语句,在./kernel/fork.c的第2843行:
SYSCALL_DEFINE2(clone3, struct clone_args __user *, uargs, size_t, size) { int err; struct kernel_clone_args kargs; pid_t set_tid[MAX_PID_NS_LEVEL]; kargs.set_tid = set_tid; err = copy_clone_args_from_user(&kargs, uargs, size); if (err) return err; if (!clone3_args_valid(&kargs)) return -EINVAL; return kernel_clone(&kargs); }
这里的 SYSCALL_DEFINE2 宏会为需要两个参数的系统调用函数产生定义,宏的第一个参数是函数名,之后就是成对的参数的类型和参数名,这里是两对,中括号内的便是它的函数体,我们画出它的调用树:
源代码过于庞大,只画出了重要节点的分支。 ↩︎