当前位置:主页 > 查看内容

Linux--进程间通信(管道、共享内存)

发布时间:2021-05-15 00:00| 位朋友查看

简介:进程间通信目的 1、数据传输 一个进程需要将它的数据发送给另一个进程。 2、资源共享 多个进程之间共享同样的资源。 3、通知事件 一个进程需要向另一个或一组进程发送消息通知它(它们)发生了某种事件如进程终止时要通知父进程。 4、进程控制 有些进程希望完……

进程间通信目的

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

管道

什么是管道

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

匿名管道

主要用于父进程与子进程之间,或者两个兄弟进程之间。在linux系统中可以通过系统调用建立起一个单向的通信管道,且这种关系只能由父进程来建立。

站在文件描述符角度–深入理解管道

在这里插入图片描述

场景一

当实际在进行读取时,读取条件不满足时(管道为空),读端会发生阻塞。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>

int main(){
  int pipefd[2]={0};
  pipe(pipefd);

  pid_t id=fork();

  if(id==0){
    close(pipefd[0]);
    const char *msg="I am child!!";
    while(1){
      write(pipefd[1],msg,strlen(msg));
      sleep(5);
    }
  }else{
    close(pipefd[1]);
    char buf[64];
    while(1){
      ssize_t s=read(pipefd[0],buf,sizeof(buf)-1);
      if(s>0){
        buf[s]=0;
        printf("father get message : %s\n",buf);
      }
      printf("等待中!!\n");
    }
  }
  return 0;
}

运行结果:
在这里插入图片描述

场景二

当实际在进行写入时,写入条件不满足(管道写满),写端就要被阻塞。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>

int main(){
  int pipefd[2]={0};
  pipe(pipefd);

  pid_t id=fork();

  if(id==0){
    close(pipefd[0]);
    const char *msg="I am child.....\n";
    int count=0;
    while(1){
      write(pipefd[1],msg,strlen(msg));
      printf("CHILD : %d\n",count++);
    }
  }else{
    close(pipefd[1]);
    char buf[64];
    while(1){
      ssize_t s=read(pipefd[0],buf,sizeof(buf)-1);
      if(s>0){
        buf[s]=0;
        printf("father get message : %s\n",buf);
        sleep(1);
      }
    }
  }
  return 0;
}

运行结果:
在这里插入图片描述

场景三

如果写端不写入并且关闭文件描述符,读端在读取完数据后会读到文件结尾。read返回0

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>

int main(){
  int pipefd[2]={0};
  pipe(pipefd);

  pid_t id=fork();

  if(id==0){
    close(pipefd[0]);
    const char *msg="I am child.....\n";
    int count=0;
    while(1){
      write(pipefd[1],msg,strlen(msg));
      printf("CHILD : %d\n",count++);
      if(count==5){
        close(pipefd[1]);
        break;
      }
    }
    exit(2);
  }else{
    close(pipefd[1]);
    char buf[64];
    while(1){
      ssize_t s=read(pipefd[0],buf,sizeof(buf)-1);
      if(s>0){
        buf[s]=0;
        printf("father get message : %s\n",buf);
        sleep(1);
      }
      printf("child exit return : %d\n",s);
    }
  }
  return 0;
}

运行结果:
在这里插入图片描述

场景四

如果读端关闭文件描述符,写端进程在后续会被OS直接杀掉。OS通过13号信号将进程杀掉。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>



int main(){
  int pipefd[2]={0};
  pipe(pipefd);

  pid_t id=fork();

  if(id==0){
    close(pipefd[0]);
    const char *msg="I am child.....\n";
    int count=0;
    while(1){
      write(pipefd[1],msg,strlen(msg));
      printf("CHILD : %d\n",count++);
      
    } 
  }else{
    close(pipefd[1]);
    char buf[64];
    int count=0;
    while(1){
      ssize_t s=read(pipefd[0],buf,sizeof(buf)-1);
      if(s>0){
        buf[s]=0;
        printf("father get message : %s\n",buf);
        sleep(1);
      }
      if(count++==3){
        close(pipefd[0]);

      }
    }

  }
  return 0;
}

检测脚本:

