mmap实例及原理分析

Comments(0)


Posted on 2015-12-01 11:30:27 os


mmap实例及原理分析

在本文的叙述中,会交替使使用进程地址空间虚拟内存空间虚拟内存,这些都代表了进程的地址空间。在一般意义上,它是逻辑上存在的,实际会因为触发缺页异常,导致内核为进程在物理地址空间中分配实际的存储空间。

1. 内存映射

在unix/linux平台下读写文件,一般有两种方式。第一种是首先open文件,接着使用read系统调用读取文件的全部或一部分。于是内核将文件的内容从磁盘上读取到内核页高速缓冲,再从内核高速缓冲读取到用户进程的地址空间。这么做需要在内核和用户空间之间做四次数据拷贝。而且当多个进程同时读取一个文件时,则每一个进程在自己的地址空间都有这个文件的副本,这样也造成了物理内存的浪费。如下图所示:

传统方式读写文件

第二种方式是使用内存映射的方式。首先open文件,接着调用mmap系统调用,将文件的内容的全部或一部分直接映射到进程的地址空间,映射完成后,进程可以像访问普通内存一样做memcpy等操作,不必再调用read/write等操作。mmap并不分配物理地址空间,它只是会占用进程的虚拟内存空间。而第一种方式则需要进程预先分配好物理内存,内核才能将页高速缓冲中的文件数据拷贝到用户进程指定的内存空间中。

使用第二种方式,当多个进程同时访问读取一个文件时,每个进程都将文件内容在内核中的页高速缓冲映射到自己的地址空间。当第一个进程访问内核中的页缓冲时,进程的机器指令会触发一个缺页中断。内核将文件的这一页数据读入到页高速缓冲,并更新进程的页表,使页表指向内核缓冲中的这个页。之后有其他进程再次访问同一页时,该页已经在内存中,内核只需要将进程的页表登记并指向内核中的页高速缓冲即可。如下图所示:

mmap方式读写文件

2. mmap和malloc

相对于mmap,malloc并不是系统调用,而是glibc的库函数。通俗的说,malloc会动态调整进程地址空间中,数据段的brk的指针。

如下图:

x86_stack

具体实现上,malloc有一些优化,它会根据申请内存的大小,使用不同的策略:

  • 分配一块小型内存(小于或等于128kb),malloc()会调用brk()调高断点(brk是将数据段(.data)的最高地址指针_edata往高地址推),分配的内存在堆区域

  • 分配一块大型内存(大于128kb),malloc()会调用mmap2()分配一块内存(mmap是在进程的虚拟地址空间中(一般是堆和栈中间)找一块空闲的)(一般是堆和栈中间区域)。

malloc分配的内存只分配了虚拟地址空间,当在第一次访问的时候,发生缺页中断,操作系统负责分配物理内存,并建立虚拟内存和物理内存之间的映射关系。

下图是IA32平台下进程虚拟地址空间示意图:

image description

其中,brk(program break)的地址即是malloc调整的指针。

3. mmap参数

mmap函数是unix/linux下的系统调用,函数原型如下:

#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);

