• Linux下的系统编程——共享存储映射(十)


    前言:

    mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。

    目录

     一.文件间进程通信:

     二、存储映射I/O:

    三、mmap函数: 

    1.创建共享内存映射:

    2.释放映射区:

    *3.使用注意事项:

    *4.mmap函数的保险调用方式:

    5.mmap父子进程通信:

    6.无血缘关系进程间 mmap 通信:                  


     一.文件间进程通信:

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

    两个完全独立没有血缘关系的进程文件之间也可以完成进程间的通信

    test1.c 先执行,将数据写入文件test.txt 

     

    1. /*
    2. * 先执行,将数据写入文件test.txt
    3. */
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #define N 5
    10. int main(void)
    11. {
    12. char buf[1024];
    13. char *str = "--------------secesuss-------------\n";
    14. int ret;
    15. int fd = open("test.txt", O_RDWR|O_TRUNC|O_CREAT, 0664);
    16. //直接打开文件写入数据
    17. write(fd, str, strlen(str));
    18. printf("test1 write into test.txt finish\n");
    19. sleep(N);
    20. lseek(fd, 0, SEEK_SET);
    21. ret = read(fd, buf, sizeof(buf));
    22. ret = write(STDOUT_FILENO, buf, ret);
    23. if (ret == -1) {
    24. perror("write second error");
    25. exit(1);
    26. }
    27. close(fd);
    28. return 0;
    29. }

    test2.c   后执行,尝试读取另外一个进程写入文件的内容

    1. /*
    2. * 后执行,尝试读取另外一个进程写入文件的内容
    3. */
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. int main(void)
    10. {
    11. char buf[1024];
    12. char *str = "----------test2 write secesuss--------\n";
    13. int ret;
    14. sleep(2); //睡眠2秒,保证test1将数据写入test.txt文件
    15. int fd = open("test.txt", O_RDWR);
    16. //尝试读取test.txt文件中test1写入的数据
    17. ret = read(fd, buf, sizeof(buf));
    18. //将读到的数据打印至屏幕
    19. write(STDOUT_FILENO, buf, ret);
    20. //写入数据到文件test.txt中, 未修改读写位置
    21. write(fd, str, strlen(str));
    22. printf("test2 read/write finish\n");
    23. close(fd);
    24. return 0;
    25. }

     

     二、存储映射I/O:

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

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

    三、***mmap函数: 

    1.创建共享内存映射:

    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

            fd:          用于创建共享内存映射区的那个文件的文件描述符。

            offset: 默认0,表示映射文件全部。偏移位置。需是 4k 的整数倍

    返回值:

            成功:映射区的首地址。

            失败:MAP_FAILED (void*(-1)), errno

    2.释放映射区:

    int munmap(void *addr, size_t length);        

        addr:mmap 的返回值

        length:大小

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. void sys_err(const char *str)
    9. {
    10. perror(str);
    11. exit(1);
    12. }
    13. int main(int argc,char *argv[])
    14. {
    15. char *p = NULL;
    16. int fd;
    17. //打开或者创建tetsmap文件
    18. fd = open("testmap",O_RDWR|O_CREAT|O_TRUNC,0644);
    19. if(fd == -1){
    20. sys_err("open error");
    21. }
    22. /*两个函数等价于ftruncate()函数
    23. lseek(fd,10,SEEK_END);
    24. write(fd,"\0",1);
    25. */
    26. ftruncate(fd,20);
    27. int len = lseek(fd,0,SEEK_END);
    28. p = mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    29. if(p == MAP_FAILED){
    30. sys_err("mmap error");
    31. }
    32. //使用p对文件进行读写操作
    33. strcpy(p,"hello mmap\n");
    34. printf("-------%s\n",p);//写操作
    35. int ret = munmap(p,len);
    36. if(ret == -1){
    37. sys_err("munmap error");
    38. }
    39. return 0;
    40. }

    *3.使用注意事项:

    1. 语法
    2. open()方法语法格式如下:
    3. os.open(file, flags[, mode]);
    4. 参数
    5. file – 要打开的文件
    6. flags – 该参数可以是以下选项,多个使用 “|” 隔开:
    7. O_RDONLY: 以只读的方式打开
    8. O_WRONLY: 以只写的方式打开
    9. O_RDWR : 以读写的方式打开
    10. O_NONBLOCK: 打开时不阻塞
    11. O_APPEND: 以追加的方式打开
    12. O_CREAT: 创建并打开一个新文件
    13. O_TRUNC: 打开一个文件并截断它的长度为零(必须有写权限)
    14. O_EXCL: 如果指定的文件存在,返回错误
    15. O_SHLOCK: 自动获取共享锁
    16. O_EXLOCK: 自动获取独立锁
    17. O_DIRECT: 消除或减少缓存效果
    18. O_FSYNC : 同步写入
    19. O_NOFOLLOW: 不追踪软链接
    20. 1)PROT_READ:表示内存段内的内容可写;
    21. 2)PROT_WRITE:表示内存段内的内容可读;
    22. 3)PROT_EXEC:表示内存段中的内容可执行;
    23. 4)PROT_NONE:表示内存段中的内容根本没法访问。

      (1). 用于创建映射区的文件大小为 0,实际指定非0大小创建映射区,出 “总线错误”

    1. fd = open("testmap",O_RDWR|O_CREAT|O_TRUNC,0644); //O_RDWR : 以读写的方式打开
    2. if(fd == -1){
    3. sys_err("open error");
    4. }
    5. //ftruncate(fd,20);
    6. //int len = lseek(fd,0,SEEK_END);
    7. int len = 20;
    8. //PROT_READ:表示内存段内的内容可写; PROT_WRITE:表示内存段内的内容可读
    9. p = mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); //可读可写

     

        (2) 用于创建映射区的文件大小为 0,实际制定0大小创建映射区, 出 “无效参数”

    1. fd = open("testmap",O_RDWR|O_CREAT|O_TRUNC,0644); //O_RDWR : 以读写的方式打开
    2. if(fd == -1){
    3. sys_err("open error");
    4. }
    5. //ftruncate(fd,20);
    6. //int len = lseek(fd,0,SEEK_END);
    7. int len = 0;
    8. //PROT_READ:表示内存段内的内容可写; PROT_WRITE:表示内存段内的内容可读
    9. p = mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); //可读可写

       

        (3). 用于创建映射区的文件读写属性为,只读。映射区属性为 读、写。 出 “无效参数”。

    1. fd = open("testmap",O_RDONLY|O_CREAT|O_TRUNC,0644); //O_RDONLY: 以只读的方式打开
    2. if(fd == -1){
    3. sys_err("open error");
    4. }
    5. ftruncate(fd,20);
    6. int len = lseek(fd,0,SEEK_END);
    7. //int len = 0;
    8. //PROT_READ:表示内存段内的内容可写; PROT_WRITE:表示内存段内的内容可读
    9. p = mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); //可读可写

        (4). 创建映射区,需要read权限。当访问权限指定为 “共享”MAP_SHARED是, mmap的读写权限,应该 <=文件的open权限。    只写不行。

    1. //fd = open("testmap",O_RDONLY|O_CREAT|O_TRUNC,0644);
    2. fd = open("testmap",O_WRONLY); //文件的只写
    3. if(fd == -1){
    4. sys_err("open error");
    5. }
    6. ftruncate(fd,20);
    7. int len = lseek(fd,0,SEEK_END);
    8. //int len = 0;
    9. p = mmap(NULL,len,PROT_WRITE,MAP_SHARED,fd,0); //文件只写

     

    	fd = open("testmap",O_RDWR);    //文件的读写
    

     

    注意:当文件是读写的时候可以写,当文件是只读的时候可以是只读。当文件是只写的时候不能写 

        (5). 文件描述符fd,在mmap创建映射区完成即可关闭。后续访问文件,用地址访问。

    1. //fd = open("testmap",O_RDONLY|O_CREAT|O_TRUNC,0644);
    2. fd = open("testmap",O_RDWR);
    3. if(fd == -1){
    4. sys_err("open error");
    5. }
    6. ftruncate(fd,20);
    7. int len = lseek(fd,0,SEEK_END);
    8. //int len = 0;
    9. p = mmap(NULL,len,PROT_WRITE,MAP_SHARED,fd,0);//文件只写
    10. if(p == MAP_FAILED){
    11. sys_err("mmap error");
    12. }
    13. close(fd); //创建映射区完成即可关闭

      (6). offset 必须是 4096的整数倍。(MMU 映射的最小单位 4k

    p = mmap(NULL,len,PROT_WRITE,MAP_SHARED,fd,1000);

    p = mmap(NULL,len,PROT_WRITE,MAP_SHARED,fd,4096);

     

        (7). 对申请的映射区内存,不能越界访问。 

    1. p = mmap(NULL,len,PROT_WRITE,MAP_SHARED,fd,0);
    2. .......
    3. strcpy(p+len+4096*4,"hello mmap\n");

      

        (8). munmap用于释放的地址,必须是mmap申请返回的地址。

    1. p = mmap(NULL,len,PROT_WRITE,MAP_SHARED,fd,0);
    2. .......
    3. strcpy(p++,"hello mmap\n");

      

     解决方法:

    1. char *temp = p;
    2. strcpy(temp++,"hello mmap\n");

      

        (9). 映射区访问权限为 “私有”MAP_PRIVATE, 对内存所做的所有修改,只在内存有效,不会反应到物理磁盘上。

    1. int fdfd = open("testmap",O_RDWR|O_CREAT|O_TRUNC,0644);
    2. //fd = open("testmap",O_RDWR);
    3. if(fd == -1){
    4. sys_err("open error");
    5. }
    6. ftruncate(fd,20);
    7. int len = lseek(fd,0,SEEK_END);
    8. //int len = 0;
    9. p = mmap(NULL,len,PROT_WRITE|PROT_READ,MAP_PRIVATE,fd,0);//可读可写

      

        (10).映射区访问权限为 “私有”MAP_PRIVATE, 需要open文件时,有读权限用于创建映射区即可。

    1. int fd;
    2. //int fd = open("testmap",O_RDWR|O_CREAT|O_TRUNC,0644);
    3. fd = open("testmap",O_RDONLY); //O_RDONLY: 以只读的方式打开
    4. if(fd == -1){
    5. sys_err("open error");
    6. }
    7. //ftruncate(fd,20);
    8. int len = lseek(fd,0,SEEK_END);
    9. //int len = 0;
    10. p = mmap(NULL,len,PROT_WRITE|PROT_READ,MAP_PRIVATE,fd,0);//可读可写
    11. if(p == MAP_FAILED){
    12. sys_err("mmap error");
    13. }

    *4.mmap函数的保险调用方式:

        1. fd = open("文件名", O_RDWR);

        2. mmap(NULL, 有效文件大小PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

    *5.mmap父子进程通信:

            父子等有血缘关系的进程之间也可以通过 mmap建立的映射区来完成数据通信。但相应的要在创建映射区的时候指定对应的标志位参数flags:

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

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

    父进程创建映射区,然后fork子进程,子进程修改映射区内容,而后,父进程读取映射区内容,查验是否共享。

    思路:

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

     指定 MAP_SHARED 权限

     fork() 创建子进程。

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

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. //全局变量
    9. int var = 100;
    10. int main(void)
    11. {
    12. int *p;
    13. pid_t pid;
    14. int fd;
    15. fd = open("temp",O_RDWR|O_CREAT|O_TRUNC,0644);
    16. if(fd < 0){
    17. perror("open error");
    18. exit(1);
    19. }
    20. unlink("temp");
    21. ftruncate(fd,4);
    22. p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); //MAP_SHARED共享映射
    23. //p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0); //MAP_PRIVATE私有映射
    24. if(p == MAP_FAILED){ //注意:不是p == NULL
    25. perror("mapp error");
    26. exit(1);
    27. }
    28. close(fd); //映射区建立完毕,即可关闭文件
    29. pid = fork(); //创建子进程
    30. if(pid == 0){ //子进程
    31. *p = 2000; //写共享内存
    32. var = 1000;
    33. printf("child *p = %d,var = %d\n",*p,var);
    34. }else if(pid > 0){ //父进程
    35. sleep(1);
    36. printf("parent *p = %d,var = %d\n",*p,var); //读共享内存
    37. wait(NULL);
    38. int ret = munmap(p,4); //释放映射区
    39. if(ret == -1){
    40. perror("munmap error");
    41. exit(1);
    42. }
    43. }
    44. return 0;
    45. }

    1. //p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); //MAP_SHARED共享映射
    2. p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0); //MAP_PRIVATE私有映射

    6.无血缘关系进程间 mmap 通信:                  

        两个进程 打开同一个文件,创建映射区。

        指定flags 为 MAP_SHARED。

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

       【注意】:无血缘关系进程间通信。

                          mmap:数据可以重复读取

                           fifo:数据只能一次读取

    匿名映射:只能用于血缘关系进程间通信

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

    写端:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. struct student {
    9. int id;
    10. char name[256];
    11. int age;
    12. };
    13. void sys_err(const char *str)
    14. {
    15. perror(str);
    16. exit(1);
    17. }
    18. int main(int argc, char *argv[])
    19. {
    20. struct student stu = {1, "xiaoming", 18};
    21. struct student *p;
    22. int fd;
    23. fd = open("test_map", O_RDWR|O_CREAT|O_TRUNC, 0664);
    24. // fd = open("test_map", O_RDWR);
    25. if (fd == -1)
    26. sys_err("open error");
    27. ftruncate(fd, sizeof(stu));
    28. p = mmap(NULL, sizeof(stu), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    29. if (p == MAP_FAILED)
    30. sys_err("mmap error");
    31. close(fd);
    32. while (1) {
    33. memcpy(p, &stu, sizeof(stu));
    34. stu.id++;
    35. sleep(2);
    36. }
    37. munmap(p, sizeof(stu));
    38. return 0;
    39. }

    读端:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. struct student {
    9. int id;
    10. char name[256];
    11. int age;
    12. };
    13. void sys_err(const char *str)
    14. {
    15. perror(str);
    16. exit(1);
    17. }
    18. int main(int argc, char *argv[])
    19. {
    20. struct student stu;
    21. struct student *p;
    22. int fd;
    23. fd = open("test_map", O_RDONLY);
    24. if (fd == -1)
    25. sys_err("open error");
    26. p = mmap(NULL, sizeof(stu), PROT_READ, MAP_SHARED, fd, 0);
    27. if (p == MAP_FAILED)
    28. sys_err("mmap error");
    29. close(fd);
    30. while (1) {
    31. printf("id= %d, name=%s, age=%d\n", p->id, p->name, p->age);
    32. sleep(1);
    33. }
    34. munmap(p, sizeof(stu));
    35. return 0;
    36. }

    7.mmap匿名映射区:

            通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创建映射区一定要依赖一个文件才能实现。通常为了建立映射区要open 一个temp文件,创建好了再unlink、close掉,比较麻烦。可以直接使用匿名映射来代替。其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定。

    匿名映射:只能用于血缘关系进程间通信。

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

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int var = 100;
    8. int main(void)
    9. {
    10. int *p;
    11. pid_t pid;
    12. /*
    13. int fd,ret;
    14. fd = open("temp", O_RDWR|O_CREAT|O_TRUNC,0644);
    15. if(fd == -1){
    16. perror("open error");
    17. exit(1);
    18. }
    19. ret = unlink("temp");
    20. if(ret == -1){
    21. perror("unlink error");
    22. exit(1);
    23. }
    24. ftruncate(fd,4);
    25. */
    26. p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    27. //p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    28. //p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
    29. if(p == MAP_FAILED){ //注意:不是p == NULL
    30. perror("mmap error");
    31. exit(1);
    32. }
    33. //close(fd);
    34. pid = fork(); //创建子进程
    35. if(pid == 0){
    36. *p = 7000; // 写共享内存
    37. var = 1000;
    38. printf("child, *p = %d, var = %d\n", *p, var);
    39. } else {
    40. sleep(1);
    41. printf("parent, *p = %d, var = %d\n", *p, var); // 读共享内存
    42. wait(NULL);
    43. int ret = munmap(p, 4); //释放映射区
    44. if (ret == -1) {
    45. perror("munmap error");
    46. exit(1);
    47. }
    48. }
    49. return 0;
    50. }

    1. int fd = open("/dev/zero",O_RDWR); ///dev/zero ---》 '\0'
    2. p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    3. // p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    4. //p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
    5. if(p == MAP_FAILED){ //注意:不是p == NULL
    6. perror("mmap error");
    7. exit(1);
    8. }
    9. close(fd);

  • 相关阅读:
    java-net-php-python-ssm高校学生学业分析及预警系统查重PPT计算机毕业设计程序
    Redis学习总结(三)
    Pygame中Sprite的使用方法6-4
    数字化转型入门
    【开源】基于SpringBoot的农村物流配送系统的设计和实现
    我如何编码8个小时而不会感到疲倦。
    win10设置透明任务栏
    应用实战|微信小程序开发示例之在线商城
    uniapp本地打包到Android Studio生成APK文件
    【计算机网络】超详细——华为eNSP的安装教程
  • 原文地址:https://blog.csdn.net/m0_63168877/article/details/132734698