while :; do ps -axj| grep mypipe | grep -v grep; echo "############################";sleep 1;done;

检测结果:

在这里插入图片描述

当OS发现读端已经关闭管道后,如果写端再写入数据是无用的因为没有进程从管道中读取数据,那么管道就是废弃管道操作系统不做任何浪费空间和低效的事情,那么只要发现就会将这个进程杀掉。

验证OS发送信号13杀死进程

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>



int main(){
  int pipefd[2]={0};
  pipe(pipefd);

  pid_t id=fork();

  if(id==0){
    close(pipefd[0]);
    const char *msg="I am child.....\n";
    int count=0;
    while(1){
      write(pipefd[1],msg,strlen(msg));
      printf("CHILD : %d\n",count++);
      
    } 
  }else{
    close(pipefd[1]);
    char buf[64];
    int count=0;
    while(1){
      ssize_t s=read(pipefd[0],buf,sizeof(buf)-1);
      if(s>0){
        buf[s]=0;
        printf("father get message : %s\n",buf);
        sleep(1);
      }
      if(count++==3){
        close(pipefd[0]);
        break;
      }
    }
    int status=0;
    waitpid(id,&status,0);
    printf("child exit get a signal , signal number : %d\n",status&0x7f);
  }
  return 0;
}

运行结果:
在这里插入图片描述

匿名管道特点

1、只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork函数,此后父子进程之间就可应用该管道。
2、管道提供流式服务
3、一般而言,进程退出,管道释放,所以管道的声明周期随进程。
4、一般而言,内核会对管道进行同步和互斥。
5、管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

命名管道

命名管道是建立在实际的磁盘介质或文件系统(而不是只存在于内存中)上有自己名字的文件,任何进程可以在任何时间通过文件名或路径名与该文件建立联系。为了实现命名管道,引入了一种新的文件类型——FIFO文件(遵循先进先出的原则)。
实现一个命名管道实际上就是实现一个FIFO文件。命名管道一旦建立,之后它的读、写以及关闭操作都与普通管道完全相同。虽然FIFO文件的inode节点在磁盘上,但是仅是一个节点而已,文件的数据还是存在于内存缓冲页面中,和普通管道相同。

创建命名管道

#命令行创建
$ mkfifo filename

#程序中创建,调用函数mkfifo
int mkfifo(const char *filename,mode_t mode);

匿名管道与命名管道的区别

1、匿名管道由pipe函数创建并打开的。
2、命名管道由mkfifo函数创建,用open打开。
3、FIFO(命名管道)和pipe(匿名管道)之间唯一区别就是创建和打开的方式不同。

例子—用命名管道实现server&client通信

//server.c
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>

#define FIFO_FILE "./fifo"
int main(){
  umask(0);
  if(-1==mkfifo(FIFO_FILE,0666)){
    perror("mkfifo failed!!\n");
  } 
  int fd=open(FIFO_FILE,O_RDONLY);
  if(fd>=0){
    while(1){
      char buf[64];
      ssize_t s=read(fd,buf,sizeof(buf)-1);

      if(s>0){
        buf[s]=0;
        printf("client# : %s",buf);
      }else if(s==0){
        printf("client quit,me too!!\n");
        break;
      }else{
        perror("error!\n");
      }
    }
  }
  return 0;
}

//client.c

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>

#define FIFO_FILE "./fifo"
int main(){
  
  int fd=open(FIFO_FILE,O_WRONLY);
 
  if(fd>=0){
    while(1){
      char buf[64];
      printf("Please input a message : ");
      fflush(stdout);
      ssize_t s=read(0,buf,sizeof(buf)-1);
      if(s>0){
        buf[s]=0;
        write(fd,buf,s);
      }
    }
  }
  return 0;
}

//Makefile文件
.PHONY:all

all:server client

server:server.c
	gcc $^ -o $@

client:client.c
	gcc $^ -o $@
.PHONY:clean

clean:
	rm -rf client server

运行结果:
在这里插入图片描述
在这里插入图片描述

共享内存

共享内存(Shared Memory)就是允许多个进程访问同一个内存空间,是在多个进程之间共享和传递数据最高效的方式。操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存连接到它们自己的地址空间中,如果某个进程修改了共享内存中的数据,其它的进程读到的数据也将会改变。

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

