线程的创建过程

简介: 【9月更文挑战第15天】线程是由内核和用户态协同实现的机制。`pthread_create` 函数在 Glibc 中定义,首先处理线程属性参数,如栈大小,默认值或传入值。每个线程有一个 `pthread` 结构来维护状态。创建线程时,需要分配线程栈,并进行以下操作:获取栈大小、设置保护区域、缓存管理、内存映射、栈初始化及保护、填充 `pthread` 结构并管理栈缓存。最终通过 `create_thread` 函数调用 `clone` 系统调用创建线程,共享进程数据结构

线程不是一个完全由内核实现的机制,它是由内核态和用户态合作完成的。pthread_create 不是一个系统调用,是 Glibc 库的一个函数,所以我们还要去 Glibc 里面去找线索。

首先处理的是线程的属性参数。例如前面写程序的时候,我们设置的线程栈大小。如果没有传入线程属性,就取默认值。

const struct pthread_attr *iattr = (struct pthread_attr *) attr;
struct pthread_attr default_attr;
if (iattr == NULL)
{
  ......
  iattr = &default_attr;
}

接下来,就像在内核里一样,每一个进程或者线程都有一个 task_struct 结构,在用户态也有一个用于维护线程的结构,就是这个 pthread 结构。

struct pthread *pd = NULL;

凡是涉及函数的调用,都要使用到栈。每个线程也有自己的栈。那接下来就是创建线程栈了。

int err = ALLOCATE_STACK (iattr, &pd);

ALLOCATE_STACK 是一个宏,我们找到它的定义之后,发现它其实就是一个函数。allocate_stack 主要做了以下这些事情:

  • 如果你在线程属性里面设置过栈的大小,需要你把设置的值拿出来;
  • 为了防止栈的访问越界,在栈的末尾会有一块空间 guardsize,一旦访问到这里就错误了;
  • 其实线程栈是在进程的堆里面创建的。如果一个进程不断地创建和删除线程,我们不可能不断地去申请和清除线程栈使用的内存块,这样就需要有一个缓存。get_cached_stack 就是根据计算出来的 size 大小,看一看已经有的缓存中,有没有已经能够满足条件的;
  • 如果缓存里面没有,就需要调用 __mmap 创建一块新的,系统调用那一节我们讲过,如果要在堆里面 malloc 一块内存,比较大的话,用 __mmap;
  • 线程栈也是自顶向下生长的,还记得每个线程要有一个 pthread 结构,这个结构也是放在栈的空间里面的。在栈底的位置,其实是地址最高位;
  • 计算出 guard 内存的位置,调用 setup_stack_prot 设置这块内存的是受保护的;
  • 接下来,开始填充 pthread 这个结构里面的成员变量 stackblock、stackblock_size、guardsize、specific。这里的 specific 是用于存放 Thread Specific Data 的,也即属于线程的全局变量;
  • 将这个线程栈放到 stack_used 链表中,其实管理线程栈总共有两个链表,一个是 stack_used,也就是这个栈正被使用;另一个是 stack_cache,就是上面说的,一旦线程结束,先缓存起来,不释放,等有其他的线程创建的时候,给其他的线程用。

真正创建线程的是调用 create_thread 函数,这个函数定义如下:

static int
create_thread (struct pthread *pd, const struct pthread_attr *attr,
bool *stopped_start, STACK_VARIABLES_PARMS, bool *thread_ran)
{
  const int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM | CLONE_SIGHAND | CLONE_THREAD | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | 0);
  ARCH_CLONE (&start_thread, STACK_VARIABLES_ARGS, clone_flags, pd, &pd->tid, tp, &pd->tid);
  /* It's started now, so if we fail below, we'll have to cancel it
and let it clean itself up.  */
  *thread_ran = true;
}

如果在进程的主线程里面调用其他系统调用,当前用户态的栈是指向整个进程的栈,栈顶指针也是指向进程的栈,指令指针也是指向进程的主线程的代码。此时此刻执行到这里,调用 clone 的时候,用户态的栈、栈顶指针、指令指针和其他系统调用一样,都是指向主线程的。

