解码程序与进程
程序与进程基础概念
程序
程序是一系列有序指令的集合,用于告诉计算机完成特定操作或解决问题。
- 编程语言发展:机器语言→汇编语言(指令集不兼容)→高级语言(C、C++、Python 等,提高开发效率)。
- 程序的存在形式:以源文件(如
.c、.py)存储在磁盘中,是静态文本,需经编译转换为可执行文件才能运行。
进程
进程是程序在处理器上的一次执行过程,是操作系统分配资源的基本单位(线程是调度的最小单位,进程包含线程)。
- 动态性:程序运行时从外存加载到内存,系统分配资源(内存、CPU),从静态变为动态。
- 核心区别:
- 程序是 “静态文本”,进程是 “动态执行过程”;
- 一个程序可对应多个进程(如多次打开同一软件)。
冯诺依曼结构
计算机硬件遵循冯诺依曼结构,由五大核心组件组成:

- 控制器:指挥程序运行,协调各组件工作。
- 运算器:执行算术运算和逻辑运算。
- 存储器:存放程序和数据(内存、外存)。
- 输入设备:将外部信息转换为计算机可识别形式(如键盘、鼠标)。
- 输出设备:将计算结果转换为用户可理解形式(如显示器、打印机)。
程序编译流程(GCC 编译器)
C 语言程序需经 4 个阶段编译生成可执行文件,GCC 是 Linux 默认 C/C++ 编译器,核心命令及流程如下:

虚拟内存
- 32 位系统中,每个进程默认拥有 4G 虚拟内存空间,分为内核空间(1G,高地址)和用户空间(3G,低地址)。
- 虚拟内存通过 MMU(内存管理单元)映射到物理内存,进程间内存空间独立,互不干扰。
编译整体流程

各阶段详细说明
预处理阶段
- 作用:处理源文件中所有
#开头的预处理指令,删除注释,添加调试信息。#include:将包含的头文件内容直接拷贝到源文件中。#define:展开所有宏定义。#if:执行条件编译,保留符合条件的代码。
- GCC 命令:
gcc -E xxx.c -o xxx.i- 参数
E:仅执行预处理,不进行后续编译、汇编、链接。 - 参数
o:指定输出文件名为xxx.i(预处理文件)。
- 参数
编译阶段
- 作用:对预处理后的
.i文件进行词法、语法分析,生成对应硬件平台的汇编文件(如 X86 平台用 GCC,ARM 平台用交叉编译器arm-linux-gcc)。 - GCC 命令:
gcc -S xxx.i -o xxx.s- 参数
S:编译到汇编语言,不进行汇编和链接。 - 输出文件
xxx.s:包含汇编指令的文本文件。
- 参数
汇编阶段
- 作用:调用汇编器
as,将汇编文件.s翻译为机器指令,生成可重定位目标文件(.o)。 - GCC 命令:
gcc -c xxx.s -o xxx.o- 参数
c:编译、汇编到目标代码,不进行链接。 - 输出文件
xxx.o:ELF 格式的可重定位文件,包含机器指令但未关联系统库。
- 参数
链接阶段
- 作用:将目标文件
.o与系统标准库(如 C 库libc.so)、其他依赖的.o文件结合,重定位函数地址,生成可执行文件。 - GCC 命令:
gcc xxx.o -o xxx -lc -lgcc- 参数
lc:链接标准 C 库(libc,可省略,GCC 默认链接)。 - 参数
lgcc:链接 GCC 基础库(可省略)。 - 输出文件
xxx:ELF 格式的可执行文件,可直接运行。
- 参数
GCC 常用参数
| 参数 | 作用说明 |
|---|---|
--help |
查看 GCC 所有参数用法 |
--version |
显示 GCC 版本信息 |
-Wall |
开启所有警告提示(推荐开发时使用) |
-g |
添加调试信息,支持 GDB 调试 |
-O2 |
开启二级优化,平衡编译速度和程序运行效率 |
-Wl,<选项> |
将后续选项传递给链接器(如-Wl,-rpath=.) |
ELF 文件结构(目标文件与可执行文件)
Linux 系统中,目标文件(.o)和可执行文件均为 ELF(Executable Linkable Format)格式,核心是按 “节(Section)” 存储不同类型数据,部分节也称为 “段(Segment)”。

