Linux内存管理: mmap详解

mmap系统调用

mmap系统调用

mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。munmap执行相反的操作,删除特定地址区域的对象映射。
当使用mmap映射文件到进程后,就可以直接操作这段虚拟地址进行文件的读写等操作,不必再调用read,write等系统调用.但需注意,直接对该段内存写时不会写入超过当前文件大小的内容.
采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。
基于文件的映射,在mmap和munmap执行过程的任何时刻,被映射文件的st_atime可能被更新。如果st_atime字段在前述的情况下没有得到更新,首次对映射区的第一个页索引时会更新该字段的值。用PROT_WRITE 和 MAP_SHARED标志建立起来的文件映射,其st_ctime 和 st_mtime在对映射区写入之后,但在msync()通过MS_SYNC 和 MS_ASYNC两个标志调用之前会被更新。
用法:

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

返回说明:

成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。errno被设为以下的某个值
EACCES:访问出错
EAGAIN:文件已被锁定,或者太多的内存已被锁定
EBADF:fd不是有效的文件描述词
EINVAL:一个或者多个参数无效
ENFILE:已达到系统对打开文件的限制
ENODEV:指定文件所在的文件系统不支持内存映射
ENOMEM:内存不足,或者进程已超出最大内存映射数量
EPERM:权能不足,操作不允许
ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
SIGSEGV:试着向只读区写入
SIGBUS:试着访问不属于进程的内存区

参数:

start:映射区的开始地址。
length:映射区的长度。
prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起

    PROT_EXEC //页内容可以被执行
    PROT_READ //页内容可以被读取
    PROT_WRITE //页可以被写入
    PROT_NONE //页不可访问

flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体

    MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
    MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
    MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
    MAP_DENYWRITE //这个标志被忽略。
    MAP_EXECUTABLE //同上
    MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
    MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。
    MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。
    MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。
    MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。
    MAP_FILE //兼容标志,被忽略。
    MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
    MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
    MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。

fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。
offset:被映射对象内容的起点。

系统调用munmap()

#include <sys/mman.h>

int munmap( void * addr, size_t len ) 
该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。 

系统调用msync()

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

一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

系统调用mmap()用于共享内存的两种方式:

1.使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下:

    fd=open(name, flag, mode); 
    if(fd<0) 
    ... 
    ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);

2.使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。
对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可.

mmap进行内存映射的原理

mmap系统调用的最终目的是将,设备或文件映射到用户进程的虚拟地址空间,实现用户进程对文件的直接读写,这个任务可以分为以下三步:
1.在用户虚拟地址空间中寻找空闲的满足要求的一段连续的虚拟地址空间,为映射做准备(由内核mmap系统调用完成)
每个进程拥有3G字节的用户虚存空间。但是,这并不意味着用户进程在这3G的范围内可以任意使用,因为虚存空间最终得映射到某个物理存储空间(内存或磁盘空间),才真正可以使用。
那么,内核怎样管理每个进程3G的虚存空间呢?概括地说,用户进程经过编译、链接后形成的映象文件有一个代码段和数据段(包括data段和bss段),其中代码段在下,数据段在上。数据段中包括了所有静态分配的数据空间,即全局变量和所有申明为static的局部变量,这些空间是进程所必需的基本要求,这些空间是在建立一个进程的运行映像时就分配好的。除此之外,堆栈使用的空间也属于基本要求,所以也是在建立进程时就分配好的,如图3.1所示:

图3.1 进程虚拟空间的划分

在内核中,这样每个区域用一个结构struct vm_area_struct 来表示.它描述的是一段连续的、具有相同访问属性的虚存空间,该虚存空间的大小为物理内存页面的整数倍。可以使用 cat /proc//maps来查看一个进程的内存使用情况,pid是进程号.其中显示的每一行对应进程的一个vm_area_struct结构.
下面是struct vm_area_struct结构体的定义:

/*
 * This struct defines a memory VMM memory area. There is one of these
 * per VM-area/task.  A VM area is any part of the process virtual memory
 * space that has a special rule for the page-fault handlers (ie a shared
 * library, the executable area etc).
 */
struct vm_area_struct {
    /* The first cache line has the info for VMA tree walking. */

    unsigned long vm_start;     /* Our start address within vm_mm. */
    unsigned long vm_end;       /* The first byte after our end address
                       within vm_mm. */

    /* linked list of VM areas per task, sorted by address */
    struct vm_area_struct *vm_next, *vm_prev;

    struct rb_node vm_rb;

