欢迎光临
我们一直在努力

Linux进程间通信:管道与信号

目录

一、为何需要进程间通信?

二、管道(Pipe)

1. 无名管道

2. 有名管道(FIFO)

三、信号(Signal):异步事件通知

四、总结与选择


在嵌入式Linux和C语言开发中,多进程架构是常态。但进程之间空间独立,如何让它们高效对话、协同工作?这就是进程间通信(IPC)要解决的核心问题。本文将聚焦两种最直接、最常用的IPC方式:管道和信号,并通过实战代码带你掌握其本质。

一、为何需要进程间通信?

进程是操作系统资源分配的最小单元。每个进程拥有独立的0-4G虚拟地址空间,这意味着一个进程无法直接访问另一个进程的变量或内存。当不同进程需要传递数据(如采集进程向处理进程发送数据)或发送控制指令(如通知另一个进程开始或停止工作)时,就必须通过操作系统提供的IPC机制。这些机制本质上是内核开辟的、可供多个进程共同访问的中间区域或消息传递路径。

二、管道(Pipe)

管道是一种单向的、先进先出的数据流通道。它就像在两个进程之间连接了一根水管,数据从一端流入,从另一端流出。

1. 无名管道

无名管道用于具有亲缘关系(如父子进程、兄弟进程)的进程间通信。它在内核中创建,通过文件描述符(一个小的非负整数)来访问。

关键函数(需包含<unistd.h>):

  • int pipe(int pipefd[2]):创建一个无名管道。

    • pipefd[0]:用于从管道读数据的文件描述符(读端)。

    • pipefd[1]:用于向管道写数据的文件描述符(写端)。

  • 成功返回0,失败返回-1。

示例:父子进程通过无名管道通信

#include "head.h"

int main(void)
{
int pipefd[2] = {0};
pid_t pid;
ssize_t nret = 0;
int cnt = 0;
char tmpbuff[4096] = {0};

pipe(pipefd);

pid = fork();
if (-1 == pid)
{
perror("fail to fork");
return -1;
}
if (0 == pid)
{
close(pipefd[0]);
sleep(5);
write(pipefd[1], "hello world", 11);
printf("子进程写入成功\\n");
#if 0
while (1)
{
write(pipefd[1], tmpbuff, sizeof(tmpbuff));
cnt++;
printf("cnt = %d\\n", cnt);
}
#endif
}
else if (pid > 0)
{
close(pipefd[0]);
nret = read(pipefd[0], tmpbuff, sizeof(tmpbuff));
printf("nret = %ld, tmpbuff = %s\\n", nret, tmpbuff);
}

while (1)
{

}

close(pipefd[0]);
close(pipefd[1]);

return 0;
}

流程解析:父进程先创建管道,然后创建子进程。子进程会继承父进程打开的文件描述符,因此父子进程都持有同一管道的读写端。为了建立单向通道,双方分别关闭不需要的一端:父关读,子关写。父进程通过write向管道写入数据,子进程通过read从管道读出。

2. 有名管道(FIFO)

无名管道只能用于亲缘进程。有名管道通过在文件系统中创建一个特殊类型文件(FIFO文件)来实现,任何知道该文件名的进程都可以打开它进行通信。

关键函数(需包含<sys/types.h>、<sys/stat.h>):

  • int mkfifo(const char *pathname, mode_t mode):创建一个FIFO特殊文件(有名管道)。

    • pathname:创建的FIFO文件路径。

    • mode:文件权限(如0666)。

  • 成功返回0,失败返回-1。

创建后,像操作普通文件一样使用open、read、write、close进行通信。

示例:两个独立进程通过有名管道通信

进程A(写端): write_fifo.c

#include "head.h"

int main(void)
{
int frtow = 0;
int fwtor = 0;
char tmpbuff[4096] = {0};
ssize_t nret = 0;

mkfifo("/tmp/rtow", 0664);
mkfifo("/tmp/wtor", 0664);

frtow = open("/tmp/rtow", O_RDONLY);
if (-1 == frtow)
{
perror("fail to open");
return -1;
}

fwtor = open("/tmp/wtor", O_WRONLY);
if (-1 == frtow)
{
perror("fail to open");
return -1;
}

printf("写端:管道打开成功\\n");

while (1)
{
memset(tmpbuff, 0, sizeof(tmpbuff));
gets(tmpbuff);
write(fwtor, tmpbuff, strlen(tmpbuff));

memset(tmpbuff, 0, sizeof(tmpbuff));
nret = read(frtow, tmpbuff, sizeof(tmpbuff));
printf("接收内容:%s\\n", tmpbuff);
}

close(frtow);
close(fwtor);

return 0;
}