核心节的作用
| 节名 | 作用说明 |
|---|---|
.text |
代码段,存放编译后的机器指令(只读) |
.data |
数据段,存放已初始化的全局变量和静态局部变量 |
.bss |
未初始化数据段,存放未初始化的全局变量和静态局部变量(编译时不分配空间,运行时分配) |
.rodata |
只读数据段,存放字符串常量、const全局变量(只读) |
.symtab |
符号表,存放函数、变量的名称和地址信息 |
.strtab |
字符串表,存储 ELF 文件中用到的所有字符串(如节名、变量名) |
.comment |
存放编译器版本信息(如GCC:(GNU)4.2.0) |
.debug |
调试信息,供 GDB 调试使用(需编译时加-g) |
目标文件与可执行文件的区别
- 目标文件(
.o):可重定位文件,仅包含当前源文件的指令和数据,未关联系统库,需链接后才能运行。 - 可执行文件:已完成链接,包含完整的指令、数据和库依赖信息,可直接被操作系统加载运行。
- 查看工具:
file 文件名(查看文件格式)、objdump -h 文件名(查看节信息)、objdump -D 文件名(反汇编可执行文件)。
示例:查看目标文件节信息
# 编译生成目标文件
gcc -c demo.c -o demo.o
# 查看demo.o的节信息
objdump -h demo.o
进程核心知识
进程的特征
- 动态性:进程是程序的执行过程,会经历创建、运行、暂停、终止等状态变化。
- 并发性:多个进程同时存在于内存中,交替占用 CPU 运行(宏观并行,微观串行),提高资源利用率。
- 独立性:进程是独立运行、资源分配和调度的基本单位,拥有独立的内存空间。
- 异步性:进程以不可预知的速度推进,受 CPU 调度、资源竞争等因素影响。
进程的组成
进程由三部分组成,缺一不可:
- 进程控制块(PCB):操作系统为每个进程分配的内存区域,记录进程的关键信息(Linux 中用
struct task_struct结构体,定义在sched.h头文件中)。 - 代码段:进程对应的程序指令(从可执行文件加载,只读)。
- 数据段:进程运行时使用的数据(全局变量、局部变量、中间结果等,可读可写)。
PCB 中的关键信息
- 进程标识符(PID):系统分配的唯一 ID,用于区分不同进程(Linux 中 PID 是用户操作进程的接口)。
- 进程当前状态:如就绪态、运行态等,是 CPU 调度的依据。
- 资源占用信息:进程占用的内存大小、CPU 时间、打开的文件等。
- 父进程 PID(PPID):记录创建当前进程的父进程 ID。
进程的五种基本状态及转换
五种状态定义