    /*
     * Largest free memory gap in bytes to the left of this VMA.
     * Either between this VMA and vma->vm_prev, or between one of the
     * VMAs below us in the VMA rbtree and its ->vm_prev. This helps
     * get_unmapped_area find a free area of the right size.
     */
    unsigned long rb_subtree_gap;

    /* Second cache line starts here. */

    struct mm_struct *vm_mm;    /* The address space we belong to. */
    pgprot_t vm_page_prot;      /* Access permissions of this VMA. */
    unsigned long vm_flags;     /* Flags, see mm.h. */

    /*
     * For areas with an address space and backing store,
     * linkage into the address_space->i_mmap interval tree, or
     * linkage of vma in the address_space->i_mmap_nonlinear list.
     *
     * For private anonymous mappings, a pointer to a null terminated string
     * in the user process containing the name given to the vma, or NULL
     * if unnamed.
     */
    union {
        struct {
            struct rb_node rb;
            unsigned long rb_subtree_last;
        } linear;
        struct list_head nonlinear;
        const char __user *anon_name;
    } shared;

    /*
     * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
     * list, after a COW of one of the file pages.  A MAP_SHARED vma
     * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack
     * or brk vma (with NULL file) can only be in an anon_vma list.
     */
    struct list_head anon_vma_chain; /* Serialized by mmap_sem &
                      * page_table_lock */
    struct anon_vma *anon_vma;  /* Serialized by page_table_lock */

    /* Function pointers to deal with this struct. */
    const struct vm_operations_struct *vm_ops;

    /* Information about our backing store: */
    unsigned long vm_pgoff;     /* Offset (within vm_file) in PAGE_SIZE
                       units, *not* PAGE_CACHE_SIZE */
    struct file * vm_file;      /* File we map to (can be NULL). */
    void * vm_private_data;     /* was vm_pte (shared mem) */

#ifndef CONFIG_MMU
    struct vm_region *vm_region;    /* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
    struct mempolicy *vm_policy;    /* NUMA policy for the VMA */
#endif
}

通常,进程所使用到的虚存空间不连续,且各部分虚存空间的访问属性也可能不同。所以一个进程的虚存空间需要多个vm_area_struct结构来描述。在vm_area_struct结构的数目较少的时候,各个vm_area_struct按照升序排序,以单链表的形式组织数据(通过vm_next指针指向下一个vm_area_struct结构)。但是当vm_area_struct结构的数据较多的时候,仍然采用链表组织的化,势必会影响到它的搜索速度。针对这个问题,vm_area_struct还添加了vm_avl_hight(树高)、vm_avl_left(左子节点)、vm_avl_right(右子节点)三个成员来实现AVL树,以提高vm_area_struct的搜索速度。
  假如该vm_area_struct描述的是一个文件映射的虚存空间,成员vm_file便指向被映射的文件的file结构,vm_pgoff是该虚存空间起始地址在vm_file文件里面的文件偏移,单位为物理页面。

Linux内存管理: mmap详解_第1张图片
图3.2 进程虚拟地址示意图

因此,mmap系统调用所完成的工作就是准备这样一段虚存空间,并建立vm_area_struct结构体,将其传给具体的设备驱动程序.

2.建立虚拟地址空间和文件或设备的物理地址之间的映射(设备驱动完成)

建立文件映射的第二步就是建立虚拟地址和具体的物理地址之间的映射,这是通过修改进程页表来实现的.mmap方法是file_opeartions结构的成员:

 int (*mmap)(struct file *,struct vm_area_struct *);

linux有2个方法建立页表:
(1) 使用remap_pfn_range一次建立所有页表.
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);
返回值:
成功返回 0, 失败返回一个负的错误值
参数说明:

vma : 用户进程创建一个vma区域

virt_addr : 重新映射应当开始的用户虚拟地址. 这个函数建立页表为这个虚拟地址范围从 virt_addr 到 virt_addr_size.

pfn :页帧号, 对应虚拟地址应当被映射的物理地址. 这个页帧号简单地是物理地址右移 PAGE_SHIFT 位. 对大部分使用, VMA 结构的 vm_paoff 成员正好包含你需要的值. 这个函数影响物理地址从 (pfn<<PAGE_SHIFT) 到 (pfn<<PAGE_SHIFT)+size.
size : 正在被重新映射的区的大小, 以字节.
prot : 给新 VMA 要求的"protection". 驱动可(并且应当)使用在vma->vm_page_prot 中找到的值.

(2) 使用nopage VMA方法每次建立一个页表项.

   struct page *(*nopage)(struct vm_area_struct *vma, unsigned long address, int *type);

