☰
Linux:进程概念
2026/10/4 19:46:30 网站建设 项目流程

1、冯诺依曼体系结构

我们常用的计算机,服务器等大部分都遵守冯诺依曼体系。

关于冯诺依曼,必须强调几点:

  • 这里的存储器指的是内存
  • 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)(数据层面)
  • 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
  • ⼀句话,所有设备都只能直接和内存打交道。

2、操作系统

任何计算机系统都包含⼀个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:

  • 内核(进程管理,内存管理,文件管理,驱动管理)
  • 其他程序(例如函数库,shell程序等等)

设计OS的目的:

  • 对下,与硬件交互,管理所有的软硬件资源
  • 对上,为用户程序(应用程序)提供⼀个良好的执行环境

3、进程

进程 = 内核数据结构(task_struct) + 自己的程序代码和数据

3.1 描述进程-PCB

基本概念

  • 进程信息被放在⼀个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
  • 课本上称之为PCB(process control block), Linux 操作系统下的 PCB 是: task_struct,task_struct是PCB的⼀种
  • 在 Linux 中描述进程的结构体叫做 task_struct 。
  • task_struct 是 Linux 内核的⼀种数据结构类型,它会被装载到RAM(内存)里并且包含着进程的信息。

3.2 task_ struct

内容分类

  • 标示符: 描述本进程的唯⼀标示符,用来区别其他进程。
  • 状态: 任务状态,退出代码,退出信号等。
  • 优先级: 相对于其他进程的优先级。
  • 程序计数器: 程序中即将被执行的下⼀条指令的地址。
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  • 上下文数据: 进程执行时处理器的寄存器中的数据
  • I ∕ O状态信息: 包括显示的I/O请求,分配给进程的I∕O设备和被进程使用的文件列表。
  • 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  • 其他信息

所有运行在系统里的进程都以 task_struct 双链表的形式存在内核里。

3.3 查看进程

  1. 进程的信息可以通过 /proc 系统文件夹查看

  2. 大多数进程信息同样可以使用top和ps这些用户级⼯具来获取

3.4 通过系统调用创建进程-fork初识

fork有两个返回值, 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)

1. fork 为什么会有两个返回值?

不是一个函数返回两次,而是调用 fork 之后,操作系统创建了一个新进程,变成两个进程,各自执行 fork 之后的代码,各自拿到一份返回值。

  • 调用fork()之前:只有父进程这 1 个进程在运行。
  • 调用fork()的一瞬间:OS 复制父进程,创建出子进程(子进程几乎完整拷贝父进程的地址空间、代码、数据)。
  • fork系统调用在父进程里返回一次;同时在新建出来的子进程里再返回一次。

所以代码上看起来:同一个fork调用,出现两个返回值。本质是两个独立进程,各自拿到返回值。

2. 两个返回值分别给谁?
  1. 父进程:fork返回 >0 的正数,这个数字就是子进程的 PID(进程号)。
  2. 子进程:fork返回0。
  3. 出错情况:fork 失败(进程数量上限等),返回-1,此时不会创建子进程。
3.同一个变量 ret,为什么 if 和 else if 可以同时成立?

很多初学者会疑惑:ret 一个变量,怎么if(ret==0)和else if(ret>0)都执行?
真相:不是同一个变量!
fork创建子进程时,会拷贝父进程的内存,子进程拥有属于自己独立的地址空间。

  • 父进程内存里的ret存的是子进程 PID(正数)
  • 子进程内存里的ret存的是 0

两个进程,两份独立的 ret 变量,各自走自己的分支。
父进程走else if(ret>0),子进程走if(ret==0),两个分支分别在两个进程里面运行,互不干扰。
并不是单进程里面一个变量同时等于 0 又大于 0。

3.5 进程状态

⼀个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务):

  • R运行状态(running): 并不意味着进程⼀定在运行中,它表明进程要么是在运行中要么在运行队列里。
  • S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
  • D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
  • T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
  • X死亡状态(dead):这个状态只是⼀个返回状态,你不会在任务列表里看到这个状态。

进程状态查看:

ps aux / ps axj 命令
  • a:显示⼀个终端所有的进程,包括其他用户的进程。
  • x:显示没有控制终端的进程,例如后台运行的守护进程。
  • j:显示进程归属的进程组ID、会话ID、父进程ID,以及与作业控制相关的信息
  • u:以用户为中心的格式显示进程信息,提供进程的详细信息,如有用户、CPU和内存使用情况等
