【Linux】进程间通信

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

目录

  • 进程间通信
    • 进程间的通信方式
    • 为什么要进程通信?
    • 进程通信的分类
  • 管道
    • 什么是管道
    • 匿名管道
    • 命名管道
  • 共享内存
    • 共享内存函数
    • 共享内存代码演示

进程间通信

进程间的通信方式

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

为什么要进程通信?

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

进程通信的分类

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

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

POSIX IPC

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

管道

什么是管道

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

匿名管道

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

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

【Linux】进程间通信_第1张图片

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

首先先介绍一个函数:int pipe(int pipefd[2]);

传入的是一个含2个元素的数组

返回值为0代表管道开通成功,-1则失败。

【Linux】进程间通信_第2张图片

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

匿名管道原理:

【Linux】进程间通信_第3张图片

代码测试:

#include
#include
#include
#include

int main()
{
  int pp[2];
  if(pipe(pp)!= 0)
  {
    //管道开通失败
    perror("pipe:");
    return 1;
  }
  
  //管道开通成功
  if(fork()==0) //创建子进程,子进程会继承父进程的 pp函数
  {
    //child write
    close(pp[0]); //pp[0]是读取,关闭子进程的读
    char message [] = "hello pipe";
    int count = 0;
    while(1)
   {
       write(pp[1],message,strlen(message));
       sleep(1);
    }
  }
  //parent read
  close(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);
  }

  return 0;
}

命名管道

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

【Linux】进程间通信_第4张图片

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

comm.h头文件:

#pragma once 

#include
#include
#include
#include
#include
#include
#include


#define MY_FIFO "./myfifo" //myfifo文件的文件名

server.c 文件:

#include"comm.h"

int main()
{
  umask(0); //设置掩码
  if(mkfifo(MY_FIFO,0666) < 0) //mkfifo的两个参数是文件名和文件权限,而mkfifo是管道文件
  {
    //管道文件已存在
    perror("mkfifo");
    return 1;
  }

  //接下来进行文件操作即可
  int fd = open(MY_FIFO,O_RDONLY);//打开管道文件
  if(fd < 0)
  {
    //打开失败
    perror("open");
    return 2;
  }

  //业务逻辑
  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); //关闭文件

  return 0;
}

client.c文件:

#include"comm.h"


int main()
{
  ssize_t fd = open(MY_FIFO,O_WRONLY); //打开管道文件
  if(fd < 0) //打开失败
  {
    perror("open");
    return 1;
  }

  //进行写入操作
  while(1)
  {
    //先读取键盘输入
    char buffer[64] = {0};
    read(0,buffer,sizeof(buffer));
    //写入管道文件,从而server从管道文件里读
    write(fd,buffer,strlen(buffer));
  }
    
  close(fd); //关闭
  
  return 0;
}

共享内存

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

共享内存示意图:

【Linux】进程间通信_第5张图片

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

struct shmid_ds {
 struct ipc_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 */
 unsigned short shm_nattch; /* no. of current attaches */
 unsigned short shm_unused; /* compatibility */
 void *shm_unused2; /* ditto - used by DIPC */
 void *shm_unused3; /* unused */
};

共享内存函数

shmget函数

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

shmflg:

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

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

shmat函数

功能:将共享内存段连接到进程地址空间
原型
 void *shmat(int shmid, const void *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函数

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

shmctl函数

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

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

【Linux】进程间通信_第6张图片

共享内存代码演示

ipcs -m可以查看共享内存的状态

ipcrm -m [shmid] 可以用命令行释放共享内存

我们先写一个头文件。

comm.h

#pragma once 
#include
#include
#include
#include
#include
#include

#define PATH_NAME "."
#define PROJ_ID 0xfff

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

server.c

#include "comm.h"

int main()
{
  //创建共享内存
  key_t key = ftok(PATH_NAME,PROJ_ID); //获取唯一标识 key
  int 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");
  else 
    printf("chmd fail\n");

  sleep(20);

  //干掉共享内存 
  shmctl(shmid,IPC_RMID,NULL);

  return 0;
}

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

【Linux】进程间通信_第7张图片

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

【Linux】进程间通信_第8张图片

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

server.c代码:

#include "comm.h"

int main()
{
  //创建共享内存
  key_t key = ftok(PATH_NAME,PROJ_ID); //获取唯一标识 key
  int 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");
  else 
    printf("chmd fail\n");
  //干掉共享内存 
  shmctl(shmid,IPC_RMID,NULL);

  return 0;
}

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

client代码:

#include"comm.h" 
int main()
{
  //获取共享内存
  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);

  return 0;
}

接下来我们测试一下

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

【Linux】进程间通信_第9张图片

连接数为2。

【Linux】进程间通信_第10张图片

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

【Linux】进程间通信_第11张图片

【Linux】进程间通信_第12张图片

然后我们ctrl+c干掉server。

【Linux】进程间通信_第13张图片

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

【Linux】进程间通信_第14张图片

我们用命令ipcrm -m [shmid]手动释放。

【Linux】进程间通信_第15张图片

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

你可能感兴趣的:(Linux之路,linux,运维,服务器)