【003 操作系统】进程间通信方式有哪些?有什么优缺点?

一、进程间通信方式及其优缺点

1、匿名管道 Pipe

原理:基于操作系统内核提供的缓冲区,它通过将一个进程的输出连接到另一个进程的输入来实现进程间通信。当一个进程向管道中写入数据时,数据会被存储在管道的缓冲区中,等待另一个进程从管道中读取数据,没读取之前写入进程不会阻塞,除非缓冲区已满。当另一个进程从管道中读取数据时,数据会从缓冲区中被读取出来,并被传递给该进程,并被传递给该进程,没有数据来时,阻塞等待(因此是同步通信)。(总结:半双工通信,数据单向流动;只能在具有亲缘关系的进程间使用)

优点:简单方便;

缺点:局限于单向通信;只能创建在它的进程以及其有亲缘关系的进程之间;缓冲区有限。

2、命名管道 FIFO

原理:命名管道的原理与匿名管道类似,也是基于操作系统内核提供的缓冲区来实现进程间通信的。当一个进程向命名管道中写入数据时,数据会被存储在命名管道的缓冲区中,等待另一个进程从命名管道中读取数据,没读取之前写入进程不会阻塞,除非缓冲区已满。当另一个进程从命名管道中读取数据时,数据会从缓冲区中被读取出来,并被传递给该进程,没有数据来时,阻塞等待(因此是同步通信)。

优点:可以实现任意关系的进程间的通信;

缺点:长期存于系统中,使用不当容易出错;缓冲区有限。

3、消息队列 message queue

原理:1、消息队列是一种先进先出的队列型数据结构,实际上是系统内核中的一个内部链表。消息被顺序插入队列中,其中发送进程将消息添加到队列末尾,接受进程从队列头读取消息。
2、多个进程可同时向一个消息队列发送消息,也可以同时从一个消息队列中接收消息。发送进程把消息发送到队列尾部,接受进程从消息队列头部读取消息,消息一旦被读出就从队列中删除。

优点:可以实现任意进程间的通信,并通过系统调用函数来实现消息发送和接收之间的同步,无需考虑同步问题,方便;

缺点:信息的复制需要额外消耗CPU的时间,不适宜于信息量大或操作频繁的场合。

4、共享内存 shared memory

原理:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC(进程间通信)方式,它是针对其它进程间通信方式运行效率低而专门设计的。

优点:无须复制,快捷,信息量大。

缺点:共享内存只是提供了数据共享的机制,但是并没有提供同步和互斥的机制。

5、信号量 semophore

原理:信号量通常是一个整数变量,用于记录某个共享资源的可用数量,它可以被多个线程或进程同时访问和修改。(信号量本质是一个计数器)

优点:可以同步进程;

缺点:不支持消息传递,只能做同步互斥,功能局限。

6、信号 signal

原理:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。主要作为进程间以及同一进程不同线程之间的同步手段。对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。例如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。

  • 进程发送信号:通过kill、raise、alarm等函数发送信号给目标进程。

  • 进程接收信号:通过signal、sigaction等函数注册信号处理函数。

  • 当信号来时调用处理函数,在函数中根据信号作出相应处理。

优点:不同信号可以传递不同的信息。通过为每个信号定义相应的处理函数可以实现相对灵活的通信。

缺点:传递信息少。

7、套接字 socket

原理:套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

优点:1)传输数据为字节级,传输数据可自定义,数据量小效率高;2)传输数据时间短,性能高;3) 适合于客户端和服务器端之间信息实时交互;4) 可以加密,数据安全性强
缺点:1) 需对传输的数据进行解析,转化成应用级的数据。

进程间通信方式以及各自的优缺点_分析总结四种进程通信方式的区别与优缺点_Echo_Anna的博客-CSDN博客

https://www.cnblogs.com/jiangknownet/p/14516630.html(总结的挺好)


二、进程间通信方式的选择

PIPE和FIFO(命名管道)用来实现进程间相互发送非常短小的、频率很高的消息,这两种方式通常适用于两个进程间的通信。

共享内存用来实现进程间共享的、非常庞大的、读写操作频率很高的数据。

其他考虑用socket。主要应用在分布式开发中。


三、线程间同步方法有哪些?

  • 原子操作
  • 自旋锁
  • 读写自旋锁
  • 顺序锁(seqlock,只包含在2.6内核及以后的版本中)
  • 信号量
  • 读写信号量
  • 互斥体
  • 大内核锁(BKL,Big Kernel Lock,只包含在2.4内核中,不讲)
  • 大读者锁(brlock,只包含在2.4内核中,不讲)
  • RCU(对读写锁的优化,只包含在2.6内核及以后的版本中)

