进程间通信

IPC(进程间通信,InterProcess Communication)

进程间通信方式有管道、

管道

管道的概念

  • 本质
    • 内核缓冲区
    • 伪文件:不占用磁盘空间
  • 特点
    • 两部分
      • 读端,写端,对应两个文件描述符
      • 数据写端流入,读端流出
    • 操作管道的进程被销毁之后,管道占用的内存空间自动被释放了
    • 管道默认是阻塞的,读写都是阻塞的。
  • 管道的原理
    • 内部实现方式:队列
      • 环形队列
      • 队列的特点是先进先出
    • 内核缓冲区大小
      • 默认为4k
      • 大小会根据实际情况做适当调整
  • 管道的局限性
    • 队列
      • 数据只能读取一次,不能重复读取
    • 半双工
    • 匿名管道
      • 适用于有血缘关系的进程
  • 创建匿名管道
    • int pipe(int fd[2]);
      • 返回值:如果匿名管道创建成功,返回0。
      • man pipe 2查看说明文档
      • fd[0]为读端文件描述符,fd[1]为写端文件描述符
  • 父子进程使用管道通信
    • 父进程间通信,实现ps aux | grep bash
      • 数据重定向:dup2
      • exclp
      • 注意事项:父进程读,关闭写端;父进程写,关闭读端
    • 兄弟进程间通信,实现ps aux | grep bash
    • 父子进程间通信是否需要sleep函数
      • 父进程写,写得慢
      • 子进程 读,读得快
    • 兄弟进程间通信
      • 父进程-资源回收

管道的读写行为

  • 读操作
    • 有数据
      • read(fd)-正常读,返回读出的字节数
    • 无数据
      • 写端全部关闭
        read解除阻塞,返回0,相当于读文件读到了尾部
      • 写端尚未全部关闭
        read阻塞
  • 写操作
    • 读端全部关闭
      • 管道破裂,进程被终止
        内核给当前进程发信号SIGPIPE
        kill -l 可以查看信号
    • 读端尚未全部关闭
      • 缓冲区写满了
        write阻塞
      • 缓冲区没有满
        write继续写直到缓冲区满
  • 如何设置非阻塞
    • 默认读写两端都阻塞
    • 设置读端为非阻塞pipe(int fd[2])
    • fcntl - 变参函数
      • 复制文件描述符-dup
      • 修改文件属性-open的时候对应的flag
    • 设置方法:
      获取原来的flags
      int flags = fcntl(fd[0], F_GETFL);
      // 设置新的flags
      flags |= O_NONBLOCK;
      fcntl(fd[0], F_SETFL, flags);

查看管道缓冲区大小

  • ulimit -a
    • ulimit用于限制shell启动进程所占用的资源,包括一各种类型的限制:所创建的内核文件的大小、进程数据块的大小、shell进程所创建文件的大小、内存锁住的大小、常驻内存集的带下、打开文件描述符的数量、分配堆栈的最大大小、CPU时间、单个用户的最大线程数、Shell进程所能使用的最大虚拟内存。
  • fpathconf函数

FIFO

  1. 特点

    • 有名管道
    • 在磁盘上有这样一个文件ls -l -> p
    • 伪文件,在磁盘大小永远为0
    • 在内核中有一个对应的缓冲区
    • 半双工的通信方式
  2. 使用场景

    • 没有亲缘关系的进程间通信
  3. 创建方式

    • 命令:mkfifo 管道名
    • 函数:mkfifo
  4. fifo文件可以使用IO函数进行操作

    • open/close
    • read/write
    • 不能执行lseek
  5. 进程间通信

    • 两个不相干的进程A(a.c) B(b.c)

    • a.c -> read

      • int fd = open(“myfifo”, O_RDONLY);
      • read(buf, sizeof(buf));
      • close(fd);

      b.c –> write
      int fd1 = open(“myfifo”, O_WRONLY);
      write(fd1, “hello world”);

内存映射区

mmap创建内存映射区。

  1. mmap-创建内存映射

    • 作用:将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。

    • 函数头文件#include<mman.h>

    • 函数原型:

      1
      void* mmap(void * adrr, size_t length, int prot, int flags, int fd, off_t offset);

      void *mmap(void *adrr, // 映射区首地址,传入NULL
      size_t length, // 映射区的大小,不能为0,实际分配的应该是4k的倍数,为什么呢?
      int prot, // 映射区权限

      • PROT_READ // 映射区必须有读权限
      • PROT_WRITE // 可选
      • PROT_READ | PROT_WRITE

      int flags, // 标志位参数

      • MAP_SHARED
        • 修改了内存数据会同步到磁盘
      • MAP_PRIVATE
        • 修改了内存数据,数据不会同步到磁盘

      int fd, // 文件描述符

      • 要映射的文件对应的文件描述符,这个文件描述符是通过对文件进行open操作得到的。

      off_t offset, // 映射文件的偏移量

      • 映射时文件指针的偏移量,必须是4k的整数倍

      );

    • 返回值

      • 调用成功时,返回映射区的首地址
      • 调用失败时,返回MAP_FAILED,其实就是一个(void*)-1
  2. munmap-释放内存映射区

    • 函数原型:int munmap(void* addr, size_t length);
      • addr-mmap的返回值,映射区的首地址
      • length-mmap的第二个参数,映射区的长度
  3. 思考题

    • 如果open时指定O_RDONLY,mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?
      • mmap调用失败
      • open文件指定的权限应该大于等于mmap第三个参数prot指定的权限
    • 如果文件偏移量为1000会怎么样?
      • 文件偏移量必须是4k(4096)的整数倍
    • mmap什么时候会调用失败?
      • 第二个参数length=0
      • 第三个参数必须指定PROT_READ
        • fd对应的打开权限必须大于等于prot指定的权限
      • 偏移量必须是4096的整数倍
    • 可以open的时候O_CREAT一个新文件来创建映射区?
      • 可以,但是需要做文件拓展
        • lseek
        • truncate(path, length)
    • mmap后关闭文件描述符,对内存映射区有没有影响?
      • 没有影响
    • 对ptr越界操作,ptr是mmap的返回值,即映射区的首地址?
      • 段错误(操作非法内存)
  4. 进程间通信

    • 有亲缘关系的
      • 父子进程共享内存映射区
    • 没有亲缘关系的
      • 如何通信?
  5. mmap实现内存映射

    • 必须有一个文件
    • 文件数据什么时候有用
      • 单纯文件映射
      • 进程间通信
        • 文件数据是没有用的
  6. 父子进程永远共享的有哪些?

    • 文件描述符
    • 内存映射区

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>

int main(int argc, char *argv[]) {
// 创建内存映射区
int fd = open("english.txt", O_RDWR);

// 获取一个文件的大小
int len = lseek(fd, 0, SEEK_END);
void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap error");
exit(1);
}
printf("%s", (char *) ptr);

// 释放文件映射区
munmap(ptr, len);
close(fd);

return 0;
}

lseek当第三个参数whence为SEEK_END,则该文件的文件偏移量设置为文件长度加offset。若函数成功执行,返回新的文件偏移量。所以,当whence为SEEK_END, offset为0时,返回当前文件的长度。

参考文献

[1]ulimit:https://man.linuxde.net/ulimit