Linux 进程管理与编程基础:从进程控制到文件系统调用
1. 进程管理基础
在 Linux 系统中,进程是程序执行的基本单位。操作系统通过 进程控制块(PCB) 来管理每个进程,在 Linux 中 PCB 的具体实现是 struct task_struct 结构体。
struct task_struct {
char name[32]; // 进程名,如 "ps"、"bash"
pid_t pid; // 进程 ID
pid_t ppid; // 父进程 ID
// ... 其他字段
};
进程号(PID) 是系统分配给每个进程的唯一标识符,范围通常从 0 到系统上限。PID 的分配是连续的,如果两个进程的 PID 不连续,说明它们之间的进程已经结束并被系统回收。
父进程与子进程 是相对关系:一个进程通过 fork() 创建的子进程,其父进程就是它自己。子进程结束后,如果父进程没有获取其退出状态,子进程会变成“僵死进程(Zombie)”。
2. 进程控制命令
Linux 提供了丰富的进程控制命令:
sleep + 数字:使当前进程休眠指定秒数。kill + 进程号:向指定进程发送信号(默认 SIGTERM)。kill -9 + 进程号:强制杀死进程(SIGKILL),即使进程处于暂停状态。ctrl+c:终止当前终端前台运行的进程(发送 SIGINT)。ctrl+z:暂停当前终端前台运行的进程(发送 SIGTSTP)。
2.1 前台与后台任务
命令后加 & 可使进程在后台运行:
sleep 300 # 前台运行
sleep 300 & # 后台运行
相关管理命令:
jobs:查看当前终端的所有后台任务。bg %N:将暂停的后台任务 N 转为后台运行。fg %N:将后台任务 N 切换到前台运行。
示例:长时间后台任务
while true; do
date
sleep 2
done &
这个循环会在后台每 2 秒打印一次时间,即使命令行正在输入其他指令,它也会继续执行。
3. 程序编译与调试
3.1 可执行文件格式
- Linux:ELF(Executable and Linkable Format)
- Windows:PE(Portable Executable)
程序源代码(如 main.c)保存在硬盘中,运行时被装载到内存执行。
3.2 GCC 分步编译
# 1. 预编译
gcc -E main.c -o main.i
2. 编译(生成汇编)
gcc -S main.i -o main.s
3. 汇编(生成目标文件)
gcc -c main.s -o main.o
4. 链接(生成可执行文件)
gcc main.o -o main
一步完成
gcc main.c -o main
3.3 Makefile
Makefile 用于自动化构建,特别适合多文件项目:
# 简单示例
main: main.o utils.o
gcc main.o utils.o -o main
main.o: main.c
gcc -c main.c
utils.o: utils.c
gcc -c utils.c
clean:
rm -f *.o main
执行 make clean 可运行 clean 目标下的指令。
3.4 Debug 与 Release 版本
- Debug 版本:包含调试信息,便于开发调试。
- Release 版本:优化后的发行版本,
gcc默认生成此版本。
4. GDB 调试基础
常用 GDB 命令:
| 命令 | 功能 |
|---|---|
l 或 list |
显示当前文件源代码 |
b 行号 或 b 函数名 |
设置断点 |
info break |
查看断点信息 |
r(run) |
运行程序 |
n(next) |
单步执行(不进入函数) |
s(step) |
单步执行(进入函数) |
c(continue) |
继续执行到下一个断点 |
p 变量 |
打印变量值 |
bt |
显示函数调用栈 |
q |
退出 GDB |
5. 静态库与动态库
5.1 库的基本概念
库是一组预先编译好的函数集合:
- 静态库:
libxxx.a,编译时链接,代码被复制到可执行文件中。 - 动态库:
libxxx.so,运行时链接,代码在内存中共享。
5.2 静态库生成与使用
# 1. 编译源文件为目标文件
gcc -c lib1.c lib2.c
2. 打包成静态库
ar rcs libmylib.a lib1.o lib2.o
3. 使用静态库编译
gcc main.c -L. -lmylib -o main_static
5.3 动态库生成与使用
# 1. 编译为位置无关代码
gcc -fPIC -c lib1.c lib2.c
2. 创建动态库
gcc -shared -o libmylib.so lib1.o lib2.o
3. 使用动态库编译
gcc main.c -L. -lmylib -o main_dynamic
4. 运行时指定库路径
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./main_dynamic
5.4 静态库与动态库对比
| 特性 | 静态库 | 动态库 |
|---|---|---|
| 链接时机 | 编译时 | 运行时 |
| 可执行文件大小 | 较大(包含库代码) | 较小(仅记录依赖) |
| 内存占用 | 每个进程独立一份 | 多个进程共享一份 |
| 更新维护 | 需重新编译程序 | 替换库文件即可 |
| 依赖关系 | 无外部依赖 | 依赖外部库文件 |
6. 进程创建与终止
6.1 fork() 系统调用
#include <unistd.h>
pid_t fork(void);
// 返回值:
// >0: 父进程中返回子进程PID
// =0: 子进程中返回0
// -1: 创建失败
子进程会复制父进程的地址空间,包括缓冲区内容。
6.2 进程终止
缓冲区刷新条件:
- 缓冲区满
- 调用
fflush(stdout) - 进程正常结束(调用
exit())
进程终止函数对比:
| 函数 | 类型 | 缓冲区处理 | 使用场景 |
|---|---|---|---|
exit() |
C标准库函数 | 刷新缓冲区 | 普通单进程程序 |
_exit() |
系统调用 | 不刷新,直接丢弃 | 子进程(避免干扰父进程) |
6.3 进程等待
#include <sys/wait.h>
pid_t wait(int *status);
// 等待任意子进程结束
// status: 存储子进程退出状态
获取退出码的两种方式:
// 方法1:右移8位
int exit_code = WEXITSTATUS(status);
// 方法2:使用宏
if (WIFEXITED(status)) {
printf("Exit code: %d\n", WEXITSTATUS(status));
}
7. 文件系统调用
7.1 标准I/O与系统调用
- 标准I/O函数:
fopen,fclose,fread,fwrite - 系统调用:
open,close,read,write
7.2 open() 函数
#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode);
// 返回值:文件描述符(fd),失败返回-1
7.3 write() 函数
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
// fd: 文件描述符
// buf: 要写入的数据
// count: 要写入的字节数
// 返回值:实际写入的字节数,失败返回-1
8. 地址空间概念
逻辑地址(虚拟地址):程序运行时使用的地址,仅在进程自身的虚拟地址空间中有效。应用程序开发者接触到的变量地址、函数地址等都是逻辑地址。
物理地址:实际内存硬件上的地址,由操作系统通过内存管理单元(MMU)进行映射。
9. 总结
本文涵盖了 Linux 进程管理的基础知识、常用命令、程序编译调试、库文件使用、进程创建与终止、文件系统调用等核心概念。掌握这些内容是进行 Linux 系统编程和深入理解操作系统原理的重要基础。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)