《Linux系统编程》笔记 第四章(三)

系列文章目录:http://blog.csdn.net/wylblq/article/details/51841684

4.3 存储映射

POSIX提供了相关调用,能使我们将文件映射到内存中,借由此机制我们可以方便的从内存中读取文件数据,也可以修改内存中的数据来改变文件内容,或实现父子进程间通信。

4.3.1 mmap()

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

addr-开发人员建议的映射文件的内存起始地址。一般来说传NULL让系统自行决议。

length-用来映射的内存大小,单位是字节。由于内存最小可寻址单位是页,因此不足一页的长度也会占用一页。

prot-内存区域的访问权限,有如下参数,可以或运算,注意要和打开文件的访问权限一致:

参数 含义
PROT_EXEC 页内容可以被执行
PROT_READ 页内容可以被读取
PROT_WRITE 页可以被写入
PROT_NONE 页不可访问,无法读写,没有意义

注意如果文件的读写权限是只写的,那么无法保证共享内存的功能可用,因为PROT_WRITE隐含了PROT_READ。

flags-共享内存的行为,常见的有如下参数:

参数 含义
MAP_FIXED 使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。该参数可以用来将不同的文件映射到同一个连续空间内
MAP_SHARED 与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()被调用,文件不保证被更新。
MAP_PRIVATE 建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和MAP_SHARED标志是互斥的,只能使用其中一个。使用该标志时内存的访问权限无需与文件访问权限一致
MAP_ANONYMOUS/MAP_ANON 创建一个匿名映射
MAP_LOCKED 将映射分页锁定在内存,防止被交换到交换分区

fd-要映射到内存中的文件描述符。

offset-文件描述符的偏移量,必须是页大小的整数倍。

在调用成功后返回指向映射区的指针,失败时返回MAP_FAILED,errno也会被设置,此处不再列举错误码类型。当程序试图访问无效的内存映射区域时,会收到SIGBUS信号;程序试图写入不可写的区域时,收到SIGSEGV信号。

下图显示了信号的可能情况:

2200-4095的区间内写入不能同步到文件,这段区域是为了保证页对齐空余出来的范围。4095-8191是映射的长度,访问这段区域会收到SIGBUS信号,超过8192是未映射的区域,访问这段区域会收到SIGSEGV信号。

4.3.1.1 页大小

前面说到offset参数必须指定为页大小的整数倍(addr参数也一般由系统保证分配,也是页面对齐的,下面相关的调用都是如此,不再赘述)。对于length,不足一页的长度会占用一页,读取多占用部分获得0,向多占用部分写入也不会影响文件。

POSIX规定了通过sysconf调用可以获得页大小:

#include <unistd.h>
long sysconf(int name);//返回name对应的值
long lPageSize = sysconf(_SC_PAGESIZE);//获取页大小,字节

Linux提供了系统调用,也能获取页大小:

#include <unistd.h>
int getpagesize (void);

此外我们还可以使用宏来在编译期获取页大小:

#include <asm/pages.h>
int iPageSize = PAGE_SIZE;

为了二进制文件的移植性考虑,建议还是使用运行期获取页大小的方式,同时将offset设置为0,addr由系统分配。

4.3.2 munmap()

在mmap()打开内存映射后,可以使用munmap()来关闭。

#include <sys/mman.h>
int munmap (void *addr, size_t len);

关闭addr起始,长度是len个字节的映射区域。

当我们mmap()映射一个文件到内存时,该文件的引用计数会增加1,当我们的进程结束、执行exec()系列函数或者关闭内存映射时,文件的引用计数会减1。为了保证数据完整性,在munmap()之前需要调用msync()来写入硬盘。

调用成功返回0,失败返回-1。

4.3.4 mmap()的优点

对比read()/write()等系统调用,mmap()有以下优点:

  1. read()/write()需要内核在用户空间内存做读写操作,mmap()直接操作内核的页缓冲,可以避免一次数据拷贝,对大文件操作来说优势明显
  2. 由于对映射的内存区域做操作,因此不像read()/write()那样需要频繁的系统调用
  3. 多个进程将同一个文件映射到内存可以实现进程间通信
  4. 在文件上移动读写位置只需要指针操作而不是lseek()

4.3.5 mmap()的缺陷

映射的区域必须是页的整数倍大小,对于映射小文件,很容易造成内存浪费。

使用mmap()来写入文件时,需要提前知道写入文件的大小,不像write()等系统调用能动态扩展文件大小。

4.3.6 调整映射大小

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/mman.h>
void * mremap (void *addr, size_t old_size, size_t new_size, unsigned long flags);

