操作系统——进程
在操作系统的世界里,进程是一个核心概念。无论是我们日常使用的图形界面程序,还是服务器后台运行的服务,本质上都是一个个进程在工作。本文将从进程的基本含义出发,逐步深入探讨进程与程序的区别、进程的状态、调度机制,以及如何在 Linux 中操作进程,最后通过实践案例帮助你巩固所学知识。
一、进程的含义:程序的 "动态人生"
简单来说,进程是程序的执行过程。当我们双击一个应用程序(比如浏览器),或者在终端运行./a.out
时,操作系统会为这个程序分配内存资源、CPU 时间片等,这个 "活动" 的过程就是进程。
进程的所有信息都被记录在一个名为PCB(Process Control Block,进程控制块) 的结构体中(在 Linux 中对应的结构体是task_struct
)。可以把 PCB 理解为进程的 "身份证 + 档案",包含了进程运行所需的全部关键信息:
- PID(Process ID,进程标识符):每个进程的唯一编号,就像人的身份证号。
- 当前工作路径:进程的 "工作目录",可通过
chdir
系统调用修改。 - umask:文件创建时的默认权限掩码(比如默认
0002
,表示新文件权限会去掉其他用户的写权限)。 - 打开的文件列表:进程已打开的文件描述符,包括普通文件、管道、设备等。
- 信号相关设置:如何处理异步事件(比如
Ctrl+C
发送的终止信号)。 - 用户 ID 和组 ID:标识进程的所属用户和用户组,用于权限控制。
- 资源上限:进程能使用的系统资源上限(可通过
ulimit -a
查看,比如最大打开文件数、最大内存使用量等)。
二、进程与程序:动态与静态的博弈
很多人会混淆 "进程" 和 "程序",其实两者的核心区别在于 "动态" 与 "静态":
维度 | 程序 | 进程 |
---|---|---|
存在形式 | 静态:存储在硬盘中的代码和数据集合 | 动态:程序的执行过程(创建→调度→消亡) |
生命周期 | 永存:只要不删除,就一直存在 | 暂时:从启动到结束,有明确的生命周期 |
状态变化 | 无:始终是固定的代码和数据 | 有:运行、暂停、阻塞等状态切换 |
资源占用 | 不占用 CPU、内存等运行时资源 | 占用 CPU、内存、文件描述符等资源 |
并发能力 | 无:无法同时 "运行" 多个副本 | 有:多个进程可并发执行 |
举个例子:我们编写的test.c
编译后生成a.out
(这是程序,静态存储在硬盘);当执行./a.out
时,操作系统会创建一个进程(有唯一 PID),这个进程就是a.out
的动态执行过程。
一个程序可以生成多个进程(比如多次运行./a.out
,会得到多个 PID 不同的进程);一个进程也可以执行多个程序(比如通过exec
系列函数切换执行的程序)。
三、进程的核心作用:实现 "同时" 做更多事
进程的核心价值是实现并发—— 让计算机 "看起来" 能同时运行多个任务。比如我们可以一边用微信发消息,一边用浏览器查资料,这背后就是多个进程在协同工作。
这里需要区分两个容易混淆的概念:
- 并发:宏观上多个任务同时进行,但微观上 CPU 通过快速切换任务(上下文切换)实现,同一时刻只有一个任务在执行(比如单 CPU 系统)。
- 并行:微观上多个任务真正同时执行,需要多个 CPU 核心(比如双核心 CPU 可同时运行两个进程)。
举个代码例子:如果我们想同时处理 "上下左右按键输入" 和 "发送视频",单线程循环嵌套无法实现(会陷入内层循环无法跳出),但通过两个进程分别处理,就能实现 "同时进行":
// 伪代码:两个进程分别处理不同任务
// 进程1:处理上下左右输入
while(1){检测上下左右按键;响应按键操作;
}// 进程2:发送视频
while(1){采集视频数据;发送视频;
}
四、进程的状态:从诞生到消亡的 "人生阶段"
进程的生命周期中会经历多种状态,不同操作系统对状态的划分略有差异:
1. 基本操作系统的三状态模型
- 就绪态:进程已获取除 CPU 外的所有资源,等待 CPU 调度。
- 执行态:进程正在 CPU 上执行代码。
- 阻塞态:进程因等待资源(比如等待键盘输入、文件读取完成)而暂停,即使分配 CPU 也无法执行。
2. Linux 中的进程状态
Linux 对进程状态的划分更细致,通过ps aux
可查看进程状态:
- R(运行态):包括正在 CPU 执行和处于就绪态的进程(等待 CPU 调度)。
- S(可唤醒睡眠态):进程等待资源,但可被信号唤醒(比如等待键盘输入时,
Ctrl+C
可终止)。 - D(不可唤醒睡眠态):进程等待关键资源(比如磁盘 I/O),不可被信号唤醒(避免数据不一致)。
- T(暂停态):进程被暂停(比如收到
SIGSTOP
信号,可通过SIGCONT
恢复)。 - Z(僵尸态):进程已结束,但父进程未回收其资源(PCB 仍存在),需父进程调用
wait
系列函数清理。
五、进程调度:操作系统的 "指挥家"
操作系统的核心功能之一是进程调度—— 决定哪个进程获取 CPU 资源,以及运行多长时间。调度的目标是提高系统吞吐量、减少响应时间,不同场景采用不同算法:
1. 调度算法分类
实时系统(要求任务在规定时间内完成):
- FIFO(先来先服务):按请求顺序调度,适合长任务。
- RR(时间片轮转):为每个任务分配固定时间片,超时后切换,适合短任务。
- 短任务优先:优先调度运行时间短的任务,减少平均等待时间。
分时系统(不保证严格时间限制,注重交互响应):
- 时间片轮询:每个进程轮流获得 CPU 时间片(比如 10ms),宏观上 "同时" 运行。
- 优先级调度:高优先级进程先执行(优先级可动态调整,避免低优先级进程饿死)。
2. 上下文切换
当调度器切换进程时,需要保存当前进程的状态(CPU 寄存器、内存映射等),加载新进程的状态,这个过程称为上下文切换。虽然上下文切换会消耗资源,但正是通过这种切换,才能实现多进程并发。
六、Linux 中查询进程的常用命令
在 Linux 中,我们可以通过以下命令查看和管理进程:
ps aux
:查看系统中所有进程的详细信息,包括:- 状态(R/S/D/T/Z)、PID、PPID(父进程 ID)、CPU 占用率、内存占用等。
- 示例:
ps aux | grep a.out
可筛选出名为a.out
的进程。
top
:动态显示进程状态(默认每秒刷新),按 CPU / 内存占用排序,适合监控系统负载。kill
和killall
:发送信号控制进程:kill -2 PID
:发送SIGINT
信号(相当于Ctrl+C
),请求进程终止。kill -9 PID
:发送SIGKILL
信号,强制终止进程(进程无法忽略)。killall -9 进程名
:终止所有同名进程(比如killall -9 a.out
)。
七、进程操作的核心函数(原语)
在 C 语言中,我们可以通过系统调用直接操作进程,最核心的是fork
、getpid
和getppid
:
fork()
:创建子进程,特点是 "一次调用,两次返回":- 父进程中返回子进程的 PID(>0)。
- 子进程中返回 0。
- 子进程是父进程的副本(复制用户空间和 PCB,但 PID 不同),变量不共享(各自有独立内存空间)。
- 示例:
#include <stdio.h> #include <unistd.h>int main() {pid_t pid = fork();if (pid > 0) {printf("父进程:PID=%d,子进程PID=%d\n", getpid(), pid);} else if (pid == 0) {printf("子进程:PID=%d,父进程PID=%d\n", getpid(), getppid());}return 0; }
getpid()
:获取当前进程的 PID。getppid()
:获取当前进程的父进程 PID。
八、实践练习:动手操作进程
练习 1:两个进程向同一文件写入数据
编写程序,创建父进程和子进程,分别向1.txt
写入带时间戳和 PID 的信息:
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main() {int fd = open("1.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);pid_t pid = fork();if (pid > 0) { // 父进程while (1) {time_t t = time(NULL);char* time_str = ctime(&t);char buf[100];sprintf(buf, "父进程 %d:%s", getpid(), time_str);write(fd, buf, strlen(buf));sleep(2); // 每2秒写一次}} else if (pid == 0) { // 子进程while (1) {time_t t = time(NULL);char* time_str = ctime(&t);char buf[100];sprintf(buf, "子进程 %d:%s", getpid(), time_str);write(fd, buf, strlen(buf));sleep(3); // 每3秒写一次}}close(fd);return 0;
}
总结
进程是操作系统资源分配和调度的基本单位,理解进程的概念、状态、调度机制,是掌握操作系统的关键。从静态的程序到动态的进程,从单个任务到多进程并发,进程的设计让计算机能够高效地处理复杂任务。
希望通过本文的讲解和实践案例,你能对 Linux 进程有更清晰的认识。不妨动手试试文中的练习,感受进程的运行机制吧!
进程的内存
Stack |(栈)
--------|
Share |(共享区)So动态库/共享库
--------|
Heap |(堆)程序原来控制 手动申请手动释放
--------|
Data |
--------|--->数据段
Code |
分布
0-3G(用户内存空间),是进程的空间,3G-4G是内核(内核内存空间)的空间,虚拟地址
虚拟地址*物理内存和虚拟内存的地址 映射表 1page=4kmmu(内存管理单元)