趣谈 Linux 操作系统
刘超
前网易杭州研究院云计算技术部首席架构师
85458 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 72 讲
趣谈 Linux 操作系统
15
15
1.0x
00:00/00:00
登录|注册

19 | 线程的创建:如何执行一个新子项目?

课堂练习
总结时刻
用户态执行线程
内核态创建任务
用户态创建线程
线程的创建

该思维导图由 AI 生成,仅供参考

上一节,我们了解了进程创建的整个过程,今天我们来看线程创建的过程。
我们前面已经写过多线程编程的程序了,你应该都知道创建一个线程调用的是 pthread_create,可你知道它背后的机制吗?

用户态创建线程

你可能会问,咱们之前不是讲过了吗?无论是进程还是线程,在内核里面都是任务,管起来不是都一样吗?但是问题来了,如果两个完全一样,那为什么咱们前两节写的程序差别那么大?如果不一样,那怎么在内核里面加以区分呢?
其实,线程不是一个完全由内核实现的机制,它是由内核态和用户态合作完成的。pthread_create 不是一个系统调用,是 Glibc 库的一个函数,所以我们还要去 Glibc 里面去找线索。
果然,我们在 nptl/pthread_create.c 里面找到了这个函数。这里的参数我们应该比较熟悉了。
int __pthread_create_2_1 (pthread_t *newthread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)
{
......
}
versioned_symbol (libpthread, __pthread_create_2_1, pthread_create, GLIBC_2_1);
下面我们依次来看这个函数做了些啥。
首先处理的是线程的属性参数。例如前面写程序的时候,我们设置的线程栈大小。如果没有传入线程属性,就取默认值。
const struct pthread_attr *iattr = (struct pthread_attr *) attr;
struct pthread_attr default_attr;
if (iattr == NULL)
{
......
iattr = &default_attr;
}
接下来,就像在内核里一样,每一个进程或者线程都有一个 task_struct 结构,在用户态也有一个用于维护线程的结构,就是这个 pthread 结构。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入介绍了线程的创建机制,强调了用户态和内核态的合作关系。在用户态,线程的创建调用的是pthread_create函数,实际上是Glibc库的一个函数。在Glibc库的nptl/pthread_create.c文件中,我们可以找到pthread_create函数的实现。该函数首先处理线程的属性参数,然后创建线程栈。为了防止栈的访问越界,在栈的末尾会有一块空间guardsize。为了避免不断地申请和清除线程栈使用的内存块,需要有一个缓存来管理线程栈。接着介绍了内核态创建任务的过程,包括了clone系统调用的实现和对标志位的处理。文章还详细解释了对于亲缘关系的影响和信号的处理。整体来说,本文深入浅出地介绍了线程的创建机制,展现了用户态和内核态的合作关系。 在用户态执行线程时,根据__clone的第一个参数,回到用户态也不是直接运行指定的函数,而是一个通用的start_thread。在start_thread入口函数中,才真正的调用用户提供的函数。在用户的函数执行完毕之后,会释放线程相关的数据,例如线程本地数据和线程数目也减一。如果这是最后一个线程了,就直接退出进程。整个线程的生命周期到这里就结束了。 文章还总结了创建进程和创建线程在用户态和内核态的不同。创建进程调用的系统调用是fork,在copy_process函数里面,会将五大结构都复制一遍,父进程和子进程各用各的数据结构。而创建线程调用的是系统调用clone,在copy_process函数里面,五大结构仅仅是引用计数加一,也即线程共享进程的数据结构。 最后,文章提出了课堂练习,鼓励读者尝试查看一个进程的线程以及线程栈的使用情况,并欢迎读者分享疑惑和见解。整篇文章内容丰富,深入浅出,适合技术人员学习和探讨。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《趣谈 Linux 操作系统》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(33)

  • 最新
  • 精选
  • jacy
    pstree -apl pid看进程树 pstack pid 看栈

    作者回复: 赞

    2019-08-01
    28
  • why
    老师, 多线程的内核栈是共享的吗, 会不会出现问题?

    作者回复: 不共享,进了内核都是单独的任务了

    2019-05-14
    2
    16
  • humor
    老师之前说过进程默认会有一个主线程,意思是在创建进程的时候也会同时创建一个线程吗?

    作者回复: 不会,这个进程的task_struct就代表这个线程

    2019-05-30
    2
    10
  • 蹦哒
    老师、同学们,不知道如下认识是否正确呢: 1.原来线程存在的价值是复用进程的部分内存(引用五大结构),又是一个享元模式(Flyweight Design Pattern)的体现 2.线程函数局部变量在用户态的线程栈中(是在进程的堆里面创建的),独立的内存块,所以多线程之间无需考虑共享数据问题;而进程的全局变量,由于多线程是共享了进程数据,再加上各个线程在内核中是独立的task被调度系统调度,随时会被抢占并且访问同一个全局变量,所以多线程之间需要做共享数据保护

    作者回复: 是的

    2020-06-14
    9
  • 徐凯
    "将这个线程栈放到 stack_used 链表中,其实管理线程栈总共有两个链表,一个是 stack_used,也就是这个栈正被使用;另一个是 stack_cache,就是上面说的,一旦线程结束,先缓存起来,不释放,等有其他的线程创建的时候,给其他的线程用。" 这一段是线程池的意思么 如果是的话 既然内部已经有这个设计 我们有时候还要在程序中自己去设计一个呢?

    作者回复: 内核没有线程池的概念,把线程弄一个池子,是业务层做的。这里只是内核栈的复用。

    2019-05-10
    7
  • lfn
    所以,线程局部变量其实是存储在每个线程自己的用户栈里咯?

    作者回复: 是的

    2019-05-10
    3
  • youyui
    线程要进行上下文切换应该有各自的内核栈吧,那内核栈在哪里创建的?

    作者回复: fork的时候

    2019-06-19
    1
  • nora
    之前总是认为线程和进程都占用了内核的taskstruct 认为实际上线程进程没啥区别,这篇文章真是醍醐灌顶啊,谢谢老师。

    作者回复: 赞,加油

    2019-05-18
    1
  • tuyu
    老师, 我最近在看k8s专栏, docker的原理是使用clone的namespace参数, 所以容器的创建, 实际上是线程的创建吗

    作者回复: 在容器那一节会讲的,clone有个特殊的参数操作namespace。其实clone和fork底层调用的是差不多的

    2019-08-26
  • kdb_reboot
    老师你终于出现了;还是之前那个问题,我现在进度比较慢,只把进程管理子系统看完了,看了一个多月,看了鸟哥私房菜 apue ulk lkd 内核代码 libc 还有你的专栏。因为进度太慢了 我的问题是后面老师还会关注这个专栏吗,或者有什么方法可以一起讨论或者联系到老师? 比如微信群或者邮箱?我做驱动开发,我感觉你的专栏完全就是为我而写的;)

    作者回复: 会关注这个专栏的

    2019-08-21
收起评论
显示
设置
留言
33
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部