- 创建态:进程正在被创建,系统分配 PCB 和初始资源,未进入就绪态。
- 就绪态:已获得除 CPU 外的所有资源,等待 CPU 调度(“万事俱备,只欠 CPU”)。
- 运行态:已获得 CPU 资源,正在执行指令。
- 阻塞态:进程执行中因某事件(如
sleep、read)无法继续,暂时放弃 CPU,等待事件完成。 - 结束态:进程完成任务或异常终止,释放资源(若未释放资源则变为僵尸态)。
状态转换规则
- 创建态 → 就绪态:进程创建完成,资源分配完毕。
- 就绪态 → 运行态:CPU 调度器选中该进程,分配 CPU 资源。
- 运行态 → 就绪态:时间片用完或有更高优先级进程进入就绪态,当前进程放弃 CPU。
- 运行态 → 阻塞态:进程触发阻塞事件(如等待 I/O、调用
sleep)。 - 阻塞态 → 就绪态:阻塞事件完成(如 I/O 结束、
sleep超时)。 - 运行态 → 结束态:进程正常退出(
return 0)或异常终止(如信号终止)。 - 结束态 → 僵尸态:进程终止但资源未被回收(需父进程调用
wait()回收)。
Linux 中进程状态查看
- 命令:
ps -aux或ps -ef(查看所有进程信息)。 - 状态标识(STAT 列):
R:运行态或就绪态(在 CPU 运行队列中)。S:可中断睡眠(阻塞态,等待事件完成)。D:不可中断睡眠(通常等待 I/O)。T:暂停态(被信号或调试器停止)。Z:僵尸态(进程终止但资源未回收)。Ss:主进程处于睡眠态,子进程运行(如systemd)。
进程控制(创建、撤销、执行)
进程控制由操作系统内核实现,核心通过系统调用接口完成,Linux 中常用接口如下:
进程树与 systemd 进程
-
Linux 中所有进程构成进程树,根进程是
systemd(PID=1,系统守护进程)。 -
所有用户进程都是
systemd的子孙进程,可通过pstree查看进程树结构。
进程创建:fork ()
fork()是 Linux 内核提供的系统调用,用于在当前进程(父进程)中创建一个新进程(子进程)。
函数原型
#include <sys/types.h>
#include <unistd.h>
/**
* 创建子进程(子进程复制父进程的代码段、数据段、堆栈段)
* @return 父进程中返回子进程的PID(正整数);子进程中返回0;失败时父进程返回-1,errno设置错误码
* @note 子进程与父进程运行在独立内存空间,写操作互不影响(读时共享,写时复制)
* 子进程不继承父进程的内存锁、定时器、未完成的异步I/O操作
* 子进程的PID唯一,PPID等于父进程PID
* 父进程与子进程的执行顺序由CPU调度器决定,无固定优先级
*/
pid_t fork(void);
使用示例
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char const *argv[]) {
// 父进程的数据段变量
int num = 10;
// 创建子进程,调用fork时子进程已复制父进程资源
pid_t child_pid = fork();
// 通过fork返回值区分父进程和子进程
if (child_pid > 0) {
// 父进程:返回值为子进程PID(正整数)
num += 5;
printf("我是父进程 | PID: %d | 子进程PID: %d | num: %d\n", getpid(), child_pid, num);
} else if (child_pid == 0) {
// 子进程:返回值为0
num -= 5;
printf("我是子进程 | PID: %d | 父进程PID: %d | num: %d\n", getpid(), getppid(), num);
} else {
// fork失败:父进程返回-1
perror("fork创建子进程失败");
return -1;
}
return 0;
}
关键说明
getpid():获取当前进程的 PID。getppid():获取当前进程的父进程 PID。- 子进程复制父进程的资源,但写操作会触发 “写时复制”(Copy-On-Write),即子进程会创建独立副本,不影响父进程数据。
进程撤销:wait () 与 waitpid ()
进程终止后需回收资源,否则会变为僵尸态(占用 PID 和内存)。wait()和waitpid()用于父进程等待子进程状态变化并回收资源。
wait () 函数原型
#include <sys/types.h>
#include <sys/wait.h>
/**
* 父进程等待子进程状态变化,回收子进程资源
* @param wstatus 指向存储子进程退出状态的整数(NULL表示不关心退出状态)
* @return 成功返回被回收子进程的PID;失败返回-1(如无子女进程)
* @note 若子进程已终止(僵尸态),函数立即返回并回收资源
* 若子进程未终止,函数阻塞直到子进程状态变化(终止、暂停、恢复)
* 仅回收第一个变为僵尸态的子进程,无法指定回收目标
*/
pid_t wait(int *wstatus);
waitpid () 函数原型
#include <sys/types.h>
#include <sys/wait.h>
/**
* 指定回收目标子进程,功能更灵活(推荐使用)
* @param pid 回收目标标识:
* - pid > 0: 回收PID等于该值的子进程
* - pid = -1:回收任意子进程(等同于wait())
* - pid = 0: 回收与父进程同进程组的任意子进程
* - pid < -1:回收进程组ID等于pid绝对值的任意子进程
* @param wstatus 存储子进程退出状态的指针(NULL表示不关心)
* @param options 回收选项(可组合使用):
* - WNOHANG:非阻塞模式,无僵尸子进程时立即返回0
* - WUNTRACED:子进程暂停时也返回状态
* - WCONTINUED:子进程被SIGCONT信号恢复时返回状态
* @return 成功返回被回收子进程的PID;WNOHANG模式下无僵尸子进程返回0;失败返回-1
* @note 可精准控制回收哪个子进程,避免wait()的盲目等待
* 子进程退出状态需通过宏解析(如WIFEXITED、WEXITSTATUS)
*/
pid_t waitpid(pid_t pid, int *wstatus, int options);
子进程退出状态解析宏
| 宏名 | 作用说明 |
|---|---|
WIFEXITED(wstatus) |
判断子进程是否正常退出(return 或 exit),正常返回非 0 |
WEXITSTATUS(wstatus) |
获取正常退出的子进程返回值(仅 WIFEXITED 为真时有效) |
WIFSIGNALED(wstatus) |
判断子进程是否被信号终止(如 kill -9),是返回非 0 |
WTERMSIG(wstatus) |
获取终止子进程的信号编号(仅 WIFSIGNALED 为真时有效) |
使用示例
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, char const *argv[]) {
pid_t child_pid = fork();
if (child_pid > 0) {
// 父进程:等待子进程终止并回收资源
int wstatus;
// 阻塞等待PID为child_pid的子进程,关心退出状态
pid_t reaped_pid = waitpid(child_pid, &wstatus, 0);
if (reaped_pid == child_pid) {
// 解析子进程退出状态
if (WIFEXITED(wstatus)) {
printf("子进程正常退出 | 退出码: %d\n", WEXITSTATUS(wstatus));
} else if (WIFSIGNALED(wstatus)) { // kill -9 child_pid
printf("子进程被信号终止 | 信号编号: %d\n", WTERMSIG(wstatus));
}
}
} else if (child_pid == 0) {
// 子进程:执行任务后退出
printf("子进程运行中 | PID: %d\n", getpid());
sleep(10); // 模拟任务执行
exit(3); // 正常退出,返回码3
} else {
perror("fork失败");
return -1;
}
return 0;
}
僵尸态进程处理
- 僵尸态(
Z):进程终止但资源未被父进程回收,由内核维护。 - 处理方法:
- 父进程调用
wait()或waitpid()回收子进程资源。 - 父进程终止后,僵尸态子进程会被
systemd(PID=1)接管并回收。 - 若父进程长期不回收,可通过
kill -9 父进程PID终止父进程,间接回收僵尸进程。 - 子进程主动告知父进程前来收尸
-
子进程在进入僵尸态时,会自动向父进程发送信号SIGCHILD,而父进程可以利用异步信号响应函数来及时处理这些僵尸子进程。
void cleanup(int sig) { // 僵尸子进程会被自动清除 wait(NULL); } int main() { // 在产生子进程之前,准备好处理它们的SIGCHILD信号 signal(SIGCHLD, cleanup); // 子进程退出,成为僵尸进程 pid_t child_pid = fork(); if(child_pid == 0) return 0; else if(child_pid > 0) { // 父进程干自己的活,无需关注子进程 while(1) pause(); } return 0; }
-
- 父进程调用
进程执行:exec 函数族与 system ()
子进程创建后默认复制父进程的代码段,若需让子进程执行新程序,可使用exec函数族或system()。
exec 函数族核心特性
- 作用:替换当前进程的代码段、数据段和堆栈段,执行新程序(进程 PID 不变,仅内容替换)。
- 命名规则:前缀
exec后接字母,代表参数传递方式和功能:l(list):参数以列表形式传递,最后必须以(char *)NULL结束。v(vector):参数以字符串数组形式传递,数组末尾必须是NULL。p(path):自动搜索环境变量PATH中的路径,无需指定程序完整路径。e(environment):自定义环境变量,需传递环境变量数组。
常用 exec 函数:execl () 与 execvp ()
execl () 函数原型与示例
#include <unistd.h>
/**
* 以列表形式传递参数,执行指定程序(需完整路径)
* @param pathname 程序完整路径(如"/bin/ls")
* @param arg 第一个参数为程序名(惯例),后续为命令行参数,最后以(char *)NULL结束
* @return 执行成功无返回(程序已替换);失败返回-1,errno设置错误码
* @note 若pathname不含"/",不会搜索PATH(需用execlp())
* 参数列表必须以NULL终止,否则会导致内存访问错误
*/
int execl(const char *pathname, const char *arg, ...);
// 示例:子进程执行"ls -l /home"
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t child_pid = fork();
if (child_pid == 0) {
// 子进程:替换为ls命令
printf("子进程执行ls -l\n");
// 参数列表:程序名"ls",参数"-l",参数"/home",结束符NULL
execl("/bin/ls", "ls", "-l", "/home", (char *)NULL);
// 若execl返回,说明执行失败
perror("execl执行失败");
exit(1);
} else if (child_pid > 0) {
wait(NULL); // 父进程等待子进程完成
} else {
perror("fork失败");
return -1;
}
return 0;
}
execvp () 函数原型与示例
#include <unistd.h>
/**
* 以数组形式传递参数,自动搜索PATH(无需完整路径)
* @param file 程序名(如"ls",自动搜索PATH)
* @param argv 参数数组,格式:{程序名, 参数1, 参数2, ..., NULL}
* @return 执行成功无返回;失败返回-1
* @note 数组末尾必须是NULL,否则参数传递不完整
*/
int execvp(const char *file, char *const argv[]);
// 示例:子进程执行"ps aux"
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t child_pid = fork();
if (child_pid == 0) {
char *const argv[] = {"ps", "aux", (char *)NULL}; // 参数数组
execvp("ps", argv); // 自动搜索PATH中的ps程序
perror("execvp执行失败");
exit(1);
} else if (child_pid > 0) {
wait(NULL);
} else {
perror("fork失败");
return -1;
}
return 0;
}
system () 函数(简化版 exec)
#include <stdlib.h>
/**
* 执行shell命令(内部调用fork()+execl("/bin/sh", "sh", "-c", command, NULL))
* @param command 待执行的shell命令字符串(如"ls -l")
* @return 命令执行完成后的状态码;command为NULL时,返回是否有shell可用(非0表示可用)
* @note 父进程会阻塞直到命令执行完成
* 内部自动处理fork和exec,使用更简单,但效率略低
* 避免传递用户输入的命令字符串(存在安全风险)
*/
int system(const char *command);
// 示例:执行"echo 'Hello Process'"
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("执行shell命令:\n");
int status = system("echo 'Hello Process'");
printf("命令执行完成,状态码:%d\n", status);
return 0;
}
popen () 函数与pclose () 函数
popen ()
#include <stdio.h>
/**
* 创建管道并执行shell命令,支持与命令的标准输入/输出进行数据交互
* @param command 待执行的shell命令字符串(如"ls -l"、"grep 'test' file.txt",支持shell语法)
* @param type 管道数据流向类型:
* - "r":读取命令的标准输出(命令→管道→当前进程)
* - "w":向命令的标准输入写数据(当前进程→管道→命令)
* @return 成功:返回FILE类型指针(可通过fread/fwrite/fgets等文件流函数操作管道);
* 失败:返回NULL,同时设置errno(如创建管道失败、fork子进程失败、shell执行失败)
* @note 内部自动创建匿名管道+fork子进程,子进程通过"/bin/sh -c command"执行命令
* 若type为"r",读取时若命令未输出会阻塞;若为"w",写入时若命令未读取会阻塞
* 必须与pclose()配对使用,否则子进程会成为僵尸进程
*/
FILE *popen(const char *command, const char *type);
// 示例:子进程(popen内部自动创建)执行"ls -l /home",当前进程读取命令输出
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
// 执行ls -l /home,以"r"模式读取命令输出
FILE *fp = popen("ls -l /home", "r");
if (fp == NULL) {
perror("popen执行失败"); // 打印错误原因(如管道创建失败)
return -1;
}
char buf[1024] = {0}; // 存储命令输出的缓冲区
// 逐行读取命令输出(fgets遇换行或缓冲区满返回)
while (fgets(buf, sizeof(buf), fp) != NULL) {
// 去除缓冲区末尾的换行符(可选,根据需求处理)
buf[strcspn(buf, "\n")] = '\0';
printf("命令输出:%s\n", buf);
}
// 关闭管道并回收子进程资源(必须调用pclose)
int status = pclose(fp);
if (status == -1) {
perror("pclose执行失败");
return -1;
}
printf("命令执行完毕,退出状态码:%d\n", status);
return 0;
}
pclose ()
#include <stdio.h>
/**
* 关闭popen()创建的管道,阻塞等待子进程执行完毕并回收资源
* @param stream popen()返回的FILE类型指针(必须是popen创建的有效流,否则行为未定义)
* @return 成功:返回命令的退出状态(需通过sys/wait.h的宏解析,如WIFEXITED/WEXITSTATUS);
* 失败:返回-1,同时设置errno(如stream无效、等待子进程时被信号中断)
* @note 会阻塞当前进程,直到popen创建的子进程完全退出
* 若不调用pclose,子进程会残留为僵尸进程,占用系统PID资源
* 退出状态解析逻辑与waitpid()一致,需结合宏判断命令退出原因
*/
int pclose(FILE *stream);
// 示例:向"cat"命令写数据,再通过pclose关闭管道并解析命令退出状态
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h> // 包含退出状态解析宏
int main() {
// 执行cat命令(cat默认读取标准输入并输出),以"w"模式向命令写数据
FILE *fp = popen("cat", "w");
if (fp == NULL) {
perror("popen执行失败");
return -1;
}
// 向cat命令的标准输入写数据(fwrite返回实际写入字节数)
const char *write_data = "Hello from popen()!\nThis is a test for pclose().";
size_t write_len = fwrite(write_data, 1, strlen(write_data), fp);
if (write_len != strlen(write_data)) {
perror("向命令写数据失败");
pclose(fp); // 即使写入失败,也要关闭管道避免僵尸进程
return -1;
}
// 关闭管道并等待子进程退出,获取退出状态
int status = pclose(fp);
if (status == -1) {
perror("pclose执行失败");
return -1;
}
// 解析命令退出状态
if (WIFEXITED(status)) {
// 命令正常退出(如return、exit)
printf("命令正常退出,退出码:%d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
// 命令被信号终止(如kill -9)
printf("命令被信号终止,信号编号:%d\n", WTERMSIG(status));
} else {
printf("命令退出状态未知\n");
}
return 0;
}
常用工具与命令
| 命令 / 工具 | 作用说明 | 示例 |
|---|---|---|
ps -aux |
查看系统所有进程的详细信息(PID、状态、CPU 占用等) | ps -aux | grep firefox(查找火狐进程) |
pstree |
以树状图显示进程间的父子关系 | pstree -p(显示 PID) |
objdump |
查看 ELF 文件的节信息、反汇编代码 | objdump -h demo.o(查看节信息) |
file |
查看文件类型(判断是否为 ELF 文件) | file demo(判断 demo 是否为可执行文件) |
kill |
发送信号终止进程 | kill -9 1234(强制终止 PID=1234 的进程) |
gcc |
C/C++ 编译器,用于编译程序 | gcc -g -o test test.c(生成带调试信息的可执行文件) |

浙公网安备 33010602011771号