参数:

  • start: 映射区开始的地址
  • length: 映射区的长度
  • prot: 期望的内存保护标志,不能与文件的打开模式冲突:
    • PROT_EXEC: 页内容可以被执行
    • PROT_READ: 页内容可以被读取
    • PROT_WRITE: 页内容可以被写入
    • PROT_NONE: 页不可访问
  • flags: 指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以使一个或者多个以下位的组合:
    • MAX_FIXD: 使用指定的映射起始地址,如果由start和len参数指定的内存重叠与现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页边界上。
    • MAX_SHARED: 与其他所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
    • MAP_PRIVATE: 建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
    • MAP_NORESERVE: 不要为这个映射保留交换空间。当交换空间被保留,对映射区的修改可能会得到保证。当交换空间不被保留,同时内存不足时,对映射区的修改会引起段异常信号。
    • MAP_LOCKED: 锁定映射区的页面,从而防止页面被交换出内存。一般是要求实时性的应用程序和一些对安全要求较高的程序使用(禁止将RAM中的数据换出到磁盘文件上)。
    • MAP_GROWNSDOWN: 用于堆栈,告诉内核VM系统,映射区可以向下扩展。
    • MAP_ANONMOUS: 匿名映射,映射区不与任何文件关联。
    • MAP_POPULATE: 问文件映射通过预读的方式准备好页表,随后对映射区的访问不会被缺页异常阻塞。
    • MAP_NONBLOCK: 仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
    • MAP_HUGETLB (since Linux 2.6.32): 申请HugePage,HugePage能减少页表项的使用,进而较少TLB失效的可能,并且在RAM中不会被换入换出。
  • fd: 有效的文件描述符。如果指定了MAP_ANONYMOUS,为了兼容问题,应该设置fd为-1。
  • offset: 应映射对象内容的起点。

msync函数原型:

#include <sys/mman.h> 

int msync ( void * addr , size_t len, int flags) 

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

4. mmap使用示例

一般使用mmap实现内存映射有两种方法,第一种是映射文件到进程的虚拟地址空间。第二种是使用匿名内存映射,用于在进程内部使用或者在父子进程之间共享内存。

这个例子是使用mmap映射普通文件,调用mmap完毕后返回映射后的地址空间,读写该内存范围内的地址相当于相应的文件位置:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define PAGE_SIZE 4096


int main(int argc, char**argv) {
    int fd;
    int i;
    unsigned char *p_map;

    if (argv[1] == NULL) {
        printf("command line is empty!\n");
        exit(1);
    }

    fd = open("test.dat", O_RDWR | O_APPEND | O_CREAT);
    if (fd<0) {
        printf("open failed!\n");
        exit(1);
    }

    p_map = (unsigned char *)mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (p_map == MAP_FAILED) {
        printf("mmap failed!\n");
        goto end;
    }

    // 读取有效数据
    for (i=0;i<PAGE_SIZE;i++) {
        if (p_map[i] == 0) {
            break;
        }
    }

    printf("length: [%d]\n%s\n", i, p_map);

    char * line = argv[1];

    printf("line length: %lu\n", strlen(line));

    // 复制数据
    memcpy(p_map, line, strlen(line));
    msync(p_map, strlen(line), MS_SYNC);

    close(fd);

end:
    munmap(p_map, PAGE_SIZE);
    return 0;
}

下面这个例子是使用mmap分配了匿名映射方式,分配了一块虚拟地址空间。调用mmap结束后并不会直接分配内存,而是等到用户代码访问该内存空间的地址时,发生缺页异常,内核捕获到缺页异常后分配页目录和页表,并将页表和实际的物理page相关联,最后更新进程的页表:

#define _GNU_SOURCE        /* or _BSD_SOURCE or _SVID_SOURCE */
#include <unistd.h>
#include <sys/syscall.h>   /* For SYS_xxx definitions */
#include <sys/types.h>
#include <sched.h>
#include <sys/mman.h>
#include <signal.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>

#define STACK_SIZE 1024*1024*1 //1M

int thread_func(void *lparam) {
    char * str = "this is temp string";
    char * str1 = "this is temp string";
    printf("thread id %d \n", (int)syscall(SYS_gettid));
    printf("thread get param : %d \n", (*(int*)lparam));
    printf("stack address: %p\n", str);
    sleep(30);
    return 0;
}


void child_handler(int sig) {
    printf("I got a SIGCHLD\n");
}