进程B(读端): read_fifo.c

#include "head.h"

int main(void)
{
int frtow = 0;
int fwtor = 0;
char tmpbuff[4096] = {0};
ssize_t nret = 0;

mkfifo("/tmp/rtow", 0664);
mkfifo("/tmp/wtor", 0664);

frtow = open("/tmp/rtow", O_WRONLY);
if (-1 == frtow)
{
perror("fail to open");
return -1;
}

fwtor = open("/tmp/wtor", O_RDONLY);
if (-1 == frtow)
{
perror("fail to open");
return -1;
}

printf("读端:管道打开成功\\n");

while (1)
{
memset(tmpbuff, 0, sizeof(tmpbuff));
nret = read(fwtor, tmpbuff, sizeof(tmpbuff));
printf("接收内容:%s\\n", tmpbuff);

memset(tmpbuff, 0, sizeof(tmpbuff));
gets(tmpbuff);
write(frtow, tmpbuff, strlen(tmpbuff));
}

close(frtow);
close(fwtor);

return 0;
}

运行方法:先在一个终端运行./read(读端会阻塞等待写端打开),再在另一个终端运行./write。数据便从进程A传递到进程B,进程B再传递数据到进程A。

管道核心特性:

  • 阻塞/同步:读空管道会阻塞,直到有数据写入;写满管道(内核缓冲区通常64K)会阻塞,直到有数据被读出。

  • 字节流:管道不维护消息边界,读写的数据长度可能不匹配。

  • 生命周期:无名管道随进程结束而销毁;有名管道文件存在于文件系统,需手动删除。

三、信号(Signal):异步事件通知

信号是进程间通信、进程与内核间通信的一种异步通知机制。它通知接收进程某个事件已经发生,类似于中断。

关键概念:

  • 信号来源:内核(如SIGSEGV-段错误)、其他进程(通过kill系统调用)、键盘(如Ctrl+C产生SIGINT)。

  • 信号处理方式:

  • 默认动作(缺省):如SIGINT默认终止进程。

  • 忽略信号(IGN):对信号不做任何处理(SIGKILL和SIGSTOP不可忽略)。

  • 捕捉信号:用户自定义一个函数(信号处理函数)来处理信号。

  • 常见信号:

    • SIGINT(2): 中断信号,Ctrl+C产生。

    • SIGQUIT(3): 退出信号,Ctrl+\\产生。

    • SIGKILL(9): 强制终止信号,不可捕捉或忽略。

    • SIGUSR1(10) / SIGUSR2(12): 用户自定义信号,常用于进程间简单通知。

关键函数:

  • void (*signal(int signum, void (*handler)(int)))(int);或更易读的typedef:为指定信号signum设置处理函数handler。

  • int kill(pid_t pid, int sig);:向指定进程ID(pid)发送信号(sig)。

  • int raise(int sig);:向进程自身发送信号。

  • unsigned int alarm(unsigned int seconds);:设置一个定时器,seconds秒后向进程发送SIGALRM信号。

示例:父子进程通过信号通信

qq

这个例子演示了典型的信号交互:父进程忽略SIGINT,捕捉SIGUSR2;子进程忽略SIGQUIT,捕捉SIGUSR1。父进程通过kill主动通知子进程,子进程处理后再反向通知父进程。

信号核心特性:

  • 异步性:信号可能在任何时刻到达。

  • 简单性:不携带复杂数据,只通知事件发生。较新的sigqueue函数可附带少量数据。

  • 不可靠性(传统信号):信号可能丢失,且不支持排队。实时信号(SIGRTMIN~SIGRTMAX)解决了此问题。

四、总结与选择

  • 管道(无名/有名):用于持续性的、流式的数据传递。无名管道简单高效,用于亲缘进程;有名管道更通用,可用于任意进程。选择它当你需要传递大量或连续的字节流数据。

  • 信号:用于事件通知或简单控制。开销极小,是异步控制进程行为的理想选择,例如命令另一个进程终止、暂停或执行特定回调。选择它当你只需要发送一个简单的“通知”,而不需要传递大量数据。

在复杂的嵌入式Linux系统中,管道常与信号结合使用:用信号通知对方“数据准备好了”或“管道已关闭”,用管道传递实际的数据负载。理解这两种基础且核心的IPC机制,是构建健壮多进程应用的基石。

赞(0)
未经允许不得转载:171主机测评 » Linux进程间通信:管道与信号
分享到: 更多 (0)

评论 抢沙发

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