欢迎光临
我们一直在努力

进程间通信---管道/信号---嵌入式(Linux)

1、进程间通信

1.1 进程间通信的方式(IPC,Inter-Process Communication)

通信方式核心特点适用场景
管道(Pipe) 最简单、最方便,基于文件描述符的字节流通信 简单数据传输、亲缘进程通信
信号(Signal) 异步通知机制,仅传递 “事件”,无法传递大量数据 进程间事件通知(如终止、暂停)
消息队列 基于 “消息” 的结构化通信,可按类型读取消息 非亲缘进程、结构化数据传输
共享内存 最高效(直接访问同一块内存),需配合同步机制(如信号量)避免资源竞争 大量数据传输、高性能场景
信号灯(信号量) 主要用于进程 / 线程同步,而非数据传输 多进程资源互斥 / 同步
本地域套接字 通用性最强(支持跨网络、跨主机,本地通信效率接近共享内存) 复杂通信场景、跨进程 / 主机

1.2 管道

管道是内核维护的一块单向缓存区,本质是 “伪文件”(无实际磁盘存储,数据在内存中),数据按 “先进先出”(FIFO)顺序传输。

1.2.1 管道特性

🔹 读端行为(阻塞模式,无 O_NONBLOCK)

管道状态read () 行为
有至少 1 个写端 + 有数据 读取数据,返回读取的字节数
有至少 1 个写端 + 无数据 阻塞等待,直到有数据写入(或所有写端关闭)
无任何写端 + 有数据 读取数据,返回读取的字节数
无任何写端 + 无数据 返回 0(EOF),不阻塞,程序可继续执行

🔹 写端行为(阻塞模式,无 O_NONBLOCK)

管道状态write () 行为
有至少 1 个读端 + 缓存区未满 写入数据,返回写入的字节数(匿名管道缓存区默认 64K,FIFO 动态分配)
有至少 1 个读端 + 缓存区满 阻塞等待,直到读端取走数据(缓存区有空位)
无任何读端

触发 SIGPIPE 信号:

① 默认处理:进程终止(“异常结束”);

② 捕获 / 忽略信号:write () 返回 – 1,errno=EPIPE

🔹(非阻塞模式 O_NONBLOCK)

  • 读端:无数据时read()返回 – 1(errno=EAGAIN),而非阻塞;
  • 写端:缓存区满时write()返回 – 1(errno=EAGAIN),而非阻塞;
  • 无读端写数据:仍触发 SIGPIPE(或返回 EPIPE),和阻塞模式一致。

1.2.2 无名管道(匿名管道,pipe)

  • 核心特性:
  • 仅适用于有亲缘关系的进程(父子进程、兄弟进程);
  • 单向通信:读端(pipefd [0])只能读,写端(pipefd [1])只能写;
  • 随进程生命周期结束而销毁(无持久化);
  • 半双工通信:数据只能沿一个方向传输,双向通信需创建两个管道。
  • 关键规则:
    • 读端关闭后,写端写入会触发SIGPIPE信号,进程默认终止;
    • 写端关闭后,读端读完缓存区数据后会返回 0(表示 EOF);
    • 缓存区满时写操作阻塞,缓存区空时读操作阻塞。

1.2.3 有名管道(命名管道,FIFO)

  • 核心特性:
  • 适用于任意进程(无需亲缘关系),通过文件系统中的 “管道文件” 标识;
  • 管道文件仅作为通信标识,数据仍存储在内核缓存区,不落地磁盘;
  • 持久化:管道文件会保留在文件系统中,直到手动删除。
  • 关键规则:
    • 打开 FIFO 时,读端会阻塞直到有写端打开,写端也会阻塞直到有读端打开;
    • 可通过open函数设置非阻塞模式(O_NONBLOCK)避免阻塞。
特性匿名管道有名管道(FIFO)
通信本质 半双工(底层特性) 半双工(底层特性)
单向通信实现 单管道,一端关读、一端关写 单管道,一端关读、一端关写
双向通信实现方式 1 单管道 + 同时持有读写端(有风险) 单管道 + 同时持有读写端(有风险)
双向通信实现方式 2 双管道(父→子、子→父,推荐) 双管道(两个 FIFO,推荐)
双向通信的核心差异 仅亲缘进程可用 任意进程可用(通过文件名)

1.3 管道函数接口

