当前位置: 首页 > news >正文

Linux中进程和线程常用的API详解

进程与线程基础及 Linux 进程间通信(IPC)详解

一、程序与进程

1. 程序(静态文件)

程序是存储在磁盘上的可执行文件,是静态实体,不占用 CPU、内存等运行时资源,仅占用磁盘空间。不同操作系统的可执行文件格式不同:

  • Windows:.exe
  • Linux:ELF(Executable and Linkable Format)
  • Android:.apk(本质是包含 ELF 可执行文件的压缩包)

2. 进程(动态执行)

进程是程序的动态执行过程,是操作系统进行资源分配和调度的基本单位,拥有独立的生命周期和运行时资源(CPU、内存、文件描述符等)。

(1)ELF 文件解析工具

Linux 下通过以下工具查看和分析 ELF 文件:

  • file 命令:查看文件类型基本信息
    示例:file /bin/ls
    输出:/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, ...

  • readelf 命令:解析 ELF 文件详细结构(需掌握核心选项)

    选项功能描述示例
    -h查看 ELF 文件头信息(位数、字节序等)readelf -h /bin/ls
    -S查看 ELF 节头信息(代码段、数据段等)readelf -S /bin/ls
(2)ELF 文件头关键信息

通过 readelf -h 可获取以下核心信息:

  • 文件位数:第 5 个字节(Magic 字段)标识,01 表示 32 位,02 表示 64 位。
  • 字节序:第 6 个字节标识,01 表示小端序,02 表示大端序。
    • 小端序(Little-Endian):低位字节存低地址,高位字节存高地址。例如 0x12345678 存储为 78 56 34 12
    • 大端序(Big-Endian):高位字节存低地址,低位字节存高地址。例如 0x12345678 存储为 12 34 56 78
(3)ELF 文件类型

ELF 格式包含 4 类核心文件,对应不同使用场景:

类型描述示例
可执行文件可直接运行,包含完整的代码和数据,加载后可执行/bin/ls、自己编译的 ./test
可重定位文件(.o)编译器生成的中间文件,需链接后才能执行(单个源文件编译产物)gcc -c test.c 生成的 test.o
共享目标文件(.so)动态共享库,可被多个程序动态链接复用,节省内存/lib/x86_64-linux-gnu/libc.so.6
核心转储文件(core)程序崩溃时生成的内存快照,用于调试(默认关闭,需 ulimit -c unlimited 开启)程序崩溃后生成的 core.12345

3. 进程控制块(PCB)—— task_struct

当 ELF 程序被执行时,Linux 内核会创建一个 task_struct 结构体 来描述该进程,即进程控制块(PCB)。它记录了进程的所有运行时信息,包括:

  • 进程 ID(PID)、父进程 ID(PPID)
  • 内存资源(虚拟地址空间、页表)
  • CPU 调度信息(优先级、状态)
  • 文件描述符表、信号处理方式
  • 锁资源、信号量等
查看 task_struct 定义

task_struct 定义在 Linux 内核头文件中,路径如下:
/usr/src/linux-headers-<版本号>/include/linux/sched.h
查看命令:

cd /usr/src/linux-headers-$(uname -r)/include/linux
vim sched.h

4. 进程查看命令

