一文带你吃透线程和进程在linux内核角度上的区别 | 青训营笔记

203 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第5篇笔记

在青训营课程中学习了go语言,Go语言内置了协程,协程本质上是用户自定义的线程。那么线程在Linux内核是如何工作的呢?他与进程又有什么区别?本文带你一探究竟!

首先说些大家肯定都知道的:

  1. 线程有自己的全局数据
  2. 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。

这样说,肯定是背诵的八股文,本文带你从内核角度重新认识线程和进程!

在linux内核角度出发的话,线程和进程都被看做是一个任务,统一被task_struct管理,链表将所有的task_struct串起来。task_struct 中,使用pid(线程id),tgid(进程的主线程id)可以区分进程和线程。struct task_struct有保存有关线程/进程中的一切信息,主要包括有线程/进程状态、与其他线程/进程关系、虚拟内存相关、日志相关、线程/进程限制等。

进程还是线程的创建都是调用系统调用接口实现的。创建的主要工作实际上就是创建task_strcut结构体,并将该对象添加至工作队列中去。而线程和进程在创建时,通过传递flag的不同,而选择不同的方式共享父进程/父线程的资源,从而造成在用户空间所看到的进程和线程的不同。无论采用何种方式创建进程和线程,最终都会调用do_fork接口。

进程和线程的do_fork.png

do_fork主要做了三件事,

1 copy_process

2 确定PID

3 wake_up_new_task

wake_up_new_task即是将新创建的线程/进程添加至调度程序的队列中 do_fork主要的一部分工作集中在copy_process中,线程与进程之间的区别也是在该接口中体现,接口的代码流程图如下所示:

copy_process进程和线程的区别.png

当上层以pthread_create接口call到kernel时,clone_flag是有CLONE_PTHREAD标识

CLONE_PTHREAD标识在最后两个步骤(设置各个ID、进程关系)时体现:(current为当前进程/线程的task_struct结构体 ,p为新创建的结构体对象)

最后一步:

if (clone_flags & CLONE_THREAD) {
    p->group_leader = current->group_leader;
    p->tgid = current->tgid;
} else {
    p->group_leader = p;
    p->tgid = p->pid;
}

在这些形如copy_abc的接口中,通过判断该flag标识,决定对内核子系统资源是与父进程/线程公用还是新创建出来。可参考下图。

CLONE_abc.png

一开始父进程和子进程对于res_abc指向同一个内容(通过dup_task_struct接口实现,子进程完全copy父进程),然后经过copy_abc程序,当有CLONE_abc标识时,父进程会共享资源,同时res_abc的引用计数+1,当!CLONE_abc时,会创建一个res_abc的副本。