返回值:
成功则返回一个有效映射页,失败返回NULL.
参数说明:

address :代表从用户空间传过来的用户空间虚拟地址.返回一个有效映射页.

(3) 使用方面的限制:
remap_pfn_range不能映射常规内存,只存取保留页和在物理内存顶之上的物理地址。因为保留页和在物理内存顶之上的物理地址内存管理系统的各个子模块管理不到。640 KB 和 1MB 是保留页可能映射,设备I/O内存也可以映射。如果想把kmalloc()申请的内存映射到用户空间,则可以通过mem_map_reserve()把相应的内存设置为保留后就可以。

3.当实际访问新映射的页面时的操作(由缺页中断完成)

(1) page cache及swap cache中页面的区分:一个被访问文件的物理页面都驻留在page cache或swap cache中,一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ,它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。

(2) 文件与 address_space结构的对应:一个具体的文件在打开后,内核会在内存中为之建立一个struct inode结构,其中的i_mapping域指向一个address_space结构。这样,一个文件就对应一个address_space结构,一个 address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此,当要寻址某个数据时,很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。
(3) 进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常。

(4) 对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区 (swap area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表。

 注:对于映射普通文件情况(非共享映射),缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到,则说明文件数据还没有读入内存,处理程序会从磁盘读入相应的页面,并返回相应地址,同时,进程页表也会更新.

(5) 所有进程在映射同一个共享内存区域时,情况都一样,在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区域对应的物理页面。

mmap使用的例子

通过strace统计系统调用的时候,经常可以看到mmap()与mmap2()。系统调用mmap()可以将某文件映射至内存(进程空间),如此可以把对文件的操作转为对内存的操作,以此避免更多的lseek()与read()、write()操作,这点对于大文件或者频繁访问的文件而言尤其受益。但有一点必须清楚:mmap的addr与offset必须对齐一个内存页面大小的边界,即内存映射往往是页面大小的整数倍,否则maaped_file_size%page_size内存空间将被闲置浪费。

演示一下,将文件/tmp/file_mmap中的字符转成大写,分别使用mmap与read/write二种方法实现。

/* * @file: t_mmap.c */
#include <stdio.h>
#include <ctype.h>
#include <sys/mman.h> /*mmap munmap*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;
    char *buf;
    off_t len;
    struct stat sb;
    char *fname = "/tmp/file_mmap";

    fd = open(fname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1)
    {
        perror("open");
        return 1;
    }
    if (fstat(fd, &sb) == -1)
    {
        perror("fstat");
        return 1;
    }

    buf = mmap(0, sb.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (buf == MAP_FAILED)
    {
        perror("mmap");
        return 1;
    }

    if (close(fd) == -1)
    {
        perror("close");
        return 1;
    }

    for (len = 0; len < sb.st_size; ++len)
    {
        buf[len] = toupper(buf[len]);
        /*putchar(buf[len]);*/
    }

    if (munmap(buf, sb.st_size) == -1)
    {
        perror("munmap");
        return 1;
    }
    return 0;
}
#gcc –o t_mmap t_mmap.c
#strace ./t_mmap
open("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3 //open,返回fd=3
fstat64(3, {st_mode=S_IFREG|0644, st_size=18, ...}) = 0 //fstat, 即文件大小18
mmap2(NULL, 18, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0xb7867000 //mmap文件fd=3
close(3)                                = 0 //close文件fd=3
munmap(0xb7867000, 18)                  = 0  //munmap,移除0xb7867000这里的内存映射

虽然没有看到read/write写文件操作,但此时文件/tmp/file_mmap中的内容已由www.perfgeeks.com改变成了WWW.PERFGEEKS.COM .这里mmap的addr是0(NULL),offset是18,并不是一个内存页的整数倍,即有4078bytes(4kb-18)内存空间被闲置浪费了。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd, len;
    char *buf;
    char *fname = "/tmp/file_mmap";
    ssize_t ret;
    struct stat sb;

    fd = open(fname, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
    if (fd == -1)
    {
        perror("open");
        return 1;
    }
    if (fstat(fd, &sb) == -1)
    {
        perror("stat");
        return 1;
    }

    buf = malloc(sb.st_size);
    if (buf == NULL)
    {
        perror("malloc");
        return 1;
    }
    ret = read(fd, buf, sb.st_size);
    for (len = 0; len < sb.st_size; ++len)
    {
        buf[len] = toupper(buf[len]);
        /*putchar(buf[len]);*/
    }

    lseek(fd, 0, SEEK_SET);
    ret = write(fd, buf, sb.st_size);
    if (ret == -1)
    {
        perror("error");
        return 1;
    }

    if (close(fd) == -1)
    {
        perror("close");
        return 1;
}
free(buf);
    return 0;
}
#gcc –o t_rw t_rw.c
open("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3 //open, fd=3
fstat64(3, {st_mode=S_IFREG|0644, st_size=18, ...}) = 0 //fstat, 其中文件大小18
brk(0)                                  = 0x9845000  //brk, 返回当前中断点
brk(0x9866000)                          = 0x9866000  //malloc分配内存,堆当前最后地址
read(3, "www.perfgeeks.com\n", 18)      = 18 //read
lseek(3, 0, SEEK_SET)                   = 0 //lseek
write(3, "WWW.PERFGEEKS.COM\n", 18)     = 18 //write
close(3)                                = 0 //close

这里通过read()读取文件内容,toupper()后,调用write()写回文件。因为文件太小,体现不出read()/write()的缺点:频繁访问大文件,需要多个lseek()来确定位置。每次编辑read()/write(),在物理内存中的双份数据。当然,不可以忽略创建与维护mmap()数据结构的成本。需要注意:并没有具体测试mmap vs read/write,即不能一语断言谁孰谁劣,具体应用场景具体评测分析。你只是要记住:mmap内存映射文件之后,操作内存即是操作文件,可以省去不少系统内核调用(lseek, read, write)。

mmap() vs malloc()

使用strace调试的时候,通常可以看到通过mmap()创建匿名内存映射的身影。比如启用dl(‘apc.so’)的时候,就可以看到如下语句。
mmap2(NULL, 31457280, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0) = 0xb5ce7000 //30M

通常使用mmap()进行匿名内存映射,以此来获取内存,满足一些特别需求。所谓匿名内存映射,是指mmap()的时候,设置了一个特殊的标志MAP_ANONYMOUS,且fd可以忽略(-1)。某些操作系统(像FreeBSD),不支持标志MAP_ANONYMOUS,可以映射至设备文件/dev/zero来实现匿名内存映射。使用mmap()分配内存的好处是页面已经填满了0,而malloc()分配内存后,并没有初始化,需要通过memset()初始化这块内存。另外,malloc()分配内存的时候,可能调用brk(),也可能调用mmap2()。即分配一块小型内存(小于或等于128kb),malloc()会调用brk()调高断点,分配的内存在堆区域,当分配一块大型内存(大于128kb),malloc()会调用mmap2()分配一块内存,与堆无关,在堆之外。同样的,free()内存映射方式分配的内存之后,内存马上会被系统收回,free()堆中的一块内存,并不会马上被系统回收,glibc会保留它以供下一次malloc()使用。

这里演示一下malloc()使用brk()和mmap2()。

/* * file:t_malloc.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv)
{
    char *brk_mm, *mmap_mm;

    printf("-----------------------\n");
    brk_mm = (char *)malloc(100);
    memset(brk_mm, '\0', 100);
    mmap_mm = (char *)malloc(500 * 1024);
    memset(mmap_mm, '\0', 500*1024);
    free(brk_mm);
    free(mmap_mm);
    printf("-----------------------\n");

    return 1;
}

#gcc –o t_malloc t_malloc.c
#strace ./t_malloc
write(1, "-----------------------\n", 24-----------------------) = 24
brk(0)                                  = 0x85ee000
brk(0x860f000)                          = 0x860f000   //malloc(100)
mmap2(NULL, 516096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7702000 //malloc(5kb)
munmap(0xb7702000, 516096)              = 0 //free(), 5kb 
write(1, "-----------------------\n", 24-----------------------) = 24

通过malloc()分别分配100bytes和5kb的内存,可以看出其实分别调用了brk()和mmap2(),相应的free()也是不回收内存和通过munmap()系统回收内存。

mmap()共享内存,进程通信

内存映射mmap()的另一个外常见的用法是,进程通信。相较于管道、消息队列方式而言,这种通过内存映射的方式效率明显更高,它不需要任务数据拷贝。这里,我们通过一个例子来说明mmap()在进程通信方面的应用。我们编写二个程序,分别是master和slave,slave根据master不同指令进行不同的操作。Master与slave就是通过映射同一个普通文件进行通信的。

/* *@file master.c */
root@liaowq:/data/tmp# cat master.c 
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void listen();

int main(int argc, char *argv[])
{
    listen();
    return 0;
}

void listen()
{
    int fd;
    char *buf;
    char *fname = "/tmp/shm_command";

    char command;
    time_t now;

    fd = open(fname, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
    if (fd == -1)
    {
        perror("open");
        exit(1);
    }
    buf = mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (buf == MAP_FAILED)
    {
        perror("mmap");
        exit(1);
    }
    if (close(fd) == -1)
    {
        perror("close");
        exit(1);
    }

    *buf = '0';
    sleep(2);
    for (;;)
    {
        if (*buf == '1' || *buf == '3' || *buf == '5' || *buf == '7')
        {
            if (*buf > '1')
                printf("%ld\tgood job [%c]\n", (long)time(&now), *buf);
            (*buf)++;
        }
        if (*buf == '9')
        {
            break;
        }
        sleep(1);
    }

    if (munmap(buf, 4096) == -1)
    {
        perror("munmap");
        exit(1);
    }
}

/* *@file slave.c */
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void ready(unsigned int t);
void job_hello();
void job_smile();
void job_bye();
char get_command(char *buf);
void wait();

int main(int argc, char *argv[])
{
    wait();
    return 0;
}

void ready(unsigned int t)
{
    sleep(t);
}

/* command 2 */
void job_hello()
{
    time_t now;
    printf("%ld\thello world\n", (long)time(&now));
}

/* command 4 */
void job_simle()
{
    time_t now;
    printf("%ld\t^_^\n", (long)time(&now));
}

/* command 6 */
void job_bye()
{
    time_t now;
    printf("%ld\t|<--\n", (long)time(&now));
}

char get_command(char *buf)
{
    char *p;
    if (buf != NULL)
    {
        p = buf;
    }
    else
    {
        return '0';
    }
    return *p;
}

void wait()
{
    int fd;
    char *buf;
    char *fname = "/tmp/shm_command";

    char command;
    time_t now;

    fd = open(fname, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
    if (fd == -1)
    {
        perror("open");
        exit(1);
    }
    buf = mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (buf == MAP_FAILED)
    {
        perror("mmap");
        exit(1);
    }
    if (close(fd) == -1)
    {
        perror("close");
        exit(1);
    }

    for (;;)
    {
        command = get_command(buf);
        /*printf("%c\n", command);*/
        switch(command)
        {
            case '0':
                printf("%ld\tslave is ready...\n", (long)time(&now));
                ready(3);
                *buf = '1';
                break;
            case '2':
                job_hello();
                *buf = '3';
                break;
            case '4':
                job_simle();
                *buf = '5';
                break;
            case '6':
                job_bye();
                *buf = '7';
                break;
            default:
                break;
        }
        if (*buf == '8')
        {
            *buf = '9';
            if (munmap(buf, 4096) == -1)
            {
                perror("munmap");
                exit(1);
            }
            return;
        }
        sleep(1);
    }
    if (munmap(buf, 4096) == -1)
    {
        perror("munmap");
        exit(1);
    }
}

执行master与slave,输出如下

root@liaowq:/data/tmp# echo “0″ > /tmp/shm_command
root@liaowq:/data/tmp# ./master
1320939445 good job [3]
1320939446 good job [5]
1320939447 good job [7]
root@liaowq:/data/tmp# ./slave
1320939440 slave is ready…
1320939444 hello world
1320939445 ^_^
1320939446 |<--

master向slave发出job指令2,4,6。slave收到指令后,执行相关逻辑操作,完成后告诉master,master知道slave完成工作后,打印good job并且发送一下job指令。master与slave通信,是通过mmap()共享内存实现的。

总结

1、 Linux采用了投机取巧的分配策略,用到时,才分配物理内存。也就是说进程调用brk()或mmap()时,只是占用了虚拟地址空间,并没有真正占用物理内存。这也正是free –m中used并不意味着消耗的全都是物理内存。
2、 mmap()通过指定标志(flag) MAP_ANONYMOUS来表明该映射是匿名内存映射,此时可以忽略fd,可将它设置为-1。如果不支持MAP_ANONYMOUS标志的类unix系统,可以映射至特殊设备文件/dev/zero实现匿名内存映射。
3、 调用mmap()时就决定了映射大小,不能再增加。换句话说,映射不能改变文件的大小。反过来,由文件被映射部分,而不是由文件大小来决定进程可访问内存空间范围(映射时,指定offset最好是内存页面大小的整数倍)。
4、通常使用mmap()的三种情况.提高I/O效率、匿名内存映射、共享内存进程通信。

你可能感兴趣的:(linux,内存管理,mmap)