Linux:追踪 pthread_create 函数源代码_glibc-2.35_linux-5.15

用户态:从头文件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_1pthread_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 的定义来追踪其是如何进行系统调用的:

__pthread_create_2_1
allocate_stack
tls_setup_tcbhead
atomic_increment
__libc_signal_block_all
create_thread
__clone_internal
__clone3
__libc_signal_restore_set

继续找 __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了。


  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 宏会为需要两个参数的系统调用函数产生定义,宏的第一个参数是函数名,之后就是成对的参数的类型和参数名,这里是两对,中括号内的便是它的函数体,我们画出它的调用树:

    clone3
    copy_clone_args_from_user
    kernel_clone
    copy_process
    sigemptyset
    INIT_HLIST_NODE
    spin_lock_irq
    recalc_sigpending
    spin_unlock_irq
    dup_task_struct
    alloc_task_struct_node
    alloc_thread_stack_node
    task_stack_vm_area
    arch_dup_task_struct
    memcpy
    fpu_clone
    memset
    fpregs_lock
    memcpy/save_fpregs_to_fpstate
    fpregs_unlock
    set_tsk_thread_flag
    trace_x86_fpu_copy_src
    trace_x86_fpu_copy_dst
    setup_thread_stack
    clear_user_return_notifier
    clear_tsk_need_resched
    set_task_stack_end_magic
    clear_syscall_work_syscall_user_dispatch
    dup_user_cpus_ptr
    refcount_set
    account_kernel_stack
    kcov_task_init
    kmap_local_fork
    ftrace_graph_init_task
    rt_mutex_init_task
    lockdep_assert_irqs_enabled
    copy_creds
    delayacct_tsk_init
    INIT_LIST_HEAD
    rcu_copy_process
    spin_lock_init
    init_sigpending
    prev_cputime_init
    task_io_accounting_init
    acct_clear_integrals
    posix_cputimers_init
    audit_set_context
    cgroup_fork
    sched_fork
    perf_event_init_task
    audit_alloc
    shm_init_task
    security_task_alloc
    copy_semundo
    copy_files
    copy_fs
    copy_sighand
    copy_signal
    copy_mm
    copy_namespaces
    copy_io
    copy_thread
    stackleak_task_init
    futex_init_task
    user_disable_single_step
    clear_task_syscall_work
    clear_tsk_latency_tracing
    cgroup_can_fork
    ktime_get_ns
    ktime_get_boottime_ns
    write_lock_irq
    klp_copy_process
    sched_core_fork
    spin_lock
    copy_seccomp
    rseq_fork
    init_task_pid_links
    hlist_del_init
    spin_unlock
    syscall_tracepoint_update
    write_unlock_irq
    proc_fork_connector
    sched_post_fork
    cgroup_post_fork
    perf_event_fork
    trace_task_newtask
    uprobe_copy_process
    copy_oom_score_adj
    add_latent_entropy
    trace_sched_process_fork
    get_task_pid
    pid_vnr
    wake_up_new_task
    put_pid

    源代码过于庞大,只画出了重要节点的分支。 ↩︎

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
pthread_create函数Linux下用于创建线程的函数。它的原型如下: ```c #include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); ``` 该函数的参数解释如下: - thread:指向pthread_t类型的指针,用于存储新创建线程的ID。 - attr:指向pthread_attr_t类型的指针,用于设置线程的属性。如果传入NULL,则使用默认属性。 - start_routine:指向一个函数的指针,该函数是新线程的起始函数。新线程将从该函数开始执行。 - arg:传递给start_routine函数的参数。 该函数的返回值为整型,表示函数执行的结果。如果成功创建线程,则返回0;如果出现错误,则返回一个错误号。 下面是一个使用pthread_create函数创建线程的示例: ```c #include <stdio.h> #include <pthread.h> void *thread_func(void *arg) { int *num = (int *)arg; printf("This is a new thread. The argument is %d\n", *num); pthread_exit(NULL); } int main() { pthread_t thread; int num = 10; int ret = pthread_create(&thread, NULL, thread_func, &num); if (ret != 0) { printf("Failed to create thread\n"); return 1; } printf("Main thread\n"); pthread_join(thread, NULL); return 0; } ``` 该示例中,我们定义了一个新线程的起始函数thread_func,该函数接收一个整型参数,并在新线程中打印该参数的值。在主线程中,我们使用pthread_create函数创建了一个新线程,并将参数传递给新线程。然后,主线程继续执行自己的代码,最后使用pthread_join函数等待新线程的结束。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lcy_Knight

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值