【005 基础知识】Linux同步机制?_Kashine的博客-CSDN博客


四、进程间通信方式详解

1、管道pipe

  • 调用fork()函数创建一个新的子进程,fork()函数会返回两次,一次在父进程中返回子进程的进程ID,一次在子进程中返回0。
  • 在父进程中,调用sleep(3)函数让子进程先执行,输出一条提示信息,关闭管道的读端,然后调用write(fd[1], "hello from father", strlen("hello from father"))函数往管道的写端写入数据。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
 
/*使用匿名管道实现进程间通信*/
int main()
{
        int fd[2];//fd[0]为读端 fd[1]为写端
        pid_t pid;
        char buf[128];
        //int pipe(int pipefd[2]);
        if(pipe(fd) == -1)//创建管道
        {
                printf("管道创建失败\n");
                perror("why"); // 打印错误信息
        }
 
        pid = fork(); // 创建子进程
 
        if(pid < 0 )
        {
                printf("子进程开辟失败\n");
                perror("why"); // 打印错误信息
        }else if(pid > 0){
 
                sleep(3);//让子进程先执行
                printf("这是一个父进程\n");//父进程完成写操作
                close(fd[0]); // 关闭读端
                write(fd[1],"hello from father",strlen("hello from father")); // 往写端写入数据
        }else{
 
                printf("这是一个子进程\n");//子进程完成读操作
                close(fd[1]); // 关闭写端
                read(fd[0],buf,sizeof(buf)); // 从管道读取数据,并存储到buf数组中
                printf("buf = %s\n",buf); // 输出读取的数据
        }
 
        return 0;
}

2、命名管道FIFO

首先创建一个命名管道:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
//       int mkfifo(const char *pathname, mode_t mode);
 
int main()
{
        if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)
        {
                printf("mkfifo failed\n");
                perror("why");
        }
 
        return 0;
}

read.c中读取管道:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
 
//       int mkfifo(const char *pathname, mode_t mode);
 
int main()
{
        int nread;
        char buf[30] = {'\0'};
 
        if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)//创建命名管道
        {
                printf("mkfifo failed\n");
                perror("why");
        }
 
        int fd = open("./myfifo",O_RDONLY);//以只读的形式打开管道,程序阻塞在这,直到有另一个进程对其执行写操作
        if(fd < 0)
        {
                printf("read open failed\n");
        }else
        {
                printf("read open successn\n");
        }
 
        while(1)
        {
                nread = read(fd,buf,sizeof(buf));
                printf("read %d byte,context is:%s\n",nread,buf);
        }
 
        close(fd);
 
        return 0;
}

write.c中写入管道:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
//       int mkfifo(const char *pathname, mode_t mode);
 
int main()
{
        int nread;
        char buf[30] = "message from myfifo";
 
        if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)//创建命名管道
        {
                printf("mkfifo failed\n");
                perror("why");
        }
 
        int fd = open("./myfifo",O_WRONLY);//打开管道,程序阻塞在这,直到其他进程为读而打开它
        if(fd < 0)
        {
                printf("write open failed\n");
        }
        else
        {
                printf("write open success\n");
        }
 
        while(1)
        {
                sleep(1);
                write(fd,buf,strlen(buf));
        }
        close(fd);
 
        return 0;
}
  1. mkfifo()函数用于创建命名管道,参数为管道路径和权限。如果管道已存在,返回-1并设置errno为EEXIST。

  2. 读进程打开管道为只读(O_RDONLY),写进程打开管道为只写(O_WRONLY)。

  3. 当管道打开后,读进程会阻塞在read调用,写进程会阻塞在write调用。

  4. 一旦两个进程都打开了管道,读进程的read就会返回写入的数据,写进程的write也会成功返回。

3、消息队列:

消息队列,是消息的链接表,存放在内核之中。一个消息队列由一个标识符(即队列ID)来标识。

用户进程可以向消息队列添加消息,也可以向消息队列读取消息。

// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msgSend.c文件:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
 