int main(int argc, char **argv) {
    setvbuf(stdout, NULL,  _IONBF, 0);
    signal(SIGCHLD, child_handler);
    //signal(SIGUSR1, SIG_IGN);

    void *pstack = (void *)mmap(NULL,
                                STACK_SIZE,
                                PROT_READ | PROT_WRITE,
                                MAP_PRIVATE | MAP_ANONYMOUS | MAP_ANON | MAP_GROWSDOWN ,
                                -1,
                                0);
    if (MAP_FAILED != pstack) {
        int ret;
        //printf("stack addr : 0x%X\n", (int)pstack);
        printf("stack addr : %p\n", pstack);
        /*
        CLONE_VM  (0x100) - tells the kernel to let the original process and the clone in the same memory space;
        CLONE_FS (0x200) - both get the same file system information;
        CLONE_FILES (0x400) - share file descriptors;
        CLONE_SIGHAND (0x800) - both processes share the same signal handlers;
        CLONE_THREAD (0x10000) - this tells the kernel, that both processes would belong to the same thread group (be threads within the same process);
        */
        int arg = 1234;
        ret = clone(thread_func,
                    (void *)((unsigned char *)pstack + STACK_SIZE),
                    CLONE_VM | CLONE_FS  | CLONE_FILES | CLONE_SIGHAND |CLONE_THREAD |SIGCHLD,
                    //CLONE_VM | CLONE_FS  | CLONE_FILES | CLONE_SIGHAND |SIGCHLD,
                    (void *)&arg);
        if (-1 != ret)
        {
            printf("PID: %d\n", getpid());
            pid_t pid = 0;
            printf("start thread %d \n", ret);
            sleep(60);
            //pid = waitpid(-1, NULL,  __WCLONE | __WALL);
            printf("child : %d exit %s\n", pid,strerror(errno));
        } else {
            printf("clone failed %s\n", strerror(errno) );
        }

    } else {
        printf("mmap() failed %s\n", strerror(errno));
    }
    return 0;
}

程序输出内容为:

image description

其中stack addr为主线程栈空间的地址,我们在/proc/1250/smaps中可以发现下面的内容:

image description

stack addr的地址0x7ffc86015df8正好在这个范围内。

stack address 1是子线程的虚拟内存空间,大小为1M:

image description

代码中出现的clone是经过glibc封装的,glibc中clone实现如下:

#include <sysdep.h>
#define _ERRNO_H    1
#include <bits/errno.h>
#include <asm-syntax.h>

/* int clone(int (*fn)(void *arg), void *child_stack, int flags, void *arg); */

        .text
ENTRY(__clone)
    /* Sanity check arguments.  */
    /* 参数检查 */
    movl    $-EINVAL,%eax
    // 保存fn参数到ecx
    movl    4(%esp),%ecx        /* no NULL function pointers */
    // exc为0则跳转
    jecxz   SYSCALL_ERROR_LABEL
    // 保存child_stack参数到ecx
    movl    8(%esp),%ecx        /* no NULL stack pointers */
    // exc为0则跳转
    jecxz   SYSCALL_ERROR_LABEL

    /* Insert the argument onto the new stack.  */
    // 将参数插入到child_stack中
    // ecx减8
    subl    $8,%ecx
    // 保存arg参数到eax
    movl    16(%esp),%eax       /* no negative argument counts */
    // 移动eax到child_stack+4
    movl    %eax,4(%ecx)

    /* Save the function pointer as the zeroth argument.
       It will be popped off in the child in the ebx frobbing below.  */
    // 保存fn参数到eax
    movl    4(%esp),%eax
    // 移动eax到child_stack
    movl    %eax,0(%ecx)

    /* Do the system call */
    // 执行系统调用
    // eax保存的是系统调用号
    // ebx保存的是flags参数
    // exc保存的是child_stack地址
    // 以及压入child_stack地址中的fn和arg参数
    pushl   %ebx    // ebx入栈
    movl    16(%esp),%ebx
    movl    $SYS_ify(clone),%eax
    int $0x80
    popl    %ebx    // ebx出栈

    // 判断系统调用返回值,为0是子线程
    // 内核生成子线程后
    // 由子线程在用户态调用fn指定的函数地址
    test    %eax,%eax
    // 小于0说明有错误发生
    jl  SYSCALL_ERROR_LABEL
    jz  thread_start

L(pseudo_end):
    ret

