Linux入门进程间的通信

Posted 世_生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux入门进程间的通信相关的知识,希望对你有一定的参考价值。

进程间通信

进程间通信介绍


进程间通信目的

进程通信的目的有四点

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

进程间通信发展

  • 管道
  • System V进程通信
  • POSIS进程间通信

进程间通信分类

管道

  • 匿名管道
  • 命名管道

System V IPV

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

POSIX IPC

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

但是这篇博客是讲管道System V共享内存

管道

什么是管道

在学Linux指令的时候,知道管道是|来表示的。
如一行指令head -n5 text.txt | tail -n3表示获取text.txt文件3~5行内容。
head命令通过选项 -n5 把text.txt文件中的前五行内容通过管道传输给tail进程把传输过来的内容中的末尾三行内容拿出来。这里管道就提供了传输数据的作用。

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

如图(简易图)

head进程是通过管道来对tail进程进行数据传输,这看似很正常。但这个管道是谁提供的呢?进程不是具有独立性吗?这两个进程是怎么玩到一块去的呢?

进程和进程具有独立性没错,但是不一定玩不到一块去。比如,人是独立的,但是人可以和人交流,只是要通过一个媒介。而OS就可以提供一个媒介——管道。
OS提供一段内存区域,让head进程和tail进程都看到这块区域,让这两个进程进行通信。所以,进程与进程之间进行通信的本质是看到同一份资源,这种资源称之为临界资源(内存、文件内核缓存等)。

匿名管道

匿名管道:创建一种无名的管道,提供具有血缘关系的进程进行通信。

函数

#include<unistd.h>
int pipe(int fd[2])
参数:fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误代码
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>

int main()

  int fd[2];
  //创建匿名管道
  if(pipe(fd)<0)
    perror("pipe!");
    return 1;
  
  //创建子进程
  int id=fork();
  if(id==0)
  //child
      close(fd[0]);
      const char* mag="hello linux\\n";
      int count=5;
      while(count)
        write(fd[1],mag,strlen(mag));
        count--;
        sleep(1);
      
      exit(0);
  
  //father
  close(fd[1]);
  char buff[64];
  while(1)
    ssize_t s=read(fd[0],buff,sizeof(buff));
    if(s>0)
      buff[s]='\\0';
      printf("child --->father#%s",buff);
      sleep(1);
    
    else if(s==0)
      printf("read end\\n");
      break;
    
    else
      printf("error\\n");
      break;
    
  
  //等待子进程结束,回收资源,防止僵尸进程。
  waitpid(id,NULL,0);
  close(fd[0]);
  return 0;

运行结果:

child --->father#hello linux
child --->father#hello linux
child --->father#hello linux
child --->father#hello linux
child --->father#hello linux
read end

<1> 如果说通信可以通过文件来作为媒介,那么为什么不直接open一个文件来呢?要用pipe来创建管道?

答:pipe创建的文件是内存文件,数据一定不会刷新到磁盘。并且用普通文件会有很多问题(同步与互斥),有IO参与会降低效率,没有必要。

<2> 创建子进程进行写入难道不会发生写时拷贝吗?

答:不会,管道是OS提供的,子进程写入时,不会改变父进程的数据区,故不会发生写时拷贝。

<3> 子进程还没写完,父进程就不会读取吗?

答:管道是自带同步与互斥的。不会发生子进程还没写完,父进程就开始读了。

用fork来共享管道的原理

父进程创建了匿名管道,父进程PCB中的files*指针指向file_struct,通过文件描述符找到file结构体,对管道进行读写。
fork子进程,是为了让子进程也可以看到相同的管道,对管道进行读写,这样就可以通信了。

管道只能单向通信,只能一方读,另一方写。所以在fork之后,要关闭掉不需要的描述符。

站在内核角度-深度理解管道

站在内核角度-管道的本质


所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件的思想”。

管道读写规则

当没有数据可读时:

  • 子进程不write,父进程会被挂起。

当管道满了时:

  • 子进程会被挂起,等待父进程read

如果管道写端对应的文件描述符被关闭,read返回0
如果父进程write关闭时,子进程read没有意义,子进程会接收到13号信号退出。

用代码演示子进程接收信号退出:

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

int main()

  int fd[2];
  if(pipe(fd)<0)
    perror("pipe!");
    return 1;
  
  pid_t id=fork();
  if(id==0)
    //child
    close(fd[0]);
    const char* mag="hello linux\\n";
    write(fd[1],mag,strlen(mag));
    exit(0);
  
  //father
  //关闭读端和写端
  close(fd[1]);
  close(fd[0]);
  int status=0;
  waitpid(id,&status,0);
  //打印信号
  printf("child get signal:%d\\n",status&0x7F);
  return 0;

运行结果:

child get signal:13

接收的是13号信号,信号是由OS发送的。

当要写入的数据量不大于PIPE_BUF时,Linux将保持原子性。
当要写入的数据量大于PIPE_BUF时,Linux将不保持原子性。

那么PIPE_BUF是什么呢?

