0


【Linux】进程间通信

       🔥🔥 欢迎来到小林的博客!!
      🛰️博客主页:✈️林 子
      🛰️博客专栏:✈️ Linux之路
      🛰️社区 :✈️ 进步学堂
      🛰️欢迎关注:👍点赞🙌收藏✍️留言

目录

进程间通信

进程间的通信方式

  • 进程间通信介绍
  • 管道
  • 消息队列
  • 共享内存
  • 信号量

为什么要进程通信?

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止 时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另 一个进程的所有陷入和异常,并能够及时知道它的状态改变

进程通信的分类

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

管道

什么是管道

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

匿名管道

两个进程要互相通信,但是进程具有独立性。想要通信,必须先看到一份公共的资源!!这里的资源:就是一段内存,可能以文件方式提供,也可能以队列的方式,也可能是最原始的内存块。而这些公共资源不属于进程!因为进程具有独立性,如果资源单独属于某一个进程,那么另外的进程要想访问这个资源,就相当于也访问了这个进程。这违反了进程具有独立性的原则。所以公共资源一定是属于OS(操作系统)!

而匿名管道只能用在有血缘关系的进程之中。因为匿名管道是通过子进程继承父进程,而产生共享资源。

在这里插入图片描述

那么我们来使用一下匿名管道。

首先先介绍一个函数:

int pipe(int pipefd[2]);
传入的是一个含2个元素的数组
返回值为0代表管道开通成功,-1则失败。

在这里插入图片描述

而要注意的是,在俩个进程利用管道进行通信时,写入时必须关闭读,读取时必须关闭写。

匿名管道原理:

在这里插入图片描述

代码测试:

#include<stdio.h>#include<string.h>#include<stdlib.h>#include<unistd.h>intmain(){int pp[2];if(pipe(pp)!=0){//管道开通失败perror("pipe:");return1;}//管道开通成功if(fork()==0)//创建子进程,子进程会继承父进程的 pp函数{//child writeclose(pp[0]);//pp[0]是读取,关闭子进程的读char message []="hello pipe";int count =0;while(1){write(pp[1],message,strlen(message));sleep(1);}}//parent readclose(pp[1]);//关闭写while(1){//持续读取   char buffer[1024*4]={0};//缓冲区ssize_t s =read(pp[0],buffer,sizeof(buffer)-1);
    buffer[s]=0;printf("child say to father :%s\n",buffer);}return0;}

命名管道

匿名管道是用来给有血缘关系的进程进行通信的。那么两个毫不相干的进程如何建立通信?这时就要用我们的命名管道了,而两个进程要建立通信必须要找到他们的共享资源。如何找到共享资源?那么我们可以使用命令

mkfifo filename

建立一个命名管道,也可以使用mkfifo函数创建。

在这里插入图片描述

然后我们要建立2个毫不相干的进程,那么我们创建2个.c文件。因为两个进程毫不相干,所以都有main函数。我们只需要像平时一样正常的读取文件即可。

comm.h头文件:

#pragmaonce #include<stdio.h>#include<string.h>#include<stdlib.h>#include<fcntl.h>#include<sys/stat.h>#include<sys/types.h>#include<unistd.h>#defineMY_FIFO"./myfifo"//myfifo文件的文件名

server.c 文件:

#include"comm.h"intmain(){umask(0);//设置掩码if(mkfifo(MY_FIFO,0666)<0)//mkfifo的两个参数是文件名和文件权限,而mkfifo是管道文件{//管道文件已存在perror("mkfifo");return1;}//接下来进行文件操作即可int fd =open(MY_FIFO,O_RDONLY);//打开管道文件if(fd <0){//打开失败perror("open");return2;}//业务逻辑while(1){// 读取并刷新char buffer[64]={0};ssize_t s =read(fd,buffer,sizeof(buffer)-1);if(s ==0)//客户端挂了{break;}//刷新到显示器printf("client : %s ",buffer);}close(fd);//关闭文件return0;}

client.c文件:

#include"comm.h"intmain(){ssize_t fd =open(MY_FIFO,O_WRONLY);//打开管道文件if(fd <0)//打开失败{perror("open");return1;}//进行写入操作while(1){//先读取键盘输入char buffer[64]={0};read(0,buffer,sizeof(buffer));//写入管道文件,从而server从管道文件里读write(fd,buffer,strlen(buffer));}close(fd);//关闭return0;}

共享内存

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到 内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

共享内存示意图:

在这里插入图片描述

而我们的共享内存需不需要被管理呢?需要! 被谁管理呢?操作系统!既然操作系统需要管理共享内存,那么在内核中就一定有共享内存对应的数据结构。

structshmid_ds{structipc_perm shm_perm;/* operation perms */int shm_segsz;/* size of segment (bytes) */
 __kernel_time_t shm_atime;/* last attach time */
 __kernel_time_t shm_dtime;/* last detach time */
 __kernel_time_t shm_ctime;/* last change time */
 __kernel_ipc_pid_t shm_cpid;/* pid of creator */
 __kernel_ipc_pid_t shm_lpid;/* pid of last operator */unsignedshort shm_nattch;/* no. of current attaches */unsignedshort shm_unused;/* compatibility */void*shm_unused2;/* ditto - used by DIPC */void*shm_unused3;/* unused */};

