在linux系统中对于进程管理文件系统和设备驱动都是通过先描述再组织的方式进行管理。那么什么是先描述什么是后组织呢简单来说先描述的意思就是将数据通过结构体进行封装实现将数据给对象化。后组织就是将这些对象通过数据结构链表队列堆栈进行连接使其建立联系实现高效关联。进程概念接下来我们来了解一下什么叫做进程。在课本上进程的描述颇为复杂但是我们在这就对进程下一个绝对正确的定义进程内核数据机构对象自己的代码与数据。在课本上我们将这个对象称之为PCB(process control block),这个对象就是用来描述进程的属性的。在linux系统中PCB就是task_struct这是linux底层用c封装的一种数据结构它存放于RAM中。所有运行在系统的进程都以task_struct双链表的形式存放在内核中因此我们通过一个进程理论上可以找到其他的进程。我们需要知道的是我们历史上执行的所有指令工具gcc编译后的程序都是进程。所有的进程都存放在/proc/这个目录下如果我们对这个目录进行ls我们会看到一堆数字此时我们再直接执行ps -ajx 我们也会也会看到一堆数据其中PID的数据就是/proc目录下的对应的数字。此时我们再ls -l,我们会看到两个符号链接一个是exe,一个是cwd。exe我们在Windows系统中也非常常见了他就是可执行文件通过ls -l我们会看到他后面跟着一个地址就是指向那个可执行文件。接着是cwd他的意思是当前工作目录指向的是进程当前所在的目录。我们通过chdir可以改变这个路径的指向。在c语言中我们可以通过fork来创建子进程fork一种写时拷贝的显著例子。那么写时拷贝是什么呢简单来说当我们在父进程创建一个子进程的时候理论上我们需要把父进程的资源全部复制一遍给子进程但是如果父进程很大几个G时这样子会很浪费时间和空间因此在只读的情况下此时子进程和父进程是共用父进程的资源的。当其中一个进程需要改数据动资源的时候会触发缺页中断此时内核才会申请新的空间把数据进行复制保证父子进程都有自己的空间。我们举一个生活中的例子想象你和同学合写一份报告没有 COW我先复印 50 页纸给你。你拿过去可能一张没改这 50 页纸就浪费了。使用 COW我发给你一个只读链接。如果你只是看我们共用这一份云端文档。一旦你想在某一段加上你的名字系统瞬间帮你生成一个这页文档的私人副本供你编辑。这就是写时拷贝的精妙之处了。然后这里我们还需要注意的是如果在新创空间之前我们把父进程给关了此时原共享的资源就属于子进程了并且该资源的权限由“只读”变成了“可写”。接下来我们再来讨论fork的返回值问题其实其本质也是写实拷贝。当我们运行如下代码时我们会发现终端会同时处理父子进程的逻辑为什么fork会返回两个值呢其实并不是fork返回了两个值而是父子进程各返回了一个值。fork以后父进程会返回子进程的pid因为一个父进程可能有多个子进程它需要知道现在生成的这个子进程是谁。子进程会返回0因为子进程只有一个父进程他只需要通过get_ppid函数就能知道他的父进程是谁因此返回0就可以。刚 fork 完父子进程的变量id理论上指向同一个物理地址。写入返回值内核在返回时实际上是在向id变量写入数据。因此在最后返回PID的时候实际上是两个进程再交互工作。pid_t id fork(); if (id 0) { // 出错 } else if (id 0) { // 子进程逻辑 } else { // 父进程逻辑 }进程状态在linux中进程主要有以下几种状态1.R running运行代表进程处于运行状态或在在运行队列里这处决于系统本身。2.S sleeping睡眠通常也会被称之为阻塞。其是一种等待状态在等待某种事件完成该状态可以被系统强行中断其资源此时依然保存在ram中比如若此时系统负载过高系统可能就会强行释放睡眠状态的资源。3.D Disk sleep磁盘睡眠有时候也叫不可中断睡眠状态uninterruptiblesleep在这个 状态的进程通常会等待IO的结束,无法用kill -9杀死进程。4.T stop停止相比于睡眠状态stop状态更多是外部强制中断暂停比如我在在运行程序时可以使用ctrl z 的方法强行中断暂停某个进程也可以敲SIGSOT进行暂停。此时再按下fg(前台运行或者bg后台运行即可恢复进程。5.t tracing stop可追踪停止该状态和T状态类型但是这个状态通常出现在调试阶段当我们使用gdb等调试软件调试的时候再通过ps查状态就能看到小t.6.Zzombie僵尸僵尸状态是进程退出后、彻底消失前的一个过渡状态。只要子进程退出父进程还在运行但未调用wait读取退出状态子进程就会成为僵尸进程导致 PID 等内核资源无法回收造成内核内存泄漏。如果父进程在未收尸的情况下退出这些僵尸子进程会被1号进程init/sysemd领养。1号进程会自动读取它们的退出状态并释放剩余资源从而清理掉这些僵尸7.X dead死亡就是进程完全结束但是一般我们看不到这个状态在僵尸状态以后立刻就是X状态。ps aux / ps axj我们日常可以使用上述命令查询进程状态。其实在状态之外还有一种现象叫做挂起挂起和阻塞有些不同挂起指的是当CPU的内存严重不足时内核会将RAM中的存储给放到磁盘中一个叫swap空间中此时他的PCB还在内核的队列中但是他的资源已经指向了磁盘的一处swap空间。当后续CPU想要运行该进程时需要先将相关资源从磁盘中再swap回来到RAM中然后才能开始运行该进程。每个CPU核心都会存在一个runqueue这个队列里面会存放那些处于running状态的进程cpu核心会根据该队列的顺序依次执行进程。当某个进程进入了S/D状态那么就会从runqueue脱离出来转而进入waitqueue队列。因此进程的变化表现之一就是在不同队列中进行流动实现对相关数据结构的增删查改。那么我们现在再回去看一下linux的PCB中双链表到底是怎么实现的呢其实在task_struct中双链表并不是直接以prenode和nextnode节点直接连接的task_truct内部封存了一个struct某一个list_head的结构体这个对象里面只存prenode和nextnode为什么要这么做呢这是为了让某一个task_struct能隶属于不同的数据结构。比如我现在一个task_strcut,他可能要在系统的双链表中也有可能要在运行队列中如果只有直接使用prenode和nextnode将会非常痛苦此时我们只要往里面再加一个list_head就可以了。有人会问了这样设计不会导致手里只有一个list_head的指针怎么知道它属于哪个 task_truct 呢这一点我们就需要回到c语言中结构体的基本知识了。当我提到“内存对齐”“漂移量”的时候你应该就懂了。struct task_struct { struct list_head tasks; // 全局链表的“钩子” struct list_head run_list; // 运行队列的“钩子” struct list_head children; // 子进程链表的“钩子” };