Linux系统编程 Part6(IPC、管道、共享存储映射)

IPC方法(Inter-Process Communication)

Linux 环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核。在内核中开辟一块缓冲区,进程1把数据从用户空间拷贝到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,Inter-Process Communication)

常用的进程间通信方式:

  • 管道(最简单,但需要血缘关系,也叫匿名管道)
  • FIFO(管道的一种,无需血缘关系,也叫命名管道)
  • 信号(开销最小)
  • 共享映射区mmap(无血缘关系)
  • 本地套接字socket(最稳定)

管道

管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。

  • 原理:管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现
  • 特性:
    • 属于伪文件,本质是内核缓冲区
    • 由两个文件描述符引用,一个表示读端,一个表示写端
    • 数据从管道的写端流入管道,从读端流出
  • 局限性:
    • 进程不能自己写数据,再自己读
    • 管道中数据不能反复读取,一旦读走,数据就没有了
    • 半双工通信方式,在同一时间数据只能单方向流动
    • 只能在有血缘关系的进程间可用(父子、兄弟)

ps:使用命令ulimit -a可以获取管道缓冲区大小,默认为4KB

pipe函数

int pipe(int fd[2]); 创建并打开管道

  • 传出参数:
    • fd[0]:读端
    • fd[1]:写端
  • 返回值:
    • 成功:0
    • 失败:-1,errno

管道的读写行为

读管道:

  • 管道有数据:read返回实际读到的字节数
  • 管道无数据:
    • 无写端:read返回0(类似于读到文件尾)
    • 有写端:read阻塞等待(等着写端的数据流过来)

写管道:

  • 无读端:异常终止(SIGPIPE信号导致)
  • 有读端:
    • 管道已满:阻塞等待
    • 管道未满:返回写出的字节个数

编程练习:

使用管道实现父子进程间通信,完成:ls | wc -l,假定父进程实现ls,子进程实现wc

提示:ls命令正常会将结果写到stdout,但现在要写入管道的写端;wc -l正常应该从stdin读取数据,但现在要从管道的读端读

完整代码:

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

//使用管道实现父子进程间通信,完成:ls | wc -l
//假定父进程实现ls,子进程实现wc
int main(int argc, char *argv[]) {
    int ret;
    int fd[2];
    ret = pipe(fd); //先创建管道,再fork进程,这样父子进程都拥有了这个管道
    if(ret == -1) {
        perror("pipe error:");
        exit(1);
    }

    pid_t pid;
    pid = fork(); //现在父子进程都掌管着同一个管道
                   //因此父进程要关闭读端,子进程关闭写端
    if(pid > 0) {
        close(fd[0]);
        dup2(fd[1], STDOUT_FILENO);  //重定向
        execlp("ls", "ls", NULL);
    } else if(pid == 0) {
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);
        execlp("wc", "wc", "-l", NULL);
    } else {
        perror("fork error:");
    }

    return 0;
}

FIFO

FIFO是一种特殊的管道,常被称为命名管道,可以用于无血缘关系的进程间通信

创建命名管道:mkfifo(命令或函数都可以)

实现非血缘关系进程间通信:把fifo当成文件操作即可

  • 读端:open fifo O_RDONLY
  • 写端:open fifo O_WRONLY

共享存储映射(mmap)

文件实现进程间通信

打开的文件是内核中的一块缓冲区,多个无血缘关系的进程可以同时访问该文件

存储映射I/O

存储映射I/O使一个磁盘文件与存储空间的一个缓冲区相映射(目的:当文件在磁盘上时,只能使用writeread等系统调用函数,而映射到存储空间上,操作文件(指针)可用的函数就很多了,比如strcpymemcmp函数等等)

创建/释放共享内存映射区

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 创建共享内存映射

  • 参数:
    • addr:指定映射区的首地址(通常传NULL,表示让系统自动分配)
    • length:表示共享内存映射区的大小(<= 文件的实际大小)
    • prot:共享内存映射区的读写属性
      • PROT_READ
      • PROT_WRITE
      • PROT_READ|PROT_WRITE
    • flags:标注共享内存的共享属性
      • MAP_SHARED
      • MAP_PRIVATE
      • MAP_ANONYMOUS
    • fd:用于创建共享内存映射区的那个文件的文件描述符
    • offset:偏移位置,必须是 4k 的整数倍(默认为0,表示映射文件全部)
  • 返回值:
    • 成功:映射区的首地址
    • 失败:MAP_FAILED (void*(-1)), errno

