从管道路由到共享内存:进程间通信的演变(内附通信方式经典面试题及详解)

简介: 进程间通信(Inter-Process Communication, IPC)是计算机科学中的一个重要概念,指的是运行在同一系统或不同系统上的多个进程之间互相发送和接收信息的能力。IPC机制允许进程间共享数据、协调执行流程,是实现分布式系统、多任务操作系统和并发编程的基础。

进程间通信概念:

进程间通信(Inter-Process Communication, IPC)是计算机科学中的一个重要概念,指的是运行在同一系统或不同系统上的多个进程之间互相发送和接收信息的能力。IPC机制允许进程间共享数据、协调执行流程,是实现分布式系统、多任务操作系统和并发编程的基础。

而进程的间的通信方式也主要分为6种,分别是:

1. 无名管道(pipe)和 有名管道(fifo)

image.gif 编辑

2. 信号(sem)

3. 共享内存(share memory)

4. 消息队列(message queue)

5. 信号灯(semaphore)

6. 套接字(socket)

不同方式的基本概念:

1. 无名管道(Pipe)

  • 概念:无名管道是一种半双工的通信方式,数据只能从管道的一端写入,从另一端读出。无名管道只能在有亲缘关系的进程间使用,通常是父子进程。
  • 特点:简单、快速,但通信方向固定,且局限于相关联进程。
  • 函数
int pipe(int fd[2])
功能:创建无名管道
参数:文件描述符 fd[0]:读端  fd[1]:写端
返回值:成功 0
      失败 -1
注意:
1. 当管道中无数据时,读会阻塞;管道中无数据,将写端关闭,读操作立即返回
2. 当管道中写满(64k)数据时,写阻塞,一旦有4k空间,写继续,直到写满为止
3. 将读端关闭,继续写数据,会导致管道破裂,进程会收到内核发送过来的SIGPIPE信号(Broken pipe)

image.gif

2. 命名管道(Named Pipe or FIFO)

  • 概念:命名管道是具有名字的管道,可以在文件系统中创建,允许任意进程通过名字打开并进行通信。
  • 特点:克服了无名管道的局限,支持无关进程间的通信,但仍然保持了管道的简单性。
  • 函数
int mkfifo(const char *filename,mode_t mode);
功能:创健有名管道
参数:filename:有名管道文件名
       mode:权限
返回值:成功:0
       失败:-1,并设置errno号
注意对错误的处理方式:
如果错误是file exist时,注意加判断,如:if(errno == EEXIST)

image.gif

3. 信号(Signals)

  • 概念:信号是一种异步通信机制,用于通知接收进程某个事件已经发生。
  • 特点:主要用于异常处理,如中断和错误报告,不是一种通用的数据传输机制。
  • 信号的种类:
2)SIGINT:结束进程,对应快捷方式ctrl+c
3)SIGQUIT:退出信号,对应快捷方式ctrl+\
9)SIGKILL:结束进程,不能被忽略不能被捕捉
15)SIGTERM:结束终端进程,kill 使用时不加数字默认是此信号
17)SIGCHLD:子进程状态改变时给父进程发的信号
19)SIGSTOP:暂停进程,不能被忽略不能被捕捉
20)SIGTSTP:暂停信号,对应快捷方式ctrl+z
26)SIGALRM:闹钟信号,alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程

image.gif

  • 信号的响应方式:
1)忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
2)捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。
3)执行缺省操作:Linux对每种信号都规定了默认操作 。

image.gif

4. 共享内存(Shared Memory)

  • 概念:共享内存允许多个进程直接访问同一块内存区域,是最快的IPC机制之一,因为数据无需在用户态和内核态之间复制。
  • 特点:速度快,但需要额外的同步机制(如信号量)来避免数据竞争。
  • 函数
key_t ftok(const char *pathname, int proj_id);
功能:产生一个独一无二的key值
参数:
    Pathname:已经存在的可访问文件的名字
    Proj_id:一个字符(因为只用低8位)
返回值:成功:key值
      失败:-1

image.gif

int shmget(key_t key, size_t size, int shmflg);
功能:创建或打开共享内存
参数:
    key  键值
    size   共享内存的大小
    shmflg   IPC_CREAT|IPC_EXCL(判错)|0666
返回值:成功   shmid
      出错    -1

image.gif

void  *shmat(int  shmid,const  void  *shmaddr,int  shmflg);
功能:映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
参数:
    shmid   共享内存的id号
    shmaddr   一般为NULL,表示由系统自动完成映射
              如果不为NULL,那么由用户指定
    shmflg:SHM_RDONLY就是对该共享内存只进行读操作
                0     可读可写
返回值:成功:完成映射后的地址,
      失败:-1的地址

image.gif

int shmdt(const void *shmaddr);
功能:取消映射
参数:要取消的地址
返回值:成功0  
      失败的-1