共享内存函数

shmget函数

功能:用来创建共享内存
原型
 intshmget(key_t key,size_t size,int shmflg);
参数
 key:这个共享内存段名字
 size:共享内存大小//建议4096的次方
 shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmflg:

ICP_CREAT

:如果共享内存不存在,则创建,如果存在,返回存在的共享内存

ICP_EXCL

: 如果共享内存不存在,则创建,如果存在,返回出错(如果调用成功,得到的一定是一个最新的)。

shmat函数

功能:将共享内存段连接到进程地址空间
原型
 void*shmat(int shmid,constvoid*shmaddr,int shmflg);
参数
 shmid: 共享内存标识
 shmaddr:指定连接的地址
 shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

说明:

shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

shmdt函数

功能:将共享内存段与当前进程脱离
原型
 intshmdt(constvoid*shmaddr);
参数
 shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

功能:用于控制共享内存
原型
 intshmctl(int shmid,int cmd,structshmid_ds*buf);
参数
 shmid:由shmget返回的共享内存标识码
 cmd:将要采取的动作(有三个可取值)  IPC_RMID代表删除
 buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

那么

key_t key

的值如何获取呢?其实这就是给内存的一个唯一标识,你想输入多少就输入多少。当然你也可以用ftok函数生成。

在这里插入图片描述

共享内存代码演示

ipcs -m

可以查看共享内存的状态

ipcrm -m [shmid]

可以用命令行释放共享内存

我们先写一个头文件。

comm.h

#pragmaonce #include<sys/types.h>#include<stdio.h>#include<string.h>#include<sys/ipc.h>#include<sys/shm.h>#include<unistd.h>#definePATH_NAME"."#definePROJ_ID0xfff

然后我们在server.c服务器上建立一个一个共享内存,并观察它的生命周期。

server.c

#include"comm.h"intmain(){//创建共享内存key_t key =ftok(PATH_NAME,PROJ_ID);//获取唯一标识 keyint shmid =shmget(key,4096,IPC_CREAT | IPC_EXCL |0666);//IPC_CREAT 表示没有内存则创建,IPC_EXCL表示|0666表示权限sleep(10);//与共享内存建立连接char* receive =(char*)shmat(shmid,NULL,0);sleep(10);//业务逻辑//脱离连接int falg =shmdt(receive);if(falg ==0)printf("chmdt success\n");elseprintf("chmd fail\n");sleep(20);//干掉共享内存 shmctl(shmid,IPC_RMID,NULL);return0;}

然后我们运行,并监测共享内存。

在这里插入图片描述

我们会发现共享内存一开始被创建出来,但是没建立连接,所以连接数是0。后面创建出来,那么就说明有1个进程连接了共享内存。然后又睡眠10秒后,共享内存连接断开。最后销毁了共享内存。

在这里插入图片描述

接下来我们完善业务逻辑:

server.c代码:

#include"comm.h"intmain(){//创建共享内存key_t key =ftok(PATH_NAME,PROJ_ID);//获取唯一标识 keyint shmid =shmget(key,4096,IPC_CREAT | IPC_EXCL |0666);//IPC_CREAT 表示没有内存则创建,IPC_EXCL表示|0666表示权限//与共享内存建立连接char* receive =(char*)shmat(shmid,NULL,0);//业务逻辑  while(1){//直接输出内存数据。printf("%s",receive);sleep(1);}//脱离连接int falg =shmdt(receive);if(falg ==0)printf("chmdt success\n");elseprintf("chmd fail\n");//干掉共享内存 shmctl(shmid,IPC_RMID,NULL);return0;}

服务端已经创建了共享内存,那么client就不用再创建了,只需要获取并连接即可。

client代码:

#include"comm.h"intmain(){//获取共享内存key_t key =ftok(PATH_NAME,PROJ_ID);int shmid =shmget(key,4096,IPC_CREAT);if(shmid !=0){//连接失败perror("shmget");}//连接共享内存char* ret =(char*)shmat(shmid,NULL,0);//业务逻辑char ch ='a';while(ch <='z'){
       ret[ch-'a']= ch;
       ch++;
       ret[ch-'a']= ch;sleep(2);}//解除连接shmdt(ret);return0;}

接下来我们测试一下

先开启server,再开启client。server开始输出共享内存的内容。

在这里插入图片描述

连接数为2。

在这里插入图片描述

client结束。server没有结束,持续输出内存里的内容。

在这里插入图片描述

在这里插入图片描述

然后我们ctrl+c干掉server。

在这里插入图片描述

然后我们发现这个共享内存还是存在。这是因为共享内存不属于进程,所以进程无法释放共享内存。只有操作系统可以释放。

在这里插入图片描述

我们用命令

ipcrm -m [shmid]

手动释放。

在这里插入图片描述

如果不释放又不使用,那么就内存泄漏了。

标签: linux 运维 服务器

本文转载自: https://blog.csdn.net/Lin5200000/article/details/131585188
版权归原作者 林 子 所有, 如有侵权,请联系我们删除。

“【Linux】进程间通信”的评论:

还没有评论