Linux 进程间通信(管道、共享内存、消息队列、信号量)

时间:2022-09-20 18:49:14

           进程通信 : 不同进程之间传播或交换信息

        为什么要进程通信呢? 协同运行,项目模块化

      通信原理 : 给多个进程提供一个都能访问到的缓冲区。

      

      根据使用场景,我们能划分为以下几种通信 :

    1.管道(匿名管道、命名管道)

        因为是半双工通信(单向传递信息),所以叫"管道"。原理是在内核中创建一个缓冲区让通信双方传递信息。

                               Linux  进程间通信(管道、共享内存、消息队列、信号量)

             匿名管道 :创建的缓冲区没有标识 , 只能用于具有亲缘关系的进程通信。

                 通信流程及代码:

                Linux  进程间通信(管道、共享内存、消息队列、信号量)

Linux  进程间通信(管道、共享内存、消息队列、信号量)

/*
* 匿名管道接口的基本使用
*/ #include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h> int main()
{
//创建管道 必须在子进程创建之前
//int pipe(int pipefd[2]);
//pipefd : 用于获取管道中的操作描述符
//pipefd[0] : 用于从管道中读取数据
//pipefd[1] : 用于向管道中写入数据
//返回值 : 成功 0 失败 -1
int pipefd[];
int ret = pipe(pipefd);
if(ret < )
{
perror("pipe error");
return -;
} int pid = fork();
if(pid < )
{
return -;
}
else if(pid == )
{
//子进程关闭写端
close(pipefd[]);
//子进程--读取管道中的数据
char buff[] = {};
read(pipefd[],buff,);
printf("buff:[%s]\n",buff);
}
else
{
//父进程关闭读端
close(pipefd[]);
//父进程--向管道中写入数据
char *ptr = "do you like me ?";
write(pipefd[],buff,strlen(ptr));
} return ;
}

                管道的读写特性 :

                   如果管道中没有数据,则read会阻塞,直到读取到数据

                     如果管道中数据满了,则write会阻塞,直到有数据被读取出去

                     如果管道中所有写端都被关闭,那么读端读完管道中的数据之后,会返回0

                     如果管道中所有读端都被关闭,那么写端写入数据的时候会触发异常,退出进程

                管道特点 :

                   1.半双工通信,数据只能一个方向流动

                   2.读写特性

                   3.内核会对管道进行同步与互斥操作(如果管道读写数据大小<=PIPE_BUF,读写操作将是原子性操作,是不可中断的)

                   4.提供字节流(不包含边界的连续流)服务(数据的传输比较灵活,但是有可能造成数据粘连(数据没有边界) )

                     5.生命周期随进程退出而退出 

                下面,用匿名管道实现ls | grep 命令:

//ls|grep的模拟实现
#include<stdio.h>
#include<unistd.h>
#include<errno.h> int main()
{
//创建匿名管道
int pipefd[];
int ret = pipe(pipefd);
if(ret<)
{
perror("pipe error\n");
return -;
} int pid1=fork();
if(pid1==)
{
//ls --- 写入到标准输出进行打印
//标准输出重定向到管道写端
close(pipefd[]);
dup2(pipefd[],);
execlp("ls","ls",NULL);
exit();
}
int pid2=fork();
if(pid2==)
{
//grep make --- 从标准输入读取数据
//标准输入重定向到管道读端
close(pipefd[]);
dup2(pipefd[],);
execlp("grep","grep","make",NULL);
exit();
} //wait之前关闭管道,防止影响子进程之间的管道交流
close(pipefd[]);
close(pipefd[]);
wait(NULL);
wait(NULL);
return ;
}

                命名管道 : 是在文件系统中创建的一个管道(FIFO)文件,所有进程都可以通过打开这个文件来进行通信。

                  特点 : 能让同一机器任意进程都可以进行通信。

                  打开特性 : 

                    管道文件如果被只读/只写方式打开,将阻塞,直到该文件被只写/只读方式打开;

                    被读写方式打开,不阻塞。

                  通信流程及代码 :

                     写端mkfifo创建管道文件->读端打开管道->两端可以进行单向通信了