//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf{
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};
 
 
int main()
{
        struct msgbuf sendbuf={888,"message from send"};
        struct msgbuf readbuf;
 
        key_t key;
 
        if((key = ftok(".",'z')) < 0){
                printf("ftok error\n");
        }
        int msgId = msgget(key,IPC_CREAT|0777);
 
        if(msgId == -1){
                printf("get quen failed\n");
        }
 
        msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
        printf("send over\n");
 
        msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),999,0);
        printf("read from get is:%s\n",readbuf.mtext);
 
        msgctl(msgId,IPC_RMID,NULL);
 
        return 0;
}

msgGet.c文件:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
 
//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf{
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};
 
int main()
{
        struct msgbuf readbuf;
        memset(readbuf.mtext, '\0', sizeof(readbuf.mtext));
        struct msgbuf sendbuf={999,"thank for your reach"};
 
        key_t key;
 
        //获取key值
        if((key = ftok(".",'z')) < 0){
                printf("ftok error\n");
        }
 
        int msgId = msgget(key,IPC_CREAT|0777);
 
        if(msgId == -1){
                printf("get quen failed\n");
                perror("why");
        }
 
        msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),888,0);
        printf("read from send is:%s\n",readbuf.mtext);
 
        msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
 
        msgctl(msgId,IPC_RMID,NULL);
 
        return 0;
}

msgsnd()用于向指定的消息队列发送消息,它包含以下参数:

  • msgId:要发送的消息队列ID。

  • &sendbuf:指向发送消息缓冲区的指针,消息结构由mtext表示。

  • strlen(sendbuf.mtext):要发送的数据的长度。

  • 0:发送标志,一般为0。

msgrcv()用于从指定消息队列接收消息,它包含以下参数:

  • msgId:要接收消息的队列ID。

  • &readbuf:指向接收消息缓冲区的指针,消息结构由mtext表示。

  • sizeof(readbuf.mtext):指定本次接收消息的最大长度。

  • 999:接收的消息类型,999表示接收任意类型的消息。

  • 0:接收标志,一般为0。

4、共享内存

共享内存,指两个或多个进程共享一个给定的存储区。

// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr); 
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

详见:https://www.cnblogs.com/52php/p/5861372.html

shmwrite.c文件:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
 
//       int shmget(key_t key, size_t size, int shmflg);
// void *shmat(int shmid, const void *shmaddr, int shmflg);
 
//       int shmdt(const void *shmaddr);
 
int main()
{
        int shmId;
        key_t key;
        char *shmaddr;
 
        if((key = ftok(".",1)) < 0){
                printf("ftok error\n");
        }
 
        shmId = shmget(key, 1024*4, IPC_CREAT|0666);//内存大小必须得是MB的整数倍
 
        if(shmId == -1){
                printf("shmget error\n");
                exit(-1);
        }
 
        /*第二个参数一般写0,让linux内核自动分配空间,第三个参数也一般写0,表示可读可写*/
        shmaddr = shmat(shmId, 0, 0);
        printf("shmat OK\n");
        strcpy(shmaddr,"I am so cool");
 
        sleep(5);//等待5秒,让别的进程去读
 
        shmdt(shmaddr);
        shmctl(shmId, IPC_RMID, 0);//写0表示不关心
        printf("quit\n");
 
        return 0;
}

shmread.c文件:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
 
//       int shmget(key_t key, size_t size, int shmflg);
// void *shmat(int shmid, const void *shmaddr, int shmflg);
 
//       int shmdt(const void *shmaddr);
 
int main()
{
        int shmId;
        key_t key;
        char *shmaddr;
 
        if((key = ftok(".",1)) < 0){
                printf("ftok error\n");
        }
 
        shmId = shmget(key, 1024*4, 0);//内存大小必须得是MB的整数倍
 
        if(shmId == -1){
                printf("shmget error\n");
                exit(-1);
        }
 
        /*第二个参数一般写0,让linux内核自动分配空间,第三个参数也一般写0,表示可读可写*/
        shmaddr = shmat(shmId, 0, 0);
        printf("shmat OK\n");
        printf("data : %s\n",shmaddr);
 
        shmdt(shmaddr);
 
        return 0;
}
if((key = ftok(".",1)) < 0){
    printf("ftok error\n");
}

可以看到,在两个程序中都使用了.和数字1作为参数,这就保证了它们生成的key值是相同的。这样才能保证操作的是同一块内存。


五、参考内容

进程间通信的六种常见方式_最常用 进程间通信方式_转角心静了的博客-CSDN博客(重点参考)

进程间通信方式以及各自的优缺点_分析总结四种进程通信方式的区别与优缺点_Echo_Anna的博客-CSDN博客

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kashine

你的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值