linux进程(1)

📅 2026/7/13 21:12:08
linux进程(1)
一、进程的基本概念和基本操作在 Linux 中进程是一个程序的执行实例是正在运行的程序抽象。操作系统通过进程来管理系统资源的分配与调度。我们可以理解为进程 内核数据结构(task_struct) 自己的程序代码和数据1. PCB与task_struct操作系统中每个进程的信息都存储在一个名为PCBProcess Control Block的数据结构中它相当于进程的“身份证”。在 Linux 中PCB 的具体实现是task_struct结构体它被加载在内存中包含了进程的所有关键信息。2. task_struct包含的主要内容分类描述标识符每个进程的唯一 PID状态运行、就绪、阻塞、退出等优先级决定 CPU 调度顺序程序计数器下一条要执行的指令地址内存指针程序代码、数据、共享内存地址上下文数据寄存器内容切换时保存I/O 状态打开的文件、I/O 请求列表记账信息CPU 时间、时钟数、用户/组 ID 等✅ 小提示所有进程的task_struct以双向链表形式组织在内核中所以对进程的管理就变成了对链表的增删查改3. 查看进程信息1.getpid()与getppid()在程序中可以通过系统调用获取当前进程的 PID 和父进程的 PPID。#include stdio.h #include unistd.h int main() { pid_t pid getpid(); // 获取当前进程 PID pid_t ppid getppid(); // 获取父进程 PID printf(当前进程 PID: %d\n, pid); printf(父进程 PPID: %d\n, ppid); while (1) { sleep(1); } return 0; }2. ps指令# 查看当前终端下的进程 ps # 查看所有进程完整格式 ps aux # 查看所有进程另一种风格 ps -ef # 查看特定 PID 的进程 ps -p 1234使用下面的代码可以快速显示你想要进程的信息ps ajx | head -1; ps axj | grep myproce ps ajx | head -1 ps axj | grep myproce这两个代码的运行效果是一样的。以第一个为例;之前的代码打印出字段名称方便理解后面输出的每一列代表什么;之后是只显示目标进程以及 grep 命令自身3. kill指令kill指令用于向进程发送信号最常用的是终止进程。kill -9 [PID] #直接终止程序ctrl C是杀掉程序4. proc指令proc是一个虚拟文件系统存在于内存中不在硬盘上。它提供了内核和进程信息的接口可以像查看普通文件一样查看系统和进程状态。# 查看系统整体信息 cat /proc/cpuinfo # CPU信息 cat /proc/meminfo # 内存信息 cat /proc/version # 内核版本 # 查看进程信息每个PID对应一个目录 ls /proc/ # 列出所有进程目录 ls /proc/1234/ # 查看PID为1234的进程信息1. /proc/[PID]/exe—— 可执行文件链接exe是一个符号链接指向启动该进程的可执行文件的绝对路径。# 查看进程的工作目录 ls -l /proc/1234/cwd # 输出/proc/1234/cwd - /home/user/project # 进入该进程的工作目录 cd /proc/1234/cwd # 实用场景查看守护进程的工作目录 readlink /proc/nginx/cwd2. /proc/PID/cwd—— 当前工作目录cwd是一个符号链接指向进程运行时所在的当前工作目录# 查看进程的工作目录 ls -l /proc/1234/cwd # 输出/proc/1234/cwd - /home/user/project # 进入该进程的工作目录 cd /proc/1234/cwd # 实用场景查看守护进程的工作目录 readlink /proc/nginx/cwd3. chdir指令与系统调用chdir用于改变当前工作目录有两种形式Shell 命令内置# 切换到指定目录 cd /home/user # 切换到上次目录 cd - # 切换到用户主目录 cd ~C 语言系统调用#include unistd.h int main() { // 改变工作目录 if (chdir(/tmp) 0) { printf(工作目录已切换到 /tmp\n); } // 获取当前工作目录 char cwd[256]; getcwd(cwd, sizeof(cwd)); printf(当前目录: %s\n, cwd); return 0; }二、进程状态1. Linux 进程状态详解在 Linux 内核中进程也称为任务的状态被定义在task_state_array[]中常见状态如下状态标识含义说明R (running)可运行状态并不一定在 CPU 上运行可能在运行队列中等待调度S (sleeping)可中断睡眠等待某个事件完成可被信号唤醒D (disk sleep)不可中断睡眠通常等待 I/O 完成不会被信号唤醒T (stopped)停止状态通过SIGSTOP暂停SIGCONT恢复t (tracing stop)跟踪停止被调试器如 gdb暂停X (dead)死亡状态进程已终止不显示在任务列表中Z (zombie)僵尸状态进程已退出但父进程未读取退出状态进程状态就是task_struct内的一个整数2. 运行、阻塞、挂起状态含义说明运行态Running进程正在 CPU 上执行任何时刻每个 CPU 核心上只能有一个进程处于运行态阻塞态Blocked进程等待某个事件如 I/O、信号量即使 CPU 空闲阻塞态进程也无法运行直到事件完成挂起态Suspended进程被换出内存暂存于磁盘当内存紧张时OS 会将部分进程挂起释放内存给更急需的进程┌─────────────────────────────────────────────────┐ │ │ ▼ │ ┌──────────┐ │ │ 就绪态 │ │ │ (Ready) │ │ └────┬─────┘ │ │ │ 调度器 │ schedule() │ (pick next task) │ │ │ ▼ │ ┌──────────┐ 事件等待 ┌──────────┐ │ │ 运行态 │ ───────────────► │ 阻塞态 │ │ │ (Running)│ (I/O/锁/信号) │ (Blocked) │ │ └────┬─────┘ └────┬─────┘ │ │ │ │ 时间片用完│ │ 事件完成 │ 或被抢占 │ wake_up() │ │ │ │ └──────────────────────────────┘ │ │ │ ▼ │ 重新进入就绪态 │ │ ┌───────────────────────────────────────────────┘ │ ▼ ╔══════════════╗ ║ 挂起态 ║ ← 内存紧张时进程被换出到磁盘swap ║ (Suspended) ║ ╚══════════════╝进程状态的变化表现之一就是要在不同的队列中进行流动本质都是数据结构的增删查改3. 理解内核链表Linux内核中的做法是将task_struct中封装了一个list_headlist_head里面封装了指向上一个节点和下一个节点的指针同时一个tack_struct中可以有多个list_head。传统链表视角结构体即节点 ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Node │ │ Node │ │ Node │ │ next ───┼───►│ next ───┼───►│ next │ │ prev ◄──┼────│ prev ◄──┼────│ prev │ │ data │ │ data │ │ data │ └─────────┘ └─────────┘ └─────────┘ 内核链表视角结构体包含节点 ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ task_struct │ │ task_struct │ │ task_struct │ │ ... │ │ ... │ │ ... │ │ list_head ───┼──►│ list_head ───┼──►│ list_head │ │ next ──────┼───► next ────────┼───► next │ │ prev ◄─────┼──── prev ◄───────┼──── prev │ │ ... │ │ ... │ │ ... │ └──────────────┘ └──────────────┘ └──────────────┘4. 僵尸进程僵尸进程是进程退出后父进程未调用wait()读取其退出状态时产生的。它只保留 PCB不占用内存资源但会导致进程表项泄漏。示例代码创建僵尸进程#include stdio.h #include stdlib.h #include unistd.h int main() { pid_t id fork(); if (id 0) { perror(fork); return 1; } else if (id 0) { printf(parent[%d] is sleeping...\n, getpid()); sleep(30); } else { printf(child[%d] is begin Z...\n, getpid()); sleep(5); exit(EXIT_SUCCESS); } return 0; }运行后子进程退出父进程未回收子进程进入Z 状态。僵尸进程的危害占用 PCBtask_struct导致内存泄漏。大量僵尸进程会耗尽进程表项无法创建新进程。进程退出内存泄漏的问题就不在了5. 孤儿进程当父进程先于子进程退出子进程变成孤儿进程会被系统的init/systemd进程PID 1收养并自动回收其资源。示例代码创建孤儿进程#include stdio.h #include unistd.h #include stdlib.h int main() { pid_t id fork(); if (id 0) { perror(fork); return 1; } else if (id 0) { printf(I am child, pid: %d\n, getpid()); sleep(10); } else { printf(I am parent, pid: %d\n, getpid()); sleep(3); exit(0); } return 0; }6. 查看进程状态使用以下命令查看进程状态ps aux # 显示所有进程详细信息 ps axj # 显示进程组、会话、父进程ID等a显示终端所有进程x显示无控制终端的进程u用户友好格式j作业控制格式三、进程的优先级1. 什么是进程优先级进程优先级Priority决定了CPU资源分配的先后顺序。简单来说优先级越高的进程越有机会被CPU优先执行。在多任务环境中合理配置优先级可以✅ 改善系统整体性能✅ 将不重要进程绑定到指定CPU避免干扰核心任务✅ 保证关键任务的响应速度2. 核心概念PRI 与 NI2.1 PRIPriority优先级表示进程被CPU执行的实际优先级数值越小优先级越高越早被执行2.2 NINice友善值用于修正优先级的数值取值范围-20 19共40个级别负值提升优先级越小越优先正值降低优先级2.3 它们的关系PRI(new) PRI(old) NI3. 使用ps -l查看关键字段$ ps -l F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 0 S 1000 24278 24277 0 80 0 - 28919 do_wait pts/0 00:00:00 bash 0 R 1000 24279 24278 0 80 0 - 38328 - pts/0 00:00:00 ps字段详解字段全称含义与优先级的关系UIDUser ID进程执行者的用户 ID决定用户能否调整进程优先级普通用户只能降优先级PIDProcess ID进程唯一标识号用于renice、top等命令指定要调整的进程PPIDParent Process ID父进程的 PID用于分析进程树继承关系可能影响优先级策略PRIPriority进程当前优先级内核视角数值越小优先级越高CPU 优先调度NINice Value进程的 Nice 值用户视角取值范围 -20~19修正 PRIPRI(new) PRI(old) NI小知识系统怎么知道我访问文件的时候是拥有者所属组还是others系统通过UID和文件的用数字代替的拥有者所属组进行对比相同就为拥有者或所属组都不同就为others。4. 动态调整进程优先级4.1 使用top动态调整$ top # 进入界面后按 r # 输入进程 PID # 输入新的 Nice 值-20~194.2 使用nice启动新进程并设置优先级# 以 Nice10 启动程序 $ nice -n 10 ./my_program # 以 Nice-5需 root启动 $ sudo nice -n -5 ./my_program4.3 使用renice调整已运行进程# 将PID为1234的进程Nice值设为5 renice 5 -p 1234 # 将用户所有进程Nice值设为10 renice 10 -u username4.4 系统调用方式C/C#include sys/time.h #include sys/resource.h int getpriority(int which, int who); int setpriority(int which, int who, int prio);5. 进阶概念并发 vs 并行概念含义类比并发单CPU上多进程快速切换执行同一时刻只有一个进程运行一台咖啡机轮流服务多个队列并行多CPU上多个进程同时执行多台咖啡机同时服务多个队列竞争性多进程竞争有限CPU资源—独立性进程间资源相互隔离—优先级设置不合理就会导致优先级低的进程长时间得不到CPU资源进而导致进程饥饿四、进程的切换和调度1. 进程切换在现代操作系统中进程切换是分时系统能实现“多任务并发”的核心机制。当操作系统决定从当前运行的进程切换到另一个进程时必须完成以下操作保存当前进程的 CPU 上下文即所有寄存器的内容到该进程的私有堆栈中从下一个进程的堆栈中恢复其上下文到 CPU 寄存器跳转到该进程上一次暂停的位置继续执行。这一过程被称为上下文切换。虽然 CPU 中的寄存器是唯一的但每个进程都可以拥有属于自己的“上下文快照”从而实现无缝切换。进程切换最核心的就是保存和恢复当前进程的硬件上下文数据即CPU内寄存器的内容时间片与调度触发当代操作系统是分时系统每个进程被分配一个时间片Time Slice。当进程消耗完时间片就会被调度器从 CPU 上剥离切换到下一个进程。这是进程调度的基本触发条件之一。死循环程序就是通过时间片这个东西不会一直占用CPU什么是寄存器寄存器Register是 CPU 内部的一小部分高速存储单元直接参与指令执行和数据运算。常见寄存器包括程序计数器PC指向下一条要执行的指令地址栈指针SP指向当前函数调用栈的顶部通用寄存器EAX、EBX、ECX 等暂存运算数据和地址状态寄存器EFLAGS记录运算结果的状态进位、溢出、零标志等⚠️ 寄存器数量极少通常几十个但访问速度极快是 CPU 执行指令的核心“工作台”。寄存器是空间 ! 寄存器里面的数据内容其他问题当进程要把自己的进程硬件上下文数据保存起来保存在哪里保存在进程中的task_struct中task_struct中有任务状态段数据结构TSS2. 进程调度Linux 2.6 的 O(1) 调度器Linux 2.6 内核引入的O(1) 调度器每个 CPU 拥有独立的runqueue结构。1. 核心数据结构runqueuestruct rq { spinlock_t lock; // 保护运行队列的自旋锁 unsigned long nr_running; // 队列中可运行进程的总数 struct prio_array *active; // 指向活动队列 struct prio_array *expired; // 指向过期队列 struct prio_array arrays[2]; // 实际存储两个队列活动过期 // ... };2.active与expired详解指针指向队列类型存放的进程active活动队列时间片尚未用完的进程expired过期队列时间片已经耗尽的进程工作流程调度器每次都从active指向的队列中选择优先级最高的进程运行当进程的时间片耗尽就被移动到expired指向的队列中当active队列为空时只需交换两个指针tmp rq-active; rq-active rq-expired; rq-expired tmp;原来的过期队列瞬间变成新的活动队列无需搬运任何进程数据效率极高。3. 优先级队列queue[140]struct prio_array的核心是一个queue数组struct prio_array { unsigned int nr_active; // 本队列中的进程总数 DECLARE_BITMAP(bitmap, MAX_PRIO1); // 位图MAX_PRIO139 struct list_head queue[MAX_PRIO]; // 140个双向循环链表 };元素含义queue[0] ~ [99]实时优先级队列通常不关心queue[100] ~ [139]普通进程优先级队列与nice值对应nice-20对应优先级100queue[priority]每个优先级对应一个双向链表相同优先级的进程按 FIFO 排队4.bitmap位图加速查找最高优先级如果没有位图调度器需要从queue[0]开始遍历到queue[139]找到第一个非空队列——最坏情况下需要检查 140 次。位图优化使用 5 个 32 位整数共 160 位实际只用 140 位每个位bit代表对应优先级的队列是否为空bit 1→ 该优先级队列非空bit 0→ 该优先级队列为空查找最高优先级进程时priority find_first_bit(bitmap, MAX_PRIO); // 一次位运算搞定 task list_entry(queue[priority]-next, task_struct, run_list);无论系统中有多少进程查找最高优先级进程的时间都是常数级 O(1)。5. 完整调度流程示意图runqueue │ ┌──────────────┴──────────────┐ ▼ ▼ active指针 expired指针 │ │ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ 活动队列 │ │ 过期队列 │ │ prio_array │ │ prio_array │ │ ┌───────────┐ │ │ ┌───────────┐ │ │ │ bitmap[5] │ │ │ │ bitmap[5] │ │ │ └───────────┘ │ │ └───────────┘ │ │ ┌───────────┐ │ │ ┌───────────┐ │ │ │queue[140] │ │ │ │queue[140] │ │ │ │ [0]→空 │ │ │ │ [0]→空 │ │ │ │ [1]→空 │ │ │ │ [1]→空 │ │ │ │ ... │ │ │ │ ... │ │ │ │[100]→P1→P4│ │ │ │[100]→P5 │ │ │ │[101]→P2 │ │ │ │[101]→P6→P7│ │ │ └───────────┘ │ │ └───────────┘ │ └─────────────────┘ └─────────────────┘ │ │ └──────────┬───────────────────┘ │ active和expired交换指针 时间片全部耗尽后