linux线程的理解,linux线程与进程的理解

首先明确一点,linux对进程和线程不作区分,统一由task_struct来管理全部进程和线程。linux

那么如何在linux下区分进程和线程呢?缓存

为何要引入线程的概念?数据结构

一个进程包含不少系统资源:进程控制块、虚存空间、文件系统,文件I/O、信号处理函数,建立一个进程的过程就是这些资源被建立的过程。多线程

系统调用fork建立一个进程时子进程是一段独立的内存空间,其中的资源是父进程资源的副本,两个进程是彻底独立不共享内存资源的,两者须要经过IPC进行通讯。app

这样的作法在有些场景下并非高效的作法,例如:好比某进程fork出一个子进程后,其子进程仅仅是为了调用exec执行另外一个执行文件,那么在fork过程当中对于内存空间的复制就是多余的;再例如:若是经过这种方式处理并行计算问题,那么就得在不一样cpu建立不一样的进程,而后经过IPC再把计算结果汇总,这样作的开销每每足以抵消并行计算带来的好处。函数

另外,进程是系统中程序执行和资源分配的基本单元,每一个进程都拥有本身的数据段、代码段和堆栈段,进程进行切换时都会伴随着上下文的切换,这也会带来开销。线程

因此把计算单元抽象到进程上是不充分的,这也就是许多系统中都引入了线程的概念的缘由。3d

Linux怎么建立进程?指针

Linux建立进程一共有三种方式:fork  vfork  clone。三个函数分别经过sys_fork()、sys_vfork()和sys_clone()调用do_fork()去作具体的建立工做,只不过传入的参数不一样。code

sys_fork():

asmlinkage long sys_fork(struct pt_regs regs)

{

return do_fork(SIGCHLD, regs.rsp, &regs, 0);

}

传入的参数SIGCHLD表示在子进程终止后将发送信号SIGCHLD信号通知父进程

sys_vfork():

asmlinkage long sys_vfork(struct pt_regs regs)

{

return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.rsp, &regs, 0);

}

sys_clone():

casmlinkage int sys_clone(struct pt_regs regs)

{

unsigned long clone_flags;

unsigned long newsp;

clone_flags = regs.ebx;

newsp = regs.ecx;

if (!newsp)

newsp = regs.esp;

return do_fork(clone_flags, newsp, &regs, 0);

}

Linux怎么建立线程?

为了方便移植,linux使用POSIX标准建立线程,在linux2.6以前使用linux Thread库实现多线程,从linux2.6开始,都是使用NPLT库来实现线程的建立。这两个库都是使用pthread_create()来建立线程。

抛开网上一大堆所谓的轻量级进程LWP,那些都过期了,都是2.6版本之前提出的概念,从2.6开始的多线程不须要理解这个LWP,只须要知道若是使用clone建立的进程与父进程共享内存空间则认为它是线程。

NPLT线程的建立 - -从用户态到内核态

3ba1641813ae028a8ffea87ca47ca10f.png

上图中,红色线以上是用户态,下面是内核态。左边虚线框内是单线程的,右边是多线程。

NPLT建立的线程属于“一对一”模型,为何叫一对一?由于调用pthread_create()时会在用户空间建立一个线程同时也会在内核空间建立与之对应的线程。

用户空间的线程 - - 负责执行线程的建立、销毁等操做

内核空间的线程 - - 负责调度

1 - pthread_create

pthread_create是建立线程的入口,主要参数是一个函数指针,其指向的函数为线程建立成功后要执行的函数。因为线程组中的各个线程是能够并行运行在不一样的CPU上的,因此各个线程必须得有本身独立的用户态栈和内核栈。Linux用户态栈的大小通常是8M,经过mmap在MemoryMapping Area中分配一块内存(不考虑用户态栈缓存)。在用户态也有一个数据结构用来描述线程(structpthread),该数据结构就放在用户态栈的最下面的位置,其中会存放线程建立成功后要执行的函数地址。

分配完structpthread和用户态栈以后(其实还有不少事情,不过都是些杂事),差很少就能够带着这些信息进入内核申请task_struct了。

2 - clone

像建立新的进程这种资源管理工做基本上都要经过内核完成,从用户态进入内核态时,都会把进程在用户态时的状态保存在内核栈中,这样完成了内核态的任务以后返回时能够恢复用户态的状态。

glibc在用户态对clone封装了一层,名为ARCH_CLONE。其中会把线程建立成功后要执行的函数地址以及参数压入用户态栈,而后再调用系统调用clone,这样系统调用clone返回后再从栈中取出线程函数以及对应的参数,继而开始执行线程函数。

系统调用(syscall)clone用于建立当前进程的一个副本,fork就是调用的clone。咱们这里是要建立线程,那么对clone的用法固然与fork有所区别了。区别主要在于clone的参数clone_flags的设置:

建立线程调用的clone:

87df71ad4b5a3ea869ab763a0947283b.png

建立进程调用的clone:

1cab0757155dac852481d4df58d423e6.png

能够看到建立线程时使用的clone_flags中多了CLONE_VM、CLONE_FS、CLONE_FILES和CLONE_SIGNAL(CLONE_SIGHAND| CLONE_THREAD),这些是实现Posixthread规范的关键。这些参数体如今NPLT线程在内核中的数据模型图中就是其中的mm、fs、files、signal、sighand和pending字段指向相同的对象。正由于这些数据的共享,线程间的数据共享和同步比在进程间简单得多。

NPLT线程在内核中的数据模型图

2b9fb5d869dd6fd5b12bf50a204461cc.png

不论是经过fork产生的进程仍是经过pthread_create产生的线程,其在内核中都对应着一个task_struct,linux_structure图中有两个task_struct,其中右边的task_struct是经过pthread_create产生的,所以左边的task_struct是threadgroup的leader。task_struct有不少字段,其中mm就是用来描述虚拟地址空间的。咱们能够看到两个task_struct的mm指向了同一个mm_struct对象,也就是前面提到的线程间共享同一个虚拟地址空间。

用户态中,多线程会做为一个进程看待,在内核中,会被抽象为“线程组”。线程组中的task_struct有不一样的pid字段,可是会有相同的tgid(threadgroup id)字段,这个tgid做为用户态进程的pid给上层使用。因此,在一个进程下的每一个线程中获取到的pid是相同的,用户态的pid与内核态的pid不是同一个东西,想获取线程在内核态的pid能够经过系统调用(gettid)来获取。线程组中的task_struct会经过一个链表(thread_group)连接起来,后面建立的线程的task_struct中的group_leader字段会指向leader。经过共享mm_struct、fs、signal相关的数据,再经过thread_group相关的设置,线程的概念基本就实现了

下一篇,咱们根据这篇的理论基础来总结一下pthread_create的使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值