但是对于线程来说,这些都要变。因为我们希望当 clone 这个系统调用成功的时候,除了内核里面有这个线程对应的 task_struct,当系统调用返回到用户态的时候,用户态的栈应该是线程的栈,栈顶指针应该指向线程的栈,指令指针应该指向线程将要执行的那个函数。

创建进程的话,调用的系统调用是 fork,在 copy_process 函数里面,会将五大结构 files_struct、fs_struct、sighand_struct、signal_struct、mm_struct 都复制一遍,从此父进程和子进程各用各的数据结构。而创建线程的话,调用的是系统调用 clone,在 copy_process 函数里面, 五大结构仅仅是引用计数加一,也即线程共享进程的数据结构。


相关文章
|
缓存 数据库
15- 什么是缓存击穿 ? 怎么解决 ?
`# 缓存击穿简介及解决方案` 缓存击穿是指大量请求同时命中已过期的缓存,导致数据库压力骤增。解决方法包括:1) 预热热点数据;2) 设置热点数据永不过期;3) 使用加锁或限流策略。
319 4
|
存储 JSON Rust
【一起学Rust | 进阶篇 | reqwest库】纯 Rust 编写的 HTTP 客户端——reqwest
【一起学Rust | 进阶篇 | reqwest库】纯 Rust 编写的 HTTP 客户端——reqwest
2002 0
|
11月前
|
安全 Java 调度
线程的四种创建方式
【10月更文挑战第22天】在多线程编程中,还需要注意线程安全、死锁等问题,以确保程序的正确性和稳定性。通过合理地运用线程创建方式和相关技术,我们可以充分发挥多线程的优势,提高程序的性能和并发处理能力。
155 12
|
3月前
|
Java
分析Java中的static、final以及static final关键字的不同用法。
综上所述,static, final以及 static final关键字在Java编程中有着明确和关键的角色。static使成员独立于类的实例而存在,final保证值或行为的不可变性,而 static final组合了静态成员的共享特性和常量的不可变性。这些特性在设计类的时候,对于资源共享、安全性保证和优化性能都是至关重要的。
198 0
|
11月前
|
XML 数据格式 索引
xpath模块使用教程
XPath 是一种在 XML 文档中查找信息的语言,广泛用于 HTML 解析。本文介绍了 XPath 的安装与使用,包括 lxml 库的安装、解析流程、基本语法、路径表达式、谓语、通配符、多路径选择、逻辑运算、属性查询、索引查询、模糊查询、内容查询、属性值获取及节点内容转换等。通过实例详细说明了各种用法,帮助读者快速掌握 XPath 的应用技巧。
763 39
|
11月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
11月前
|
IDE Linux 开发工具
物理设备命名规则
在Linux系统中,一切皆为文件,包括硬件设备。udev设备管理器负责将硬件名称规范化,以便用户通过设备文件名了解设备属性和分区信息。常见的设备文件名包括:IDE设备(/dev/hd[a-d])、SCSI、SATA、U盘(/dev/sd[a-p])、软驱(/dev/fd[0-1])、光驱(/dev/cdrom)和鼠标(/dev/mouse)。主分区或扩展分区编号从1到4,逻辑分区从5开始。
339 2
|
10月前
|
存储 安全 Java
ConcurrentLinkedQueue详解
通过本文的介绍,希望您能够深入理解 `ConcurrentLinkedQueue`的工作原理、主要特性、常用方法以及实际应用,并在实际开发中灵活运用这些知识,编写出高效、健壮的并发程序。
249 3
|
缓存 网络协议 开发者
HTTP1.0、HTTP1.1 、HTTP2.0和HTTP3.0 的区别【面试题】
HTTP1.0、HTTP1.1 、HTTP2.0和HTTP3.0 的区别【面试题】
1674 0
HTTP1.0、HTTP1.1 、HTTP2.0和HTTP3.0 的区别【面试题】