Linux ---- 进程间通信

进程间通信


进程是操作系统的资源分配单元,不同进程(通常指的是用户进程)之间的资源是独立的,没有关联,无法直接在一个进程中直接访问另一个进程的资源。 但是,进程不是独立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信( IPC)


进程间通信的目的

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

实现进程间通信的方法

在这里插入图片描述



无名管道


管道也叫无名管道,它是是UNIX系统IPC (进程间通信)的最古老形式,所有的UNIX系统都支持这种通信机制。

管道有如下特点:

  • 半双工,数据在同一时刻只能在一个方向上流动。

  • 数据只能从管道的一端写入,从另一端读出。

  • 写入管道中的数据遵循先入先出的规则。

  • 管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。

  • 管道不是普通的文件,不属于某个文件系统,其只存在于内存中。

  • 管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。

  • 从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据。

  • 管道没有名字,只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用。

对于管道特点的理解,管道是一种特殊类型的文件,在应用层体现为两个打开的文件描述符。



pipe 函数

创建无名管道
pipefd : 为 int 型数组的首地址,其存放了管道的文件描述符 pipefd[0]、 pipefd [1].
当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。一般文件 I/O 的函数都可以用来操作管道(1seek()除外)。

在这里插入图片描述


设置为非阻塞


//获取原先flags
int flags = fcntl(fd[0], F_GETFL);
//设置新的flags
flag |= O_NONBLOCK;
fcntl(fd[0], F_SETFL, flags);


无名管道的特点


  1. 如果写端没有关闭,管道中没有数据,这个时候读管道进程去读管道会阻塞。
    如果写端没有关闭管道中有数据这个时候读管道进程会将数据读出。下一次读没有数据就会阻塞.

  2. 管道所有的写端关闭,读进程去读管道的内容,读取全部内容,最后返回0

  3. 所有读端没有关闭,如果管道被写满了,写管道进程写管道会被阻塞.

  4. 所有的读端被关闭,写管道进程写管道会收到一个信号,然后退出.


查看管道缓冲区大小

在这里插入图片描述

使用函数查看
在这里插入图片描述


有名管道


无名管道由于没有名字,只能用于存在“亲缘关系”的进程间通信。为了克服这个缺点,提出了命名管道(FIFO),也叫有名管道、FIFO文件。
命名管道(FIFO)不同于无名管道之处在于它提供了一个路径名与之关联,以FIFO的文件形式存在于文件系统中,这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信,因此,通过FIFO不相关的进程也能交换数据。

命名管道(FIFO)和无名管道(pipe)有一些特点是相同的,不一样的地方在于:
FIFO在文件系统中作为一个特殊的文件而存在,但FIFO中的内容却存放在内存中。
当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用。
FIFO有名字,不相关的进程可以通过打开命名管道进行通信。



创建有名管道

在这里插入图片描述

函数创建有名管道

在这里插入图片描述

参数:
pathname :普通的路径名,也就是创建后 FIFO 的名字。
mode :文件的权限,与打开普通文件的 open函数中的mode
返回值:
成功:0状态码
失败:如果文件已经存在,则会出错且返回-1。

在这里插入图片描述



读写有名管道


使用mkfifo创建的FIFO文件,可以使用open打开,常见的文件l/O函数都可用于fifo。如: close,read、write、unlink等。
FIFO严格遵循先进先出(FIFO),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek() 等文件定位操作。



有名管道特点

一个为只读而打开一个管道的进程会阻塞直到另外一个进程为只写打开该管道

一个为只写而打开一个管道的进程会阻塞直到另外一个进程为只读打开该管道

其余读写特性和无名管道相同。



共享内存映射

存储映射IO(Memory-mapped I/O)使一个磁盘文件与存储空间中的一个缓冲区相映射。

当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用read和write函数的情况下,使用地址(指针)完成I/O操作。
共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式,因为进程可以直接读写内存,而不需要任何数据的拷贝。

在这里插入图片描述


mmap 函数

将一个文件映射到内存中。

在这里插入图片描述

在这里插入图片描述

注意点:

  • 第一个参数 默认为NULL
  • 第二个参数要映射的文件大小 >0
  • 第三个参数:PROT_READ 、PROT_WRITE
  • 第四个参数:MAP_SHARED 或 MAP_PRIVATE
  • 第五个参数:打开的文件对应的文件描述符
  • 第六个参数: 4的整数倍,通常为0

munmap 函数

释放内存映射区

参数:
addr: 使用mmap函数创建的映射区的首地址
length: 映射区的大小

函数原型:
在这里插入图片描述

小结

  • 创建映射区的过程中,隐含着一次对映射文件的读操作。
  • 当MAP_SHARED时,要求:映射区的权限应<=文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE则无所,因为mmap中的权限是对内存的限制。
  • 映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭。
  • 特别注意,当映射文件大小为O时,不能创建映射区。所以,用于映射的文件必须要有实际大小。mmap使用时常会出现总线错误,通常是由于共享文件存储空间大小引起的。
  • munmap传入的地址一定为mmap的返回地址。文件偏移量必须为4的整数倍。
  • mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。


匿名共享映射

父子进程间通信还需要建立映射区要open一个temp文件,创建好了再unlink、close掉,比较麻烦。可以直接使用匿名映射来代替。
其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定。

使用MAP_ANONYMoUS 或 (MAP_ANON)

·MAP_ANONYMOUS和MAP_ANON这两个宏是Linux操作系统特有的宏。


父子进程通过匿名映射实现通信

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

int main()
{
    
    char buf[256] = "AKLDJSADSS";
    void* addr =mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);  
    if(MAP_FAILED == addr){
      printf("mmap error\n");
      return -1;
    }
    
    pid_t ret = fork();
    if(0 == ret){	//子进程负责写
        memmove(addr, buf, strlen(buf) + 1);
        return 0;
    }
    int stat = -1;
    wait(&stat);//等待子进程结束,回收资源
    
    memset(buf, 0, 256);
    memmove(buf, addr, strlen(addr) + 1);
    printf("%s\n",buf);

    munmap(addr, 1024);

}


版权声明:本文为juggte原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。