命令功能描述示例
pstree以树状图展示进程间的父子关系pstree(查看所有进程树)
ps -ef查看系统中所有进程的详细信息(PID、PPID 等)`ps -ef

二、进程状态

Linux 进程有 7 种核心状态,可归纳为 5 大类,状态转换是进程调度的核心逻辑。

1. 进程的“诞生”—— fork() 系统调用

  • 触发条件:父进程调用 fork() 系统调用。
  • 核心逻辑:内核复制父进程的上下文(PCB、内存空间等),创建一个几乎完全相同的子进程(子进程 PID 唯一,PPID 为父进程 PID)。
  • 初始状态:子进程创建后进入 就绪态(TASK_RUNNING),等待 CPU 调度。

2. 核心状态解析

状态分类内核标识含义与典型场景
就绪态TASK_RUNNING进程已准备好运行,等待 CPU 时间片(放在就绪队列中)。
执行态TASK_RUNNING进程正在 CPU 上执行代码(内核复用 TASK_RUNNING 标识,通过是否在 CPU 上区分就绪/执行)。
睡眠态(挂起态)TASK_INTERRUPTIBLE可中断睡眠:等待非关键事件(如 sleep(10)、键盘输入),可被信号(如 SIGINT)唤醒。
TASK_UNINTERRUPTIBLE不可中断睡眠:等待关键硬件操作(如磁盘修复),仅事件完成后唤醒,ps 显示为 D 状态。
暂停态TASK_STOPPED进程被暂停信号(如 SIGSTOP、Ctrl+Z)暂停,可通过 SIGCONT 恢复。
TASK_TRACED进程被调试器(如 gdb)跟踪,处于暂停调试状态。
退出相关状态EXIT_ZOMBIE(僵尸态)进程已终止,但父进程未读取其退出状态,保留 PCB(ps 显示为 Z 状态)。
EXIT_DEAD(死亡态)父进程调用 wait()/waitpid() 读取退出状态后,内核释放所有资源(进程彻底消失)。

三、进程控制核心函数

1. fork()——创建子进程

函数原型
#include <unistd.h>
pid_t fork(void);
返回值规则
  • 父进程:返回子进程的 PID(正数)。
  • 子进程:返回 0。
  • 失败:返回 -1(如内存不足)。
示例代码(父子进程区分)
#include <stdio.h>
#include <unistd.h>int main() {pid_t pid = fork();if (pid < 0) {perror("fork error"); // 错误处理return -1;} else if (pid == 0) {// 子进程逻辑printf("我是子进程,PID:%d,PPID:%d\n", getpid(), getppid());} else {// 父进程逻辑printf("我是父进程,PID:%d,子进程PID:%d\n", getpid(), pid);pause(); // 暂停父进程,避免子进程先退出}return 0;
}
关键特性:写时复制(Copy-On-Write)

父子进程初始共享同一份物理内存,但当任一进程修改数据(栈、堆、全局变量等)时,内核才为修改的页分配新物理内存,避免不必要的复制开销。示例如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int g_num = 123; // 全局变量int main() {int stack_num = 10; // 栈变量int *heap_num = malloc(4); // 堆变量*heap_num = 100;pid_t pid = fork();if (pid == 0) {// 子进程修改数据(触发写时复制)g_num++;stack_num++;(*heap_num)++;printf("子进程:g_num=%d, stack_num=%d, heap_num=%d\n", g_num, stack_num, *heap_num);free(heap_num);} else {sleep(1); // 等待子进程修改完成// 父进程数据未被修改printf("父进程:g_num=%d, stack_num=%d, heap_num=%d\n", g_num, stack_num, *heap_num);free(heap_num);}return 0;
}

输出结果
子进程:g_num=124, stack_num=11, heap_num=101
父进程:g_num=123, stack_num=10, heap_num=100

2. exit()/_exit()——进程退出

函数区别
函数功能描述缓冲区处理
exit(int status)终止进程,执行退出清理(调用 atexit() 注册的函数),刷新标准 I/O 缓冲区。刷新缓冲区
_exit(int status)直接终止进程,不执行清理,不刷新缓冲区(内核级退出)。不刷新缓冲区
退出码规则
  • exit(0)/exit(EXIT_SUCCESS):正常退出。
  • exit(1)/exit(EXIT_FAILURE):异常退出(非 0 即可,通常用 1)。
  • 退出码范围:0~255,超出则取模 256。
示例代码(atexit() 注册退出函数)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>// 注册的退出函数(栈式调用,反向执行)
void clean1() { printf("clean1: 退出清理1\n"); }
void clean2() { printf("clean2: 退出清理2\n"); }int main() {atexit(clean1); // 先注册atexit(clean2); // 后注册printf("程序执行中(未刷新缓冲区)"); // 无换行符,缓冲区未刷新
#ifdef USE__EXIT_exit(0); // 直接退出,不刷新缓冲区,不执行 clean1/clean2
#elseexit(0);  // 刷新缓冲区,执行 clean2 → clean1(反向)
#endifreturn 0;
}

编译与运行

  • 正常退出(exit(0)):
    gcc test.c -o test && ./test
    输出:程序执行中(未刷新缓冲区)clean2: 退出清理2 clean1: 退出清理1
  • 直接退出(_exit(0)):
    gcc test.c -o test -DUSE__EXIT && ./test
    输出:程序执行中(未刷新缓冲区)(无清理函数执行)

3. wait()/waitpid()——回收子进程

父进程通过这两个函数回收子进程的退出状态,避免子进程成为僵尸进程。

函数原型
#include <sys/wait.h>
#include <sys/types.h>// 等待任意子进程退出,获取退出状态
pid_t wait(int *status);// 等待指定 PID 的子进程退出,可设置非阻塞
pid_t waitpid(pid_t pid, int *status, int options);
核心参数说明(waitpid()
  • pid:指定等待的子进程 PID(-1 表示等待任意子进程)。
  • status:存储子进程退出状态的指针(需通过宏解析)。
  • options:选项(0 表示阻塞,WNOHANG 表示非阻塞)。
退出状态解析宏

通过 status 指针获取子进程退出详情,核心宏如下:

功能描述适用场景
WIFEXITED(status)判断子进程是否正常退出(exit/_exit正常退出
WEXITSTATUS(status)提取正常退出的退出码(需先通过 WIFEXITED 判断)正常退出
WIFSIGNALED(status)判断子进程是否被信号终止信号终止(如 SIGKILL、SIGSEGV)
WTERMSIG(status)提取终止子进程的信号编号(需先通过 WIFSIGNALED 判断)信号终止
WIFSTOPPED(status)判断子进程是否被暂停暂停状态(如 SIGSTOP)
示例代码(回收正常退出的子进程)
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>int main() {pid_t pid = fork();if (pid == 0) {// 子进程:正常退出,退出码 42printf("子进程 PID:%d,即将退出\n", getpid());exit(42);} else {// 父进程:等待子进程退出int status;pid_t ret = waitpid(pid, &status, 0); // 阻塞等待if (ret == -1) {perror("waitpid error");return -1;}// 解析退出状态if (WIFEXITED(status)) {printf("子进程 %d 正常退出,退出码:%d\n", ret, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("子进程 %d 被信号 %d 终止\n", ret, WTERMSIG(status));}}return 0;
}

输出结果
子进程 PID:1234,即将退出
子进程 1234 正常退出,退出码:42

示例代码(回收被信号终止的子进程)
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>int main() {pid_t pid = fork();if (pid == 0) {// 子进程:触发段错误(SIGSEGV,信号 11)int *null_ptr = NULL;*null_ptr = 10; // 非法内存访问exit(0); // 不会执行} else {// 父进程:等待子进程退出int status;pid_t ret = waitpid(pid, &status, 0);if (WIFSIGNALED(status)) {printf("子进程 %d 被信号 %d 终止(段错误)\n", ret, WTERMSIG(status));}}return 0;
}

输出结果
子进程 1235 被信号 11 终止(段错误)

4. exec 系列函数——替换进程映像

exec 系列函数将当前进程的代码段和数据段替换为新的可执行文件(如 /bin/ls),实现“进程复用”(PID 不变,仅映像替换)。

核心函数(常用 execl
#include <unistd.h>// 格式:路径 +  argv[0] + 参数列表 + NULL
int execl(const char *path, const char *arg0, ..., (char *)NULL);
示例代码(子进程执行 /bin/ls
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>int main() {pid_t pid = fork();if (pid == 0) {// 子进程:替换为 ls 命令(显示当前目录下的文件)printf("子进程即将执行 ls 命令\n");execl("/bin/ls", "ls", "-l", NULL); // 路径:/bin/ls,参数:-l// execl 成功则不返回,失败才执行以下代码perror("execl error");exit(1);} else {wait(NULL); // 等待子进程执行完成printf("父进程:子进程执行完毕\n");}return 0;
}

输出结果
子进程即将执行 ls 命令
total 8
-rwxr-xr-x 1 user user 4568 Aug 10 15:30 test
父进程:子进程执行完毕

四、进程组、会话与终端

1. 进程组(Process Group)

  • 定义:由一个或多个进程组成的集合,用于统一管理(如向组内所有进程发送信号)。
  • 核心属性
    • 进程组 ID(PGID):组内所有进程的 PGID 相同。
    • 组长进程:PGID 等于其 PID 的进程(组长可终止,但组内有进程则组存在)。

2. 会话(Session)

  • 定义:进程组的集合,由会话首进程(创建会话的进程)初始化。
  • 核心属性
    • 会话分为 前台进程组后台进程组
    • 前台进程组可接收终端输入(如 Ctrl+C 发送 SIGINT),后台进程组不可。
    • 会话首进程终止时,会话依然存在。

3. 终端(Terminal)

  • 定义:用户与系统交互的接口(如 SSH 终端、本地终端)。
  • 关联关系:一个控制终端对应一个会话,终端产生的信号(如 Ctrl+Z)发送给前台进程组。

五、守护进程(Daemon)

1. 定义与特点

守护进程是 Linux 中 脱离终端、后台长期运行 的进程,用于执行周期性任务(如日志收集、服务监听)。核心特点:

  • 脱离终端:不依赖任何交互窗口,终端关闭不影响其运行。
  • 父进程为 init(或 systemd):启动后断开与原父进程的联系,由系统初始化进程托管。
  • 后台运行:ps 显示为 daemon 或无终端关联(TTY 为 ?)。

2. 守护进程编写流程(简化版)

  1. fork() 创建子进程,父进程退出(脱离原进程组)。
  2. 子进程调用 setsid() 创建新会话(脱离原终端)。
  3. fork() 创建孙子进程,子进程退出(避免成为会话首进程,无法再次脱离终端)。
  4. 切换工作目录(如 /),关闭不需要的文件描述符,重定向标准 I/O 到 /dev/null
  5. 执行核心业务逻辑(如循环监听端口)。

六、Linux 进程间通信(IPC)

Linux 提供多种 IPC 机制,适用于不同场景,以下是核心方式:

1. 管道(Pipe)—— 匿名管道

  • 定义:内核维护的字节流缓冲区,用于父子进程或兄弟进程间的单向通信。
  • 核心特点
    • 半双工:数据只能单向流动(需两个管道实现双向通信)。
    • 无名称:仅能在有亲缘关系的进程间使用。
    • 基于文件描述符:pipe(fd) 创建两个描述符,fd[0] 读,fd[1] 写。
示例代码(父子进程管道通信)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>int main() {int fd[2];if (pipe(fd) == -1) { // 创建管道perror("pipe error");return -1;}pid_t pid = fork();if (pid == 0) {// 子进程:写数据(关闭读端)close(fd[0]);char msg[] = "你好,父进程!";write(fd[1], msg, strlen(msg));close(fd[1]);exit(0);} else {// 父进程:读数据(关闭写端)close(fd[1]);char buf[100] = {0};ssize_t n = read(fd[0], buf, sizeof(buf));if (n > 0) {printf("父进程收到:%s\n", buf);}close(fd[0]);wait(NULL);}return 0;
}

输出结果
父进程收到:你好,父进程!

2. 有名管道(FIFO)

  • 定义:有文件系统路径的管道(如 /tmp/myfifo),可在无亲缘关系的进程间通信。
  • 核心特点
    • 有名称:通过文件路径标识,任意进程可通过路径访问。
    • 阻塞特性:open 时若管道未被另一端打开,会阻塞直到另一端连接。
示例代码(两个独立进程 FIFO 通信)
发送端(Jack)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>#define FIFO_PATH "/tmp/jack_rose_fifo"int main() {// 若 FIFO 不存在则创建if (mkfifo(FIFO_PATH, 0666) == -1 && errno != EEXIST) {perror("mkfifo error");return -1;}// 以只写方式打开 FIFO(阻塞直到读端打开)int fd = open(FIFO_PATH, O_WRONLY);if (fd == -1) {perror("open error");return -1;}// 循环发送消息char buf[100] = {0};while (1) {printf("Jack: ");fgets(buf, sizeof(buf), stdin);write(fd, buf, strlen(buf));}close(fd);return 0;
}
接收端(Rose)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>#define FIFO_PATH "/tmp/jack_rose_fifo"int main() {// 若 FIFO 不存在则创建if (mkfifo(FIFO_PATH, 0666) == -1 && errno != EEXIST) {perror("mkfifo error");return -1;}// 以只读方式打开 FIFO(阻塞直到写端打开)int fd = open(FIFO_PATH, O_RDONLY);if (fd == -1) {perror("open error");return -1;}// 循环接收消息char buf[100] = {0};while (1) {memset(buf, 0, sizeof(buf));ssize_t n = read(fd, buf, sizeof(buf) - 1);if (n > 0) {printf("Rose 收到: %s", buf);}}close(fd);return 0;
}

运行方式

  1. 先启动接收端:gcc rose.c -o rose && ./rose
  2. 再启动发送端:gcc jack.c -o jack && ./jack
  3. 发送端输入消息,接收端实时显示。

3. 共享内存(Shared Memory)

  • 定义:内核创建的一块内存区域,多个进程可将其映射到自身虚拟地址空间,实现高效数据共享(无需拷贝,直接访问内存)。
  • 核心 API
    • shm_open():创建或打开共享内存对象(类似文件操作)。
    • ftruncate():设置共享内存大小。
    • mmap():将共享内存映射到进程虚拟地址空间。
    • munmap():解除映射。
    • shm_unlink():删除共享内存对象(所有进程解除映射后释放)。
示例代码(父子进程共享内存通信)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/stat.h>#define SHM_NAME "/my_shared_mem"
#define SHM_SIZE 1024int main() {// 1. 创建/打开共享内存对象int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);if (shm_fd == -1) {perror("shm_open error");return -1;}// 2. 设置共享内存大小if (ftruncate(shm_fd, SHM_SIZE) == -1) {perror("ftruncate error");return -1;}// 3. 映射共享内存到虚拟地址空间char *shm_ptr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);if (shm_ptr == MAP_FAILED) {perror("mmap error");return -1;}// 关闭文件描述符(映射后不再需要)close(shm_fd);pid_t pid = fork();if (pid == 0) {// 子进程:写入数据strcpy(shm_ptr, "共享内存通信成功!");printf("子进程写入:%s\n", shm_ptr);exit(0);} else {// 父进程:读取数据wait(NULL);printf("父进程读取:%s\n", shm_ptr);// 4. 解除映射munmap(shm_ptr, SHM_SIZE);// 5. 删除共享内存对象(所有进程解除映射后释放)shm_unlink(SHM_NAME);}return 0;
}

编译与运行
gcc shm_test.c -o shm_test -lrt && ./shm_test-lrt 链接实时库)
输出结果
子进程写入:共享内存通信成功!
父进程读取:共享内存通信成功!

4. 消息队列(Message Queue)

  • 定义:内核维护的消息链表,进程可按优先级发送/接收消息(类似“邮箱”)。
  • 核心特点
    • 消息有序:按优先级或发送顺序排队。
    • 非阻塞选项:支持超时机制(mq_timedsend/mq_timedreceive)。
    • 基于描述符:通过 mq_open() 获取消息队列描述符。
核心 API 与结构体
  • mq_attr 结构体:描述消息队列属性(最大消息数、单条消息最大大小等)。
    struct mq_attr {long mq_flags;    // 标志(忽略)long mq_maxmsg;   // 最大消息数long mq_msgsize;  // 单条消息最大字节数long mq_curmsgs;  // 当前消息数(忽略)
    };
    
  • mq_open():创建或打开消息队列。
  • mq_timedsend():发送消息(支持超时)。
  • mq_timedreceive():接收消息(支持超时)。
  • mq_unlink():删除消息队列。
示例代码(父子进程消息队列通信)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <time.h>
#include <sys/wait.h>
#include <stdlib.h>#define MQ_NAME "/father_son_mq"
#define MAX_MSG_NUM 10    // 最大消息数
#define MAX_MSG_SIZE 100  // 单条消息最大大小int main() {// 1. 初始化消息队列属性struct mq_attr attr;attr.mq_maxmsg = MAX_MSG_NUM;attr.mq_msgsize = MAX_MSG_SIZE;attr.mq_flags = 0;attr.mq_curmsgs = 0;// 2. 创建/打开消息队列mqd_t mq_fd = mq_open(MQ_NAME, O_CREAT | O_RDWR, 0666, &attr);if (mq_fd == (mqd_t)-1) {perror("mq_open error");return -1;}pid_t pid = fork();if (pid == 0) {// 子进程:接收消息char buf[MAX_MSG_SIZE] = {0};struct timespec timeout;for (int i = 0; i < 5; i++) {// 设置超时时间(当前时间 + 15 秒)clock_gettime(CLOCK_REALTIME, &timeout);timeout.tv_sec += 15;// 接收消息(超时返回错误)ssize_t n = mq_timedreceive(mq_fd, buf, MAX_MSG_SIZE, NULL, &timeout);if (n == -1) {perror("mq_timedreceive error");break;}printf("子进程收到:%s\n", buf);}exit(0);} else {// 父进程:发送消息char buf[MAX_MSG_SIZE] = {0};struct timespec timeout;for (int i = 0; i < 5; i++) {// 构造消息sprintf(buf, "父进程的第 %d 条消息", i + 1);// 设置超时时间(当前时间 + 5 秒)clock_gettime(CLOCK_REALTIME, &timeout);timeout.tv_sec += 5;// 发送消息if (mq_timedsend(mq_fd, buf, strlen(buf), 0, &timeout) == -1) {perror("mq_timedsend error");break;}printf("父进程发送:%s\n", buf);sleep(1); // 间隔 1 秒发送}// 等待子进程结束wait(NULL);// 3. 关闭消息队列描述符mq_close(mq_fd);// 4. 删除消息队列mq_unlink(MQ_NAME);}return 0;
}

编译与运行
gcc mq_test.c -o mq_test -lrt && ./mq_test
输出结果
父进程发送:父进程的第 1 条消息
子进程收到:父进程的第 1 条消息
父进程发送:父进程的第 2 条消息
子进程收到:父进程的第 2 条消息

5. 信号(Signal)

信号是 Linux 中轻量级的进程间通信机制,用于通知进程发生了某种事件(如中断、错误)。

(1)常用信号与编号
信号名称编号含义与默认行为
SIGINT2中断(Ctrl+C),默认终止进程
SIGKILL9强制终止,不可被捕获/阻塞
SIGSTOP19暂停(Ctrl+Z),不可被捕获/阻塞
SIGSEGV11段错误(非法内存访问),默认终止并生成 core 文件
SIGCONT18恢复暂停的进程
(2)信号发送函数—— kill()/sigqueue()
  • kill():发送信号给指定进程。
    原型:int kill(pid_t pid, int sig);
    示例:kill(1234, SIGINT);(给 PID 1234 的进程发送中断信号)。

  • sigqueue():发送信号并携带数据(仅支持实时信号)。
    原型:int sigqueue(pid_t pid, int sig, const union sigval value);
    示例:

    union sigval val;
    val.sival_int = 1001; // 携带整数数据
    sigqueue(1234, SIGUSR1, val); // 发送自定义信号 SIGUSR1
    
(3)信号捕获函数—— signal()/sigaction()
  • signal():简单信号捕获(不推荐,兼容性差)。
    原型:void (*signal(int sig, void (*handler)(int)))(int);
    示例:

    #include <stdio.h>
    #include <signal.h>void sigint_handler(int sig) {printf("捕获到 SIGINT 信号(编号:%d),不终止进程!\n", sig);
    }int main() {signal(SIGINT, sigint_handler); // 捕获 SIGINTwhile (1) { sleep(1); } // 循环等待信号return 0;
    }
    
  • sigaction():功能强大的信号捕获(推荐,支持携带数据、信号掩码等)。
    示例(捕获信号并接收数据):

    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>// 信号处理函数(支持接收数据)
    void sigusr1_handler(int sig, siginfo_t *info, void *arg) {printf("捕获到信号:%d\n", sig);printf("携带的数据:%d\n", info->si_int); // 读取 sigqueue 发送的数据
    }int main() {struct sigaction act;act.sa_sigaction = sigusr1_handler; // 设置处理函数act.sa_flags = SA_SIGINFO; // 启用数据接收sigemptyset(&act.sa_mask); // 清空信号掩码// 注册 SIGUSR1 信号的处理函数sigaction(SIGUSR1, &act, NULL);printf("进程 PID:%d,等待 SIGUSR1 信号...\n", getpid());while (1) { sleep(1); }return 0;
    }
    
(4)信号阻塞—— sigprocmask()

通过信号掩码(sigset_t)阻塞指定信号,阻塞期间信号会被挂起,解除阻塞后再处理。
示例:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>void sigint_handler(int sig) {printf("捕获到 SIGINT 信号\n");
}int main() {// 1. 注册信号处理函数signal(SIGINT, sigint_handler);// 2. 初始化信号集,添加 SIGINTsigset_t set;sigemptyset(&set);sigaddset(&set, SIGINT);// 3. 阻塞 SIGINT(期间按 Ctrl+C 不会触发处理函数)printf("开始阻塞 SIGINT,持续 5 秒...\n");sigprocmask(SIG_BLOCK, &set, NULL);sleep(5);// 4. 解除阻塞(挂起的 SIGINT 会立即触发处理函数)printf("解除阻塞 SIGINT\n");sigprocmask(SIG_UNBLOCK, &set, NULL);while (1) { sleep(1); }return 0;
}

七、线程(Thread)

1. 进程与线程的核心区别

对比维度进程(Process)线程(Thread)
资源分配单位系统资源分配的基本单位(独立内存、文件描述符等)CPU 调度的基本单位(共享进程资源)
资源占用占用资源多,创建/销毁开销大仅需少量栈空间,创建/切换开销小
独立性进程间独立,一个崩溃不影响其他进程线程依赖进程,一个线程崩溃可能导致整个进程崩溃
通信方式依赖 IPC 机制(管道、共享内存等)直接共享进程资源(全局变量、堆内存等)

2. 线程的共享与非共享资源

(1)共享资源(进程级资源)
  • 文件描述符表
  • 信号处理方式
  • 当前工作目录
  • 用户 ID 和组 ID
  • 内存地址空间(代码段 .text、数据段 .data、堆 .heap、共享库)
(2)非共享资源(线程级资源)
  • 线程 ID(TID)
  • 处理器现场(寄存器、程序计数器)
  • 独立的用户栈和内核栈
  • errno 变量(每个线程独立)
  • 信号屏蔽字
  • 调度优先级

3. 线程核心 API(POSIX 线程库 pthread

编译时需链接线程库:gcc test.c -o test -lpthread

(1)pthread_self()——获取当前线程 ID
#include <stdio.h>
#include <pthread.h>int main() {// 主线程 ID(%lu 对应 unsigned long)printf("主线程 ID:%lu\n", pthread_self());return 0;
}
(2)pthread_create()——创建线程
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 线程函数(返回值和参数均为 void*,需强制类型转换)
void *thread_func(void *arg) {char *msg = (char *)arg;printf("子线程 ID:%lu,收到参数:%s\n", pthread_self(), msg);sleep(3); // 模拟业务逻辑pthread_exit("子线程退出!"); // 线程退出并返回数据
}int main() {pthread_t tid; // 线程 IDchar *msg = "Hello, Thread!";// 创建线程(attr 为 NULL 表示默认属性)int ret = pthread_create(&tid, NULL, thread_func, (void *)msg);if (ret != 0) {perror("pthread_create error");return -1;}printf("主线程 ID:%lu,子线程 ID:%lu\n", pthread_self(), tid);// 等待子线程退出并回收资源(类似 wait())void *exit_msg;pthread_join(tid, &exit_msg);printf("子线程返回:%s\n", (char *)exit_msg);return 0;
}

输出结果
主线程 ID:140709376947008,子线程 ID:140709368554240
子线程 ID:140709368554240,收到参数:Hello, Thread!
子线程返回:子线程退出!

(3)pthread_join()——回收线程资源
  • 功能:阻塞等待指定线程退出,回收其资源,获取退出状态(类似进程的 waitpid())。
  • 注意:仅适用于 可接合属性 的线程(默认属性),若线程为 分离属性pthread_join() 会直接返回失败。
(4)线程属性——分离属性(Detached)

分离属性的线程退出时会自动释放资源,无需 pthread_join() 回收(避免僵尸线程)。
示例代码:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>void *thread_func(void *arg) {printf("分离线程 ID:%lu\n", pthread_self());sleep(2);return NULL;
}int main() {pthread_t tid;pthread_attr_t attr;// 1. 初始化线程属性pthread_attr_init(&attr);// 2. 设置分离属性pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);// 3. 创建分离线程pthread_create(&tid, &attr, thread_func, NULL);// 4. 销毁线程属性(不再需要)pthread_attr_destroy(&attr);// 尝试回收分离线程(会失败)void *exit_msg;int ret = pthread_join(tid, &exit_msg);if (ret != 0) {printf("pthread_join 失败:%s\n", strerror(ret));}sleep(3); // 等待分离线程执行完成return 0;
}

输出结果
分离线程 ID:140709368554240
pthread_join 失败:Invalid argument

4. 示例代码(创建两个线程并发执行)

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 线程 1 函数
void *thread1_func(void *arg) {while (1) {printf("线程 1(%lu):%s\n", pthread_self(), (char *)arg);sleep(1);}
}// 线程 2 函数
void *thread2_func(void *arg) {while (1) {printf("线程 2(%lu):%s\n", pthread_self(), (char *)arg);sleep(1);}
}int main() {pthread_t tid1, tid2;// 创建两个线程pthread_create(&tid1, NULL, thread1_func, "Hello World!");pthread_create(&tid2, NULL, thread2_func, "Hello Thread!");// 等待线程(避免主线程先退出)pthread_join(tid1, NULL);pthread_join(tid2, NULL);return 0;
}

输出结果(两个线程交替执行):
线程 1(140709368554240):Hello World!
线程 2(140709360161536):Hello Thread!
线程 1(140709368554240):Hello World!
线程 2(140709360161536):Hello Thread!


文章转载自:

http://p5jl7h1l.fhwfk.cn
http://GBaFjgpQ.fhwfk.cn
http://Au8n08oy.fhwfk.cn
http://NIfHimrJ.fhwfk.cn
http://w8W6hHdI.fhwfk.cn
http://y1LmdBRX.fhwfk.cn
http://JHY9zOGF.fhwfk.cn
http://BiJKRPUu.fhwfk.cn
http://0hZeGSGk.fhwfk.cn
http://Sne6hmv3.fhwfk.cn
http://tEFfsCot.fhwfk.cn
http://n6aGbqJO.fhwfk.cn
http://uPWMQzNP.fhwfk.cn
http://J7Ma7PPC.fhwfk.cn
http://VDaC2REy.fhwfk.cn
http://4JTCCxYf.fhwfk.cn
http://BTmJJjq2.fhwfk.cn
http://zhRK2rIh.fhwfk.cn
http://OUp2i2BV.fhwfk.cn
http://chkhtv5U.fhwfk.cn
http://p8ix49K7.fhwfk.cn
http://gkGbEK8d.fhwfk.cn
http://i58ICkhc.fhwfk.cn
http://nbr6AaJe.fhwfk.cn
http://IT2mfSA6.fhwfk.cn
http://iB04E0VH.fhwfk.cn
http://BTovlJLU.fhwfk.cn
http://qdBQzpnR.fhwfk.cn
http://cYazhnTY.fhwfk.cn
http://IUQs650V.fhwfk.cn
http://www.dtcms.com/a/380743.html

相关文章:

  • 【AI论文】多模态大型语言模型的视觉表征对齐
  • php学习(第四天)
  • Vue中使用keep-alive实现页面前进刷新、后退缓存的完整方案
  • Jenkins运维之路(Jenkins流水线改造Day02-1-容器项目)
  • Netty从0到1系列之Netty逻辑架构【上】
  • com.google.common.cache实现本地缓存
  • LeetCode 面试经典 150 题之最后一个单词的长度:逆向遍历高效解法
  • 详解JWT
  • Spring Boot 分布式事务常见问题:Seata、XA 与本地消息表对比
  • 如何在Hugging Face中下载全部文件?
  • AI之VideoTool:AI-Video-Transcriber​​​​​​​的简介、安装和使用方法、案例应用之详细攻略
  • Qt6实现了一个打地鼠小游戏,支持AI自动打地鼠
  • Chapter5—抽象工厂模式
  • WebSocket连接状态监控与自动重连实现
  • 目标计数论文阅读(1)Class-Agnostic Counting
  • LVGL移植2048小游戏全攻略
  • 大模型系列——ChatBI重构企业知识库
  • DEM(数字高程模型)详解
  • 软考 系统架构设计师系列知识点之杂项集萃(144)
  • R语言生物群落(生态)数据统计分析与绘图实践技术应用
  • DPO 深度解析:从公式到工程,从偏好数据到可复用训练管线
  • 今天继续学习Linux系统中shell脚本
  • 开源端到端训练多模态大模型LLaVA 深度拆解
  • 周志华《机器学习导论》第10章 降维与度量学习
  • PyQt置顶窗口
  • 基于图像和激光的多模态点云融合与视觉定位
  • 企业数据防护利器:Curtain e-locker 支持NCA合规
  • 【Vue2 ✨】Vue2 入门之旅 · 进阶篇(九):Vue2 性能优化
  • Java面试问题记录(二)
  • 势能分析 线段树 学习记录