前言

进程间通信(Inter-Process Communication,IPC)是指在操作系统中,不同进程之间交换数据和信息的技术。由于进程空间是独立的,每个进程独享自己的0-4G虚拟内存空间,因此需要借助特定的机制来实现进程间的数据交换。本文将详细介绍Linux下常用的进程间通信方式。


一、进程间通信概述

1. 进程间通信的必要性

  • 进程空间是独立的,无法直接访问其他进程的内存空间
  • 多个进程协同工作需要进行数据交换
  • 进程间通信是构建复杂应用程序的基础

2. 进程间通信的方法

  1. 管道(无名管道、有名管道)
  2. 信号
  3. 消息队列
  4. 共享内存
  5. 信号灯

二、管道通信

1. 无名管道

1.1 概念

无名管道只能用于具有亲缘关系的进程间通信(如父子进程之间)。

1.2 函数接口

pipe函数

int pipe(int pipefd[2]);
  • 功能:创建一段用于进程间通信的内核缓存区
  • 参数
    • pipefd[0]:读文件描述符
    • pipefd[1]:写文件描述符
  • 返回值:成功返回0,失败返回-1
1.3 管道特性

关于写端:

  • 管道中有写端存在时:
    • 管道中有数据则阻塞等待直到数据被读出
  • 管道中没有写端时:
    • 管道中有数据则直接读出
    • 管道中没有数据则直接结束,不需要阻塞等待

关于读端:

  • 管道中有读端存在时:
    • 如果管道没有写满(64K),直接写入数据
    • 如果管道已写满,则需要有数据读出才能继续写入
  • 管道中没有读端时:
    • 写入数据会产生管道破裂的错误,使进程直接退出

2. 有名管道(FIFO)

2.1 概念

有名管道可以用于任意两个进程间的通信,不局限于具有亲缘关系的进程。

2.2 函数接口

mkfifo函数

int mkfifo(const char *pathname, mode_t mode);
  • 功能:创建一个有名管道文件
  • 参数
    • pathname:有名管道的路径
    • mode:权限(如0664)
  • 返回值:成功返回0,失败返回-1
2.3 特性说明
  • 读写两端必须同时加入才能继续向下执行
  • 如果只有读端或写端,则会阻塞等待直到另一端加入
2.4 聊天程序示例

write.c(负责发送数据)

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(void)
{
    int fd = open("/tmp/fifo", O_WRONLY);
    char buf[1024] = {0};
    
    while (1) {
        gets(buf);
        write(fd, buf, strlen(buf));
    }
    
    close(fd);
    return 0;
}

read.c(负责接收数据)

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(void)
{
    mkfifo("/tmp/fifo", 0664);
    int fd = open("/tmp/fifo", O_RDONLY);
    char buf[1024] = {0};
    
    while (1) {
        read(fd, buf, sizeof(buf));
        printf("收到消息:%s\n", buf);
    }
    
    close(fd);
    return 0;
}

三、信号通信

1. 信号概述

信号是内核层与应用层通信的一种机制,用于通知进程发生了某种事件。

2. 信号处理方式

  1. 缺省:按照默认方式处理信号
  2. 忽略:不处理信号
  3. 捕捉:按照指定的方式处理信号

3. 常见信号类型

信号编号 信号名称 说明
2 SIGINT 从键盘输入Ctrl+C产生
3 SIGQUIT 从键盘输入Ctrl+\产生
9 SIGKILL 不能被忽略和捕捉
11 SIGSEGV 段错误信号
13 SIGPIPE 管道破裂信号
14 SIGALRM 定时时间到达信号
17 SIGCHLD 子进程结束信号
19 SIGSTOP 暂停信号
20 SIGTSTP 挂起信号(Ctrl+Z)

4. 函数接口

4.1 signal函数
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • 功能:改变信号的处理方式
  • 参数
    • signum:信号的编号
    • handler
      • SIG_IGN:忽略信号
      • SIG_DFL:缺省处理信号
      • 自定义函数指针:捕捉信号
  • 返回值:成功返回上一次注册的信号处理方式,失败返回SIG_ERR

使用示例:

#include <stdio.h>
#include <signal.h>

void handler(int signum)
{
    printf("收到信号:%d\n", signum);
}

int main(void)
{
    // 捕捉SIGINT信号
    signal(SIGINT, handler);
    
    // 忽略SIGQUIT信号
    signal(SIGQUIT, SIG_IGN);
    
    while (1) {
        sleep(1);
    }
    
    return 0;
}
4.2 pause函数
int pause(void);
  • 功能:让进程睡眠,直到收到信号
4.3 alarm函数
unsigned int alarm(unsigned int seconds);
  • 功能:隔seconds秒后给调用进程发送SIGALRM信号

使用示例:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void handler(int signum)
{
    printf("定时时间到!\n");
    alarm(2);  // 重新定时
}

int main(void)
{
    signal(SIGALRM, handler);
    alarm(2);  // 2秒后发送SIGALRM信号
    
    while (1) {
        pause();  // 等待信号
    }
    
    return 0;
}

四、其他进程间通信方式

1. 消息队列

消息队列是内核中存放消息的链表,允许多个进程向同一个队列读写消息。

2. 共享内存

共享内存是最快的IPC方式,多个进程共享同一块物理内存区域,无需数据复制。

3. 信号灯

信号灯(信号量)主要用于进程间同步,控制多个进程对共享资源的访问。


总结

本文详细介绍了Linux下进程间通信的几种主要方式:

  1. 无名管道:适用于有亲缘关系的进程间通信
  2. 有名管道:适用于任意进程间的通信
  3. 信号:内核与应用层通信的异步通知机制

在实际开发中,需要根据具体场景选择合适的IPC方式:

  • 简单数据流通信:使用管道
  • 异步事件通知:使用信号
  • 大量数据交换:使用共享内存
  • 多个进程协同:使用消息队列或信号灯
Logo

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

更多推荐