1.3.1 pipe(创建无名管道)

  • 函数原型:

    int pipe(int pipefd[2]);

  • 功能:创建一个无名管道,在内核中分配缓存区,并返回两个文件描述符用于读写该缓存区。
  • 参数说明:
    • pipefd:长度为 2 的 int 数组,用于接收管道的读 / 写文件描述符:
      • pipefd[0]:管道读端文件描述符(仅能读,写操作会报错);
      • pipefd[1]:管道写端文件描述符(仅能写,读操作会报错)。
  • 返回值:
    • 成功:返回 0;
    • 失败:返回 – 1,且设置errno(如 EMFILE 表示文件描述符耗尽,ENFILE 表示系统文件表满)。
  • 注意事项:
  • 创建管道后,通常需要配合fork()创建子进程,子进程会继承管道的文件描述符;
  • 父子进程通信时,需关闭不需要的端(如父进程写、子进程读,则父进程关闭 pipefd [0],子进程关闭 pipefd [1]),避免缓存区异常。

1.3.2 mkfifo(创建有名管道)

  • 函数原型:

    int mkfifo(const char *pathname, mode_t mode);

  • 功能:在文件系统中创建一个有名管道(FIFO)文件,作为进程间通信的标识。
  • 参数说明:
    • pathname:FIFO 文件的路径(如./my_fifo),需确保路径合法且无同名文件;
    • mode:FIFO 文件的权限(如 0664,和普通文件权限规则一致,最终权限会受 umask 影响)。
  • 返回值:
    • 成功:返回 0;
    • 失败:返回 – 1,且设置errno(如 EEXIST 表示文件已存在,EACCES 表示权限不足)。
  • 注意事项:
  • 创建 FIFO 后,需通过open()函数打开(读端用 O_RDONLY,写端用 O_WRONLY);
  • FIFO 文件仅为标识,数据传输仍在内核缓存区完成,删除文件不影响正在进行的通信。

1.3.3 示例 1:无名管道(父子进程通信)

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main() {
int pipefd[2];
pid_t pid;
char buf[1024];

// 1. 创建无名管道
if (pipe(pipefd) == -1) {
perror("pipe error");
return -1;
}

// 2. 创建子进程
pid = fork();
if (pid == -1) {
perror("fork error");
return -1;
}

if (pid == 0) { // 子进程:读数据
close(pipefd[1]); // 关闭写端(子进程只读)
int n = read(pipefd[0], buf, sizeof(buf)-1);
if (n > 0) {
buf[n] = '\\0';
printf("子进程读到数据:%s\\n", buf);
}
close(pipefd[0]); // 关闭读端
return 0;
} else { // 父进程:写数据
close(pipefd[0]); // 关闭读端(父进程只写)
char *msg = "Hello 子进程!这是父进程的消息";
write(pipefd[1], msg, strlen(msg));
close(pipefd[1]); // 关闭写端(触发子进程读EOF)
wait(NULL); // 等待子进程结束
return 0;
}
}

1.3.4 示例 2:有名管道(任意进程通信)

进程 A(写端):

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>

int main() {
// 1. 创建有名管道
if (mkfifo("./my_fifo", 0664) == -1) {
perror("mkfifo error");
return -1;
}

// 2. 打开FIFO写端
int fd = open("./my_fifo", O_WRONLY);
if (fd == -1) {
perror("open error");
return -1;
}

// 3. 写入数据
char *msg = "Hello FIFO!这是进程A的消息";
write(fd, msg, strlen(msg));
close(fd);
return 0;
}

进程 B(读端):

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

int main() {
char buf[1024];
// 1. 打开FIFO读端(若未创建,需先运行进程A)
int fd = open("./my_fifo", O_RDONLY);
if (fd == -1) {
perror("open error");
return -1;
}

// 2. 读取数据
int n = read(fd, buf, sizeof(buf)-1);
if (n > 0) {
buf[n] = '\\0';
printf("进程B读到数据:%s\\n", buf);
}
close(fd);
return 0;
}

编译运行:

# 终端1:编译并运行进程A(写端)
gcc fifo_write.c -o fifo_write && ./fifo_write
# 终端2:编译并运行进程B(读端)
gcc fifo_read.c -o fifo_read && ./fifo_read

输出(终端 2):进程B读到数据:Hello FIFO!这是进程A的消息

1.4 信号

用户态与内核态通过信号完成异步信息通知

多进程可通过信号完成简单的异步通信

1.4.1 信号类型:kill -l(列出所有信号)