thread_start:
    subl    %ebp,%ebp   /* terminate the stack frame */
    // ebx中的值是ENTRY宏保存的第一个参数即fn的地址
    call    *%ebx
#ifdef PIC
    call    L(here)
L(here):
    popl    %ebx
    addl    $_GLOBAL_OFFSET_TABLE_+[.-L(here)], %ebx
#endif
    pushl   %eax
    // 子线程执行完毕后退出
    call    JUMPTARGET (_exit)

PSEUDO_END (__clone)

weak_alias (__clone, clone)

5. mmap在内核中的实现

5.1 进程的地址空间

在内核空间中申请内存,由于内核代码优先级最高并且内核信任自身,所以会被立刻执行。而相对于内核来说,进程中的内存申请总是被尽量推出,并且内核不信任用户进程,内核需要捕获用户进程中对内存地址访问产生的异常,例如读写权限不足,以及请求的页不存在。

举例来说,当内核载入用户可执行文件时,用户进程并不一定立刻访问所有的代码页。类似的,当用户进程执行malloc()获取内存时,并不意味着进程很快就会访问所有获取的内存。

在x86下,进程的线性地址空间由两部分构成,0~3G为用户空间,3~4G为内核态空间。由于内核为每个进程分配了不同的页目录表,所以每个进程的用户线性地址空间相同。而所有进程的3~4G内核态空间都相同。并且内核可以通过增加或删除线性地址区间来动态的修改进程的线性地址空间。

下面的情况会进程会获得新的线性地址空间:

  • 用户在控制台执行一条命令时,shell会创建新的进程执行这个命令。内核会分配一个全新的地址空间给新进程。
  • 正在运行的进程会使用exec函数载入别的程序,这种情况下进程标识符不变,但是新创建的线性地址空间会分配给这个进程。
  • 正在运行的进程执行内存映射时,内核会分配一个新的线性区来映射这个文件。
  • 进程创建一个IPC共享线性区与其他进程共享数据。这样内核也会分配一个新的线性区来实现。

下面是一些和创建、删除线性区相关的系统调用:

  • brk(): 改变进程堆的大小
  • execve(): 载入一个新的可执行文件,从而改变进程的地址空间
  • _exit(): 结束当前进程并撤销它的地址空间
  • fork(): 创建一个新进程,并为它创建新的地址空间
  • mmap()/mmap2(): 为文件创建一个内存映射(或匿名内存映射),扩大进程的地址空间
  • shmat(): 创建一个共享线性区
  • shmdt(): 撤销一个共享线性区

用户进程经过编译、链接后形成的映象文件有一个代码段和数据段(包括data段和bss段),其中代码段在下,数据段在上。数据段中包括了所有静态分配的数据空间,即全局变量和所有申明为static的局部变量,这些空间是进程所必需的基本要求,这些空间是在建立一个进程的运行映像时就分配好的。除此之外,堆栈使用的空间也属于基本要求,所以也是在建立进程时就分配好的,如下图:

image description

5.2 vm_area_struct结构体

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


#include <linux/mm_types.h>

/* This struct defines a memory VMM memory area. */

struct vm_area_struct {
struct mm_struct * vm_mm; /* VM area parameters */
unsigned long vm_start;
unsigned long vm_end;

/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next;
pgprot_t vm_page_prot;
unsigned long vm_flags;

/* AVL tree of VM areas per task, sorted by address */
short vm_avl_height;
struct vm_area_struct * vm_avl_left;
struct vm_area_struct * vm_avl_right;

/* For areas with an address space and backing store,
vm_area_struct *vm_next_share;
struct vm_area_struct **vm_pprev_share;
struct vm_operations_struct * vm_ops;
unsigned long vm_pgoff; /* offset in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */
struct file * vm_file;
unsigned long vm_raend;
void * vm_private_data; /* was vm_pte (shared mem) */
};

通常,进程所使用到的虚存空间不连续,且各部分虚存空间的访问属性也可能不同。所以一个进程的虚存空间需要多个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文件里面的文件偏移,单位为物理页面。   