将addr对应的映射从old_size调整到new_size。flags的取值可以是0或者MREMAP_MAYMOVE,0代表不允许内核移动映射区域,MREMAP_MAYMOVE则表示内核可以根据实际情况移动映射区域以找到一个符合new_size大小要求的内存区域。len和prot参数都会被向上取整到页大小的整数倍。调用成功返回映射的地址,失败则返回MAP_FAILED。

4.3.7 改变映射区域的权限

#include <sys/mman.h>
int mprotect (const void *addr, size_t len, int prot);

将len长度,addr起始对应的映射的访问权限设置为prot。该调用在Linux上可以修改任意的内存段,而不仅仅是mmap()创建的,但是要保证addr是页对齐的。prot的取值和mmap()提供的参数一致。

4.3.8 使用映射机制同步文件

我们对可写存储映射的修改在显式调用同步操作之前是不会保证立即同步到文件中的。

#include <sys/mman.h>
int msync (void *addr, size_t len, int flags);

将以addr起始,长度为len的映射立即同步到内存中。flags取值含义如下:

取值 含义
MS_ASYNC msync调用会立即返回,不等到更新的完成
MS_SYNC msync调用会等到更新完成之后返回
MS_INVALIDATE 在共享内容更改之后,使得文件的其他映射失效,从而使得共享该文件的其他进程去重新获取最新值。

调用成功返回0,失败返回-1。

下面代码演示如何将数据通过存储映射的方式写入硬盘:

