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 命令:

命令 功能
llist 显示当前文件源代码
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 进程终止

缓冲区刷新条件

  1. 缓冲区满
  2. 调用 fflush(stdout)
  3. 进程正常结束(调用 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 系统编程和深入理解操作系统原理的重要基础。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