进程与多任务编程
进程的概念
进程是进行中的程序,即程序的一次执行过程。它包含了程序在内存中的代码、数据以及CPU的执行状态。进程是程序的实例,是操作系统进行资源分配和调度的基本单位。
程序:静态的代码文件,存储在硬盘上。
进程:动态的执行实体,加载到内存中运行。
示例:
当我们运行一个程序时,比如 ./a.out,操作系统会将程序加载到内存中,创建一个进程来执行它。每个进程都有独立的内存空间和资源。
为什么需要进程?
早期的计算机一次只能运行一个程序,随着CPU性能的提升,现代操作系统支持多任务处理,即同时运行多个进程。进程的概念使得操作系统能够有效地管理多个程序的并发执行。
并发:多个进程在同一时间段内交替执行。
并行:多个进程在同一时刻同时执行(需要多核CPU)。
进程的组成
进程由以下几部分组成:
代码段:存放程序的执行代码。
数据段:存放全局变量和静态变量。
堆:动态分配的内存区域。
栈:存放函数调用的局部变量和返回地址。
PCB(进程控制块):操作系统用于管理进程的数据结构,包含进程的PID、PPID、状态等信息。
示例:
使用 size ./a.out 命令可以查看进程的内存布局:
bash
$ size ./a.out
text data bss dec hex filename
1515 600 8 2123 84b ./a.out
text:代码段
data:已初始化的全局变量
bss:未初始化的全局变量
进程的状态
进程在执行过程中会处于不同的状态,常见的状态包括:
R(运行态):进程正在执行或等待CPU调度。
S(可中断睡眠态):进程等待某个事件完成(如I/O操作)。
D(不可中断睡眠态):进程等待不可中断的事件(如磁盘I/O)。
T(暂停态):进程被暂停执行。
Z(僵尸态):进程已终止,但父进程尚未回收其资源。
示例:
使用 ps -aux 命令可以查看进程的状态:
bash
$ ps -aux | grep a.out
进程管理命令
top:实时查看系统进程状态。
ps:查看当前进程信息。
pstree:以树状结构显示进程关系。
kill:向进程发送信号,终止进程。
示例:
终止进程:
bash
$ kill -9 5266
进程的创建与结束
在Linux下,进程的创建通过 fork() 系统调用实现。
fork():创建一个子进程,子进程是父进程的副本。
父进程返回子进程的PID。
子进程返回0。
示例:
创建子进程并打印信息:
代码示例
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf(“子进程: PID = %d\n”, getpid());
} else if (pid > 0) {
printf(“父进程: PID = %d, 子进程PID = %d\n”, getpid(), pid);
} else {
perror(“fork失败”);
}
return 0;
}
输出:
父进程: PID = 1234, 子进程PID = 1235
子进程: PID = 1235
进程间的关系
孤儿进程:父进程终止后,子进程由 init 进程收养。
僵尸进程:子进程终止后,父进程未回收其资源。
示例:
查看进程树:
bash
$ pstree -sp 1234
进程的应用场景
并发执行:通过创建多个子进程,实现并发处理任务。
执行不同程序:通过 fork() 和 exec() 函数族,子进程可以执行与父进程不同的程序。
示例:
使用 exec() 执行新程序:
c
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
execl("/bin/ls", “ls”, NULL);
} else if (pid > 0) {
printf(“父进程: PID = %d\n”, getpid());
} else {
perror(“fork失败”);
}
return 0;
}
输出:
父进程: PID = 1234
file1.txt file2.txt
进程间的数据独立性
父子进程拥有独立的内存空间,对数据的修改不会相互影响。
示例:
父子进程修改全局变量:
c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int global_var = 0;
int main() {
pid_t pid = fork();
if (pid == 0) {
global_var += 2;
printf(“子进程: global_var = %d\n”, global_var);
} else if (pid > 0) {
global_var += 1;
printf(“父进程: global_var = %d\n”, global_var);
} else {
perror(“fork失败”);
}
return 0;
}
输出:
父进程: global_var = 1
子进程: global_var = 2
总结
进程是操作系统进行资源分配和调度的基本单位,通过 fork() 系统调用可以创建子进程,实现并发执行。父子进程拥有独立的内存空间,数据修改不会相互影响。进程的状态和关系可以通过 ps、pstree 等命令查看,进程的终止可以通过 kill 命令实现。
拓展:
在多核CPU环境下,进程可以并行执行,进一步提升系统性能。此外,进程间通信(IPC)机制如管道、消息队列、共享内存等,可以实现进程间的数据交换和协作。
代码示例:
以下是一个简单的多进程并发处理任务的示例:
c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void task(int id) {
printf(“任务 %d 开始执行\n”, id);
sleep(2); // 模拟任务执行时间
printf(“任务 %d 执行完成\n”, id);
}
int main() {
for (int i = 0; i < 3; i++) {
pid_t pid = fork();
if (pid == 0) {
task(i);
exit(0);
} else if (pid < 0) {
perror(“fork失败”);
exit(1);
}
}
for (int i = 0; i < 3; i++) {
wait(NULL); // 等待所有子进程结束
}
printf(“所有任务执行完成\n”);
return 0;
}
输出:
任务 0 开始执行
任务 1 开始执行
任务 2 开始执行
任务 0 执行完成
任务 1 执行完成
任务 2 执行完成
所有任务执行完成
通过 fork() 创建多个子进程,每个子进程执行不同的任务,父进程等待所有子进程结束后再继续执行。