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://www.pswp.cn/bicheng/96722.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/96722.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VS Code 插件开发教程

VS Code 插件开发教程 概述 Visual Studio Code&#xff08;简称 VS Code&#xff09;是一款由 Microsoft 开发的开源轻量级编辑器&#xff0c;支持跨平台&#xff08;Windows、macOS、Linux&#xff09;。 其最大的优势之一是强大的插件系统&#xff0c;开发者可以通过编写扩…

Docker技术解析

1.Docker安装 1.如果Ubuntu自带的Docker版本太低&#xff0c;我们需要卸载旧版本并安装新的 sudo apt-get remove docker docker-engine docker.io containerd runc2. 备份原有软件源 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak3.选择合适的镜像源 # 或者使用…

TCP套接字的使用

Java中使⽤TCP协议通信,使用ServerSocket来建立链接,使用Socket进行通信.ServerSocketServerSocket是创建TCP服务端Socket的api,主要方法:方法签名说明ServerSocket(int port)创建一个服务端流套接字Socket,并绑定指定端口Socket accpet()开始监听指定端口,有客户端链接后,返回…

linux执行systemctl enable xxxxx 报 Failed to execute operation: Bad message

linux执行systemctl enable redis.service 报 Failed to execute operation: Bad message 如果在执行 systemctl enable 命令时遇到 "Failed to execute operation: Bad message" 错误&#xff0c;可能是由于以下几个原因导致的。你可以按照以下步骤进行排查和解决&a…

终端之外:解锁Linux命令行的魔法与力量

Linux命令行的核心理念 在记忆具体的指令之前&#xff0c;先理解它的哲学&#xff1a; 一切皆文件 &#xff1a;硬件设施&#xff0c;进程&#xff0c;目录…在Linux中几乎所有资源都被抽象为文件&#xff0c;这意味着你可以通过同样的指令&#xff08;如 ench ,cat&#xff…

CSS 动画实战:实现电商中“加入购物车”的抛物线效果

引言 在电商网站中&#xff0c;“加入购物车”动画 是提升用户体验的经典交互之一。一个小小的商品图标从页面飘向购物车&#xff0c;不仅直观地反馈了操作结果&#xff0c;还能增加趣味性与沉浸感。 实现这一效果的方式有很多&#xff0c;比如 JavaScript 计算路径 动画&…

深度学习之损失函数

深度神经网络由多层网络连接而成&#xff0c;网络连接处防止线性直接相关&#xff0c;采用非线性函数进行逐层隔离&#xff0c;真正实现每层参数的独立性&#xff0c;也就是只对本层提取到的特征紧密相关。因为如果是线性函数直接相连就成了一层中间网络了&#xff0c;只不过参…

Oracle OCP认证考试题目详解082系列第32题

考察知识点:Oracle profiles(配置文件) 英语题目 32.Which are two of the account management capabilities that can be configured using Oracle profiles? A.the number of days for which an account may be logged in to one or more sessions before it is locked…

Docker 部署 MongoDB:单节点与副本集的最佳实践

Docker 部署 MongoDB&#xff1a;单节点与复制集的企业级最佳实践引言&#xff1a;容器化有状态服务的范式转变第一部分&#xff1a;基础概念与生产环境考量1.1 核心 Docker 概念深度解析1.2 Volume vs. Bind Mount&#xff1a;生产环境抉择1.3 获取与验证官方镜像官方镜像默认…

公司本地服务器上搭建部署的办公系统web项目网站,怎么让外网访问?有无公网IP下的2种通用方法教程

本地物理服务器计算机搭建部署应用包括网站等&#xff0c;然后在局域网内的访问外&#xff0c;还需要提供外地的连接访问&#xff0c;这是比较常见的跨网通信需求。如在家或在外访问公司内部办公系统网站&#xff0c;这就涉及内网IP和公网IP的转换&#xff0c;或域名的解析使用…

整体设计 之 绪 思维导图引擎 之 引 认知系统 之 引 认知系统 之 序 认知元架构 之6 拼句 之1 (豆包助手 之8)