#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#define WIRTE_SIZE 50
int main ()
{
    int fd = open("test.txt", O_CREAT|O_RDWR);
    ftruncate (fd, WIRTE_SIZE);//新创建的文件必须要形成空洞文件,否则写入的数据会被放弃
    //映射的长度必须大于等于后面访问的范围,否则会收到SIGBUS信号
    void * p =mmap(NULL, WIRTE_SIZE, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
    if(p == MAP_FAILED)
    {
        perror("mmap");
        return -1;
    }
    memcpy(p, "123", 3);
    msync(p, WIRTE_SIZE, MS_SYNC);
    return 0;
}

下面是一道练习题:使用mmap()实现命令cp类似的功能:

//使用方式 a.out xxx xxx
#include <stdio.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
//使用存储映射实现cp命令功能
int main(int argc,char *argv[])
{
    if(argc < 3)
    {
        printf("please start as ‘a.out srcfile destfile‘\n");
        return 0;
    }
    int fd = open(argv[1], O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        return -1;
    }
    //文件存在则不覆盖
    /*if(access(argv[2], F_OK) != -1)
    {
        printf("please change a destfile name\n");
        return 0;
    }*/
    struct stat buf;
    fstat(fd, &buf);
    printf("file size:%ld\n", buf.st_size);
    //不能用creat,因为creat打开文件的模式是只写,无法与mmap配合。前面说了mmap只能和有读权限的文件描述符配合
    //int fdDest = creat(argv[2], S_IRWXU);
    int fdDest = open(argv[2], O_RDWR|O_CREAT, S_IRWXU);
    if(fdDest < 0)
    {
        perror("creat");
    }
    if(ftruncate(fdDest, buf.st_size)<0)
    {
        perror("ftruncate");
        return 0;
    }
    char* s = (char*)mmap(NULL, buf.st_size, PROT_WRITE|PROT_READ, MAP_PRIVATE, fd, 0);
    if(s == MAP_FAILED)
    {
        perror("mmap1");
        return 0;
    }
    char* d = (char*)mmap(NULL, buf.st_size, PROT_WRITE, MAP_SHARED, fdDest, 0);
    if(d == MAP_FAILED)
    {
        perror("mmap2");
        return 0;
    }
    memcpy(d, s, buf.st_size);
    if(msync(d, buf.st_size, MS_ASYNC)<0)
    {
        perror("mmap");
        return 0;
    }
    return 0;
}
时间: 2024-11-07 21:04:39

《Linux系统编程》笔记 第四章(三)的相关文章

linux系统编程之信号(四)

今天继续探讨信号相关的东东,话不多说,正入正题: 信号在内核中的表示: 下面用图来进一步描述这种信号从产生到递达之间的状态(信号阻塞与未诀): 那是怎么来决定的呢?下面慢慢来举例分解: 所以,通过这些图,可以描述信号从产生到递达的一个过程,上面的理解起来可能有点难,下面会用代码来进一步阐述,在进行实验之前,还需了解一些函数的使用,这些函数在实验中都会被用到,也就是信号集操作函数. 信号集操作函数: 其中解释一下sigset_t,百度百科解释为: 而这个函数的意义就是将这64位清0 这个函数的意义

Linux系统编程笔记

写在开篇:出于对未来职业规划的考虑(其实还是一团糟),制定了一个基本的学习方向,那就是从系统编程学习API慢慢的深入内核,这是一个比较成熟的学习路线.所以从本篇开始,在这段时间会陆续记录Linux系统编程的学习笔记,除了供学习之余复习只用,同时也期望能记录初入职场摸爬滚打的第一个3年. 第一章 文件I/O 文件访问的基本调用一般是 read()和write(),但是在访问文件之前,要做的是一项很重要的工作就是:打开,没错!通过调用 open()或create()实现 #include <sys/

Python核心编程笔记——第四章

第四章 Python对象 1.类型也是对象: a = 4;type(a)-><type 'int'>,这里"<type 'int'>"是一个类型对象(可以赋值给一个变量),可以使用 type(type(a)) 来验证 2.Python2.2开始,类和类型统一,类=类型,实例是类型的对象 3.任何对象都天生具有布尔值 4.整数对象,字符串对象,元组对象都是不可变对象,Python会高效的缓存整数和字符串对象,所以 a=3;b=1+2;(a is b)会返回T

linux系统编程笔记:文件编程

1.创建文件: 创建文件可以使用Linux系统调用create,其原型为:int creat(const char*filename,mode_t mode) filename:你要创建的文件名(包含路径,缺省为在当前路径下创建文件) mode_t:创建模式,表示你创建的文件的权限. S_IRUSR  可读 S_IWUSR  可写 S_IXUSR  可执行 S_IRWXU  可读可写可执行 也可以直接用数字来表示,比如mode_t取0666时表示创建的文件对于文件所有者和文件所有者所在的组以及其

嵌入式 Linux系统编程(四)——文件属性

嵌入式 Linux系统编程(四)--文件属性 一.文件属性概述 Linux 文件的属性主要包括:文件的节点.种类.权限模式.链接数量.所归属的用户和用户组.最近访问或修改的时间等内容.文件属性示例如下: 多个文件属性查看: ls -lih 1341714 -rw-r--r-- 1 root root 2.5K May 28 10:24 bit_marco.c 1341718 -rw-r--r-- 1 root root 2.1K May 28 09:08 bit_marco.c~ 1341706

嵌入式 Linux系统编程(三)——标准IO库

嵌入式 Linux系统编程(三)--标准IO库 与文件IO函数相类似,标准IO库中提供的是fopen.fclose.fread.fwrite等面向流对象的IO函数,这些函数在实现时本身就要调用linux的文件IO这些系统调用. 一.标准IO库函数的缓冲机制 由于IO设备的访问速度与CPU的速度相差好几个数量级,为了协调IO设备与CPU的速度的不匹配,对于块设备,内核使用了页高速缓存,即数据会先被拷贝到操作系统内核的页缓存区中,然后才会从操作系统内核的缓存区拷贝到应用程序的地址空间. 当应用程序尝

《Linux Shell脚本攻略》 笔记 第四章:高效文本处理

<Linux Shell脚本攻略> 笔记 第四章:高效文本处理 1.IP地址的正则表达式: [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} 2.grep用法 //在多级目录中对文本进行递归检索 [[email protected] program_test]# grep "yang" ./ -Rn ./test.txt:6:laoyang ./right.txt:1:1 yang man //忽略大小写匹配 [[email pr

linux系统编程综合练习-实现一个小型的shell程序(四)

上节中已经对后台作业进行了简单处理,基本上要实现的功能已经完了,下面回过头来,对代码进行一个调整,把写得不好的地方梳理一下,给代码加入适当的注释,这种习惯其实是比较好了,由于在开发的时候时间都比较紧,都只是想办法去尽快实现,而肯定会有一些代码是写得不太好的,所以有时间的话最好是从头至尾将整个代码进行梳理,也许在梳理的过程中会发现许多不足的地方,好了,下面开始: 而这个信号安装函数是在init.c中实现的: 接下来进行shell循环: 它的实现是在parse.c中: 如注释所示,可以挪至init.

Linux系统编程(第2版)笔记 (本书基本上就是Linux C API的简单使用说明,入门级别的)

Linux系统编程(第2版) 跳转至: 导航. 搜索 目录 1 入门和基本概念 2 文件I/O 3 缓冲I/O 4 高级文件I/O 5 进程管理 6 高级进程管理 7 线程 8 文件和目录管理 9 内存管理 10 信号 11 时间(这里谈不上系统编程了,就是C库API) 12 附录A C语言的GCC扩展 13 附录B 参考书目 入门和基本概念 文件I/O read(): EINTR EAGAIN 其他错误:EBADF EFAULT EINVAL EIO Append模式:每次write之前的文件