进程 线程 协程
进程 线程 协程,网上资料繁多,有些是个人理解,存在一定误导性。
操作系统 考试经常会考:进程是资源的分配单位,线程是调度单位;进程切换开销大、线程切换开销小等等。网上经常会说协程就是线程等等,在linux 的世界里:线程是轻量级进程,协程是轻量级线程;
听者知道说者为什么这样说,这叫主动,否则为被动,只有主动才能掌控节奏 ,源码会告诉我们一切。
当我们描述一个事物时,信息自然是越多越好的,但主体信息 总无非:共性、个性、起源
共性用于基本描述生成概念性认识、个性用于共性中的个体标注,是信息的进一步详细,才有了管理。
对 进程描述而言程序中描述它就是一个 task_struct,描述一个进程所需的各种资源(虚拟内存地址空间、cpu 寄存器、各种相关资源等), pid 为其身份证号,
linux 线程 (轻量级进程LWP)依然使用进程(task_struct)的方式来描述,然而又要满足虚拟地址空间共享等,在task_strcut 的成员变量中, (同一个线程组的进程)mm_struct 、file_struct 等指针,指向同一块区域,否则指向各自自己的区域(父子进程的mm 待后续分析)由于PID 是进程管理的身份证号,因此每一个task_struct 的pid 不一样。
thread_info 描述进程的基本信息
mm_struct 描述内存的结构体指针,
file_struct 描述文件描述符
signal_struct 描述信号
但是我们的用户程序中getpid()调用为什么返回一样呢,查看内核源码发现返回的是tgid,同一个线程组的tgid 值为主线程 thread_leader 的 pid.
/**
* sys_getpid - return the thread group id of the current process
*
* Note, despite the name, this returns the tgid not the pid. The tgid and
* the pid are identical unless CLONE_THREAD was specified on clone() in
* which case the tgid is the same in all threads of the same group.
*
* This is SMP safe as current->tgid does not change.
*/
SYSCALL_DEFINE0(getpid)
{
return task_tgid_vnr(current);
}
起源
不管是系统调用 fork、vfork、kernel_thread、clone (pthread_create ), 均调用的是do_fork. 可见linux 中连线程的诞生都是使用进程创建的接口。
内核线程
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
return _do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
(unsigned long)arg, NULL, NULL, 0);
}
内核线程使用的是主存而非虚拟地址空间(mm 指针为空,相应地active_mm),且不被trace(内核线程一般完成较为重要的功能,比如维护网络连接ksoftirq、脏内存回写等,如果被trace 极易发生系统级故障)。
flags 来控制新进程与旧进程的关系,是否共享vm 文件等等。
用户线程
pthread_create 本质上也是调用do_fork 创建的进程,由do_fork创建的进程都会 加入kernel 维护的 进程链表参与 kernel 调度。只不过与内核线程相比,多了虚拟地址空间访问,运行于用户态,使用系统调用陷入内核态交互处理,开销相对较内核线程大。
以上线程统可以用进程来称呼,均是linux 内核负责管理进程的生命周期
协程
以上由pthread 库用户创建的线程,本质上还是由内核参与调度处理,切换开销较大(调度要在用户态和内核态的切换),且由于线程当作进程处理,维护了很多描述信息,还要考虑抢占等复杂的内核机制。针对以上问题,更轻量级线程,协程诞生:协程完全是用户态维护的线程(不考虑系统调用),调度也由用户态自己控制,协程切换等也都由用户程序自己维护,对内核而言是看不见协程的
g g g g g g
lwp lwp lwp lwp
CPU cpu
内核调度lwp,lwp 调度G
golang 的协程实现原理见: