UNIX环境高级编程—存储映射IOmmap函数

http://blog.csdn.net/ctthuangcheng/article/details/9278107
共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据: 一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。

一. 传统文件访问

UNIX访问文件的传统方法是用open打开它们,如果有多个进程访问同一个文件,则每一个进程在自己的地址空间都包含有该

文件的副本,这不必要地浪费了存储空间. 下图说明了两个进程同时读一个文件的同一页的情形. 系统要将该页从磁盘读到高

速缓冲区中,每个进程再执行一个存储器内的复制操作将数据从高速缓冲区读到自己的地址空间.


二. 共享存储映射

现在考虑另一种处理方法: 进程A和进程B都将该页映射到自己的地址空间,当进程A第一次访问该页中的数据时,它生成一

个缺页中断. 内核此时读入这一页到内存并更新页表使之指向它.以后,当进程B访问同一页面而出现缺页中断时,该页已经在

内存,内核只需要将进程B的页表登记项指向次页即可. 如下图所示:


三、mmap()及其相关系统调用

mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访

问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

mmap函数把一个文件或一个Posix共享内存区对象映射到调用进程的地址空间。使用该函数有三个目的:

(1)使用普通文件以提供内存映射I/O;

(2)使用特殊文件以提供匿名内存映射;

(3)使用shm_open以提供无亲缘关系进程间的Posix共享内存区。

  1. #include<sys/mman.h>
  2. void*mmap(void*addr,size_tlen,87); background-color:inherit; font-weight:bold">intprot,87); background-color:inherit; font-weight:bold">intflags,87); background-color:inherit; font-weight:bold">intfd,off_toff);

其中addr可以指定描述符fd应被映射到的进程内空间的起始地址。它通常被指定为一个空指针,这样告诉内核自己去选择起始地址。无论哪种情况下,该函数的返回值都是描述符fd所映射到内存区的起始地址。

注意:fd指定要被映射文件的描述符,在映射该文件到一个地址空间之前,先要打开该文件。

同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。

len是映射到调用进程地址空间中的字节数,它从被映射文件开头起第off个字节处开始算。off通常设置为0.


内存映射区得保护由prot参数指定,它使用如下的常值。该参数的常见值是代表读写访问的PROT_READ | PROT_WRITE。

对指定映射区的prot参数指定,不能超过文件open模式访问权限。例如:若该文件是只读打开的,那么对映射存储区就不能指定PROT_WRITE。

flags使用如下的常值指定。MAP_SHARED或MAP_PRIVATE这两个标志必须指定一个,并可有选择的或上MAP_FIXED。如果指定了MAP_PRIVATE,那么调用进程被映射数据所作的修改只对该进程可见,而不改变其底层支撑对象(或者是一个文件独享,或者是一个共享内存区对象)。如果指定了MAP_SHARED,那么调用进程对被映射数据所作的修改对于共享该对象的所有进程都可见,而且确实改变了其底层支撑对象。

mmap成功返回后,fd参数可以关闭。该操作对于由mmap建立的映射关系没有影响。

为从某个进程的地址空间删除一个映射关系,我们调用munmap。

其中addr参数由mmap返回的地址,len是映射区的大小。再次访问这些地址将导致向调用进程产生一个SIGSEGV信号(当然这里假设以后的mmap调用并不重用这部分地址空间)。

如果被映射区是使用MAP_PRIVATE标志映射的,那么调用进程对它所作的变动都会被丢弃掉,即不会同步到文件中

注意:进程终止时,或调用munmap之后,存储映射区就被自动解除映射。关闭文件描述符fd并不解除,munmap不会影响被映射的对象,在解除了映射之后,对于MAP_PRIVATE存储区的修改被丢弃。


内核的虚拟内存算法保持内存映射文件(一般在硬盘上)与内存映射区(在内存中)的同步,前提是它是一个MAP_SHARED内存区。这就是说,如果我们修改了处于内存映射到某个文件的内存区中某个位置的内容,那么内核将在稍后的某个时刻相应的更新文件。然而有时候我们希望确信硬盘上的文件内容与内存映射区中的内容一致,于是调用msync来执行这种同步。

其中addr和len参数通常指代内存中的整个内存映射区,不过也可以指定该内存区的一个子集。flags参数如下所示的各常值的组合。



MS_ASYNCMS_SYNC这两个常值中必须指定一个,但不能都指定。他们的差别是,一旦写操作已由内核排入队列,MS_ASYNC即返回,而MS_SYNC则要等到写操作完成后才返回。如果指定了MS_INVALIDATE,那么与其最终副本不一致的文件数据的所有内存中副本都失效。后续的引用将从文件中取得数据。

为何使用mmap

到此为止就mmap的描述符间接说明了内存映射文件:我们open它之后调用mmap把它映射到调用进程地址空间的某个文件。使用内存映射文件得到的奇妙特性是,所有的I/O都在内核的掩盖下完成,我们只需编写存取内存映射区中各个值得代码。我们决不调用read,write或lseek。这么一来往往可以简化我们的代码。

然而需要了解以防误解的说明是,不是所有文件都能进行内存映射。例如,试图把一个访问终端或套接字的描述符映射到内存将导致mmap返回一个错误。这些类型的描述符必须使用read和write(或者他们的变体)来访问。

mmap的另一个用途是在无亲缘关系的进程间提供共享内存区。这种情形下,所映射文件的实际内容成了被共享内存区的初始内容,而且这些进程对该共享内存区所作的任何变动都复制回所映射的文件(以提供随文件系统的持续性)。这里假设指定了MAP_SHARED标志,它是进程间共享内存所需求的。


示例代码:

1 通过共享映射的方式修改文件

注释掉44-46与没有注释,运行结果都为:

copy

