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

目录

[一. 进程间通信IPC](#一. 进程间通信IPC)

[1.1 概念](#1.1 概念)

[1.2 进程间通信方式](#1.2 进程间通信方式)

[1.3 常见进程间通信方法:](#1.3 常见进程间通信方法:)

[1.3.1 同一主机间进行通信:](#1.3.1 同一主机间进行通信:)

[1.3.2 不同主机间进行通信:](#1.3.2 不同主机间进行通信:)

[二. 管道](#二. 管道)

[2.1 管道概念及其特质](#2.1 管道概念及其特质)

[2.2 管道的局限性](#2.2 管道的局限性)

[2.3 pipe函数](#2.3 pipe函数)

[2.3.1 函数介绍](#2.3.1 函数介绍)

[2.3.2 使用方法](#2.3.2 使用方法)

[2.4 管道的读写行为](#2.4 管道的读写行为)

[2.5 练习](#2.5 练习)

[2.5.1 练习:](#2.5.1 练习:)

[2.5.2 练习:](#2.5.2 练习:)

[2.5.3 练习:](#2.5.3 练习:)

[2.6 管道缓冲区的大小](#2.6 管道缓冲区的大小)

[2.7 管道的优缺点](#2.7 管道的优缺点)

[三. FIFO有名管道](#三. FIFO有名管道)

[3.1 概念](#3.1 概念)

[3.2 创建方式](#3.2 创建方式)

[3.3 练习](#3.3 练习)

[3.3.1 使用有名管道来完成无血缘关系间进程的半双工通信](#3.3.1 使用有名管道来完成无血缘关系间进程的半双工通信)

[3.3.2 使用有名管道来完成无血缘关系间进程的全双工通信](#3.3.2 使用有名管道来完成无血缘关系间进程的全双工通信)

使用线程:

使用进程:

[四. 共享存储映射](#四. 共享存储映射)

[4.1 文件完成进程间通信](#4.1 文件完成进程间通信)

[4.2 存储映射I/O](#4.2 存储映射I/O)

[4.3 mmap函数](#4.3 mmap函数)

[4.3.1 mmap函数](#4.3.1 mmap函数)

[4.3.2 扩展文件大小的方法](#4.3.2 扩展文件大小的方法)

[4.3.3 munmap函数](#4.3.3 munmap函数)

[4.3.4 mmap函数的注意事项](#4.3.4 mmap函数的注意事项)

[4.4 使用mmap函数建立映射区](#4.4 使用mmap函数建立映射区)

[4.5 使用mmap函数完成父子进程间通信](#4.5 使用mmap函数完成父子进程间通信)

[4.6 使用mmap函数完成无血缘关系进程通信](#4.6 使用mmap函数完成无血缘关系进程通信)

一. 进程间通信IPC

1.1 概念

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

1.2 进程间通信方式

在进程间完成数据传递需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信方式有:

|-------|------------|
| 管道 | 使用简单 |
| 信号 | 开销小 |
| 共享映射区 | 无血缘关系间进程通信 |
| 套接字 | 最稳定 |

1.3 常见进程间通信方法:

1.3.1 同一主机间进行通信:

无名管道、有名管道、内存共享、消息队列、信号、信号量集

1.3.2 不同主机间进行通信:

套接字

二. 管道

2.1 管道概念及其特质

管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:

  1. 其本质是一个伪文件(实为内核缓冲区)

备注:七大文件类型:

普通文件(-),软连接(l),目录(d)---占用磁盘空间。

字符设备(c),块设备(b),管道(p),套接字(s)---伪文件,不占磁盘

空间,只占内存,占一块缓冲区。

  1. 由两个文件描述符引用,一个表示读端,一个表示写端。(从程序的角度)。

  2. 规定数据从管道的写端流入管道,从读端流出。

管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。

2.2 管道的局限性

管道的局限性:

① 数据不能进程自己写,自己读。

② 管道中数据不可反复读取。一旦读走,管道中不再存在。

③ 采用半双工通信方式,数据只能在单方向上流动。

常见的通信方式有,单工通信、半双工通信、全双工通信。

④ 只能在有公共祖先的进程间使用管道。

2.3 pipe函数

2.3.1 函数介绍

原型:

作用:创建并打开管道。 函数调用成功返回r/w两个文件描述符。无需open,但需手动close 。规定:fd0 → r; fd1 → w,就像0对应标准输入,1对应标准输出一样。向管道文件读写数据其实是在读写内核缓冲区

返回值:成功返回0,失败返回-1并且设置errno。

2.3.2 使用方法

管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。如何实现父子进程间通信呢?通常可以采用如下步骤:

  1. 父进程调用pipe函数创建管道,得到两个文件描述符fd0、fd1 指向管道的读端和写端。

  2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道(父子共享文件描述符)。

  3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列 实现的(++这也说明了管道中的数据一经读走,就不存在了++),数据从写端流入管道,从读端流出,这样就实现了进程间通信。

eg:

2.4 管道的读写行为

使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):

  1. 如果所有指向管道写端 的文件描述符都关闭 了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0就像读到文件末尾一样

  2. 如果有指向管道写端 的文件描述符没关闭 (管道写端引用计数大于0),而持有管道写端的进程也没有向管道中写数据 ,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。

  3. 如果所有指向管道读端 的文件描述符都关闭 了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。具体方法信号章节详细介绍。

  4. 如果有指向管道读端 的文件描述符没关闭 (管道读端引用计数大于0),而持有管道读端 的进程也没有从管道中读数据 ,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。

总结:

① 读管道: 1. 管道中有数据,read返回实际读到的字节数。

  1. 管道中无数据:(1) 管道写端被全部关闭,read返回0 (好像读到文件结尾)

(2) 写端没有全部被关闭,read阻塞等待(不久的将来可能

有数据递达,此时会挂起,让出cpu)

② 写管道: 1. 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进

程不终止----管道破裂

  1. 管道读端没有全部关闭: (1) 管道已满,write阻塞。

(2) 管道未满,write将数据写入,并返回实际写

入的字节数。

使用程序可以知道管道缓冲区的大小:65536 64K

cpp 复制代码
int main()
{
    pid_t pid;
    int pipefd[2], ret;
    ret = pipe(pipefd);
    if(ret == -1){
        perror("pipe()");
        exit(1);
    }
    int cnt = 0;
    while(1)
    {
        write(pipefd[1], "a", 1);
        cnt++;
        printf("cnt = %d\n",cnt);
    }
    close(pipefd[1]);
    exit(0);
}

结果:

2.5 练习

2.5.1 练习:

使用管道实现父子进程间通信,完成:ls | wc --l (统计当前目录下一共有多少个文件或者目录)。

ls命令正常会将结果集写出到stdout,但现在会写入管道的写端;wc --l 正常应该从stdin读取数据,但此时会从管道的读端读。

程序执行,发现程序执行结束,shell还在阻塞等待用户输入。这是因为,shell → fork → ./a.out, 程序的子进程将stdin重定向给管道,父进程执行的ls会将结果集通过管道写给子进程。若父进程在子进程打印wc的结果到屏幕之前被shell调用wait回收,shell就会(抢占终端)先输出$提示符。

2.5.2 练习:

使用管道实现兄弟进程间通信。 兄:ls 弟: wc -l 父:等待回收子进程。

bash 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/wait.h>

int main()
{
    int fd[2],ret,i;
    pid_t pid;
    ret = pipe(fd);
    if(ret == -1)
    {
        perror("pipe()");
        exit(1);
    }
    for(i = 0; i < 2; i++)
    {
        pid = fork();
        if(pid == -1)
        {
            perror("fork()");
            exit(1);
        }
        if(pid == 0)
            break;
    }
    if(i == 0)
    {
        close(fd[0]);
        dup2(fd[1],STDOUT_FILENO);
        execlp("ls","ls",NULL);
        perror("execlp()");
        exit(1);
    }
    if(i == 1)
    {
        close(fd[1]);
        dup2(fd[0],STDIN_FILENO);
        execlp("wc","wc","-l",NULL);
        perror("execlp()");
        exit(1);
    }
    if(i == 2)
    {
        close(fd[1]);//一定要关闭父的写端,不然会一直阻塞
        close(fd[0]);
        wait(NULL);
        wait(NULL);
    }
    return 0;
}

2.5.3 练习:

使用管道来复制文件

cpp 复制代码
int main()
{
    int pipefd[2], ret, fd;
    int cnt, pos;
    ssize_t size, wsize;
    pid_t pid;
    char buff[512] = { 0 };
    ret = pipe(pipefd);
    if(ret < 0){
        perror("pipe()");
        exit(1);
    }
    pid = fork();
    if(pid < 0){
        perror("fork()");
        exit(1);
    }
    else if(pid > 0){
        close(pipefd[0]);//关闭读端
        fd = open("stdio.h",O_RDONLY);//打开要复制的文件
        if(fd < 0){
            perror("open()");
            exit(1);
        }
        //避免管道缓冲区的大小对写管道的影响
        //如果size的大小比管道缓冲区大,则可能写一次写不完,要写多次
        //需要循环来写
        while(1){
            size = read(fd, buff, sizeof(buff));
            if(size <= 0){
                break;
            }
            cnt = size;
            pos = 0;
            while(cnt){
                wsize = write(pipefd[1], buff + pos, cnt);//写入管道
                cnt -= wsize;
                pos += wsize;    
            }
        }
        close(pipefd[1]);
        close(fd);
        wait(NULL);
        exit(0);
    }
    else{
        close(pipefd[1]);
        fd = open("1.txt", O_RDWR | O_CREAT | O_TRUNC, 0664);//打开目的文件
        if(fd < 0){
            perror("open()");
            exit(1);
        }
        //写如文件中也是一样的
        //内核有预读入,缓输出的一个机制,实际就是一个内核缓冲区,有一定的大小
        //当写入的数据量大于这个缓冲区大小时,就会一次写不完,需要循环写
        while(1){
            size = read(pipefd[0], buff, sizeof(buff));
            if(size <= 0){
                break;
            }
            cnt = size;
            pos = 0;
            while(cnt){
                wsize = write(fd, buff + pos, cnt);
                cnt -= wsize;
                pos += wsize;
            }
        }
        close(pipefd[0]);
        close(fd);
        exit(0);
    }
}

结果:

2.6 管道缓冲区的大小

管道允许有一个写端多个读端,或者一个读端多个写端。但尽量避免这种情况。

查看管道缓冲区大小的命令:ulimit -a

可以看到管道缓冲区大小为:512x8=4096字节

2.7 管道的优缺点

优点:简单,相比信号,套接字实现进程间通信,简单很多。

缺点:1. 只能单向通信,双向通信需建立两个管道。

  1. 只能用于父子、兄弟进程(有共同祖先)间通信。该问题后来使用fifo有名管道解决。

三. FIFO有名管道

3.1 概念

FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于"有血缘关系"的进程间。但通过FIFO,不相关的进程也能交换数据

FIFO是Linux基础文件类型中的一种。但,FIFO文件在磁盘上没有数据块,仅仅用来标识内核中一条通道。各进程可以打开这个文件 进行read/write,实际上是在读写内核通道,这样就实现了进程间通信。

3.2 创建方式

  1. 命令:mkfifo 管道名

  2. 库函数

**返回值:**成功:0; 失败:-1且errno

**参数:**mode是权限:mode & ~umask;

一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件I/O函数都可用于fifo。如:close、read、write、unlink等。

eg:

写端:

读端:

3.3 练习

3.3.1 使用有名管道来完成无血缘关系间进程的半双工通信

注意:管道是单向的要想使用管道完成通信,必须使用两个管道

管道在打开时候一定要将读写端同时打开,只打开一端时,会阻塞等待另一端打开。

**半双工:**同一时刻只能有一个读,一个写

cpp 复制代码
int main()
{
    int ret, fdw, fdr;
    ssize_t size;
    ret = mkfifo("fifo1", 0664);
    if(ret == -1 && errno != EEXIST){//如果文件存在就不报错
        perror("mkfifo1()");
        return -1;
    }
    ret = mkfifo("fifo2", 0664);
    if(ret == -1 && errno != EEXIST){//如果文件存在就不报错
        perror("mkfifo2()");
        return -1;
    }
    fdw = open("fifo1", O_WRONLY);//当只打开管道的写端,没有打开管道读端时会阻塞
                                //要求读写端同时打开
    if(fdw < 0){
        perror("open fifo1()");
        return -1;
    }
    fdr = open("fifo2", O_RDONLY);
    if(fdr < 0){
        perror("open fifo2()");
        return -1;
    }

    char buff1[512];
    while(1){
        fgets(buff1, sizeof(buff1), stdin);
        buff1[strlen(buff1) - 1] = '\0';
        write(fdw, buff1, strlen(buff1));
        if(0 == strcmp(buff1, ".quit")){
            break;
        }
        //
        memset(buff1, 0, sizeof(buff1));
        size = read(fdr, buff1, sizeof(buff1));
        if(0 == strcmp(buff1, ".quit")){
            break;
        }
        printf("size is %ld, buff is %s\n",size, buff1);        
    }
    close(fdw);
    close(fdr);
    return 0;
}
cpp 复制代码
int main()
{
    int ret, fdw, fdr;
    ssize_t size;
    ret = mkfifo("fifo1", 0664);
    if(ret == -1 && errno != EEXIST){//如果文件存在就不报错
        perror("mkfifo1()");
        return -1;
    }
    ret = mkfifo("fifo2", 0664);
    if(ret == -1 && errno != EEXIST){//如果文件存在就不报错
        perror("mkfifo2()");
        return -1;
    }
    fdr = open("fifo1", O_RDONLY);
    if(fdr < 0){
        perror("open fifo1()");
        return -1;
    }
    fdw = open("fifo2", O_WRONLY);
    if(fdw < 0){
        perror("open fifo2()");
        return -1;
    }
    char buff2[512];
    while(1){
        memset(buff2, 0, sizeof(buff2));
        size = read(fdr, buff2, sizeof(buff2));
        if(0 == strcmp(buff2, ".quit")){
            break;
        }
        printf("size is %ld, buff is %s\n",size, buff2);       
        //
        fgets(buff2, sizeof(buff2), stdin);
        buff2[strlen(buff2) - 1] = '\0';
        write(fdw, buff2, strlen(buff2));
        if(0 == strcmp(buff2, ".quit")){
            break;
        }
    }
    close(fdw);
    close(fdr);
    return 0;
}

3.3.2 使用有名管道来完成无血缘关系间进程的全双工通信

注意:管道是单向的要想使用管道完成通信,必须使用两个管道

**全双工:**可以同时读写(要使用到多任务并发),但是退出时需要双方都进行确认才可以退出

使用线程:
cpp 复制代码
void *task_w(void *arg)
{
    int ret = mkfifo("fifo1", 0664);
    if(ret == -1 && errno != EEXIST){
        perror("mkfifo1()");
        exit(1);
    }
    int fdw = open("fifo1", O_WRONLY);
    if(fdw < 0){
        perror("open fifo1()");
        exit(1);
    }
    char buff[512];
    while(1){
        fgets(buff, sizeof(buff), stdin);
        buff[strlen(buff) - 1] = '\0';
        write(fdw, buff, strlen(buff));
        if(0 == strcmp(buff, ".quit")){
            break;
        }      
    }
    close(fdw);
    return NULL;
}

void *task_r(void *arg)
{
    int ret = mkfifo("fifo2", 0664);
    if(ret == -1 && errno != EEXIST){
        perror("mkfifo2()");
        exit(1);
    }
    int fdr = open("fifo2", O_RDONLY);
    if(fdr < 0){
        perror("open fifo2()");
        exit(1);
    }
    char buff[512];
    while(1){
        memset(buff, 0, sizeof(buff));
        ssize_t size = read(fdr, buff, sizeof(buff));
        if(0 == strcmp(buff, ".quit")){
            break;
        }
        printf("size is %ld, buff is %s\n",size, buff);        
    }
    close(fdr);
    return NULL;
}

int main()
{
    int ret, fdw, fdr;
    ssize_t size;
    pthread_t tidw, tidr;
    pthread_create(&tidw, NULL, task_w, NULL);
    pthread_create(&tidr, NULL, task_r, NULL);
    pthread_join(tidw, NULL);
    pthread_join(tidr, NULL);
    return 0;
}
cpp 复制代码
void *task_w(void *arg)
{
    int ret = mkfifo("fifo2", 0664);
    if(ret == -1 && errno != EEXIST){
        perror("mkfifo1()");
        exit(1);
    }
    int fdw = open("fifo2", O_WRONLY);
    if(fdw < 0){
        perror("open fifo2()");
        exit(1);
    }
    char buff[512];
    while(1){
        fgets(buff, sizeof(buff), stdin);
        buff[strlen(buff) - 1] = '\0';
        write(fdw, buff, strlen(buff));
        if(0 == strcmp(buff, ".quit")){
            break;
        }      
    }
    close(fdw);
    return NULL;
}
void *task_r(void *arg)
{
    int ret = mkfifo("fifo1", 0664);
    if(ret == -1 && errno != EEXIST){
        perror("mkfifo1()");
        exit(1);
    }
    int fdr = open("fifo1", O_RDONLY);
    if(fdr < 0){
        perror("open fifo1()");
        exit(1);
    }
    char buff[512];
    while(1){
        memset(buff, 0, sizeof(buff));
        ssize_t size = read(fdr, buff, sizeof(buff));
        if(0 == strcmp(buff, ".quit")){
            break;
        }
        printf("size is %ld, buff is %s\n",size, buff);        
    }
    close(fdr);
    return NULL;
}
int main()
{
    int ret, fdw, fdr;
    ssize_t size;
    pthread_t tidw, tidr;
    pthread_create(&tidw, NULL, task_w, NULL);
    pthread_create(&tidr, NULL, task_r, NULL);
    pthread_join(tidw, NULL);
    pthread_join(tidr, NULL);
    return 0;
}
使用进程:
cpp 复制代码
int main()
{
    pid_t pid = fork();
    if(pid > 0){
        int ret = mkfifo("fifo1", 0664);
        if(ret == -1 && errno != EEXIST){
            perror("mkfifo1()");
            exit(1);
        }
        int fdw = open("fifo1", O_WRONLY);
        if(fdw < 0){
            perror("open fifo1()");
            exit(1);
        }
        char buff[512];
        while(1){
            fgets(buff, sizeof(buff), stdin);
            buff[strlen(buff) - 1] = '\0';
            write(fdw, buff, strlen(buff));
            if(0 == strcmp(buff, ".quit")){
                break;
            }      
        }
        close(fdw);
        wait(NULL);
    }
    else if(pid == 0){
        int ret = mkfifo("fifo2", 0664);
        if(ret == -1 && errno != EEXIST){
            perror("mkfifo2()");
            exit(1);
        }
        int fdr = open("fifo2", O_RDONLY);
        if(fdr < 0){
            perror("open fifo2()");
            exit(1);
        }
        char buff[512];
        while(1){
            memset(buff, 0, sizeof(buff));
            ssize_t size = read(fdr, buff, sizeof(buff));
            if(0 == strcmp(buff, ".quit")){
                break;
            }
            printf("size is %ld, buff is %s\n",size, buff);        
        }
        close(fdr);
        return 0;
    }
    return 0;
}
cpp 复制代码
int main()
{
    pid_t pid = fork();
    if(pid > 0){
        int ret = mkfifo("fifo2", 0664);
        if(ret == -1 && errno != EEXIST){
            perror("mkfifo1()");
            exit(1);
        }
        int fdw = open("fifo2", O_WRONLY);
        if(fdw < 0){
            perror("open fifo2()");
            exit(1);
        }
        char buff[512];
        while(1){
            fgets(buff, sizeof(buff), stdin);//若缓冲区空间足够,
            //fgets会自动给,最后添加'\0'
            buff[strlen(buff) - 1] = '\0';//将\n替换为\0
            write(fdw, buff, strlen(buff));//strlen决定了每次写管道只写
            //第一个\0之前的字符串,故写时无需清理缓冲区
            if(0 == strcmp(buff, ".quit")){
                break;
            }      
        }
        close(fdw);
        wait(NULL);//若子进程先结束则父进程阻塞等待
    }
    else if(pid == 0){
        int ret = mkfifo("fifo1", 0664);
        if(ret == -1 && errno != EEXIST){
            perror("mkfifo1()");
            exit(1);
        }
        int fdr = open("fifo1", O_RDONLY);
        if(fdr < 0){
            perror("open fifo1()");
            exit(1);
        }
        char buff[512];
        while(1){
            memset(buff, 0, sizeof(buff));//清空缓冲区
            //读时需要清理缓冲区
            ssize_t size = read(fdr, buff, sizeof(buff));
            if(0 == strcmp(buff, ".quit")){
                break;
            }
            printf("size is %ld, buff is %s\n",size, buff);        
        }
        close(fdr);
        return 0;
    }
    return 0;
}

四. 共享存储映射

4.1 文件完成进程间通信

使用文件也可以完成IPC,理论依据是,fork后,父子进程共享文件描述符。也就共享打开的文件。

无血缘关系的进程也可打开同一文件进行通信:

4.2 存储映射I/O

存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用read和write函数的情况下,使用地址(指针)完成I/O操作。

使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap函数来实现。

4.3 mmap函数

4.3.1 mmap函数

返回:成功:返回创建的映射区首地址;失败: MAP_FAILED

参数:

addr:建立映射区的首地址,通常直接传递NULL,让系统自动分配。

length:共享内存映射区的大小。(<=文件的实际大小)。

prot:共享内存映射区的读写属性(PROT_READ、PROT_WRITE、

PROT_READ|PROT_WRITE)。

flags:共享内存的共享属性。

MAP_SHARED: 会将映射区所做的操作反映到物理设备(磁盘)上。

MAP_PRIVATE: 映射区所做的修改不会反映到物理设备。

fd:用来建立映射区的文件描述符。

offset:映射文件的偏移(4k的整数倍)。

4.3.2 扩展文件大小的方法

扩展文件大小的两种方法:

方法一:

bash 复制代码
lseek(fd,10,SEEK_END);//将文件偏移指针移动到当前文件末尾+10的位置上
write(fd,"\0",1);//引起I/O操作,在文件末尾之外的位置进行写,内核会自动填充中间的空洞文件

文件的大小被扩展到了11。

方法二:

使用函数ftruncate():

作用:函数会使由路径名指定或由文件描述符引用的常规文件被截断 为恰好长度为"长度"字节的大小。如果该文件先前的大小大于此尺寸,那么多余的数据就会丢失。如果该文件先前的尺寸较短,则会对其进行扩展(扩展时自动填充'\0') ,并且扩展部分会被读取为空字节('\0')。文件偏移量未发生改变。

总结:可以把文件变大或者变小。

返回值: 成功:0 失败:-1且设置errno

**注意:**这里文件描述符fd打开时,必须要写权限:O_WRONLY或者O_RDWR。

函数ftrunctae需要文件的写权限才可以成功扩展文件大小!!!

则方法一的两条语句可用下面这一条语句代替:

bash 复制代码
ftruncate(fd,11);

4.3.3 munmap函数

作用:释放映射区。

参数:addr就是mmap的返回值。length就是共享内存映射区的大小。

返回值: 成功:0 失败:-1且errno

4.3.4 mmap函数的注意事项

  1. 可以open的时候O_CREAT一个新文件来创建映射区吗?

创建出来的新文件大小为0:情况一:当length不为0时:会出现总线错误。

情况二:当length为0时:会出现Invalid argument。

  1. 如果open时O_RDONLY,mmap时PROT参数指定PROT_READ|PROT_WRITE会怎样?

会报错:Invalid argument。

如果open时O_RDONLY,mmap时PROT参数指定PROT_READ会怎样?

如果有写操作的话会出错,比如:strcpy(p,"hello mmap"); 但mmap未出错。

如果open时O_ WRONLY,mmap时PROT参数指定PROT_WRITE会怎样?

报错:Permission denied。原因:mmap建立映射区需要文件的读权限

综上:在共享属性是MAP_SHARED的前提下,mmap的读写权限应该<=文件的open权限。

  1. 文件描述符先关闭,对mmap映射有没有影响?

无影响,在mmap之后立即close(fd),后序访问文件用内存地址访问即可。

  1. 如果文件偏移量为1000会怎样?

报错Invaid argument。原因:必须是4096的整数倍,与MMU有关,MMU映射的最小单位为

4k。

  1. 对mem越界操作会怎样?

不一定会报错,但是最好不要越界访问。

  1. 如果mem++,munmap可否成功?

不能,报错:Invalid argument。

  1. mmap什么情况下会调用失败?

参数传递有误时。在使用时注意参数的正确使用。

  1. 如果不检测mmap的返回值,会怎样?

一定检查!

总结:使用mmap 时务必注意以下事项:

  1. 创建映射区的过程中,隐含着一次对映射文件的读操作,open时一定加上读权限
  2. 当MAP_SHARED时,要求:映射区的权限应 <= 文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE则无所谓,因为mmap中的权限是对内存的限制。
  3. 映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭。
  4. 特别注意,当映射文件大小为0时,不能创建映射区。所以:用于映射的文件必须要有实际大小!! mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。如,400字节大小的文件,在建立映射区时offset 4096字节,则会报出总线错。
  5. munmap传入的地址一定是mmap的返回地址。坚决杜绝指针++操作。
  6. 如果文件偏移量必须为4K的整数倍
  7. mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。

4.4 使用mmap函数建立映射区

cpp 复制代码
int main()
{
    int fd;
    char *p;
    fd = open("mmap_test",O_RDWR|O_CREAT|O_TRUNC,0644);
    if(fd < 0)
    {
        perror("open()");
        exit(1);
    }
/*
    lseek(fd, 20, SEEK_END);
    write(fd,"\0",1);
*/
    int ret = ftruncate(fd,20);//扩展文件大小
    if(ret == -1)
    {
        perror("ftruncate()");
        exit(1);
    }


    off_t len = lseek(fd,0,SEEK_END);
    p = mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd
             ,0);
    if(p == MAP_FAILED)
    {
        perror("mmap()");
        exit(1);
    }
    strcpy(p,"hello world!\n");
    printf("%s",p);

    ret = munmap(p,len);//释放映射区
    if (ret == -1)
    {
        perror("munmap()");
        exit(1);
    }
    return 0;
}

4.5 使用mmap函数完成父子进程间通信

注意:共享属性必须是MAP_SHARED才可以完成父子进程通信

MAP_PRIVATE: (私有映射) 父子进程各自独占映射区

MAP_SHARED: (共享映射) 父子进程共享映射区

cpp 复制代码
int var = 100;

int main()
{
    pid_t pid;
    int fd;
    int *p;
    fd = open("mmap_fork_test",O_RDWR|O_CREAT|O_TRUNC,0644);
    if(fd < 0)
    {
        perror("open()");
        exit(1);
    }
    int ret = ftruncate(fd,4);
    if(ret == -1)
    {
        perror("ftrunctae()");
        exit(1);
    }
//    int len = lseek(fd,0,SEEK_END);

     p =  mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
   // p =  mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);//共享属性必须是MAP_SHARED
    if(p == MAP_FAILED)
    {
        perror("mmap()");
        exit(1);
    }
    close(fd);

    pid = fork();
    if(pid == -1)
    {
        perror("fork()");
        exit(1);
    }
    else if(pid == 0)
    {
        *p = 2000;
        var = 200;
        printf("child:*p = %d,var = %d\n",*p,var);
        exit(0);
    }
    else
    {
        sleep(1);
        printf("parent:*p = %d,var = %d\n",*p,var);
        wait(NULL);
        exit(0);
    }

    return 0;
}

4.6 使用mmap函数完成无血缘关系进程通信

写端进程:

cpp 复制代码
typedef struct stu
{
    char name[10];
    int age;
    int score;
}stu;

int main()
{
    stu s1 = {"xiaoming",18,60};
    stu *p;
    int fd, ret;
    fd = open("wr_mmap_test",O_RDWR|O_CREAT|O_TRUNC,0644);
    if(fd == -1)
    {
        perror("open()");
        exit(1);
    }
    
    ret =  ftruncate(fd,sizeof(stu));
    if(ret == -1)
    {
        perror("ftruncate()");
        exit(1);
    }

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

    close(fd);

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

    ret = munmap(p,sizeof(stu));
    if(ret == -1)
    {
        perror("munmap()");
        exit(1);
    }

    return 0;
}

读端进程:

cpp 复制代码
typedef struct stu
{
    char name[10];
    int age;
    int score;
}stu;

int main()
{
    stu *p;
    int fd, ret;
    fd = open("wr_mmap_test",O_RDONLY);
    if(fd == -1)
    {
        perror("open()");
        exit(1);
    }

    p = mmap(NULL,sizeof(stu),PROT_READ,MAP_SHARED,fd,0);
    if(p == MAP_FAILED)
    {
        perror("mmap()");
        exit(1);
    }
    close(fd);

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

    ret = munmap(p,sizeof(stu));
    if(ret == -1)
    {
        perror("munmap()");
        exit(1);
    }

    return 0;
}
相关推荐
爱网络爱Linux1 小时前
Linux 服务器开机慢?启动链路优化实战
linux·运维·redhat·rhce·rhca·红帽认证
buhuizhiyuci1 小时前
【Linux篇】数字世界的底层认识, 它是底层的地基——进程概念的认识
linux·运维·服务器
A_humble_scholar1 小时前
Linux(六)深入理解 Linux 进程管理:从硬件到调度
linux·网络
曦月合一2 小时前
在 Linux 服务器上执行这些命令来导入 SSL 证书
linux·服务器·ssl
一拳一个娘娘腔2 小时前
CVE-2026-46300 — “Fragnesia“ 深度拆解:当修复补丁亲手唤醒了另一只恶魔
linux·安全
花伤情犹在2 小时前
Hermes 清理飞书会话操作指南
linux·sqlite·飞书·agent·hermes
小小测试开发2 小时前
Goose AI Agent 完全指南:Linux 基金会加持的开源 AI 编程助手
linux·人工智能·开源
风曦Kisaki2 小时前
#Linux监控与安全Day01:Zabbix部署全流程,基础监控配置与自定义监控项
linux·运维·安全·云计算·zabbix
2401_834636995 小时前
Linux 负载均衡全实战:Nginx+HAProxy+LVS 从原理到落地
linux·nginx·负载均衡