共享内存不提供任何同步和互斥机制,也就是说,在某一个进程对共享内存的进行读写的时候,不会阻止其它的进程对它的读写。如果要对共享内存的读/写加锁,可以使用信号灯。

共享内存函数

Linux中提供了一组函数用于操作共享内存,程序中需要包含以下头文件:

#include <sys/ipc.h>
#include <sys/shm.h>

shmget函数

功能:用于创建共享内存


//原型:

int shmget(key_t key, size_t size, int shmflg);

//参数key是共享内存的键值,是一个整数,是共享内存在系统中的编号,不同共享内存的编号不能相同,这一点由程序员保证。可以用函数:key_t ftok(const char *pathname, int proj_id);生成唯一键值。

//参数size是待创建的共享内存的大小,以字节为单位。通常操作系统在底层分配页(4K)的倍数

//参数shmflg是共享内存的访问权限,与文件的权限一样,由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的,如果共享内存不存在,就创建一个共享内存。

//返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmctl函数

功能:控制共享内存,不止是删除,还有其他功能

//原型:
int shmctl(int shm_id, int command, struct shmid_ds *buf);
//参数:
	shm_id:由shmget返回的共享内存标志码
	command:将要采取的动作(三个常用动作)IPC_RMID:删除共享内存段,其他的查看文档
	buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
//注意,用root创建的共享内存,不管创建的权限是什么,普通用户无法删除。

//返回值:成功返回0,失败返回-1

shmat函数

功能:把共享内存连接到当前进程的地址空间。

//原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);

//参数:
	shmid:由shmget返回的共享内存标志码
	shmaddr:指定连接的地址,通常为空,表示让系统来选择共享内存的地址。
	shmflg:是一组标志位,通常为0,它的两个可能取值是SHM_RND和SHM_RDONLY
	
//返回值:
	成功返回一个指针,指向共享内存第一个字节的指针;失败返回-1

参数说明

shmaddr为NULL,操作系统自动选一个地址。
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存。

shmdt函数

功能:将共享内存段与当前进程脱离。

//原型:
int shmdt(const void *shmaddr);
//参数
	shmaddr:由shmat所返回的指针
//返回值
	成功返回0,失败返回-1

注意:将共享内存与当前进程脱离,不等于删除共享内存段,删除共享内存段用shmtcl函数。

实例代码

创建两个进程,一个客户端,一个服务器,让客户端往共享内存中写字符串,服务器打印共享内存中的字符串。

//server.c
#include<stdio.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include"comm.h"
#include<unistd.h>

int main(){
  //生成操作系统层面唯一码
  key_t key=ftok(PATHNAME,PROJ_ID);
  printf("key : %p\n",key);
  //创建共享内存
  int shmid=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);
  if(shmid<0){
    perror("shmget error!!\n");
    return 1;
  } 
  
  //将进程与共享内存连接
  char *str=(char*)shmat(shmid,NULL,0);

  while(1){
    printf("%s\n",str);
    sleep(1);
  }
  //将进程与共享内存取消关联
  shmdt(str);

  //删除共享内存
  shmctl(shmid,IPC_RMID,NULL);

  return 0;
}


//client.c
#include<stdio.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include"comm.h"
#include<unistd.h>

int main(){
  //生成操作系统层面唯一码
  key_t key=ftok(PATHNAME,PROJ_ID);
  
  int shmid=shmget(key,SIZE,IPC_CREAT); 
  //将进程与共享内存连接
  char *str=(char*)shmat(shmid,NULL,0);

  //往共享内存中写入数据
  for(int i=0;i<26;++i){
    str[i]='a'+i;
    sleep(5);
  }

  //将进程与共享内存取消关联
  shmdt(str);


  return 0;
}

//Makefile
.PHONY:all
all:server client

server:server.c
	gcc $^ -o $@

client:client.c
	gcc $^ -o $@ -std=c99

.PHONY:clean
clean:
	rm -rf server client

运行结果:
在这里插入图片描述

;原文链接:https://blog.csdn.net/weixin_44627813/article/details/115443283
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文


随机推荐