int munmap(void *addr, size_t length); 释放共享内存映射区

  • 参数:
    • addr:mmap函数的返回值
    • length:共享内存映射区的大小

注意事项:

  1. 用于创建映射区的文件大小为0,实际创建非0大小的映射区,报错“总线错误”
  2. 用于创建映射区的文件大小为0,实际创建0大小的映射区,报错“无效参数”
  3. 用于创建映射区的文件读写属性为只读,映射区属性为读、写,报错“无效参数”
  4. 创建映射区需要那个文件有读权限,只有写权限是不行的。当访问权限指定为MAP_SHARED时, mmap的读写权限应该 <=文件的open权限
  5. 文件描述符fd在mmap创建映射区完成后即可关闭,后续访问文件是用地址访问的
  6. offset偏移量必须是 4096的整数倍(因为MMU映射的最小单位是4k)
  7. 对申请的映射区内存,不能越界访问
  8. munmap用于释放的地址,必须是mmap申请返回的地址
  9. 映射区访问权限为MAP_PRIVATE,对内存所做的所有修改只在内存上生效,不会映射到磁盘上
  10. 映射区访问权限为MAP_PRIVATE,只需要open文件时有读权限即可,用于创建映射区
  11. mmap创建映射区出错率很高,因此一定要检查函数返回值,确保映射区建立成功再进行后续操作

mmap函数的保险调用方式

  1. fd = open("文件名", O_RDWR);
  2. mmap(NULL, 有效文件大小, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

编程练习:创建共享内存映射区进行文件读写

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <sys/mman.h>

void sys_err(const char *str) {
    perror(str);
    exit(1);
}

int main(int argc, char *argv[]) {
    char *p = NULL;
    int fd;
    
    fd = open("testmap", O_RDWR | O_CREAT | O_TRUNC, 0644);
    if(fd == -1) {
        sys_err("open error");
    }
    
    ftruncate(fd, 20); //将文件大小截断为20字节,需要写权限
    int len = lseek(fd, 0, SEEK_END); //获取文件当前大小

    p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(p == MAP_FAILED) {
        sys_err("mmap error");
    }
    
    close(fd);  

    //这样就可以使用p对文件进行读写操作
    strcpy(p, "hello mmap"); //写
    printf("---%s\n", p); //读

    int ret = munmap(p, len);
    if(ret == -1) {
        sys_err("munmap error");
    }

    return 0;
}

mmap父子进程通信

父进程先创建映射区:open(…, O_RDWR); mmap(…, MAP_SHARED, …);

fork()再创建子进程

一个进程读, 另外一个进程写

※mmap无血缘关系进程间通信【必须会写】

两个进程打开同一个文件,创建映射区,指定参数flags为MAP_SHARED

一个进程写入,另外一个进程读出

mmap_w.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <dirent.h>
#include <errno.h>

struct student {
    int id;
    char name[256];
    int age;
};

void sys_err(const char *str) {
    perror(str);
    exit(1);
}

int main(int argc, char *argv[]) {
    struct student stu = {1, "guapi", 18};
    struct student *p;
    int fd = 0;

    fd = open("test_brommp", O_RDWR | O_CREAT | O_TRUNC, 0664);
    if (fd == -1) {
        sys_err("open error");
    }

    ftruncate(fd, sizeof(stu));

    p = mmap(NULL, sizeof(stu), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED) {
        sys_err("mmap error");
    }

    close(fd);

    while (1) {
        memcpy(p, &stu, sizeof(stu));
        stu.id++;
        sleep(1);
    }

    munmap(p, sizeof(stu));

    return 0;
}

mmap_r.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <dirent.h>
#include <errno.h>

struct student {
    int id;
    char name[256];
    int age;
};

void sys_err(const char *str) {
    perror(str);
    exit(1);
}

int main(int argc, char *argv[]) {
    struct student stu;
    struct student *p; 
    int fd = 0;
    
    fd = open("test_brommp", O_RDONLY);
    if(fd == -1) {
        sys_err("open error");
    }
    
    p = mmap(NULL, sizeof(stu), PROT_READ, MAP_SHARED, fd, 0);
    if(p == MAP_FAILED) {
        sys_err("mmap error");
    }

    close(fd);

    while(1) {
        printf("%d %s %d\n", p->id, p->name, p->age);
        sleep(1);
    }

    munmap(p, sizeof(stu));

    return 0;
}

ps:无血缘关系进程间通信,用mmap方法数据可以重复读取,而用fifo的话数据只能读取一次

匿名映射

不再依附于文件建立共享映射区,但只能用于有血缘关系进程间通信

p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);


不准投币喔 👆

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