奇葩的linux进程线程设计

发布于:2024-07-02 ⋅ 阅读:(17) ⋅ 点赞:(0)

哈喽,我是子牙老师。今天咱们聊聊linux的进程线程设计,会讲到很多很多曾经困惑着你,或一直折磨着你的东东

对于linux来说,它的机制有很多。但是你掌握它的内存机制、进程线程进制,其他机制学起来会非常轻松。所以linux的进程机制,大家在学习的时候一定要学得非常透彻!不要放过任何一个疑点!对于你理解容器技术,如docker、k8s,进程机制也至关重要!

坦白讲,linux的进程线程机制设计得比较绕,不像Windows的进程线程设计的那么容易理解。不清楚这里为什么要设计成这样。一个东西用一个struct来表示,多清晰,非要混在一起。吐槽完了咱们就开始吧,来看看这个奇葩设计是如何设计的

谈到进程线程,就不得不说到协程,我之前用C语言写过一个协程

如果你有这些疑惑,墙裂建议你把这篇文章吃透:

  1. linux系统的进程线程与Windows系统的进程线程有何不同
  2. linux进程包含哪些资源
  3. 如何理解linux中的进程、线程、内核线程、用户线程、线程组、进程组
  4. 什么是线程上下文
  5. 有没有进程上下文这一说
  6. linux内核如何识别是进程还是线程
  7. linux内核如何识别是子进程还是线程
  8. linux中有哪些创建进程与线程的方式
  9. linux内核支持协程吗

正文,enjoy

进程与线程

先搞清楚什么是进程、什么是线程。进程是os中资源管理最小单元,线程是os中调度最小单元。是不是很抽象?是不是没概念?那我举个例子吧,我最擅长的就是化抽象为具象

如果把整个人类社会比喻成操作系统,那人,就是进程。人管理的资源是什么?大脑、心脏、手脚……那线程是什么呢?线程就是你掌握的能力,你去干的每件事。你干每件事,都是共享你的大脑、心脏、手脚……就像真实的os中说的,线程不独占资源,线程共享资源。有没有get?没有get的话,多理解几遍,因为你没有这个认知,你学这些,就像在浆糊里打滚,一团乱

在真实的os中,进程管理哪些资源呢?以linux为例
在这里插入图片描述

因为线程是最小调度单位,所以只有线程上下文一说,没有进程上下文一说。准确表达就是:进程切换、线程调度。切换进程怎么理解?就相当于换一个人干活。上面那个例子,对于你理解任何进程线程现象,真的很不错

对了,很多小伙伴对线程上下文没概念,我再让你这个认知具象化
在这里插入图片描述

一个进程中可以创建多少线程呢?理论上来说是无限的,但是linux做了限制
在这里插入图片描述

顺便提下协程吧,协程不是内核态实现的,是用户态实现的,协程的本质是分享线程的CPU时间片资源,不用进内核。协程的设计,一般都是主动释放时间片,因为被动释放,就需要内核或者硬件的介入,协程就失去了存在的意义,就跟线程是一个概念了。我之前写过协程,感兴趣的移步【C语言写协程】

理想的进程线程设计

如果你自己写操作系统,对于进程、线程,你是不是会用不同的结构体来表示。Windows就是这么干的,但是linux不是。咱们来看看Windows的进程、线程
在这里插入图片描述
在这里插入图片描述

接下来咱们就去看看linux的进程线程设计,不管是进程、子进程,还是线程,都对应的是同一个结构体task_struct

奇葩的linux进程线程设计

因为Linux中的进程、子进程、线程,都是用一个结构体task_struct表示的,又引出了几个新的名词,属实令人费解,接下来咱们透彻分析一下

有这么几个名词:内核线程、用户线程、线程、进程、线程组、进程组

先看一个进程、子进程、线程在Linux内核中的存在形式,即task_struct长啥样
在这里插入图片描述

从Linux的角度来说,线程也是一种进程,只不过是共享某个进程的用户虚拟地址空间

那什么是用户线程,什么是内核线程呢
在这里插入图片描述

在Linux中,只有一个线程的进程,称为进程,如果包含多个线程,就有了新的名词,线程组
在这里插入图片描述

那什么是进程组呢?就是一堆不共享用户虚拟地址空间的进程绑在一起
在这里插入图片描述

既然都是同一个task_struct,那Linux内核如何区分是进程还是线程呢?你得知道在Linux内核层面,进程与线程是这样表示的
在这里插入图片描述

所以你这样就可以识别
在这里插入图片描述

Linux内核是如何区分是子进程还是线程的呢?你如果有答案,你就看懂了Linux的进程线程设计,如果没有,说明你还是没有get到那种感觉
在这里插入图片描述

那Linux内核是如何区分是父进程还是子进程呢?这个应该是相对来说的,就是谁是父谁是子。理解这两个字段即可
在这里插入图片描述

比如有进程A、进程B,代码这样实现
在这里插入图片描述

至此,就把Linux的进程线程机制讲完了。纸上得来终觉浅,绝知此事要躬行,下一篇文章,我会写代码论证分析,并将代码分享出来。

linux为什么要这么设计

出于好奇,我看了下这块是由哪些大牛设计的,亮瞎
在这里插入图片描述

他们为什么要设计成这样呢?我也特地问了下ChatGPT
在这里插入图片描述

看完以后,我的感觉:嗯,牛逼,这样设计就没有缺点吗?
在这里插入图片描述

看吧,linux内核,这么多世界级的大佬写出来的,依然不能保证十全十美。所以,任何事物,都是选择,有利必有弊,没有完美的东西。

我猜测:未来,有可能会进行拆分,将进程、线程用不同的结构体来表示。我为什么做这样的猜测?因为我读了linux内核的源码,复杂得一批!没有Windows内核源码读起来清爽。很难想象如果要扩展,会牵涉多少细节的调整,会不会因忽略某个细节引起连锁反应。

如何创建进程

如图
在这里插入图片描述

Linux中,创建子进程,现在一般用fork,底层是写时复制技术。就是创建子进程的时候,原样copy父进程。比如内存页表,直接copy父进程的,当有数据修改时,页表描述符PTE是只读的,写的时候,触发page fault异常,完成物理页的分配与挂载,生成新的page的内存空间

创建线程一般用pthread_create,底层是调用的clone函数,类似这样
在这里插入图片描述

不管是创建子进程,还是线程,最终都会进入内核函数_do_fork,核心逻辑如下
在这里插入图片描述

至此,Linux中如何创建子进程、线程的整体框架应该清晰了吧

来玩个奇思妙想,如果fork在用户态用clone实现,你觉得这个代码要怎么写?
在这里插入图片描述

对了,我没贴clone函数原型。
在这里插入图片描述

对了,上面说的Linux进程包含的资源,那一大堆的CLONE_,就是clone的flags。你悟了吗?