一、冯・诺依曼体系结构核心考点五大硬件组成输入设备鼠标、键盘、摄像头、网卡、话筒。把外部信息送入计算机。输出设备显示器、音响、网卡。把计算机的数据给外面。网卡、磁盘既是输入也是输出设备既能读数据也能写数据存储器这里指内存主存存放程序和数据。运算器负责算术运算加减乘除、逻辑运算与或非、比较。控制器指挥、控制所有硬件按顺序干活。运算器 控制器 合起来就是CPU中央处理器冯诺依曼核心思想程序和数据预先存放在存储器内存里面CPU 从内存读取指令执行。所以程序运行前必须先加载到内存。这是体系结构规定。硬件之间靠总线连接分两类系统总线CPU 和内存通信IO 总线CPU / 内存 和外设键盘、硬盘等通信总线一组并行的信号线一堆电线 配套控制逻辑是计算机各个硬件之间传输数据、传递控制信号的公共通道。形象比喻就像高速公路一根电线 单车道总线 一整条多车道高速路很多根线捆在一起所有部件都接在这条路上可以互相传数据。总线里面分 3 类信号线路数据线传输真正的数据比如读取的代码、数字地址线告诉硬件要去访问哪个内存 / 设备地址控制线传递命令比如 “读数据”、“写数据”课件里的两种总线系统总线CPU ↔ 内存 之间的高速通道。专门负责 CPU 和内存交换指令、数据速度很快。IO 总线CPU / 内存 ↔ 外设键盘、硬盘、网卡、显示器。外设速度慢走这条通道。简单一句话总线是一堆电线组成的公共通信通道硬件之间靠它互相发数据、发指令。不是一根单独电线是一整套线路。二、存储分级金字塔必考从金字塔顶端到底部离 CPU 越来越远速度越来越慢容量越来越大单价越便宜寄存器 RegsCPU 芯片内部速度最快容量极小价格最贵。L1 / L2 / L3 Cache三级高速缓存CPU 内部缓存比寄存器慢、比内存快。Memory 内存我们常说的内存条。SSD 固态硬盘HDD 机械硬盘缓存的作用解决 CPU 运算速度极快内存 / 硬盘速度跟不上的矛盾把常用数据提前放到高速缓存减少等待。图里的问题进度条数据可能被缓存缓存在哪里答CPU 的三级缓存L1/L2/L3 cache。CPU 读取数据优先去缓存找命中就直接拿不用访问内存提升速度。寄存器CPU 内部最最临时的 “小格子”只存当前这一刻正在运算的数据不能用来缓存进度条这类数据。CacheL1/L2/L3 高速缓存专门存放近期大概率会重复使用的数据就是题目问的缓存。寄存器当前运算现场一次性、瞬时Cache复用数据的高速缓存短期保存给后面重复读取用冯诺依曼数据流例题题目 1登录 QQ不考虑网络题目 2QQ 聊天发送 “在吗” 给朋友整张图的数据流冯诺依曼模型所有程序运行都要先加载入内存。所有外设的数据都要先进内存CPU 只能和内存打交道不能直接读写硬盘 / 键盘。操作系统管理硬件 / 资源的核心思路先描述struct 结构体再组织链表等数据结构管理对象本质是管理描述对象的数据应用程序访问 OS 必须走系统调用。一、分层结构图从上到下用户层人做操作敲命令、写代码、管理电脑。用户操作接口shell 外壳就是我们的 Linux 命令行接收用户输入指令。libC 语言标准库库函数比如printf()就在这一层。printf是库函数不是系统调用系统调用接口system call内核对外提供的入口应用程序想要操作系统干活必须走这里。库函数底层一般会调用系统调用。操作系统内核四大核心模块内存管理进程管理文件管理驱动管理驱动程序专门用来控制硬件的小程序网卡驱动、硬盘驱动底层硬件网卡、硬盘、显示器等物理设备二、重点库函数 vs 系统调用课件右上角考点一句话printf 是库函数库函数封装了系统调用。2. 两者区别系统调用操作系统内核提供的原生接口是用户程序进入内核的唯一通道属于操作系统。库函数lib在用户层的封装函数是对系统调用做包装方便程序员使用。不是所有库函数都会触发系统调用部分库函数只在用户层做计算比如字符串处理strlen完全不进内核。3. 两层划分用户部分用户、shell、lib 库库函数在这里用户态系统软件部分系统调用接口、OS 内核、驱动内核态硬件部分真实硬件进程已经加载到内存中的程序就是进程正在运行 / 准备运行都算磁盘上放的只是程序可执行文件静态的一旦加载进内存就变成进程动态的。1. PCB 进程控制块重点PCB 全称Process Control Block进程控制块本质内核里面的一个 struct 结构体用来描述一个进程的全部属性。c运行struct PCB { // 进程属性 int pid; // 进程编号 int status; // 进程状态就绪/运行/阻塞 int priority; // 优先级 // 其他各种资源、寄存器、内存指针等信息 struct PCB *next; // 链表指针用来把所有进程串起来 };进程 PCB内核描述信息 自己的代码 数据PCB操作系统用来认识、管理这个进程的描述信息在内核里代码 数据程序本身加载到内存2. 复用之前那句核心思想先描述再组织先描述创建struct PCB结构体记录每个进程所有属性pid、状态、优先级再组织用 PCB 里面的next指针把所有进程串成链表操作系统管理进程本质就是对 PCB 链表做增删改查。创建进程 → 新建 PCB 节点插入链表销毁进程 → 删除链表节点。3. 整套流程磁盘上myprocess可执行程序静态文件运行程序操作系统把代码数据加载进内存OS 内核新建 PCB 结构体描述这个新进程把这个 PCB 节点挂入进程链表CPU 从内存读取代码执行操作系统靠 PCB 链表管理所有进程多进程并发运行PCB 是进程存在的唯一标志1. PCB 与 task_struct核心Linux 里描述进程用PCB名叫task_struct结构体task_struct里面存这个进程全部属性PID、进程状态、优先级、工作目录、打开的文件、内存信息等等。内核里所有进程的 task_struct用双向链表串起来管理。操作系统思想先描述结构体再组织链表2. /proc 目录重点/proc是虚拟文件系统没有存到磁盘上是内核实时给你暴露出来的进程信息。ls /proc/26617 -l26617就是进程 PID每个数字文件夹对应一个正在运行的进程。看里面两个软链接cwd - /home/whb/108/lesson10cwd current work directory进程当前工作目录大白话这个进程是在哪个文件夹启动的。你代码写fopen(log.txt, w);只写文件名就会在 cwd 这个目录下创建 log.txt不是可执行文件在哪就一定在哪exe - /home/whb/108/lesson10/myprocessexe 指向这个进程对应的可执行程序文件myprocess。就是这个进程跑的是哪个程序。/proc目录下的 cwd、exe 软链接只是把内核 PCB 里存的这两个属性暴露出来给我们查看。真正的价值是进程运行的时候内核靠这两个信息管理进程。/proc是虚拟文件系统不占磁盘是内核实时把【26617 这个进程的 PCB (task_struct) 里面存的信息】伪装成文件 / 文件夹给我们看。cmdline文件里面存启动这个进程敲的完整命令。比如你执行./myprocesscmdline 里面就存这个命令字符串。comm文件存进程的简短名字程序名一般就是 myprocess。environ文件存这个进程的环境变量PCB 里保存的环境变量信息。环境变量就是进程运行时自带的配置信息由父进程传给子进程保存在 PCB/proc/PID/environ用来查看。fd 文件夹file descriptor 文件描述符超级重要打开 fd 进去ls /proc/26617/fd里面每一个数字软链接代表这个进程当前打开的所有文件。0标准输入 stdin键盘1标准输出 stdout屏幕2标准错误 stderr你代码 fopen 打开的 log.txt也会在这里多出一条软链接。所有打开的文件都记录在 task_struct 的文件描述符表中fd 文件夹就是查看这个表的窗口。剩下的 attr、cgroup、cpuset 这类属于进程资源限制、属性初学阶段不用背。1. ps aux一次性快照推荐上课用bash运行ps aux直接列出所有正在运行的进程第一列就是 PID。专门找我们写的myprocessbash运行ps aux | grep myprocessgets 不检查长度缓冲区溢出C11 废除读字符串用 fgets。char buf[10]; fgets(buf, sizeof(buf), stdin);fork#include stdio.h #include unistd.h #include sys/types.h int main() { printf(begin: 我是一个进程, pid: %d, ppid: %d\n, getpid(), getppid()); pid_t id fork(); if(id 0) { //子进程 while(1) { printf(我是子进程, pid: %d, ppid: %d\n, getpid(), getppid()); sleep(1); } } else if(id 0) { //父进程 while(1) { printf(我是父进程, pid: %d, ppid: %d\n, getpid(), getppid()); sleep(1); } } else { //fork失败 } return 0; }1. fork 到底干了什么核心 4 步操作系统创建子进程 PCBtask_struct填充 PCB 里面信息pid、ppid、状态、内存地址空间等子进程指向和父进程一模一样的代码代码只读父子共享父子两个进程都是独立 PCB操作系统调度器可以调度 CPU 运行它们进程 PCB内核结构体 代码 数据2. fork 为什么会返回两次【面试高频】普通函数调用一次返回一次。fork调用一次返回两次在父进程里面返回子进程的 pid大于 0在子进程里面返回0返回负数fork 创建失败资源不足大白话fork 内部操作系统新建了一份子进程。两个独立进程各自执行 fork 之后的 return 语句。不是一个函数神奇返回两次而是现在有两个进程各自拿到一个返回值。考点fork 之前的代码只会执行一遍父进程执行fork 之后的代码父子进程都会执行。3. 写时拷贝为什么变量看起来一样修改后互不干扰fork 刚创建完父子进程共享同一份数据只读不拷贝。一旦任意一个进程尝试修改数据操作系统立刻单独拷贝一份副本给这个进程各自修改互不影响。这个机制叫写时拷贝。所以fork 之后父子的变量初始值一样但是是独立的内存修改互不影响。4. getpid() / getppid()getpid()获取当前进程自己的 pid系统调用getppid()获取当前进程的父进程 pid子进程的 ppid 父进程 pid父进程的 ppid 你运行这个程序的 shellbash的 pid。5. 父子进程谁先跑不确定由操作系统调度器决定没有固定顺序。有可能父先跑也有可能子先跑。代码不能依赖先后顺序。6. 代码段为什么可以共享代码指令是只读的父子都只能读不会修改所以不需要拷贝直接共享节省内存。只有数据变量在修改的时候才会拷贝。7. 三个核心思考题课件红笔标注Q1为什么子进程返回 0父进程返回子进程 pid子进程没有 “子”用 0 标记自己是子进程父进程需要拿到子进程 pid方便后续操作wait 等待、kill 关闭子进程。Q2一个函数怎么做到返回两次不是函数魔法fork 创建新进程两个独立进程各自执行 return两个进程各自拿到返回值。Q3同一个变量 id为什么父子里面内容不一样因为写时拷贝。fork 之后 id 是父进程的变量创建子进程时共享在 fork 内部 return 赋值的时候发生写操作触发写时拷贝父子各自拥有独立 id 变量值不同。8. 考点总结fork 是系统调用创建子进程代码共享数据写时拷贝返回值父 0子 0失败 0fork 前代码执行一次fork 后父子都会执行父子执行顺序不确定调度器决定进程独立PCB 是内核描述进程的结构体#include stdio.h // printf #include unistd.h // fork()、getpid()、getppid()、sleep() #include sys/types.h // pid_t 类型pid_t 类型头文件#include sys/types.h本质就是整型专门用来存进程 id。大白话拆解 bash 执行命令fork exec想象场景你在黑框里敲ls回车bash 做两件事。exec execute 执行新程序fork 本意叉子分叉对应进程分叉创建子进程第一步fork () —— 复制一个自己bash 的副本bash 本身是一个正在运行的进程。调用fork()操作系统复制一份一模一样的 bash这就是子进程。现在有两个 bash一个老 bash原来的一个新复制出来的子 bash。两个现在代码、数据完全一样。第二步exec —— 子进程 “换脑子”变成 ls子 bash 调用 exec 系统调用。不改变进程 PID只是把这个进程里面的代码和数据全部清空换成 ls 程序。✅ 关键点区分fork新建进程产生新 PID复制原来的程序exec不新建进程PID 不变只是把当前进程里面的程序替换成别的程序完整流程一句话你敲命令回车 → bash 调用 fork复制出一个子 bash子进程子 bash 立刻调用 exec把自己替换成ls程序子进程就变成 ls执行 ls 的功能原来的 bash 原地等待为什么不能直接在 bash 里面跑 ls如果直接在 bash 内部运行 lsls 执行完会把 bash 本身给干掉终端就直接退出了。所以必须先 fork 复制个子进程让子进程去 exec 跑新程序父 bash 不受影响。PGID Process Group ID进程组 ID大白话一组关联在一起的进程放在同一个小组里这个小组的编号就是 PGID。同一个进程组里PGID 全部相同。一、通用操作系统五状态模型课本理论新建刚创建进程PCB 还没完全建好就绪进程资源全部备好只差 CPU等待调度器分配 CPU 时间片运行进程正在 CPU 上跑代码阻塞等待进程等某个事件读磁盘、键盘输入就算 CPU 空闲也不跑终止进程跑完等待回收资源切换规则就绪 → 运行调度器选中拿到 CPU 时间片运行 → 就绪时间片用完进程被换下 CPU进程切换运行 → 阻塞进程主动等 IO比如读文件阻塞 → 就绪IO 事件完成唤醒进程运行 / 就绪 → 终止程序退出二、Linux 实际的 6 种进程状态重点ps 命令看到的 STATplaintextR (running), /* 0 */ R状态 S (sleeping), /* 1 */ 浅度睡眠 D (disk sleep), /* 2 */ 深度睡眠 T (stopped), /* 4 */ 暂停 t (tracing stop),/*8 */ 调试暂停 X (dead), /*16 */ 死亡 Z (zombie), /*32 */ 僵尸1. R 运行态Running包含两种情况正在 CPU 上执行代码在就绪队列排队等着 CPU就绪Linux 把就绪也归到 R时间片CPU 分给每个进程一小段时间比如 10ms时间到就强制切走实现并发。进程切换把旧进程现场保存换另一个进程上 CPU。进程现场是什么进程上下文context就是进程在 CPU 上运行时所有寄存器里的值eax、ebx、esp、eip、标志寄存器等等。eip 特别关键保存下一条要执行代码的地址。CPU 的寄存器只有一套CPU 本身不会保存这些数据。当时间片到了要换下当前进程操作系统内核来做保存动作。怎么保存进程切换流程触发切换时间片耗尽 / 进程主动阻塞触发中断进入操作系统内核。保存现场内核把当前 CPU 所有寄存器的值压到这个进程自己的内核栈里。把这一组寄存器数据存进这个进程对应的task_structPCB关联的内核栈。这一步就是「保存旧进程现场」选择新进程调度器从运行队列选出下一个要跑的进程。恢复现场把新进程内核栈里面存放的寄存器数据放回 CPU 寄存器。回到用户代码新进程继续运行。一句话理解寄存器是 CPU 临时工作台。换进程前把工作台上面所有数据打包存到这个进程专属的内核栈内存里下次轮到它运行再把数据放回 CPU 寄存器程序就能接着上次的地方继续跑仿佛从没被打断。考点区分用户栈存函数局部变量属于进程用户空间内核栈专门用来保存进程切换时的寄存器上下文在内核空间每个进程独立一份。进程什么时候会停下、进入内核发生进程切换。1. 进程主动阻塞进程自己主动调用系统调用去等待某个事件主动放弃 CPU。举例子read()读取键盘输入没输入就等read()读磁盘文件等磁盘把数据拿出来sleep () 休眠等待指定时间。进程自己说我现在没事干我要等 IOCPU 给别人用吧主动进入阻塞状态。注意主动阻塞不是时间片到了是进程自己主动不想继续跑了。2. 触发中断中断就是打断 CPU 当前正在做的事让 CPU 进入内核去处理这件事。分两种场景这里区分开1主动阻塞陷入trap软中断进程调用系统调用比如 read这是软中断 / 陷入。进程从用户态 → 进入内核态。内核一看哦这个进程要等 IO那就把它放到等待队列执行进程切换。很多课件会口语化统称 “触发中断”严格来说系统调用属于陷入 trap和硬件中断不是完全一样但效果都是进入内核完成进程切换。2时间片用完硬件时钟中断CPU 上有硬件定时器时钟硬件分给进程的时间片倒计时结束硬件定时器给 CPU 发硬件中断强制打断当前正在跑的进程进入内核内核调度器执行切换进程换下当前进程选另一个就绪进程上来跑3. 一句话串起来进程主动阻塞比如调用 read 读磁盘→ 发起系统调用触发陷入软中断进入内核。内核把这个进程放进等待队列保存当前进程上下文挑新进程上 CPU。对比记忆考试常考时间片耗尽硬件时钟中断被动被 CPU 赶下来进程是 R 状态进程等 IO 主动放弃 CPU系统调用trap 软中断主动阻塞进程变成 S/D 状态两种状态对比用户态CPU 跑应用程序代码我们写的程序内核态CPU 跑操作系统内核代码OS 处理调度、IO、保存上下文核心一句话就绪队列 和 等待队列是两个完全不同的队列放的进程状态不一样就绪队列runqueue放R 就绪态的进程。这些进程准备好了只等 CPU随时可以上 CPU 运行。等待队列waitqueue放S/D 阻塞睡眠态的进程。这些进程在等某个事件IO、键盘、磁盘就算 CPU 空闲它现在也不能跑。2. S 浅睡眠可中断睡眠 sleeping进程在等事件等待键盘输入、等待管道数据✅可以被信号唤醒kill、CtrlC都能杀掉进程 PCB 放进等待队列不在 CPU 运行队列。3. D 深度睡眠不可中断睡眠 disk sleep【高频考点】进程等待磁盘 IO 完成比如写大块数据到硬盘核心特点不响应任何信号kill -9 也杀不掉为什么设计磁盘读写正在进行此时如果把进程杀掉磁盘数据可能损坏丢失。必须等 IO 结束自动回到就绪态。4. T 停止态 stopped进程被暂停不再运行。比如CtrlZ把前台程序放到后台暂停。可以通过fg命令恢复运行。5. t tracing stop 调试暂停gdb 调试程序时断点停下进程就是 t 状态。调试器控制程序停在断点处。6. X dead 死亡态进程彻底结束OS 直接回收全部资源用户几乎看不到这个状态。7. Z 僵尸进程 zombie ⭐必考子进程代码已经跑完退出了但是父进程没有调用 wait/waitpid 回收子进程的 PCB子进程的 PCBtask_struct还留在内存保存退出信息等待父读取进程已经死掉但是 PCB 没释放就是僵尸 Z危害大量僵尸进程会内存泄漏占用 PCB 资源耗尽系统进程名额✅ 解决父进程调用wait()/waitpid()回收子进程。孤儿进程配套考点父进程先退出死掉子进程还活着 → 孤儿进程。OS 会把孤儿进程的父设置为1 号进程init/systemd由 1 号进程领养。等孤儿进程以后退出1 号进程自动回收它不会变成僵尸。一句话区分僵尸子死父活着但父不回收 PCB孤儿父死子还活着被 1 号进程收养三、底层内核怎么维护进程task_structOS 描述进程用struct task_struct结构体PCB保存进程全部信息PID、PPID、状态、内存、文件、上下文。组织管理用双向链表把所有 task_struct 串起来。运行队列 runqueue放 R 状态的进程调度器从这里挑进程上 CPU等待队列 waitqueueS/D 睡眠状态的进程放在对应设备的等待队列键盘、磁盘等待事件完成唤醒。挂起swap 交换内存不够时把不活跃进程的代码数据挪到磁盘 swap 分区腾出内存。PCB 还留在内存标记挂起。四、核心考点总结理论 OS 五状态新建、就绪、运行、阻塞、终止Linux 实际状态R S D T t X ZS 可中断睡眠D 不可中断磁盘 IO杀不掉Z 僵尸子退出父不回收 PCB孤儿父提前退出被 1 号收养时间片 进程切换实现 CPU 并发跑多个程序task_structPCB描述进程链表组织运行队列、等待队列分开管理wait/waitpid 用来回收子进程避免僵尸进程Linux 内核里的内嵌双向链表容器链表重点不是链表节点包含结构体是结构体里面内嵌链表节点。task_structPCB里面包含struct node link;这个链表成员。1. 两个结构体c运行// 链表节点结构体只有前后指针 struct node { struct node *next; struct node *prev; }; // 进程控制块PCB里面包含进程全部信息并且内嵌一个node类型的link成员 struct task_struct { // 一大堆进程属性pid、状态、上下文、内存... struct node link; // 内嵌的链表节点 };画图里的意思task_struct是一个大盒子盒子里面塞了一小块struct node link图里红框部分。多个task_struct靠内部的link的prev/next互相串起来形成双向链表。❗ 普通链表写法链表节点存一个指向数据的指针。✅ Linux 内核这种数据结构体内部自带链表节点。2. 关键问题已知struct node* start拿到 link 的地址怎么找回整个 task_struct 的起始地址图里公式c运行(task_struct *)( (char*)start - ((task_struct*)0)-link );((task_struct*)0)-link就是link成员在task_struct内部的偏移量。假想把task_struct放在地址 0 处link 的地址就是它相对于结构体头部的字节偏移。start是link成员的地址也就是红框的起始地址start - 偏移量往回减掉 link 在结构体里面的偏移字节就得到整个 task_struct 结构体的起始地址。一句话拿到内嵌节点 link 的指针反向算出外层大结构体 task_struct 的首地址。内核宏container_of底层就是这个原理container_of 宏考试必考就是图上这个公式c运行#define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)-member ) *__mptr (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );})ptr内嵌成员的指针这里就是struct node *starttype外层结构体类型task_structmember内嵌成员名字link用法示例c运行struct node* link_ptr ...; struct task_struct* p container_of(link_ptr, struct task_struct, link);作用给你结构体里面某个成员的指针返回整个外层结构体的指针。3. 为什么内核要用这种内嵌链表一个task_struct里面可以内嵌多个链表节点图里写了 link1、link同一个 PCB可以同时挂在多个不同链表挂在运行队列 runqueue用 link同时挂在别的设备等待队列用 link1 这就是课件上画的一个 task_struct 里面放多个struct node成员挂多条链表。不需要额外分配链表节点内存节省开销。链表只需要关心prev/next不关心外层结构体是什么链表代码通用。4. 图右侧的二叉树部分顺带看一眼c运行struct node { struct node *left; struct node *right; };同一个思想结构体里面内嵌二叉树节点用left/right指针把很多大结构体串成二叉树。同样可以用container_of拿到树节点指针反向找到外层结构体。5. 完整流程串起来结合前面进程知识点内核把所有task_struct依靠内部内嵌的link节点串成双向链表R 状态进程全部通过link串进 runqueue 运行队列S/D 睡眠进程通过内嵌 node挂到外设对应的等待队列。调度器遍历链表拿到struct node*调用container_of反向得到task_struct读取进程状态、PID 等信息。考点总结Linux 内核链表结构体内部内嵌链表节点不是节点存数据指针container_of offsetof通过内部成员指针反向求外层结构体首地址图上公式一个 task_struct 可以内嵌多个链表节点同时挂在多条不同链表运行队列、等待队列内核靠双向链表组织全部 PCBtask_structcontainer_ofcontainer容器这里指外层结构体容纳成员的那个结构体of…… 的字面含义获取成员所属的容器外层结构体container_of(ptr, type, member)通过结构体内部成员的指针得到外层容器结构体的首地址一、优先级 vs 权限开篇区分权限能不能访问资源能不能做这件事优先级多个进程抢同一个资源时谁先拿到 CPU、谁后拿到访问的先后顺序权限主语进程或者用户权限描述这个主体能不能去访问某个资源比如root 进程可以读所有文件普通用户进程不能读 root 的文件。判断「能不能做」。为什么要有优先级CPU 资源有限进程数量很多进程之间存在竞争性。操作系统要管理竞争设置优先级保证合理调度。如果低优先级进程长期抢不到 CPU代码一直无法推进这个现象叫饥饿问题。二、Linux 优先级PRI 和 nice1. 两个概念PRIpriority内核的进程优先级数值越小优先级越高。普通用户进程默认PRI80普通进程范围60 ~ 99nice优先级修正值用户能调整的参数nice 取值范围[-20, 19]nice -20 → 最高优先级nice 19 → 最低优先级公式PRI(new) PRI(old) nice注意不是直接改 PRI用户只能修改 nice由内核重新计算 PRI。普通用户只能把 nice 调大降低优先级root 用户可以调小 nice提高优先级。2. 查看和修改命令ps -al查看进程能看到 PRI、NI (nice)nice创建进程时直接设置 nice 值renice修改已经在运行的进程的 nicetop命令进入 top 界面也可以交互式修改进程 nice举例默认 PRI80nice0renice -5 进程号 → PRI 80 (-5) 75优先级变高。三、Linux O (1) 调度算法旧 2.6 内核经典调度课件重点目标不管有多少就绪进程找最高优先级进程的耗时永远固定O (1) 时间复杂度。1. runqueue 运行队列结构每个 CPU 有独立的struct runqueue里面有active 数组run存放当前时间片还没用完的就绪进程。数组下标 优先级run[pri]保存该优先级下所有task_struct链表。普通进程优先级60~99对应数组下标。expired 数组wait存放时间片已经耗尽的进程等待下一轮调度。位图 bitmap比特图一个二进制大数组每 1bit 代表一个优先级。bit1代表这个优先级下有就绪进程bit0该优先级没有就绪进程。内核直接扫描 bitmap找到第一个为 1 的 bit就是当前最高优先级。查找速度极快O (1)。2. O (1) 调度完整流程调度时扫描 active 的 bitmap找到最小的有就绪进程的优先级取出进程运行。进程时间片用完把这个进程从 active 链表摘下放到 expired 数组对应优先级链表。active 数组全部进程时间片耗尽active 为空直接swap(run, wait)交换 active 和 expired 数组的指针。原来的 expired 变成新 active原来 active 变成空 expired。只是交换指针不需要拷贝大量数据速度极快。3. bitmap 原理补充位图用 bit 标记哪个优先级有任务。比如bit60 置 1 → 优先级 60 有就绪进程。内核硬件指令直接找第一个置 1 的 bit不需要遍历全部进程链表实现 O (1)。对比如果没有 bitmap只能从头到尾遍历全部进程进程越多越慢O (n)。四、考点汇总优先级决定 CPU 调度先后权限判断能不能访问资源。资源有限进程竞争长期得不到 CPU →饥饿。PRI内核优先级数字越小优先级越高nice 是修正值范围 [-20,19]。PRI(new)PRI(old)nice普通用户不能设置负 nice。查看ps -al修改nice、renice、top。O (1) 调度runqueue 分为 active时间片未用完、expired时间片耗尽两组数组。bitmap 位图标记哪些优先级存在就绪任务快速定位最高优先级任务。active 空的时候直接 swap 交换 active 与 expired 指针不需要拷贝数据。O (1) 含义查找最高优先级进程的时间和进程总数无关固定耗时。