翻译过来的) POSIX.1-2001规定,小于PIPE_BUF字节的写入(2)必须是原子的:输出数据作为连续序列写入管道。写入超过PIPE_BUF字节可能是非原子的:内核可能会将数据与其他进程写入的数据交错。POSIX.1-2001要求管道长度至少为512字节。(在Linux上,PIPE_BUF是4096精确的语义取决于文件描述符是否为非阻塞(O_NON?块),管道是否有多个写入程序,以及在n上,要写入的字节数

管道有多大?代码测试一下。

#include<stdio.h>
#include<unistd.h>
int main()

  int fd[2];
  pipe(fd);
  pid_t id=fork();
  if(id==0)
    close(fd[0]);
    char a='a';
    int count=0;
    while(1)
      write(fd[1],&a,1);
      count++;
      printf("%d:a\\n",count);
    
  
  sleep(1000);
  return 0;

管道特点

  1. 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

  2. 管道提供流式服务

  3. 一般而言,进程退出,管道释放,所以管道的生命周期随进程

  4. 一般而言,内核会对管道操作进行同步与互斥

  5. 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

命名管道

匿名管道对具有血缘关系的进程进行通信,那么两个毫不相关的进程是如何通信的呢?

答:进程的通信本质是看到同一份资源,毫不相干的进程可以通过命名管道进行通信。

  • 匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
  • 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
  • 命名管道是一种特殊类型的文件

创建一个命名管道

命名管道可以直接在命令行上进行创建。

mkfifo filename


利用命名管道让两个毫不相干的进程进行通信

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

int main()

  int fd=open("filename",O_WRONLY);
  char *mag="ni hao a\\n";
  write(fd,mag,strlen(mag));
  close(fd);
  return 0;


//client.c ->读
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>

int main()

  char buff[64];
  int fd=open("filename",O_RDONLY);
  ssize_t s=read(fd,buff,sizeof(buff));
  buff[s]='\\0';
  printf("srever----->client:%s",buff);
  close(fd);
  return 0;

运行结果

srever----->client:ni hao a

命名管道也可以从程序里创建,相关函数有:

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *filename,mode_t mode);
第一个参数是文件名或者路径。第二个参数是文件的权限。
成功是返回0,失败是返回-1。

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

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。
  • 命名管道作用于毫不相干的进程,匿名管道作用于具有血缘关系的进程

例子1-用命名管道实现文件拷贝

原理:

把一个普通文件的内容通过server1进程读取到管道中,再通过client1进程创建新的一个文件,并把管道中的内容写入新文件中,这样就完成有文件的copy.

//server1.c  读取普通文件内容——>把内容写入管道中。
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>

int main()

  umask(0);
  int fd1=open("copyfile",O_RDONLY);//以读的方式打开
  if(mkfifo("filename",0666)<0)
    perror("mkfifo!");
    return 1;
  
  int fd=open("filename",O_WRONLY);//创建管道
  char mag[128];
  while(1)
    mag[0]=0;
    ssize_t s=read(fd1,mag,sizeof(mag));//读取普通文件的数据
    if(s>0)
      mag[s]=0;
      write(fd,mag,strlen(mag));//把数据写入管道中
    
    else
      break;
    
  
    return 0;


//client1 读取管道内容,并把读取到的内容写入到新创建的文件中。
#include<stdio.h>                                                                                                                                                                                        
#include<sys/types.h>                  
#include<sys/stat.h>                   
#include<fcntl.h>                      
#include<unistd.h>                     
#include<string.h>

int main()

  umask(0);
  int fd1=open("copy",O_WRONLY|O_CREAT,0666);//以写的方式创建一个新文件

  int fd=open("filename",O_RDONLY);//以读的方式打开管道
  char buff[128];
  while(1)
  	buff[0]=0;
  	ssize_t s=read(fd,buff,sizeof(buff));//读取管道的数据
  	if(s>0)
    	write(fd1,buff,strlen(buff));//把读到的数据写入到新创建的文件中
  	
  	else
     	break;
  	
  
  return 0;

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

//server 写端
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>

int main()

  umask(0);
  if(mkfifo("filename",0666)<0)//创建命名管道
    perror("mkfifo!");
    return 1;
  
  int fd=open("filename",O_WRONLY);//只以写的方式打开命名管道
  if(fd<0)
  	perror("open!");
  	return 1;
  
  char mag[128];
  while(1)
    mag[0]=0;
    printf("server say $:");//提示发信息
    ffshul(stdout);//刷新缓冲区
    ssize_t s=read(0,mag,sizeof(mag));//从键盘中获取内容填入mag
    if(s>0)
      mag[s-1]=0;
      write(fd,mag,strlen(mag));//把mag的内容写入命名管道
    
    else
      printf("error\\n");
      break;
    
  
    return 0;


//client读端
#include<stdio.h>                                                                                                                                                                                        
#include<sys/types.h>                  
#include<sys/stat.h>                   
#include<fcntl.h>                      
#include<unistd.h>                     
#include<string.h>

int main()

  int fd=open("filename",O_RDONLY);
  if(fd<0)
  	perror("open!");
  	return 1;
  
  char buff[128];
  while(1)
    buff[0]=0;
    ssize_t s=read(fd,buff,sizeof(buff));
    if(s>0)
      buff[s]=0;
      printf("server-->client#:%s\\n",buff);
    
  
  return 0;

例子3-用命名管道实现对进程的控制

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

int main()

  umask(0);
  if(mkfifo("filename",0666)<0)
    perror("mkfifo!");
    return 1;
  
  int fd=open("filename",O_WRONLY);
  
  char mag[128];
  while(1)
    mag[0]=0;
    printf("server say $:");
    fflush(stdout);
    ssize_t s=read(0,mag,sizeof(mag));
    ifLinux入门进程间的通信

进程通信

Linux eventfd分析

进程间通信

进程间的mutex

临界区与互斥量区别