//命名管道 读端demo
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<errno.h> int main()
{
int fd=open("./test.fifo",O_RDONLY);
while()
{
char buff[]={};
int ret=read(fd,buff,);
if(ret>)
{
printf("client say:%s\n",buff);
}
else if(ret == )
{
printf("write close!\n");
return -;
}
else
{
perror("read error");
return -;
}
}
close(fd);
return ;
}
//命名管道基本使用 写端demo
// int mkfifo(cosnt char* pathname,mode_t mode)
// mode : 权限 返回值: 成功0 失败-1
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
#include<sys/stat.h> int main()
{
int ret = mkfifo("./test.fifo",);
if(ret<)
{
perror("mkfifio error");
return -;
} int fd = open("./test.fifo",O_WRONLY);
if(fd<)
{
perror("open error");
return -;
}
printf("open fifo success!\n"); while()
{
char buff[] = {};
scanf("%s",buff);
write(fd,buff,strlen(buff));
printf("buff:[%s]\n",buff);
}
close(fd);
return ;
}

               运行演示 :

                Linux  进程间通信(管道、共享内存、消息队列、信号量)

                  Linux  进程间通信(管道、共享内存、消息队列、信号量)

                匿名管道和命名管道特点相同(除了亲缘关系)。

                匿名管道和命名管道的区别 :

                    1.匿名管道用pipe函数创建打开,命名管道由mkfifo函数创建,并用open打开

                    2.匿名管道只能用于亲缘关系的进程,命名管道能适用于同一机器上任意进程间通信

                 本质上他们都是在内核中创建的一块缓冲区

      

    2.共享内存(最快通信)

          相关命令:   ipcs --- 查看所有进程间通信方式

                             -m 查看共享内存   -s  查看信号量   -q 查看消息队列    

               ipcrm --- 删除进程间通信方式  -m 删除共享内存(删除时先判断链接数,链接数为0才会删除)

          原理是 , 开辟一块内存,把内存映射到虚拟地址空间,然后直接通过虚拟地址空间对数据进行操作

                                  Linux  进程间通信(管道、共享内存、消息队列、信号量)

                                    如上图,总共发生了两次数据拷贝: 1.用户空间到物理内存  2.物理内存到用户空间

          代码及流程如下:

              Linux  进程间通信(管道、共享内存、消息队列、信号量)

//共享内存写入
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/shm.h>
#define IPC_KEY 0x123456
#define SHM_SIZE 4096 int main()
{
//key_t ftok(const char *pathname,int proj_id);
//pathname : 文件名
//proj_id : 自定义数字
// 通过文件名找到inode节点号与proj_id合在一起生成一个key值(相当于宏)
//比如: key_t key = ftok(".",PROJ_ID); //int shmget(key_t key,size_t size,int shmflg);
//key : 共享内存在操作系统中的标识符
//size : 共享内存大小
//shmflg (选项标志):
// IPC_CREAT: 存在打开,否则创建
// IPC_EXCL : 存在报错,否则创建
// mode_flags : 权限
//返回值 : 进程对共享内存的操作句柄 失败返回-1
int shmid = shmget(IPC_KEY,,IPC_CREAT | );
if(shmid < )
{
perror("shmget error!");
return -;
} //void *shmat(int shmid,const void *shamaddr,int shmflg);
// shmat : 共享内存的操作句柄
// shmaddr : 用户指定共享内存映射在虚拟地址空间的首地址
// 置NULL,让操作系统分配
//shmflg : SHM_RDONLY -- 只读
// 0 -- 可读可写
//返回值 : 成功:映射首地址 失败:(void*)-1
void *shm_start = (char*)shmat(shmid,NULL,);
if(shm_start == (void*)-)
{
perror("shmat error");
return -;
} //进行内存操作
int i=;
while(i!=10)
{
sprintf(shm_start,"haha~~+%d\n",i++);
sleep();
} //解除映射
//int shmdt(const void *shmaddr);
//shmaddr :映射首地址
//return : success 0 failure -1
shmdt(shm_start); //int shmctl(int shmid,int cmd,struct shmid_ds *buf);
//shmid : 句柄
//cmd : 对共享内存的操作
// IPC_RMID 删除共享内存
//buf : 设置/获取属性信息 不想设置 NULL
//删除共享内存,并不会直接删除而是判断当前链接数
//若不为0,则拒绝连接
//直到为0,才会删除共享内存
return ;
}
//读共享内存
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/shm.h>
#define IPC_KEY 0x123456
#define SHM_SIZE 4096 int main()
{
int shmid = shmget(IPC_KEY,SHM_SIZE,IPC_CREAT | );
void* shm_start = (char*)shmat(shmid,NULL,);
while()
{
printf("%s\n",shm_start);
sleep();
}
shmdt(shm_start);
shmctl(shmid,IPC_RMID,NULL);
return ;
}

        共享内存特点 :

            1.共享内存是最快的ipc,一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传递不用在内核和用户态之间来回切换

            2.没有保证进程的同步互斥

    3.消息队列

            就是一个存储消息的链表。消息有特定的格式和优先级。对消息队列有写权限的进程可以添加消息,有读权限的进程可以读出消息。

            原理 : 内核中创建一个优先级队列,实现进程间数据传输

            Linux  进程间通信(管道、共享内存、消息队列、信号量)

    4.信号量    

          本质是一个计数器,用来计数资源  

          作用 : 保护共享(临界)资源,保证进程间的同步与互斥

          工作原理 : PV操作 --- 本身具有原子性,因为要保护资源

          P(申请): 如果计数>0,资源-1;如果计数=0,则挂起等待;

          V(释放): 如果有进程挂起等待则唤醒;没有进程等待则计数+1;

         