image.gif

int  shmctl(int  shmid,int  cmd,struct  shmid_ds   *buf);
功能:(删除共享内存),对共享内存进行各种操作
参数:
    shmid   共享内存的id号
    cmd     IPC_STAT 获得shmid属性信息,存放在第三参数
            IPC_SET 设置shmid属性信息,要设置的属性放在第三参数
            IPC_RMID:删除共享内存,此时第三个参数为NULL即可
返回:成功0 
     失败-1
用法:shmctl(shmid,IPC_RMID,NULL);

image.gif

5. 消息队列(Message Queues)

  • 概念:消息队列是系统维护的一个链表,链表中的元素是消息,进程可以将消息添加到队列尾部,也可以从队列头部删除消息。
  • 特点:提供了比管道更灵活的数据传输机制,可以指定消息的长度和类型,适合传输结构化的数据。
  • 函数
int msgget(key_t key, int flag);
功能:创建或打开一个消息队列
参数:  key值
       flag:创建消息队列的权限IPC_CREAT|IPC_EXCL|0666
返回值:成功:msgid
       失败:-1

image.gif

int msgsnd(int msqid, const void *msgp, size_t size, int flag); 
功能:添加消息
参数:msqid:消息队列的ID
      msgp:指向消息的指针。常用消息结构msgbuf如下:
          struct msgbuf{
            long mtype;          //消息类型
            char mtext[N]};   //消息正文
   size:发送的消息正文的字节数
   flag:IPC_NOWAIT消息没有发送完成函数也会立即返回    
         0:直到发送完成函数才返回
返回值:成功:0
      失败:-1
使用:msgsnd(msgid, &msg,sizeof(msg)-sizeof(long), 0)
注意:消息结构除了第一个成员必须为long类型外,其他成员可以根据应用的需求自行定义。

image.gif

int msgrcv(int msgid,  void* msgp,  size_t  size,  long msgtype,  int  flag);
功能:读取消息
参数:msgid:消息队列的ID
     msgp:存放读取消息的空间
     size:接受的消息正文的字节数
    msgtype:0:接收消息队列中第一个消息。
            大于0:接收消息队列中第一个类型为msgtyp的消息.
            小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。
     flag:0:若无消息函数会一直阻塞
        IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG
返回值:成功:接收到的消息的长度
      失败:-1

image.gif

int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
功能:对消息队列的操作,删除消息队列
参数:msqid:消息队列的队列ID
     cmd:
        IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中。
        IPC_SET:设置消息队列的属性。这个值取自buf参数。
        IPC_RMID:从系统中删除消息队列。
     buf:消息队列缓冲区
返回值:成功:0
      失败:-1
