一、进程的概念
1.进程的定义
进程是程序在内存中运行时的一个实例,是操作系统进行资源分配和调度的基本单位。例如,当我们运行一个程序(如 QQ)时,每次运行都会创建一个新的进程。即使运行的是同一个程序,每次创建的进程都是独立的。
例如,`main.c`是程序的源代码文件,而`a.out`是编译后的可执行程序。运行`a.out`的命令(如`./a.out`)会将程序加载到内存中,形成一个进程。
2.进程与程序的区别
• 程序:静态的代码和数据的集合,存储在磁盘上。
• 进程:动态的程序执行过程,运行在内存中。
例如:
• 程序:`main.c`(源代码文件)。
• 进程:编译后运行的`a.out`文件。
二、为什么需要进程?
1.历史背景
• 第一台计算机(ENIAC)一次只能运行一个程序。
• 随着 CPU 性能的提升,单片机和多核处理器的出现,计算机需要同时处理多个任务。
2.进程的作用
• 并发与并行:实现多个任务同时运行(并发)或同时执行(并行)。
• 资源管理:操作系统通过进程来管理计算机的软硬件资源。
三、进程的组成
1.程序的组成
程序由代码和数据组成,具体分为以下几个部分:
• 代码区(text):存放程序的指令。
• 数据区(data):存放已初始化的全局变量和静态变量。
• BSS 区(bss):存放未初始化的全局变量和静态变量。
• 堆(heap):动态分配的内存区域。
• 栈(stack):用于函数调用时的局部变量存储。
2.进程的组成
进程不仅包括程序的代码和数据,还包括进程控制块(PCB,Process Control Block)。
PCB 的内容
• PID(进程 ID):唯一标识一个进程。
• PPID(父进程 ID):标识父进程的 ID。
• 当前工作路径:进程的工作目录。
• 打开的文件列表:进程打开的文件描述符。
• 信号相关设置:处理异步事件的信号信息。
• 用户 ID 和组 ID:标识进程的用户和组。
四、进程的状态
进程的状态可以通过`ps`命令查看,常见的状态包括:
状态 含义
R 运行态或可运行态
S 可中断睡眠态
D 不可中断睡眠态
T 暂停态
Z 僵尸态
X 已退出
可以通过以下命令查看进程状态:
• `ps -aux`:查看系统中所有进程的详细信息。
• `ps -eLf`:查看系统中所有进程的线程信息。
五、进程管理的命令
1.`top`命令
类似于 Windows 的任务管理器,用于动态查看系统中进程的运行情况。可以实时显示系统的 CPU 使用率、内存使用率以及各进程的资源占用情况。
2.`ps`命令
用于查看当前系统中的进程信息。常用参数包括:
• `-e`:显示所有进程。
• `-f`:显示完整的进程信息。
• `-L`:显示所有线程。
• `-aux`:显示更详细的进程信息。
常用示例:
```bash
ps -eLf | head -1 # 查看进程信息的标题行
ps -eLf | grep a.out # 查找包含 "a.out" 的进程
ps -aux | grep a.out # 查找包含 "a.out" 的进程
```
3.`pstree`命令
用于查看进程的树状结构。可以清晰地展示进程之间的父子关系。常用参数包括:
• `-p`:显示进程 ID。
• `-sp`:显示进程的父进程 ID。
示例:
```bash
pstree -sp 5934 # 查看 PID 为 5934 的进程的树状结构
```
4.`kill`命令
用于向进程发送信号。常用的信号包括:
• `-9`:强制终止进程。
• `-15`(默认):向进程发送终止信号。
示例:
```bash
kill -9 5266 # 强制终止 PID 为 5266 的进程
kill -l # 查看所有可用的信号
```
六、Linux 下的进程编程
1.创建进程:`fork()`
`fork()`是创建进程的核心函数,用于从当前进程中克隆一个子进程。
函数原型
```c
pid_t fork(void);
```
返回值
• 在父进程中:返回子进程的 PID(>0)。
• 在子进程中:返回 0。
• 失败时:返回-1,并设置`errno`。
示例代码
```c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("Fork failed");
return 1;
} else if (pid == 0) {
// 子进程
printf("This is the child process, PID: %d\n", getpid());
} else {
// 父进程
printf("This is the parent process, PID: %d, Child PID: %d\n", getpid(), pid);
}
return 0;
}
```
2.进程的状态与关系
• 孤儿进程:父进程退出后,子进程仍然存在,由`init`进程(PID 为 1)收养。
• 僵尸进程:子进程退出后,父进程未回收子进程的资源,子进程进入僵尸态。
3.练习
练习 1
创建子进程,让父进程和子进程分别打印"hello"。
```c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("Fork failed");
return 1;
} else if (pid == 0) {
// 子进程
while (1) {
printf("Child: hello\n");
sleep(1);
}
} else {
// 父进程
while (1) {
printf("Parent: hello\n");
sleep(1);
}
}
return 0;
}
```
练习 2
通过`fork()`创建多个进程,验证父子进程的关系。
```c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
for (int i = 0; i < 5; i++) {
pid_t pid = fork();
if (pid < 0) {
perror("Fork failed");
return 1;
} else if (pid == 0) {
printf("Child process %d, PID: %d, Parent PID: %d\n", i, getpid(), getppid());
break;
}
}
if (getpid() == getppid()) {
printf("Parent process, PID: %d\n", getpid());
}
return 0;
}
```
七、总结
进程是操作系统中非常重要的概念,通过`fork()`可以创建子进程,实现多任务并发处理。在实际开发中,进程编程广泛应用于服务器程序、多任务处理等领域。
希望本文对你理解进程的概念和使用有所帮助!