Linux 进程间通信(管道、共享内存、消息队列、信号量)的更多相关文章

  1. Linux进程间通信—使用共享内存

    Linux进程间通信-使用共享内存 转自: https://blog.csdn.net/ljianhui/article/details/10253345 下面将讲解进程间通信的另一种方式,使用共享内 ...

  2. Linux进程间通信&lpar;四&rpar; - 共享内存

    共享内存的优势 采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝.对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只 ...

  3. Linux进程间通信——使用共享内存

    一.什么是共享内存 顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存.共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式.不同进程之间共享的内存通常安排为同一段物理内存. ...

  4. Linux进程间通信——使用共享内存&lpar;转&rpar;

    一.什么是共享内存 顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存.共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式.不同进程之间共享的内存通常安排为同一段物理内存. ...

  5. linux进程间通信之共享内存篇

    本文是对http://www.cnblogs.com/andtt/articles/2136279.html*享内存(上)的进一步阐释说说明 1 共享内存的实现原理 共享内存是linux进程间通讯的 ...

  6. Linux进程间通信——使用System V 消息队列

    消息队列 消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法. 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构.我们可以通过发送消息来避免命名管道的同步和阻塞问 ...

  7. linux进程间通信之共享内存学习记录

    进程 狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed). 广义定义:进程是一个具有一定独立功能的 ...

  8. Linux进程间通信之共享内存

    一,共享内存  内核管理一片物理内存,允许不同的进程同时映射,多个进程可以映射同一块内存,被多个进程同时映射的物理内存,即共享内存.  映射物理内存叫挂接,用完以后解除映射叫脱接. 1,共享内存的特点 ...

  9. linux进程间通信同步-共享内存

    参考:https://www.cnblogs.com/charlesblc/p/6142868.html 使用有名信号量,sem_open().sem_close().sem_post().sem_w ...

随机推荐

  1. Table的行列合并

    <table border="1" width="200" height="200"> <tr> <td ro ...

  2. 大数据架构-使用HBase和Solr将存储与索引放在不同的机器上

    大数据架构-使用HBase和Solr将存储与索引放在不同的机器上 摘要:HBase可以通过协处理器Coprocessor的方式向Solr发出请求,Solr对于接收到的数据可以做相关的同步:增.删.改索 ...

  3. Nginx 开启PATHINFO支持ThinkPHP框架实例

    ThinkPHP支持通过PATHINFO和URL rewrite的方式来提供友好的URL,只需要在配置文件中设置 'URL_MODEL' => 2 即可.在Apache下只需要开启mod_rew ...

  4. HttpClient实现客户端与服务器的通信

    本篇主要讲解了利用HttpClient实现 windows主机与linux服务器的通信与传递数据 HttpClient代码,服务器端配置 系统和安装软件 1)ubuntu 14.04 64位系统 2) ...

  5. js获取时间&lpar;本周、本季度、本月&period;&period;&rpar;

    /** * 获取本周.本季度.本月.上月的开端日期.停止日期 */ var now = new Date(); //当前日期 var nowDayOfWeek = now.getDay(); //今天 ...

  6. 一个自己做的easyui datagird扩展

    var a; $(function () { $("body").bind("contextmenu", function () { return false; ...

  7. 【HDOJ】1109 Run Away

    基础模拟退火. /* poj 1379 */ #include <iostream> #include <cstdio> #include <cstdlib> #i ...

  8. 利用redis做频率限制第一篇

    public Result checkRateLimit(String clientIp, int ipTime, int ipCount) { // 每个ip的redis的key都不一样 Strin ...

  9. 浴室沉思:聊聊DAL和Repository

    这是一个由DDD群引发的随笔 在写了上一篇随笔<关于ORM的浴室沉思>后一些朋友私聊我,很多刚接触DDD的朋友会对Repository(仓储层)这东西有点疑惑,为什么要叫仓储层?是不是三层 ...

  10. Python django解决跨域请求的问题

    解决方案 1.安装django-cors-headers pip3 install django-cors-headers 2.配置settings.py文件 INSTALLED_APPS = [ . ...