信号名编号触发方式 / 含义
SIGINT 2 终端输入 ctrl+c 触发,默认终止进程
SIGQUIT 3 终端输入 ctrl+\\ 触发,默认终止进程并生成核心转储文件(core dump)
SIGKILL 9 强制杀死进程,不能被忽略、捕捉、阻塞(唯一无法拦截的终止信号)
SIGSTOP 19 强制暂停进程,不能被忽略、捕捉、阻塞(唯一无法拦截的暂停信号)
SIGSEGV 11 段错误:进程非法访问内存(如访问空指针、越界数组),默认终止进程并生成 core dump
SIGPIPE 13 管道破裂:向无读端的管道 / 套接字写入数据,默认终止进程
SIGTSTP 20 终端输入 ctrl+z 触发,默认挂起进程(可通过 fg/bg 恢复)
SIGALRM 14 定时信号:alarm() 设置的定时时间到后,内核向进程发送该信号,默认终止进程
SIGCHLD 17 子进程终止 / 暂停 / 恢复时,内核向父进程发送该信号,默认忽略
SIGCONT 18 恢复被暂停的进程(如 SIGSTOP/SIGTSTP 暂停的进程),默认让进程继续执行
SIGIO 29 异步 IO 信号:进程注册的 IO 事件就绪时(如套接字可读),内核向对应进程发送该信号

注:信号编号随架构不同略有差异

1.4.2 信号的处理方式

9号(SIGKILL)和19号(SIGSTOP)信号不能被忽略和捕捉

  • 缺省(SIG_DFL):按照系统默认行为处理信号(如 SIGINT 默认终止进程)
  • 忽略(SIG_IGN):收到信号后不做任何处理(如 SIGCHLD 默认忽略)
  • 捕捉:自定义信号处理函数,收到信号时按照用户指定的方式处理信号

1.4.3 核心函数接口

signal 函数(设置信号处理方式,简易接口)

#include <signal.h>

// 信号处理函数类型定义
typedef void (*sighandler_t)(int);
// 核心接口
sighandler_t signal(int signum, sighandler_t handler);

等价于:
void (*signal(int signum, void (*handler)(int))) (int)

  • 功能:修改指定信号(signum)的处理方式
  • 参数:
    • signum:要处理的信号编号(如 2/SIGINT)
    • handler:处理方式:
      • SIG_IGN:忽略该信号
      • SIG_DFL:恢复默认处理方式
      • 自定义函数指针:捕捉信号并执行自定义逻辑
  • 返回值:
    • 成功:返回上一次注册的信号处理函数指针
    • 失败:返回 SIG_ERR(需检查 errno)

raise 函数(向自身进程发信号)

#include <signal.h>
int raise(int sig);

  • 功能:给调用该函数的进程发送指定信号(等价于 kill(getpid(), sig))
  • 返回值:成功返回 0,失败返回非 0

alarm 函数(设置定时信号)

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

  • 功能:设置定时器,seconds 秒后内核向进程发送 SIGALRM 信号
  • 特性:
    • 一个进程只能有一个 alarm 定时器,重复调用会覆盖之前的定时
    • alarm(0) 会取消当前定时器
  • 返回值:返回之前定时器剩余的秒数(无定时器则返回 0)

pause 函数(挂起进程)

#include <unistd.h>
int pause(void);

  • 功能:将调用的进程挂起(阻塞),直到收到一个未被忽略的信号时2被唤醒
  • 返回值:
    • 被信号唤醒后,若信号被捕捉且处理函数返回,pause 返回 – 1,errno 设为 EINTR
    • 若信号默认处理是终止进程,则 pause 不会返回

kill 函数(向指定进程发信号)

#include <signal.h>
#include <sys/types.h>

int kill(pid_t pid, int sig);

  • 功能:向指定 PID 的进程 / 进程组发送信号
  • 参数:
    • pid:
      • pid > 0:发送信号给 PID 为pid的进程
      • pid = 0:发送信号给当前进程所在进程组的所有进程
      • pid = -1:发送信号给所有有权限发送的进程(除 init 进程)
      • pid < -1:发送信号给进程组 ID 为-pid的所有进程
    • sig:信号编号(如 9/SIGKILL),若为 0 则仅检查进程是否存在(不发信号)
  • 返回值:成功返回 0,失败返回 – 1(errno 标识错误原因)
赞(0)
未经允许不得转载:171主机测评 » 进程间通信---管道/信号---嵌入式(Linux)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址