用法:msgctl(msgid, IPC_RMID, NULL

image.gif

6. 信号量(Semaphores)

  • 概念:信号量是一种计数器,用于控制进程对共享资源的访问,可以解决多个进程同时访问共享内存时的同步问题。
  • 特点:主要用于进程间的同步,而不是通信。
  • 函数
1. semget
int semget(key_t key, int nsems, int semflg);
功能:创建/打开信号灯
参数:key:ftok产生的key值
    nsems:信号灯集中包含的信号灯数目
    semflg:信号灯集的访问权限,通常为IPC_CREAT |IPC_EXCL|0666
返回值:成功:信号灯集ID
       失败:-1

image.gif

2. semctl
int semctl ( int semid, int semnum,  int cmd…/*union semun arg*/);
功能:信号灯集合的控制(初始化/删除)
参数:semid:信号灯集ID
    semnum: 要操作的集合中的信号灯编号
     cmd:
        GETVAL:获取信号灯的值,返回值是获得值
        SETVAL:设置信号灯的值,需要用到第四个参数:共用体
        IPC_RMID:从系统中删除信号灯集合
返回值:成功 0
      失败 -1
用法:初始化:
union semun{
    int val;
}mysemun;
mysemun.val = 10;
semctl(semid, 0, SETVAL, mysemun);
获取信号灯值:函数semctl(semid, 0, GETVAL)的返回值

image.gif

3. semop
int semop ( int semid, struct sembuf  *opsptr,  size_t  nops);
功能:对信号灯集合中的信号量进行PV操作
参数:semid:信号灯集ID
     opsptr:操作方式
     nops:  要操作的信号灯的个数 1个
返回值:成功 :0
      失败:-1
struct sembuf {
   short  sem_num; // 要操作的信号灯的编号
   short  sem_op;  //    0 :  等待,直到信号灯的值变成0
                   //   1  :       
                   //   -1 :  申请资源,P操作                    
    short  sem_flg; // 0(阻塞),IPC_NOWAIT, SEM_UNDO
};
用法:
申请资源 P操作:
    mysembuf.sem_num = 0;
    mysembuf.sem_op = -1;
    mysembuf.sem_flg = 0;
    semop(semid, &mysembuf, 1);
释放资源 V操作:
    mysembuf.sem_num = 0;
    mysembuf.sem_op = 1;
    mysembuf.sem_flg = 0;
    semop(semid, &mysembuf, 1);

image.gif

7. 套接字(Sockets)

  • 概念:套接字是一种用于网络通信的IPC机制,也适用于本地进程间的通信。
  • 特点:提供了非常灵活的通信机制,支持多种通信协议,如TCP和UDP,适用于分布式系统和网络编程。

应用链接:TCP和UDP的服务器和客户端通信代码实现,非常简单易懂!(附源码,小白必看!)_tcp或者udp代码实现客户端与服务器之间通讯-CSDN博客

应用实例:

无名管道:

pipe.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#define BUFFER_SIZE 100
int main() {
    int fd[2]; // 文件描述符数组,用于无名管道
    pid_t pid;
    // 创建无名管道
    if (pipe(fd) == -1) {
        perror("Pipe creation failed");
        return 1;
    }
    pid = fork();
    if (pid < 0) { // 错误
        perror("Fork failed");
        return 1;
    } else if (pid > 0) { // 父进程
        close(fd[0]); // 关闭读端
        char message[] = "Hello from parent";
        write(fd[1], message, strlen(message)+1); // 写入消息
        close(fd[1]); // 关闭写端
    } else { // 子进程
        close(fd[1]); // 关闭写端
        char buffer[BUFFER_SIZE];
        read(fd[0], buffer, BUFFER_SIZE); // 读取消息
        printf("Received: %s\n", buffer);
        close(fd[0]); // 关闭读端
    }
    return 0;
}

image.gif

image.gif 编辑

有名管道:

name_pipe.c

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#define FIFO_NAME "/tmp/my_fifo"
#define BUFFER_SIZE 100
int main() {
    mkfifo(FIFO_NAME, 0666); // 创建有名管道
    int fd;
    char buffer[BUFFER_SIZE];
    // 父进程:写入消息
    pid_t pid = fork();
    if (pid > 0) {
        fd = open(FIFO_NAME, O_WRONLY);
        if (fd == -1) {
            perror("Open failed");
            return 1;
        }
        write(fd, "Hello from named pipe", strlen("Hello from named pipe")+1);
        close(fd);
    } else if (pid == 0) {
        // 子进程:读取消息
        fd = open(FIFO_NAME, O_RDONLY);
        if (fd == -1) {
            perror("Open failed");
            return 1;
        }
        read(fd, buffer, BUFFER_SIZE);
        printf("Received: %s\n", buffer);
        close(fd);
    }
    unlink(FIFO_NAME); // 删除有名管道
    return 0;
}

image.gif

image.gif 编辑

消息队列:

message_queue.c

#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MSGKEY 1234 // 定义消息队列的key
typedef struct msgbuf {
    long mtype; // 消息类型
    char mtext[100]; // 消息文本
} message_buffer;
int main(int argc, char *argv[]) {
    int msqid; // 消息队列标识符
    message_buffer my_msg;
    long msgflg = IPC_CREAT | 0666; // 创建消息队列的标志和权限掩码
    // 创建消息队列
    if ((msqid = msgget(MSGKEY, msgflg)) < 0) {
        perror("msgget error");
        exit(1);
    }
    // 发送消息
    my_msg.mtype = 1; // 设置消息类型
    strcpy(my_msg.mtext, "Hello, this is a test message!"); // 设置消息文本
    if (msgsnd(msqid, &my_msg, sizeof(my_msg.mtext), 0) < 0) {
        perror("msgsnd error");
        exit(1);
    }
    // 接收消息
    if (msgrcv(msqid, &my_msg, sizeof(my_msg.mtext), 1, 0) < 0) {
        perror("msgrcv error");
        exit(1);
    }
    printf("Received message: %s\n", my_msg.mtext);
    // 清理消息队列
    if (msgctl(msqid, IPC_RMID, NULL) < 0) {
        perror("msgctl error");
        exit(1);
    }
    return 0;
}

image.gif

image.gif 编辑

共享内存:

image.gif 编辑

经典面试题:

       

  1. 什么是进程间通信(IPC)?
       进程间通信(IPC)是指在多任务操作系统中,两个或多个进程之间交换数据和控制信息的机制。它是实现分布式计算和多进程系统协作的基础。
  2. 进程间通信的主要方式有哪些?
       主要方式包括管道(Pipes)、消息队列(Message Queues)、共享内存(Shared Memory)、信号量(Semaphores)、套接字(Sockets)和远程过程调用(RPCs)。
  3. 无名管道(Pipe)和有名管道(FIFO)的主要区别是什么?
          无名管道仅限于有亲缘关系的进程(如父子进程)之间通信,且存在于内存中;有名管道(FIFO)则是命名的,存在于文件系统中,允许任何进程打开并进行通信。
  4. 信号量(Semaphore)的作用是什么?
       信号量用于控制对共享资源的访问,防止多个进程同时访问同一资源造成冲突,实现进程间的同步。
  5. 共享内存是如何实现进程间通信的?
      共享内存允许多个进程映射同一块物理内存区域,直接读写这块内存来交换数据,是最快的IPC方式之一,但需要额外的同步机制(如信号量)来防止数据竞争。
  6. 套接字(Socket)在进程间通信中扮演什么角色?
      套接字提供了一种通用的网络通信接口,既可用于本地进程间通信,也可用于不同机器间的远程通信。
  7. 在Linux中,如何创建一个消息队列?
     使用msgget系统调用,传入消息队列的键(key)和标志(flags)来创建或访问一个消息队列。
  8. 进程间通信中,什么是死锁?
      死锁发生在两个或多个进程互相等待对方持有的资源而不释放自己所占有的资源,导致所有相关进程都无法继续执行的状态。
  9. 进程间通信中,哪种通信方式最快?为什么?

           共享内存,因为共享内存是通过地址映射的方式进行数据的通信,无需多余的拷贝赋值 操         作。

     10. 在使用共享内存时,为什么需要信号量?

           信号量用于确保多个进程在访问共享内存时不会发生数据竞争或不一致,实现对共享资源的安全访问。

       至此文章结束,希望可以帮到大家~

相关文章
|
11天前
|
存储 Unix Linux
进程间通信方式-----管道通信
【10月更文挑战第29天】管道通信是一种重要的进程间通信机制,它为进程间的数据传输和同步提供了一种简单有效的方法。通过合理地使用管道通信,可以实现不同进程之间的协作,提高系统的整体性能和效率。
|
11天前
|
消息中间件 存储 供应链
进程间通信方式-----消息队列通信
【10月更文挑战第29天】消息队列通信是一种强大而灵活的进程间通信机制,它通过异步通信、解耦和缓冲等特性,为分布式系统和多进程应用提供了高效的通信方式。在实际应用中,需要根据具体的需求和场景,合理地选择和使用消息队列,以充分发挥其优势,同时注意其可能带来的复杂性和性能开销等问题。
|
1月前
|
JavaScript 前端开发
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
39 0
vue尚品汇商城项目-day01【8.路由跳转与传参相关面试题】
|
29天前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。
|
2月前
|
缓存 监控 NoSQL
阿里面试让聊一聊Redis 的内存淘汰(驱逐)策略
大家好,我是 V 哥。粉丝小 A 面试阿里时被问到 Redis 的内存淘汰策略问题,特此整理了一份详细笔记供参考。Redis 的内存淘汰策略决定了在内存达到上限时如何移除数据。希望这份笔记对你有所帮助!欢迎关注“威哥爱编程”,一起学习与成长。
|
1月前
|
存储 Python
Python中的多进程通信实践指南
Python中的多进程通信实践指南
17 0
|
2月前
|
Java Android开发 数据安全/隐私保护
Android中多进程通信有几种方式?需要注意哪些问题?
本文介绍了Android中的多进程通信(IPC),探讨了IPC的重要性及其实现方式,如Intent、Binder、AIDL等,并通过一个使用Binder机制的示例详细说明了其实现过程。
302 4
|
2月前
|
消息中间件 Unix Linux
C语言 多进程编程(二)管道
本文详细介绍了Linux下的进程间通信(IPC),重点讨论了管道通信机制。首先,文章概述了进程间通信的基本概念及重要性,并列举了几种常见的IPC方式。接着深入探讨了管道通信,包括无名管道(匿名管道)和有名管道(命名管道)。无名管道主要用于父子进程间的单向通信,有名管道则可用于任意进程间的通信。文中提供了丰富的示例代码,展示了如何使用`pipe()`和`mkfifo()`函数创建管道,并通过实例演示了如何利用管道进行进程间的消息传递。此外,还分析了管道的特点、优缺点以及如何通过`errno`判断管道是否存在,帮助读者更好地理解和应用管道通信技术。
|
2月前
|
SQL 网络协议 数据库连接
已解决:连接SqlServer出现 provider: Shared Memory Provider, error: 0 - 管道的另一端上无任何进程【C#连接SqlServer踩坑记录】
本文介绍了解决连接SqlServer时出现“provider: Shared Memory Provider, error: 0 - 管道的另一端上无任何进程”错误的步骤,包括更改服务器验证模式、修改sa用户设置、启用TCP/IP协议,以及检查数据库连接语句中的实例名是否正确。此外,还解释了实例名mssqlserver和sqlserver之间的区别,包括它们在默认设置、功能和用途上的差异。
|
3月前
|
开发者 API Windows
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
66 0

热门文章

最新文章