3.51 僵尸进程
  • 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死进程
  • 僵死进程会以终止状态保持在进程表中,并且会⼀直在等待父进程读取退出状态代码,因为需要告诉父进程任务进行得怎么样。
  • 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

僵尸进程的危害:

  1. 僵尸进程已释放用户态内存,但内核中 task_struct 等 PCB 资源未释放,会造成内核内存资源泄漏。
  2. 它持续占用 PID 与内核进程表项,大量堆积会耗尽 PID,导致系统无法创建新进程。
  3. 大量僵尸进程还会干扰进程排查,掩盖系统其他异常问题。
3.52 孤儿进程

问题:父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?

  • 父进程先退出,子进程就称之为“孤儿进程”
  • 孤儿进程被1号init / systemd进程领养,当然要有init / systemd进程回收

模拟孤儿进程:

3.6 进程优先级

  • cpu资源分配的先后顺序,就是指进程的优先权(priority)。
  • 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
  • 还可以把进程运行到指定的CPU上,这样⼀来,把不重要的进程安排到某个CPU,可以改善系统整体性能。
3.61 查看系统进程


在linux或者unix系统中,用ps ‒l命令则会类似输出以下几个内容:
我们很容易注意到其中的几个重要信息,有下:

  • UID : 代表执行者的身份
  • PID : 代表这个进程的代号
  • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI :代表这个进程可被执行的优先级,其值越小越早被执行
  • NI :代表这个进程的nice值
3.62 PRI and NI
  • PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
  • 那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值
  • PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
  • 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
  • 所以,调整进程优先级,在Linux下,就是调整进程nice值
  • nice其取值范围是-20至19,⼀共40个级别。
3.63 PRI vs NI
  • 需要强调⼀点的是,进程的nice值不是进程的优先级,他们不是⼀个概念,但是进程nice值会影响到进程的优先级变化。
  • 可以理解nice值是进程优先级的修正数据
3.64 查看进程优先级的命令

⽤top命令更改已存在进程的nice:

  • top
  • 进入top后按“r”‒>输入进程PID‒>输入nice值

3.7 进程切换

CPU上下文切换:其实际含义是任务切换, 或者CPU寄存器切换。当多任务内核决定运行另外的任务时, 它保存正在运行任务的当前状态, 也就是CPU寄存器中的全部内容。这些内容被保存在任务自己的堆栈中, 入栈工作作完成后就把下⼀个将要运行的任务的当前状况从该任务的栈中重新装入CPU寄存器,并开始下⼀个任务的运行, 这⼀过程就是context switch。


每个进程都会分配对应的时间片,如果没在时间片指定的时间内完成任务,该进程会被暂时切下来,需要进程自己保存自己得上下文数据,目的是下次回来能够恢复,按照之前得逻辑向后运行。

3.8 Linux内核进程O(1)调度队列

  • 时间片还没有结束的所有进程都按照优先级放在该队列
  • nr_active: 总共有多少个运行状态的进程
  • queue[140]: ⼀个元素就是⼀个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下标就是优先级!

从该结构中,选择⼀个最合适的进程,过程是怎么的呢?

  1. 从0下表开始遍历queue[140]
  2. 找到第⼀个非空队列,该队列必定为优先级最高的队列
  3. 拿到选中队列的第⼀个进程,开始运行,调度完成
  4. 遍历queue[140]时间复杂度是常数
  • bitmap[5]:⼀共140个优先级,⼀共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个比特位表示队列是否为空
3.81 过期队列
  • 过期队列和活动队列结构⼀模⼀样
  • 过期队列上放置的进程,都是时间片耗尽的进程
  • 当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算
3.82 active指针和expired指针
  • active指针永远指向活动队列
  • expired指针永远指向过期队列
  • 可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时⼀直都存在的。
  • 新加进来的进程也会放在过期队列里面,相当于进程状态中的就绪状态
  • 只要能够交换active指针和expired指针的内容,就相当于有具有了⼀批新的活动进程!

4、调度算法为o(1)

原因:

  1. 从 active 选最高优先级进程运行;
  2. 进程时间片用完,把它挪到 expired 队列;
  3. active 全部空了的时候,直接交换 active 和 expired 指针,不需要移动任何进程,只是交换两个指针变量(常数操作)。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询