• Linux系统线程创建的过程


    推荐使用线程的理由有哪些

    1. 线程共享进程的所有资源,如mm_struct,所以线程的创建比进程更快;
    2. 由于同一个进程的线程间共享mm_struct,所以线程间的数据传输效率会更高,不需要依赖什么通信机制;
    3. 一个进程中,可以实现程序真正的并行;
    4. 多CPU系统中,多线程可以真正的并发执行,提高资源利用率

    进程和线程的关系:

    进程 = = 资源管理 + 线程

    线程可以访问的数据

    1. 代码,.text
    2. 线程栈,从进程的堆上开辟,大小默认为 8M
    3. 全局数据
    4. 线程私有数据,thread_local,对应linux上的接口分别是,pthread_key_create(),ptread_setspecific(),pthread_getspecific()以及pthread_key_delete()
    #ulimit -a
    core file size          (blocks, -c) 0
    data seg size           (kbytes, -d) unlimited
    scheduling priority             (-e) 0
    file size               (blocks, -f) unlimited
    pending signals                 (-i) 15276
    max locked memory       (kbytes, -l) 65536
    max memory size         (kbytes, -m) unlimited
    open files                      (-n) 1024
    pipe size            (512 bytes, -p) 8
    POSIX message queues     (bytes, -q) 819200
    real-time priority              (-r) 0
    stack size              (kbytes, -s) 8192
    cpu time               (seconds, -t) unlimited
    max user processes              (-u) 15276
    virtual memory          (kbytes, -v) unlimited
    file locks                      (-x) unlimited
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    进程维护线程栈空间

    使用两个链表来管理这些线程栈:

    1. stack_used 维护还没有退出的线程的线程栈
    2. stack_cache 维护已经退出的线程的线程栈。一般退出的线程的线程栈并不马上销毁,而是缓存在堆中,等下次有新的线程启动了,直接拿来使用即可,不需要重新的申请分配。

    Linux系统创建线程的过程

    1. 用户程序调用Linux库函数,pthread_create()

    2. 试图根据设置用户态的栈的大小从stack_cache找出合适大小的线程栈,如果有,设置为线程栈,如果没有则从堆中申请指定大小的空间作为线程栈;

    3. 创建一个pthread实例,将这个实例放在线程栈的栈低位置,pthread中存放可当前线程的数据,如线程的私有数据,栈的大小,需要执行的函数等。

    4. 主线程调用create_thread(),create_thread()将调用clone()产生系统调用,陷入内核

    5. 将主线程的寄存器信息保存在主线程内核栈中,然后调用sys_clone()

    6. 紧接着调用do_fork():

      创建task_struct,以及对应的内核栈

      不需要复制进程的task_struct,共享进程的task_struct,类似于浅拷贝

    7. 维护亲缘关系:

      tgid 所属进程的pid

      pid 自己的进程id

    如果tgid==pid,那么表示当前task是一个进程,如果tgid!=pid,表示当前task是一个线程。

    1. 将该线程的task_struct加入task_struct的链表队列中,返回用户空间

    这里调用clone()前,进入内核态前,保存的是主线程的栈和指令指针,按照一般的系统调用来看,
    返回用户态后,恢复用户态的执行后,应该是恢复主线程的状态。但是这里clone()与一般的系统调用
    不一样,clone()返回后,恢复的栈是刚刚创建的线程的栈,栈顶指针等都是指向刚刚创建的线程的栈。

    1. 调用start_thread(),指向线程函数;
    2. 直到线程结束,将线程栈归还给stack_cache.

    小结

    线程的创建是用户空间和内核空间共同配合完成的,上述过程中前三部都是发生在用户态,旨在产生一个用户态的thread实例,后面的步骤发生在内核态,旨在产生一个内核态的thread实例,将用户态和内核态的thread实例一一对应,实际上就是一对一的线程模型。

  • 相关阅读:
    网络安全(黑客)自学
    SpringSecurity - 自定义用户认证
    java网络编程
    浅识JVM
    几款很好看的爱心表白代码(动态)
    c++ 异常处理简单示例
    Failed to execute org.scala-tools:maven-scala-plugin:2.15.2解决
    【web开发】12、Django知识点回顾
    为网页添加一个弹窗提示音乐播放
    UE学习笔记-- UE4 相比较 UE5 的 double属性,删除编辑器界面右边的图标。
  • 原文地址:https://blog.csdn.net/KingOfMyHeart/article/details/125475198