摘要(AI生成)认知演进 中 交流句子所包含的 信息描述框架 < i , j > ( m , n )本体论基础&#xff08;数学约束&#xff09;&#xff1a; n n元&#xff08;维度&#xff09;n次&#xff08;层次&#xff09;n个&#xff08;方程&#xff09;n场&#xff08;场景&am…

微软的两个调试器debugpy和python

在生成launch.json文件时&#xff0c;新版本的python扩展解释器类型是debugpy&#xff0c;而不是就版本的type:python&#xff0c;那么两者的区别在哪&#xff1f;1. 历史演变背景&#xff08;1&#xff09;旧版&#xff08;Python扩展 < 2021.09&#xff09;使用 "typ…

【连载2】C# MVC 自定义错误页设计:404/500 处理与 SEO 优化

在开发ASP.NET MVC 应用时&#xff0c;自定义错误页是提升用户体验和 SEO 表现的重要环节。默认的错误页不仅不美观&#xff0c;还可能泄露技术细节&#xff0c;影响用户体验和搜索引擎排名。 实现自定义错误页的完整代码 配置 Web.config 自定义错误页 在 ASP.NET 中&#…

mcp解读——概述及整体架构

概念介绍 什么是模型上下文协议 &#xff08;MCP&#xff09; MCP&#xff08;模型上下文协议&#xff09;是一种用于将 AI 应用程序连接到外部系统的开源标准。 使用 MCP&#xff0c;Claude 或 ChatGPT 等人工智能应用程序可以连接到数据源&#xff08;例如本地文件、数据库&a…

AI 赋能云端运维:基于 MCP 协议深度集成 Codebuddy CLI 与腾讯云 Lighthouse 的实战全解

摘要 在云计算技术飞速演进的今天&#xff0c;服务器的管理与运维正经历着从传统手动操作、脚本自动化到智能化、对话式交互的深刻变革。本文将系统性地、全流程地展示如何将腾讯云 Lighthouse 轻量应用服务器与尖端的 AI 编程助手 Codebuddy CLI 进行深度集成。我们将从服务器…

【Proteus仿真】【51单片机】教室灯光控制器设计

文章目录一、功能简介二、软件设计三、实验现象联系作者一、功能简介 本项目使用Proteus8仿真51单片机控制器&#xff0c;使用LCD1602液晶、DS1302时钟模块、人体红外感应模块、开关LED指示灯、继电器、PCF8591 ADC模块、光敏传感器、按键模块等。 主要功能&#xff1a; 系统运…

成为一个年薪30W+的FPGA工程师是一种什么体验?

FPGA&#xff08;Field-Programmable Gate Array&#xff09;是现场可编程门阵列&#xff0c;通过硬件描述语言设计电路&#xff0c;可实现并行计算&#xff0c;广泛应用于通信、人工智能、工业控制等领域。FPGA工程师的工作包括RTL设计、仿真验证、时序分析等。尽管并非所有公…

ZooKeeper Java客户端与分布式应用实战

1. ZooKeeper Java客户端实战 ZooKeeper应用开发主要通过Java客户端API连接和操作ZooKeeper集群&#xff0c;有官方和第三方两种客户端选择。 1.1 ZooKeeper原生Java客户端 依赖引入 <dependency><groupId>org.apache.zookeeper</groupId><artifactId>…

0303 【软考高项】项目管理概述 - 组织系统(项目型组织、职能型组织、矩阵型组织)

0303 【软考高项】项目管理概述 - 组织系统&#xff08;项目型组织、职能型组织、矩阵型组织&#xff09; 目录0303 【软考高项】项目管理概述 - 组织系统&#xff08;项目型组织、职能型组织、矩阵型组织&#xff09;一、基本概念二、职能型组织二、项目型组织三、矩阵型组织3…

计算机视觉与模式识别前沿一览:2025年8月arXiv 热点研究趋势解析

本推文分析了arXiv中Computer Vision and Patteren Recognition(计算机视觉与模式识别)领域2025年8月发布的近50篇论文的研究热点&#xff0c;旨在帮助读者快速了解近期领域内的前沿技术与研究方向。arXiv是全球最具影响力的开放电子预印本平台之一&#xff0c;由美国国家科学基…