huangcheng@ubuntu:~$catdata.txt
  • aaaaaaaaaaaa
  • bbbbbbbbbbbb
  • cccccccccccc
  • dddddddddddd
  • eeeeeeeeeeee
  • ffffffffffff
  • huangcheng@ubuntu:~$./a.outdata.txt
  • aaaaaaaaaaaa
  • bbbbbbbbbbbb
  • cccccccccccc
  • dddddddddddd
  • eeeeeeeeeeee
  • ffffffffffff
  • huangcheng@ubuntu:~$catdata.txt
  • bbbbbbb9bbbb
  • ffffffffffff

  • 2 私有映射无法修改文件

    运行结果:

    copy

    ffffffffffff

    3.两个进程中通信
    两个程序映射同一个文件到自己的地址空间,进程A先运行,每隔两秒读取映射区域,看是否发生变化.进程B后运行,它修改映射区域,然后推出,此时进程A能够观察到存储映射区的变化。
    进程A的代码:

    mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。
    mmap()必须以PAGE_SIZE()为单位进行映射,而内存也只能以页为单位进行映射, 若要映射非PAGE_SIZE整数倍的地址范围,要先进行内存对齐,强行以PAGE_SIZE的倍数大小进行映射
    mmap操作提供了一种机制,让用户程序直接访问设备内存,这种机制,相比较在用户空间和内核空间互相拷贝数据,效率更高。在要求高性能的应用中比较常用。mmap映射内存必须是页面大小的整数倍,面向流的设备不能进行mmap,mmap的实现和硬件有关。
    内存映射一个普通文件时,内存中映射区的大小(mmap的第二个参数)通常等于该文件的大小,然而文件的大小和内存的映射区大小可以不同。
    我们要展示的第一种情形的前提是:文件大小等于内存映射区大小,但这个大小不是页面大小的倍数。
    1. #include<stdio.h>
    2. #include<stdlib.h>
    3. #include<sys/stat.h>
    4. #include<fcntl.h>
    5. #include<sys/types.h>
    6. #include<unistd.h>
    7. #include<sys/mman.h>
    8. #definemax(A,B)(((A)>(B))?(A):(B))
    9. char**argv)
    10. {
    11. char*ptr;
    12. size_tfilesize,mmapsize,pagesize;
    13. if(argc!=4)
    14. printf("usage:tes1<pathname><filesname><mmapsize>\n");
    15. filesize=atoi(argv[2]);
    16. mmapsize=atoi(argv[3]);
    17. /*openfile:createortruncate;setfilesize*/
    18. fd=open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0777);
    19. lseek(fd,filesize-1,SEEK_SET);
    20. write(fd,"",1);
    21. ptr=mmap(NULL,0);
    22. close(fd);
    23. pagesize=sysconf(_SC_PAGESIZE);
    24. printf("PAGESIZE=%ld\n",(long)pagesize);
    25. for(i=0;i<max(filesize,mmapsize);i+=pagesize)
    26. {
    27. printf("ptr[%d]=%d\n",i,ptr[i]);
    28. ptr[i]=1;
    29. ptr[i+pagesize-1]=1;
    30. }
    31. printf("ptr[%d]=%d\n",ptr[i]);
    32. exit(0);
    33. }

    命令行参数
    16-19 命令行参数有三个,分别指定即将创建并映射到内存的文件的路径名,该文件将被设置成的大小以及内存映射区得大小。

    创建,打开并截断文件;设置文件大小
    22-24 待打开的文件若不存在则创建之,若已存在则把它的大小截短成0.接着把该文件的大小设置成由命令行参数指定的大小,办法是把文件读写指针移动到这个大小减去1的字节位置,然后写1个字节。

    内存映射文件
    25-26 使用作为最后一个命令行参数指定的大小对该文件进行内存映射。其描述符随后被关闭。

    输出页面大小
    28-29 使用sysconf获取系统实现的页面大小并将其输出。

    读出和存入内存映射区
    31-38 读出内存映射区中每个页面的首字节和尾字节,并输出他们的值。我们预期这些值全为0.同时把每个页面的这两个字节设置为1,。我们预期某个引用会最终引发一个信号,它将终止程序。当for循环结束时,我们输出下一页的首字节,并预期这会失败。


    运行结果:

    注意:

    下面的程序展示了处理一个持续增长的文件的一种常用技巧:指定一个大于该文件大小的内存映射区大小,跟踪该文件的当前大小(以确保不访问当前文件尾以远的部分),然后就让该文件的大小随着往其中每次写入数据而增长。

    版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

    相关推荐


    用的openwrt路由器,家里宽带申请了动态公网ip,为了方便把2280端口映射到公网,发现经常被暴力破解,自己写了个临时封禁ip功能的脚本,实现5分钟内同一个ip登录密码错误10次就封禁这个ip5分钟,并且进行邮件通知使用步骤openwrt为19.07.03版本,其他版本没有测试过安装bashmsmtpopkg
    #!/bin/bashcommand1&command2&wait从Shell脚本并行运行多个程序–杨河老李(kviccn.github.io)
    1.先查出MAMP下面集成的PHP版本cd/Applications/MAMP/bin/phpls-ls 2.编辑修改.bash_profile文件(没有.bash_profile文件的情况下回自动创建)sudovim~/.bash_profile在文件的最后输入以下信息,然后保存退出exportPATH="/Applications/MAMP/bin/php/php7.2.20/b
    1、先输入locale-a,查看一下现在已安装的语言2、若不存在如zh_CN之类的语言包,进行中文语言包装:apt-getinstalllanguage-pack-zh-hans3、安装好后我们可以进行临时修改:然后添加中文支持: locale-genzh_CN.UTF-8临时修改> export LC_ALL='zh_CN.utf8'> locale永久
    BashPerlTclsyntaxdiff1.进制数表示Languagebinaryoctalhexadecimalbash2#[0~1]0[0~7]0x[0~f]or0X[0~f]perl0b[0~1]0[0~7]0x[0~f]tcl0b[0~1]0o[0~7]0x[0~f]bashdifferentbaserepresntationreference2.StringlengthLanguageStr
    正常安装了k8s后,使用kubect工具后接的命令不能直接tab补全命令补全方法:yum-yinstallbash-completionsource/usr/share/bash-completion/bash_completionsource<(kubectlcompletionbash)echo"source<(kubectlcompletionbash)">>~/.bashrc 
    参考这里启动jar包shell脚本修改过来的#!/bin/bash#默认应用名称defaultAppName='./gadmin'appName=''if[[$1&&$1!=0]]thenappName=$1elseappName=$defaultAppNamefiecho">>>>>>本次重启的应用:$appName<
    #一个数字的行#!/bin/bashwhilereadlinedon=`echo$line|sed's/[^0-9]//g'|wc-L`if[$n-eq1]thenecho$linefidone<1.txt#日志切割归档#!/bin/bashcd/data/logslog=1.logmv_log(){[-f$1]&&mv$1$2
    #文件增加内容#!/bin/bashn=0cat1.txt|whilereadlinedon=[$n+1]if[$n-eq5]thenecho$lineecho-e"#Thisisatestfile.\n#Testinsertlineintothisfile."elseecho$linefidone#备份/etc目录#
    # su - oraclesu: /usr/bin/ksh: No such file or directory根据报错信息:显示无法找到文件 /usr/bin/ksh果然没有该文件,但是发现存在文件/bin/ksh,于是创建了一个软连接,可以规避问题,可以成功切换到用户下,但无法执行系统自带命令。$. .bash_profile-ksh: .: .b
    history显示历史指令记录内容,下达历史纪录中的指令主要的使用方法如果你想禁用history,可以将HISTSIZE设置为0:#exportHISTSIZE=0使用HISTIGNORE忽略历史中的特定命令下面的例子,将忽略pwd、ls、ls-ltr等命令:#exportHISTIGNORE=”pwd:ls:ls-ltr:”使用HIS
    一.命令历史  1.history环境变量:    HISTSIZE:输出的命令历史条数,如history的记录数    HISTFILESIZE:~/.bash_history保存的命令历史记录数    HISTFILLE:历史记录的文件路径    HISTCONTROL:     ignorespace:忽略以空格开头的命令
    之前在网上看到很多师傅们总结的linux反弹shell的一些方法,为了更熟练的去运用这些技术,于是自己花精力查了很多资料去理解这些命令的含义,将研究的成果记录在这里,所谓的反弹shell,指的是我们在自己的机器上开启监听,然后在被攻击者的机器上发送连接请求去连接我们的机器,将被攻击者的she
    BashOne-LinersExplained,PartI:Workingwithfileshttps://catonmat.net/bash-one-liners-explained-part-oneBashOne-LinersExplained,PartII:Workingwithstringshttps://catonmat.net/bash-one-liners-explained-part-twoBashOne-LinersExplained,PartII
    Shell中变量的作用域:在当前Shell会话中使用,全局变量。在函数内部使用,局部变量。可以在其他Shell会话中使用,环境变量。局部变量:默认情况下函数内的变量也是全局变量#!/bin/bashfunctionfunc(){a=99}funcecho$a输出>>99为了让全局变量变成局部变量
    1、多命令顺序执行;  命令1;命令2  多个命令顺序执行,命令之间没有任何逻辑联系&&  命令1&&命令2  逻辑与,当命令1正确执行,才会执行命令2||  命令1||命令2  逻辑或,当命令1执行不正确,才会执行命令2例如:ls;date;cd/home/lsx;pwd;who ddif=输入文件of=输
    原博文使用Linux或者unix系统的同学可能都对#!这个符号并不陌生,但是你真的了解它吗?首先,这个符号(#!)的名称,叫做"Shebang"或者"Sha-bang"。Linux执行文件时发现这个格式,会把!后的内容提取出来拼接在脚本文件或路径之前,当作实际执行的命令。 Shebang这个符号通常在Unix系统的脚本
    1、历史命令history[选项][历史命令保存文件]选项:-c:  清空历史命令-w:  把缓存中的历史命令写入历史命令保存文件 ~/.bash_historyvim/etc/profile中的Histsize可改存储历史命令数量历史命令的调用使用上、下箭头调用以前的历史命令使用“!n”重复执行第n条历史
    目录1.Shell脚本规范2.Shell脚本执行3.Shell脚本变量3.1环境变量3.1.1自定义环境变量3.1.2显示与取消环境变量3.1.3环境变量初始化与对应文件的生效顺序3.2普通变量3.2.1定义本地变量3.2.2shell调用变量3.2.3grep调用变量3.2.4awk调用变量3.3
       http://www.voidcn.com/blog/wszzdanm/article/p-6145895.html命令功能:显示登录用户的信息命令格式:常用选项:举例:w显示已经登录的用户及正在进行的操作[root@localhost~]#w 11:22:01up4days,21:22, 3users, loadaverage:0.00,0.00,0.00USER