下图是用户进程线性地址空间示意图:

image description

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

5.3 创建vm_area_struct

建立文件映射的第二步就是建立虚拟地址和具体的物理地址之间的映射,这是通过修改进程页表来实现的.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 成员正好包含你需要的值.

(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()把相应的内存设置为保留后就可以。

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

(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) 所有进程在映射同一个共享内存区域:

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

6. pmap分析进程内存映射

可以在/proc/PID/smaps文件,查看进程的内存映射区域信息。但是使用pmap可以用更易读的方式输出:

Address           Kbytes     RSS   Dirty Mode  Mapping
0000000000400000    2800    1612       0 r-x-- python2.7
00000000008bb000       4       4       4 r---- python2.7
00000000008bc000     468     328     232 rw--- python2.7
0000000000931000      72      68      68 rw---   [ anon ]
00000000018a2000    1796    1684    1684 rw---   [ anon ]
00007fca1bb9a000     212       4       4 r--s- hosts
00007fca1bbcf000    1284    1144    1144 rw---   [ anon ]
00007fca1bd10000      92      48       0 r-x-- datetime.x86_64-linux-gnu.so
00007fca1bd27000    2044       0       0 ----- datetime.x86_64-linux-gnu.so
00007fca1bf26000       4       4       4 r---- datetime.x86_64-linux-gnu.so
00007fca1bf27000      16      16      16 rw--- datetime.x86_64-linux-gnu.so
00007fca1bf2b000     256     256     256 rw---   [ anon ]
00007fca1bf6b000      16      12       0 r-x-- _hashlib.x86_64-linux-gnu.so
00007fca1bf6f000    2044       0       0 ----- _hashlib.x86_64-linux-gnu.so
00007fca1c16e000       4       4       4 r---- _hashlib.x86_64-linux-gnu.so
00007fca1c16f000       4       4       4 rw--- _hashlib.x86_64-linux-gnu.so
00007fca1c170000    1736     924       0 r-x-- libcrypto.so.1.0.0
00007fca1c322000    2044       0       0 ----- libcrypto.so.1.0.0
00007fca1c521000     108     108     108 r---- libcrypto.so.1.0.0
00007fca1c53c000      44      44      44 rw--- libcrypto.so.1.0.0
00007fca1c547000      16      16      16 rw---   [ anon ]
00007fca1c54b000     336     224       0 r-x-- libssl.so.1.0.0
00007fca1c59f000    2048       0       0 ----- libssl.so.1.0.0
00007fca1c79f000      12      12      12 r---- libssl.so.1.0.0
00007fca1c7a2000      28      28      28 rw--- libssl.so.1.0.0
00007fca1c7a9000      32      32       0 r-x-- _ssl.x86_64-linux-gnu.so
00007fca1c7b1000    2044       0       0 ----- _ssl.x86_64-linux-gnu.so
00007fca1c9b0000       4       4       4 r---- _ssl.x86_64-linux-gnu.so
00007fca1c9b1000       4       4       4 rw--- _ssl.x86_64-linux-gnu.so
00007fca1c9b2000      44      12       0 r-x-- libnss_files-2.19.so
00007fca1c9bd000    2044       0       0 ----- libnss_files-2.19.so
00007fca1cbbc000       4       4       4 r---- libnss_files-2.19.so
00007fca1cbbd000       4       4       4 rw--- libnss_files-2.19.so
00007fca1cbbe000      44      16       0 r-x-- libnss_nis-2.19.so
00007fca1cbc9000    2044       0       0 ----- libnss_nis-2.19.so
00007fca1cdc8000       4       4       4 r---- libnss_nis-2.19.so
00007fca1cdc9000       4       4       4 rw--- libnss_nis-2.19.so
00007fca1cdca000      92      20       0 r-x-- libnsl-2.19.so
00007fca1cde1000    2044       0       0 ----- libnsl-2.19.so
00007fca1cfe0000       4       4       4 r---- libnsl-2.19.so
00007fca1cfe1000       4       4       4 rw--- libnsl-2.19.so
00007fca1cfe2000       8       0       0 rw---   [ anon ]
00007fca1cfe4000      36      24       0 r-x-- libnss_compat-2.19.so
00007fca1cfed000    2044       0       0 ----- libnss_compat-2.19.so
00007fca1d1ec000       4       4       4 r---- libnss_compat-2.19.so
00007fca1d1ed000       4       4       4 rw--- libnss_compat-2.19.so
00007fca1d1ee000    1044     116       0 r-x-- libm-2.19.so
00007fca1d2f3000    2044       0       0 ----- libm-2.19.so
00007fca1d4f2000       4       4       4 r---- libm-2.19.so
00007fca1d4f3000       4       4       4 rw--- libm-2.19.so
00007fca1d4f4000      96       8       0 r-x-- libz.so.1.2.8
00007fca1d50c000    2044       0       0 ----- libz.so.1.2.8
00007fca1d70b000       4       4       4 r---- libz.so.1.2.8
00007fca1d70c000       4       4       4 rw--- libz.so.1.2.8
00007fca1d70d000       8       4       0 r-x-- libutil-2.19.so
00007fca1d70f000    2044       0       0 ----- libutil-2.19.so
00007fca1d90e000       4       4       4 r---- libutil-2.19.so
00007fca1d90f000       4       4       4 rw--- libutil-2.19.so
00007fca1d910000      12       8       0 r-x-- libdl-2.19.so
00007fca1d913000    2044       0       0 ----- libdl-2.19.so
00007fca1db12000       4       4       4 r---- libdl-2.19.so
00007fca1db13000       4       4       4 rw--- libdl-2.19.so
00007fca1db14000    1772     684       0 r-x-- libc-2.19.so
00007fca1dccf000    2044       0       0 ----- libc-2.19.so
00007fca1dece000      16      16      16 r---- libc-2.19.so
00007fca1ded2000       8       8       8 rw--- libc-2.19.so
00007fca1ded4000      20      16      16 rw---   [ anon ]
00007fca1ded9000     100      68       0 r-x-- libpthread-2.19.so
00007fca1def2000    2044       0       0 ----- libpthread-2.19.so
00007fca1e0f1000       4       4       4 r---- libpthread-2.19.so
00007fca1e0f2000       4       4       4 rw--- libpthread-2.19.so
00007fca1e0f3000      16       4       4 rw---   [ anon ]
00007fca1e0f7000     140     120       0 r-x-- ld-2.19.so
00007fca1e11a000    1024    1024    1024 rw---   [ anon ]
00007fca1e24b000     788     788     788 rw---   [ anon ]
00007fca1e315000      16      16      16 rw---   [ anon ]
00007fca1e319000       4       4       4 r---- ld-2.19.so
00007fca1e31a000       4       4       4 rw--- ld-2.19.so
00007fca1e31b000       4       4       4 rw---   [ anon ]
00007ffdf51c3000     132      32      32 rw---   [ stack ]
00007ffdf51ef000       8       4       0 r-x--   [ anon ]
ffffffffff600000       4       0       0 r-x--   [ anon ]
---------------- ------- ------- -------
total kB           45736    9656    5624
  • [anon]表示匿名内存映射
  • [stack]表示进程的栈
  • 文件名则表示文件映射。
  • Address是线性地址空间的开始地址
  • Kbytes表示此线性地址区域的大小
  • RSS是Resident Set Size的缩写,是真实使用的物理内存大小
  • Dirty表示此线性区域被写入的大小
  • Mode表示映射的权限,
    • r-x--表示内存可读可执行,
    • r---表示只读,一般是代码页的映射,
    • rw---一般是数据段的映射。
前一篇: 关于多核编程的一点想法 后一篇: 内核内存映射示例

Captcha:
验证码

Email:

Content: (Support Markdown Syntax)