精华内容
下载资源
问答
  • Linux虚拟内存管理

    千次阅读 多人点赞 2017-05-22 21:21:15
    Linux虚拟内存管理有几个关键概念: 每个进程有独立的虚拟地址空间,进程访问的虚拟地址空间并不是真正的物理地址 虚拟地址可通过每个进程上页表与物理地址进行映射,获得真正的物理地址 如果虚拟地址所对应...

    Linux的虚拟内存管理有几个关键概念:

    1. 每个进程有独立的虚拟地址空间,进程访问的虚拟地址空间并不是真正的物理地址
    2. 虚拟地址可通过每个进程上页表与物理地址进行映射,获得真正的物理地址
    3. 如果虚拟地址所对应的物理地址不在物理内存中,则产生缺页中断,真正分配物理地址,同时更新进程的页表;如果此时物理内存已经耗尽,则根据内存替换算法淘汰部分页面至物理磁盘中。

    一. Linux虚拟地址空间如何分布?32位和64位有何不同?

    Linux使用虚拟地址空间,大大增加了进程的寻址空间,由低地址到高地址分别是:

    1. 只读段:该部分空间只能读,不能写,包括代码段,rodata段(C常量字符和#define定义的常量)
    2. 数据段:保存全局变量、静态空间变量
    3. 堆:就是平时所说的动态内存,malloc/new大部分都源于此。其中堆顶的位置可以通过brk和sbrk进行动态调整
    4. 文件映射区域:如动态库,共享内存等映射物理空间的内存,一般是mmap函数所分配的虚拟地址空间
    5. 栈:用于维护函数调用的上下文空间,一般为8M,可以通过ulimit -s查看
    6. 内核虚拟空间:用户代码不可见的区域,由内核管理

    下图是32位OS典型的虚拟地址空间分布:
    这里写图片描述

    32位系统有4G的地址空间,其中0X08048000~0Xbfffffff是用户空间,0Xc0000000~0Xffffffff是内核空间,包含内核代码和数据、与进程相关的数据结构(如页表,内核栈)等。另外%esp执行栈顶,往低地址方向变化;brk/sbrk函数控制堆顶往高地址方向变化。

    可以通过以下代码验证进程的地址空间分布,其中sbrk(0)函数用于返回栈顶指针。

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    int   global_num = 0;
    char  global_str_arr[65536] = { 'a' };
    int main(int argc, char** argv)
    {
        char* heap_var = NULL;
        int local_var = 0;
        printf("Address of function main 0x%lx\n", main);
        printf("Address of global_num 0x%lx\n", &global_num);
        printf("Address of global_str_arr 0x%lx ~ 0x%lx\n", &global_str_arr[0], &global_str_arr[65535]);
        printf("Top of stack is 0x%lx\n", &local_var);
        printf("Top of heap is 0x%lx\n", sbrk(0));
        heap_var = malloc(sizeof(char)* 127 * 1024);
        printf("Address of heap_var is 0x%lx\n", heap_var);
        printf("Top of heap after malloc is 0x%lx\n", sbrk(0));
        free(heap_var);
        heap_var = NULL;
        printf("Top of heap after free is 0x%lx\n", sbrk(0));
        return 1;
    }

    32位系统的结果如下,与上图的划分保持一致,并且栈顶指针在malloc和free一个127K的存储空间时都发生了变化(增大和缩小)

    Address of function main 0x8048474
    Address of global_num 0x8059904
    Address of global_str_arr 0x8049900 ~ 0x80598ff
    Top of stack is 0xbfd0886c
    Top of heap is 0x805a000
    Address of heap_var is 0x805a008
    Top of heap after malloc is 0x809a000
    Top of heap after free is 0x807b000

    但是64位系统的结果怎样呢?64位系统是否拥有2^64的地址空间呢?
    64位的结果如下:

    Address of function main 0x400594
    Address of global_num 0x610b90
    Address of global_str_arr 0x600b80 ~ 0x610b7f
    Top of stack is 0x7fff2e9e4994
    Top of heap is 0x8f5000
    Address of heap_var is 0x8f5010
    Top of heap after malloc is 0x935000
    Top of heap after free is 0x916000

    从结果可知,与上图的分布并不一致。而事实上,64位系统的虚拟地址空间划分发生了变化:

    1. 地址空间大小不是2^32,也不是2^64,而一般是2^48。因为并不需要2^64这么大的寻址空间,过大空间只会导致资源的浪费。64位Linux一般使用48位来表示虚拟地址空间,40位表示物理地址,可通过/proc/cpuinfo来查看:
      address sizes : 40 bits physical, 48 bits virtual
    2. 其中,0x0000000000000000~0x00007fffffffffff表示用户空间,0xFFFF800000000000~ 0xFFFFFFFFFFFFFFFF表示内核空间,共提供 256TB(2^48) 的寻址空间。这两个区间的特点是,第47位与48~63位相同,若这些位为0表示用户空间,否则表示内核空间
    3. 用户空间由低地址到高地址仍然是只读段,数据段,堆,文件映射区域和栈

    二.malloc是如何分配内存的?

    malloc是glibc中的内存分配函数,也是最常用的动态内存分配函数,其内存必须通过free进行释放,否则导致内存泄漏。
    关于malloc获得虚拟空间的表现,与glibc的版本有关,但大体逻辑上:

    1.若分配内存小于128K,调用sbrk(),将堆顶指针向高地址移动,获得新的虚拟空间;
    2.若分配内存大于128K,调用mmap(),在文件映射区域中分配匿名虚拟空间;
    3.这里讨论的是简单情况,如果涉及并发则可能会复杂一些,不过先不讨论。
    其中sbrk()就是修改栈顶指针位置,而mmap可用于生成文件的映射以及修改匿名页面的内存,这里指的是匿名页面。

    而这个128K,是glibc的默认配置,可通过函数mallopt来设置,可通过以下例子来说明:

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <malloc.h>
    void print_info(
            char*      var_name,
            char*      var_ptr,
            size_t     size_in_kb
    )
    
    {
       printf("Address of %s(%luk) 0x%lx,  now heap top is 0x%lx\n",
        var_name, size_in_kb, var_ptr, sbrk(0));
    }
    int main(int argc, char** argv)
    {
            char *heap_var1, *heap_var2, *heap_var3 ;
            char *mmap_var1, *mmap_var2, *mmap_var3 ;
            char *maybe_mmap_var;
            printf("Orginal heap top is 0x%lx\n", sbrk(0));
            heap_var1 = malloc(32*1024);
            print_info("heap_var1", heap_var1, 32);
            heap_var2 = malloc(64*1024);
            print_info("heap_var2", heap_var2, 64);
            heap_var3 = malloc(127*1024);
            print_info("heap_var3", heap_var3, 127);
            printf("\n");
            maybe_mmap_var = malloc(128*1024);
            print_info("maybe_mmap_var", maybe_mmap_var, 128);
            //mmap
            mmap_var1 = malloc(128*1024);
            print_info("mmap_var1", mmap_var1, 128);
            // set M_MMAP_THRESHOLD to 64k
            mallopt(M_MMAP_THRESHOLD, 64*1024);
            printf("set M_MMAP_THRESHOLD to 64k\n");
            mmap_var2 = malloc(64*1024);
            print_info("mmap_var2", mmap_var2, 64);
            mmap_var3 = malloc(127*1024);
            print_info("mmap_var3", mmap_var3, 127);
            return 1;
    }

    这个例子很简单,通过 malloc 申请多个不同大小的动态内存,同时通过接口 print_info 打印变量大小和地址等相关信息,其中 sbrk(0) 可返回堆顶指针位置。另外,粗体部分是将 MMAP 分配的临界点由 128k 转为 64k ,再打印变量地址的不同。

    下面是 Linux 64 位机器的执行结果(后文所有例子都是通过 64 位机器上的测试结果):

    Orginal heap top is 0x17da000
    Address of heap_var1(32k) 0x17da010,  now heap top is 0x1803000
    Address of heap_var2(64k) 0x17e2020,  now heap top is 0x1803000
    Address of heap_var3(127k) 0x17f2030,  now heap top is 0x1832000
    Address of maybe_mmap_var(128k) 0x1811c40,  now heap top is 0x1832000
    Address of mmap_var1(128k) 0x7f4a0b1f2010,  now heap top is 0x1832000
    set M_MMAP_THRESHOLD to 64k
    Address of mmap_var2(64k) 0x7f4a0b1e1010,  now heap top is 0x1832000
    Address of mmap_var3(127k) 0x7f4a0b1c1010,  now heap top is 0x1832000

    三.malloc分配多大的内存,就占用多大的物理内存空间吗?

    malloc分配的内存是虚拟地址空间,而虚拟地址空间和物理地址空间使用进程页表进行映射,那么分配了空间就是占用物理内存空间了吗?

    首先,进程使用了多少内存可通过ps -aux命令查看,其中关键的两个信息(第五,六列)为:
    1. VSZ,virtual memory size,表示进程总共使用的虚拟地址空间大小,包括进程地址空间的代码段,数据段,堆,文件映射区域,栈,内核空间等所有虚拟地址使用的总和,单位为K。
    2. RSS,resident set size,表示进程实际使用的物理空间大小,RSS总小于VSZ

    可通过一个例子说明这个问题 :

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <malloc.h>
    char ps_cmd[1024];
    void print_info(
            char*      var_name,
            char*      var_ptr,
            size_t     size_in_kb
    )
    
    {
            printf("Address of %s(%luk) 0x%lx,  now heap top is 0x%lx\n",
                     var_name, size_in_kb, var_ptr, sbrk(0));
            system(ps_cmd);
    }
    
    int main(int argc, char** argv)
    {
            char *non_set_var, *set_1k_var, *set_5k_var, *set_7k_var;
            pid_t pid;
            pid = getpid();
            sprintf(ps_cmd, "ps aux | grep %lu | grep -v grep", pid);
            non_set_var = malloc(32*1024);
            print_info("non_set_var", non_set_var, 32);
            set_1k_var = malloc(64*1024);
            memset(set_1k_var, 0, 1024);
            print_info("set_1k_var", set_1k_var, 64);
            set_5k_var = malloc(127*1024);
            memset(set_5k_var, 0, 5*1024);
            print_info("set_5k_var", set_5k_var, 127);
            set_7k_var = malloc(64*1024);
            memset(set_1k_var, 0, 7*1024);
            print_info("set_7k_var", set_7k_var, 64);
            return 1;
    }

    该代码扩展了上一个例子print_info能力,处理打印变量信息,同时通过 ps aux 命令获得当前进程的 VSZ 和 RSS 值。并且程序 malloc 一块内存后,会 memset 内存的若干 k 内容。

    执行结果为:

    Address of non_set_var(32k) 0x502010,  now heap top is 0x52b000
    
    mysql    12183  0.0  0.0   2692   452 pts/3    S+   20:29   0:00 ./test_vsz
    
    Address of set_1k_var(64k) 0x50a020,  now heap top is 0x52b000
    
    mysql    12183  0.0  0.0   2692   456 pts/3    S+   20:29   0:00 ./test_vsz
    
    Address of set_5k_var(127k) 0x51a030,  now heap top is 0x55a000
    
    mysql    12183  0.0  0.0   2880   464 pts/3    S+   20:29   0:00 ./test_vsz
    
    Address of set_7k_var(64k) 0x539c40,  now heap top is 0x55a000
    
    mysql    12183  0.0  0.0   2880   472 pts/3    S+   20:29   0:00 ./test_vsz

    由以上结果可知:
    1. VSZ并不是每次malloc后都增长,是与上一节说的堆顶没发生变化有关,因为可重用堆顶内剩余的空间,这样malloc是很轻量和快速的;
    2. 如果VSZ发生变化,基本与分配内存量相当,因为VSZ是计算虚拟地址空间总大小;
    3. RSS的增量很少,是因为malloc分配的内存并不就马上分配实际的存储空间,只有第一次使用,如第一个memset后才分配;
    4. 由于每个物理页面大小是4K,不管memset其中的1K,还是5K,7K,实际占用物理内存总是4K的倍数。所以RSS的增量总是4K的倍数;
    5. 因此,不是malloc后马上占用实际内存,而是第一次使用时发现虚存对应的物理页面未分配,产生缺页中断,才真正分配物理页面,同时更新进程页表的映射关系。这也是Linux虚拟内存管理的核心概念之一。

    四.如何查看进程虚拟地址空间的使用情况

    进程地址空间被分成了代码段,数据段,堆,文件映射区域,栈等区域,怎么查询这些虚拟地址空间的使用情况呢?
    Linux提供了pmap命令来查看这些信息,通常使用pmap -d pid(高版本可提供pmap -x pid)查询,如下所示:

    mysql@ TLOG_590_591:~/vin/test_memory> pmap -d 17867
    
    17867: test_mmap
    
    START       SIZE     RSS   DIRTY PERM OFFSET   DEVICE MAPPING
    
    00400000      8K      4K      0K r-xp 00000000 08:01  /home/mysql/vin/test_memory/test_mmap
    
    00501000     68K      8K      8K rw-p 00001000 08:01  /home/mysql/vin/test_memory/test_mmap
    
    00512000     76K      0K      0K rw-p 00512000 00:00  [heap]
    
    0053e000    256K      0K      0K rw-p 0053e000 00:00  [anon]
    
    2b3428f97000    108K     92K      0K r-xp 00000000 08:01  /lib64/ld-2.4.so
    
    2b3428fb2000      8K      8K      8K rw-p 2b3428fb2000 00:00  [anon]
    
    2b3428fc1000      4K      4K      4K rw-p 2b3428fc1000 00:00  [anon]
    
    2b34290b1000      8K      8K      8K rw-p 0001a000 08:01  /lib64/ld-2.4.so
    
    2b34290b3000   1240K    248K      0K r-xp 00000000 08:01  /lib64/libc-2.4.so
    
    2b34291e9000   1024K      0K      0K ---p 00136000 08:01  /lib64/libc-2.4.so
    
    2b34292e9000     12K     12K     12K r--p 00136000 08:01  /lib64/libc-2.4.so
    
    2b34292ec000      8K      8K      8K rw-p 00139000 08:01  /lib64/libc-2.4.so
    
    2b34292ee000   1048K     36K     36K rw-p 2b34292ee000 00:00  [anon]
    
    7fff81afe000     84K     12K     12K rw-p 7fff81afe000 00:00  [stack]
    
    ffffffffff600000   8192K      0K      0K ---p 00000000 00:00  [vdso]
    
    Total:    12144K    440K     96K

    从这个结果可以看到进程虚拟地址空间的使用情况,包括起始地址、大小、实际使用内存、脏页大小、权限、偏移、设备和映射文件等,pmap命令就是基于下面两文件进行解析的:

    /proc/pid/maps
    /proc/pid/smaps

    并且对于上述每个内存块区间,内核会使用一个vm_area_struct结构来维护,同时提供页面建立与物理内存的映射关系,如下图所示:
    这里写图片描述

    五.free的内存真的释放了吗(还给OS)?

    前面的示例都有一个严重的问题,就是分配的内存都没有释放,即导致内存泄漏,原则上所有malloc/new分配的内存,都需要free/delete来释放,但是free了的内存真的释放了吗?

    要说清楚这个问题,可通过下面例子来说明:

    1.初始状态:如图 (1) 所示,系统已分配 ABCD 四块内存,其中 ABD 在堆内分配, C 使用 mmap 分配。为简单起见,图中忽略了如共享库等文件映射区域的地址空间。

    2.E=malloc(100K):分配 100k 内存,小于 128k ,从堆内分配,堆内剩余空间不足,扩展堆顶 (brk) 指针。

    3.free(A):释放 A 的内存,在 glibc 中,仅仅是标记为可用,形成一个内存空洞 ( 碎片 ) ,并没有真正释放。如果此时需要分配 40k 以内的空间,可重用此空间,剩余空间形成新的小碎片。

    这里写图片描述

    4.free(C):C 空间大于 128K ,使用 mmap 分配,如果释放 C ,会调用 munmap 系统调用来释放,并会真正释放该空间,还给 OS ,如图 (4) 所示。

    5.free(D):与释放 A 类似,释放 D 同样会导致一个空洞,获得空闲空间,但并不会还给 OS 。此时,空闲总空间为 100K ,但由于虚拟地址不连续,无法合并,空闲空间无法满足大于 60k 的分配请求。

    6.free(E):释放 E ,由于与 D 连续,两者将进行合并,得到 160k 连续空闲空间。同时 E 是最靠近堆顶的空间,glibc 的 free 实现中,只要堆顶附近释放总空间(包括合并的空间)超过 128k ,即会调用 sbrk(-SIZE) 来回溯堆顶指针,将原堆顶空间还给 OS ,如图 (6) 所示。而堆内的空闲空间还是不会归还 OS 的。

    这里写图片描述

    由此可见:
    1. malloc使用mmap分配的内存(大于128K),free会调用unmap系统调用马上还给OS,实现真正释放。
    2. 堆内的内存,只有释放堆顶的空间,同时堆顶总连续空间大于128K才使用sbrk(-SIZE)回收内存,真正归还OS。
    3. 堆内的空闲空间,是不会归还给OS的。

    六.程序代码中malloc的内存都有相应的free,就不会出现内存泄漏了吗?

    狭义上的内存泄漏是指malloc的内存,没有free,导致内存泄漏,直到程序结束。而广义上的内存泄漏是进程使用内存量不断增加,或大大超出了系统原设计的上限。

    上一节说到,free 了的内存并不会马上归还 OS ,并且堆内的空洞(碎片)更是很难真正释放,除非空洞成为了新的堆顶 。所以,如上一例子情况 (5) ,释放了 40k 和 60k 两片内存,但如果此时需要申请大于 60k (如 70k ),没有可用碎片,必须向 OS 申请,实际使用内存仍然增大。

    因此,随着系统频繁的malloc和free,尤其是对于小块内存,堆内将产生越来越多不可用的碎片,导致“内存泄漏”。而这种“泄露”现象使用 valgrind 是无法检测出来的。

    下图是 MySQL 存在大量分区表时的内存使用情况 (RSS 和 VSZ) ,疑似“内存泄露”。
    这里写图片描述

    因此,当我们写程序时,不能完全依赖于glibc的malloc和free的实现。更好方式是建立进程的内存池,即一次分配(malloc)大块内存,小内存从内存池中获得,当进程结束或该块内存不可用时,一次释放(free),可大大减少碎片的产生。

    七.既然堆内内存不能直接释放,为什么不全部使用mmap来分配

    由于堆内碎片不能直接释放,而问题5中说到的mmap分配的内存可以通过unmap进行free,实现真正释放。既然堆内碎片不能直接释放,导致疑似“内存泄漏”问题,为什么malloc不全部使用mmap来实现呢?而仅仅对于大于128K的大块内存才使用mmap?

    其实,进程向OS申请和释放紧致空间的接口 sbrk/mmap/unmap都是系统调用,频繁的系统调用都比较消耗系统资源。并且,mmap申请的内存被unmap后,重新申请会产生更多的缺页中断。例如mmap分配 1M 空间,第一次调用产生了大量缺页中断(1M/4K次),当unmap后再次分配 1M 空间,会再次产生大量缺页中断。缺页中断属于内核行为,会导致内核态 CPU 消耗较大。另外,使用 mmap 分配小内存,会导致地址空间的分片更多,内核的管理负担更大。

    而堆是一个连续空间,并且堆内碎片由于没有归还给OS,如果可重用碎片,在此访问该内存很可能不需要任何系统调用和缺页中断,这将大大降低CPU的消耗。

    因此,glibc的malloc实现中,充分考虑了sbrk和mmap行为上的差异和优缺点,默认分配大块内存(128K)才使用mmap获得地址空间,也可通过 mallopt(M_MMAP_THRESHOLD,SIZE)来修改这个临界值。

    八.如何查看进程的缺页中断信息?

    可通过以下命令查看缺页中断信息:

    ps -o majflt,minflt -C <program_name>
    ps -o majflt,minflt -p <pid>

    其中, majflt 代表 major fault ,指大错误, minflt 代表 minor fault ,指小错误。这两个数值表示一个进程自启动以来所发生的缺页中断的次数。其中 majflt 与 minflt 的不同是, majflt 表示需要读写磁盘,可能是内存对应页面在磁盘中需要 load 到物理内存中,也可能是此时物理内存不足,需要淘汰部分物理页面至磁盘中。

    例如,下面是 mysqld 的一个例子。

    mysql@ TLOG_590_591:~> ps -o majflt,minflt -C mysqld
    MAJFLT MINFLT
    144856 15296294

    如果进程的内核态 CPU 使用过多,其中一个原因就可能是单位时间的缺页中断次数多个,可通过以上命令来查看。

    如果 MAJFLT 过大,很可能是内存不足。
    如果 MINFLT 过大,很可能是频繁分配 / 释放大块内存 (128k) , malloc 使用 mmap 来分配。对于这种情况,可通过 mallopt(M_MMAP_THRESHOLD, SIZE)增大临界值,或程序实现内存池。

    九.如何查看堆内内存的碎片情况?

    glibc 提供了以下结构和接口来查看堆内内存和 mmap 的使用情况:

    struct mallinfo {
      int arena;    /* non-mmapped space allocated from system */
      int ordblks;  /* number of free chunks */
      int smblks;   /* number of fastbin blocks */
      int hblks;    /* number of mmapped regions */
      int hblkhd;   /* space in mmapped regions */
      int usmblks;  /* maximum total allocated space */
      int fsmblks;  /* space available in freed fastbin blocks */
      int uordblks; /* total allocated space */
      int fordblks; /* total free space */
      int keepcost; /* top-most, releasable (via malloc_trim) space */
    };
    
    
    /* 返回 heap(main_arena) 的内存使用情况,以 mallinfo 结构返回 */
    struct mallinfo mallinfo();
    /* 将 heap 和 mmap 的使用情况输出到 stderr  */
    void malloc_stats();

    可通过以下例子来验证 mallinfo 和 malloc_stats 输出结果:

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <malloc.h>
    size_t  heap_malloc_total, heap_free_total,
                    mmap_total, mmap_count;
    void print_info()
    {
            struct mallinfo mi = mallinfo();
            printf("count by itself:\n");
            printf("\theap_malloc_total=%lu heap_free_total=%lu heap_in_use=%lu\n\
    \tmmap_total=%lu mmap_count=%lu\n",
                    heap_malloc_total*1024, heap_free_total*1024, heap_malloc_total*1024 - heap_free_total*1024,
                    mmap_total*1024, mmap_count);
            printf("count by mallinfo:\n");
            printf("\theap_malloc_total=%lu heap_free_total=%lu heap_in_use=%lu\n\
    \tmmap_total=%lu mmap_count=%lu\n",
                    mi.arena, mi.fordblks, mi.uordblks,
                    mi.hblkhd, mi.hblks);
          printf("from malloc_stats:\n");
            malloc_stats();
    }
    
    #define ARRAY_SIZE 200
    int main(int argc, char** argv)
    {
            char** ptr_arr[ARRAY_SIZE];
            int i;
            for( i = 0; i < ARRAY_SIZE; i++) {
                    ptr_arr[i] = malloc(i * 1024);
                    if ( i < 128)
                            heap_malloc_total += i;
                    else {
                            mmap_total += i;
                            mmap_count++;
                    }
    
            }
            print_info();
            for( i = 0; i < ARRAY_SIZE; i++) {
                    if ( i % 2 == 0)
                            continue;
                    free(ptr_arr[i]);
                    if ( i < 128)
                            heap_free_total += i;
                    else {
                            mmap_total -= i;
                            mmap_count--;
                    }
            }
            printf("\nafter free\n");
            print_info();
            return 1;
    }

    该例子第一个循环为指针数组每个成员分配索引位置 (KB) 大小的内存块,并通过 128 为分界分别对 heap 和 mmap 内存分配情况进行计数;第二个循环是 free 索引下标为奇数的项,同时更新计数情况。通过程序的计数与 mallinfo/malloc_stats 接口得到结果进行对比,并通过 print_info 打印到终端。

    下面是一个执行结果:

    count by itself:
            heap_malloc_total=8323072 heap_free_total=0 heap_in_use=8323072
            mmap_total=12054528 mmap_count=72
    count by mallinfo:
            heap_malloc_total=8327168 heap_free_total=2032 heap_in_use=8325136
            mmap_total=12238848 mmap_count=72
    from malloc_stats:
    Arena 0:
    system bytes     =    8327168
    in use bytes     =    8325136
    Total (incl. mmap):
    system bytes     =   20566016
    in use bytes     =   20563984
    max mmap regions =         72
    max mmap bytes   =   12238848
    
    after free
    
    count by itself:
            heap_malloc_total=8323072 heap_free_total=4194304 heap_in_use=4128768
            mmap_total=6008832 mmap_count=36
    count by mallinfo:
            heap_malloc_total=8327168 heap_free_total=4197360 heap_in_use=4129808
            mmap_total=6119424 mmap_count=36
    from malloc_stats:
    Arena 0:
    system bytes     =    8327168
    in use bytes     =    4129808
    Total (incl. mmap):
    system bytes     =   14446592
    in use bytes     =   10249232
    max mmap regions =         72
    max mmap bytes   =   12238848

    由上可知,程序统计和 mallinfo 得到的信息基本吻合,其中 heap_free_total 表示堆内已释放的内存碎片总和。

    如果想知道堆内片究竟有多碎 ,可通过 mallinfo 结构中的 fsmblks 、 smblks 、 ordblks 值得到,这些值表示不同大小区间的碎片总个数,这些区间分别是 0~80 字节, 80~512 字节, 512~128k 。如果 fsmblks 、 smblks 的值过大,那碎片问题可能比较严重了。

    不过, mallinfo 结构有一个很致命的问题,就是其成员定义全部都是 int ,在 64 位环境中,其结构中的 uordblks/fordblks/arena/usmblks 很容易就会导致溢出,应该是历史遗留问题,使用时要注意!

    十. 除了 glibc 的 malloc/free ,还有其他第三方实现吗?

    其实,很多人开始诟病 glibc 内存管理的实现,就是在高并发性能低下和内存碎片化问题都比较严重,因此,陆续出现一些第三方工具来替换 glibc 的实现,最著名的当属 google 的 tcmalloc 和 facebook 的 jemalloc 。

    展开全文
  • Linux虚拟内存设置

    千次阅读 2017-08-15 22:39:54
    Linux设置虚拟内存   说起Windows的虚拟内存,想必大家都不会陌生吧,修改Windows系统的虚拟内存很简单,大家都会,但是Linux操作系统的虚拟内存修改你会吗,如果不会,那一起来学学吧。   1、打开...
    Linux设置虚拟内存




     
    说起Windows的虚拟内存,想必大家都不会陌生吧,修改Windows系统的虚拟内存很简单,大家都会,但是Linux操作系统的虚拟内存修改你会吗,如果不会,那一起来学学吧。
     
    1、打开终端,切换到root用户,输入:free -m查看内存状态
     
    [root@lxt lxt]# free -m
     
    total used free shared buffers cached
     
    Mem: 498 357 141 0 27 162
     
    -/+ buffers/cache: 167 331
     
    Swap: 1023 0 1023
     
    2、输入df -B M(或df -m)查看各分区当前使用情况
     
    [root@lxt lxt]# df -B M
     
    文件系统 1M-块 已用 可用 已用% 挂载点
     
    /dev/mapper/vg_lxt-lv_root
     
    12875M 4059M 8162M 34% /
     
    /dev/sda7 194M 14M 170M 8% /boot
     
    tmpfs 250M 1M 249M 1% /dev/shm
     
    (fdisk -l可查看磁盘分区情况)
     
    3、选择一个较大的分区,建立分区文件:
     
    [root@lxt lxt]# dd if=/dev/zero of=/swapadd bs=1024 count=524288
     
    524288+0 records in
     
    524288+0 records out
     
    536870912 bytes (537 MB) copied,13.0709 秒,41.1 MB/秒
     
    以上命令在根目录新建一个名为swapadd,大小为512M的虚拟内存文件
     
    4、移动该文件到空间较大的其他分区:
     
    [root@lxt lxt]# mkdir /mnt/swap
     
    [root@lxt /]# mv swapadd /mnt/swap
     
    5、执行以下命令启用虚拟内存并重启电脑
     
    [root@lxt /]# mkswap /mnt/swap/swapadd
     
    Setting up swapspace version 1, size = 524284 KiB
     
    no label, UUID=a5c8b651-6f64-4414-bb5f-580b742acfce
     
    [root@lxt /]# swapon /mnt/swap/swapadd
     
    查看内存:
     
    [root@lxt /]# free -m
     
    total used free shared buffers cached
     
    Mem: 498 492 6 0 15 302
     
    -/+ buffers/cache: 174 323
     
    Swap: 1535 0 1535
     
    6、如果不需要使用新增的虚拟内存,则输入:
     
    swapoff -v /mnt/swap/swapadd


    本文来自: 网络安全攻防研究室(www.91ri.org) 请尊重作者,转载时带上版权信息。 详细内容请参考:http://www.91ri.org/1964.html
    展开全文
  • 简述Linux虚拟内存管理

    千次阅读 2019-01-01 14:01:48
    虚拟存储(virtual memory, VM)的基本思想是: 维护一个虚拟的逻辑内存机制(通常比物理内存大得多), 进程都基于这个虚拟内存, 在进程运行时动态的将虚拟内存地址映射到实际的物理内存. VM的设计体现了软件工程思想: ...

    原文地址:https://cloud.tencent.com/ developer/article/1157420

    虚拟存储

    虚拟存储(virtual memory, VM)的基本思想是: 维护一个虚拟的逻辑内存机制(通常比物理内存大得多), 进程都基于这个虚拟内存, 在进程运行时动态的将虚拟内存地址映射到实际的物理内存.

    VM的设计体现了软件工程思想: 封装, 抽象, 依赖倒置, 非常棒. 每个运行中的进程无需再去关心实际物理内存是多大, 分配内存会不会超出限制, 哪些内存已经被其他进程占用等等, 这些都交由kernel的内存管理单元来解决, 暴露给进程的"接口"只有每个进程独有的虚拟地址空间.

    如上图所示: 程序中产生的内存地址成为虚拟地址(virtual address), 又称为逻辑地址(logic address), 逻辑地址被送到内存管理单元(memory manager unit, MMU), 映射成物理内存地址之后, 再送到内存总线上.

    分页

    MMU的主要职责就是将逻辑地址, 转成物理地址. 以32位Linux为例, MMU可以当成一个数学函数: f(x) = y, 输入x是一个0-4G范围内的逻辑地址, 输出y是实际的物理地址.

    最简单暴力的方法, 莫过于直接建一层映射关系, 不过这样映射表就得4G大小了……

    因为实际上我们并不需要把所有的映射关系都建立起来, 而只需要为用到的内存做映射, 所以, 前辈们用了分页的方法来解决这个问题(实际是一个多阶哈希).

    一个典型的二级页表来处理分页: 32位的逻辑地址被分成了3段, 10位的一级页表索引(page table 1 index), 10位的二级页表索引(page table 2 index)和剩下的12位页面偏移量(page offset). 所谓页面(page), 是现在大部分MMU中用来管理内存的单位, Linux下常见的page大小是4k(12位的偏移量刚好是一个page, 即4k).

    对于一个进程而言, 它需要用到的页表: 一级页表,以及部分用到的二级页表(不需要全部的). 以一个占用16M内存地址空间的进程为例, 理论上它只需要1个一级页表, 和4个二级页表, 页表开销即 5 * 4k = 20k, 能节省大量的页表开销.

    在多级页表中, 页表分级越多, 越灵活, 但是带来的时间成本也就越高, 复杂度也越高. 二级, 或者三级页表是一个比较合理的选择. 为了兼容不同的CPU, Linux 2.6.11 之后使用了四级分页机制, 在不同的CPU环境下可以灵活扩展成二级或者三级.

    逻辑地址映射成物理地址的过程是通过MMU硬件来完成的. 除此之外, 还有一个TLB的硬件, translation lookaside buffer, 即页表缓冲, 它是一块高速cache, 通过CR3寄存器来刷新, 能加速虚拟内存寻址的过程.

    页面置换

    进程中用到的代码段, 数据段和堆栈的总大小可能超过可用的物理内存总数, VM提供了一种机制来解决这个问题: 把当前使用的那一部分放到内存中, 其他部分保存在磁盘上, 并在需要时在磁盘和内存中做交换. 这就是页面置换.

    当一个逻辑地址, 经过MMU映射后发现, 对应的页表项还没有映射到物理内存, 就会触发缺页错误(page fault): CPU 需要陷入 kernel, 找到一个可用的物理内存页面, 从页表项映射过去. 如果这个时候没有空闲的物理内存页面, 就需要做页面置换了, 操作系统通过某些算法, 从物理内存中选一个当前在用的页面, (是否需要写到磁盘, 取决于有没有被修改过), 重新调入, 建立页表项到之的映射关系.

    分段

    分段的思想, 说穿了就是把内存分成若干段, 每个段是一个单独的地址空间, 有自己的起始的基地址, 根据 基地址+偏移量 来做寻址.

    分段的好处是带来了比较大的灵活性, 也更安全. 每个段都构成了自己的独立地址空间, 增大或者减小而不会影响其他段. 还可以对每个段设置不同的保护级别.

    Linux下采用的是段页式内存管理, 先分段, 再分页. 但是因为Linux中所有的段基址都设置成了0, 段偏移量相当于就是线性地址, 只用了一个地址空间, 效果上就是正常的分页. 这么做的原因是为了兼容各种硬件体系.

    虽然Linux下, "分段"只是一个摆设, 但是在进程的内存管理中, 还是应用了分段的思想的: 每一个进程在运行时, 它的逻辑地址空间都会被分为代码段, 数据段, 堆, 栈等, 当访问段之外的内存地址时, kernel 能监测到并给出段错误(segment fault).

    VM管理

    Linux kernel 主要提供了两种内存分配算法: buddy 和 slab, 结合使用。buddy 提供了2的幂大小内存块的分配方法,具有数组特性,简单高效, 但是缺点在于内存碎片。slab 提供了小对象的内存分配方法, 实际上是一个多级缓存列表, 最小的分配单位称为一个slab(一个或者多个连续页), 被分配为多个对象来使用.

    kswapd 是一个 daemon 进程, 对系统内存做定时检查, 一般是1秒一次. 如果发现没有足够的空闲页面, 就做页回收(page reclaiming), 将不再使用的页面换出. 如果要换出的页面脏了, 往往还需要写回到磁盘或者swap.

    bdflush 也是 daemon 进程, 周期性的检查脏缓冲(磁盘cache), 并写回磁盘. 不过在 Linux 2.6 之后, pdflush 取代了 bdflush, 前者的优势在于: 可以开多个线程, 而 bdflush 只能是单线程, 这就保证了不会在回写繁忙时阻塞; 另外, bdflush 的操作对象是缓冲, 而 pdflush 是基于页面的, 显然 pdflush 的效率会更高.

    观察内存

    "pmap –x pid" 这个命令, 能将/proc/pid/maps中的数据, 以更人性化的方式展示出来:

    从上图可以看到, 每一项内容都清晰的标出了对象, 内存起始地址(逻辑地址), 占用的内存大小, 实际分配的内存(RSS, 也就是常驻内存), 以及脏内存, 这些单位都是kb, 并给出了最终的统计结果. 统计结果的前两项就是 top 中显示的 VIRT 和 RSS.

    VM tuning

    这里就只关注 Linux 2.6 之后的情况了(2.4之前诸如 bdflush 就不在讨论范围之内). 所有的VM可以调整的参数项, 都在/proc/sys/vm目录下:

    可以"sysctl vm.param"观察参数的值, "sysctl –w vm.param=value"来修改参数.具体的每一项参数的含义可以参考: https://www.kernel.org/doc/Documentation/sysctl/vm.txt.

    1. pdflush调优, 其实这一块跟磁盘IO关系比较紧.

    • dirty_writeback_centisecs, 默认是500, 单位是毫秒. 意思是每5秒唤醒 pdflush (多个线程), 将脏页面写回磁盘. 把这个参数调低可以增加 pdflush 被唤醒的频率, 不过在内核实现中, pdflush 在需要的时候会自动被唤醒, 所以这个参数的效果不可预期.

    • dirty_expire_centiseconds, 默认是3000, 单位是毫秒, 是指脏页面的过期时间, 超过了这个时间, 就会触发 pdflush 做回写.

    • dirty_background_ratio, 默认是10, 是指总内存中脏页面的百分比. 低于这个阈值时, pdflush 才会停止做回写, 有的内核版本的默认值是5.

    • dirty_ratio, 这也是一个百分比, 默认40, 是总内存中脏页面的百分比. 超过这个阈值, 就一定等待 pdflush 向磁盘回写. 与 dirty_background_ratio 的区别在与: 如果 cache 的增长超过了 pdflush 的回写速率时, 有可能 pdflush 来不及回写, 在超过40\%这个阈值时, 进程就会等待, 直到 pdflush 处理到这个阈值之下. 此时就是一个IO瓶颈.

    IO比较重的时候, 可以考虑的调优手段: 首先尝试调低 dirty_background_ratio, 其次是 dirty_background_ratio, 然后是 dirty_expire_centiseconds, dirty_writeback_centisecs 这一项可以不用考虑.

    1. swapness, 这个表示了 swap 分区的使用程度, 等于0时表示尽可能不用 swap, 等于100表示积极的使用 swap, 默认是60. 这个参数取决于具体的需求.

    2. drop_caches, 这个跟cache有关, 默认是0. 设置不同的参数可以回收系统的 cache 和 buffers, 不过略显粗暴(cache 和 buffer 的存在是有意义的).

    • free pagecache: sysctl -w vm.drop_caches=1

    • free dentries and inodes: sysctl -w vm.drop_caches=2

    • free pagecache, dentries and inodes: sysctl -w vm.drop_caches=3

    展开全文
  • Linux虚拟内存不足,扩展虚拟内存的解决办法 virtual memory exhausted: Cannot allocate memory 在编译软件的时候,出现了这个错误: virtual memory exhausted: Cannot allocate memory 使用free -m命令检查swap...

    Linux虚拟内存不足,扩展虚拟内存的解决办法

    virtual memory exhausted: Cannot allocate memory

    在编译软件的时候,出现了这个错误:

    virtual memory exhausted: Cannot allocate memory

    使用free -m命令检查swap分区情况

    lk@Mibook:~$ free -m
                  total        used        free      shared  buff/cache   available
    Mem:           3828        1212         113        1022        2501        1315
    Swap:           976         856         120
    

    发现交换分区只有976M ,显然太小了,所以考虑增加虚拟内存。

    1 创建用于交换分区的空间

    标准格式:

    $ dd if=/dev/zero of=/mnt/swap bs=1024 count=4096000 
    

    运行后:

    lk@Mibook:~$ sudo dd if=/dev/zero of=/mnt/swap bs=1024 count=4096000 
    4096000+0 records in
    4096000+0 records out
    4194304000 bytes (4.2 GB, 3.9 GiB) copied, 19.1016 s, 220 MB/s
    

    count=1024 代表设置1G大小swap分区,

    我这里设置了4096,也就是4个G大小的swap分区。

    2 设置交换分区文件

    标准格式:

    $ mkswap /mnt/swap
    

    运行后:

    lk@Mibook:~$ sudo mkswap /mnt/swap
    Setting up swapspace version 1, size = 3.9 GiB (4194299904 bytes)
    no label, UUID=3a46d2c9-2eba-433e-ba9a-6e98a5f256ba
    

    3 立即启用交换分区文件

    标准格式:

    $ swapon /mnt/swap
    

    运行后:

    lk@Mibook:~$ sudo swapon /mnt/swap
    swapon: /mnt/swap: insecure permissions 0644, 0600 suggested.
    

    查看一下/etc/rc.local,如果有swapoff -a需要改为swapon -a

    lk@Mibook:~$ cat /etc/rc.local
    #!/bin/sh -e
    #
    # rc.local
    #
    # This script is executed at the end of each multiuser runlevel.
    # Make sure that the script will "exit 0" on success or any other
    # value on error.
    #
    # In order to enable or disable this script just change the execution
    # bits.
    #
    # By default this script does nothing.
    
    exit 0
    

    4 设置开机启用swap分区

    查看/etc/fstab文件

    lk@Mibook:~$ cat /etc/fstab
    # /etc/fstab: static file system information.
    #
    # Use 'blkid' to print the universally unique identifier for a
    # device; this may be used with UUID= as a more robust way to name devices
    # that works even if disks are added and removed. See fstab(5).
    #
    # <file system> <mount point>   <type>  <options>       <dump>  <pass>
    # / was on /dev/sda2 during installation
    UUID=388726b7-1e49-429a-b9dd-7e285931159b /               ext4    errors=remount-ro 0       1
    # /boot/efi was on /dev/sda1 during installation
    UUID=5E17-7068  /boot/efi       vfat    umask=0077      0       1
    # swap was on /dev/sda3 during installation
    UUID=600bf7c2-11c2-4e94-9ee5-062cbe203afc none            swap    sw              0       0
    

    使用sudo gedit /etc/fstab命令打开,在末尾添加/mnt/swap swap swap defaults 0 0

    再次使用cat /etc/fstab命令查看确认

    lk@Mibook:~$ cat /etc/fstab
    # /etc/fstab: static file system information.
    #
    # Use 'blkid' to print the universally unique identifier for a
    # device; this may be used with UUID= as a more robust way to name devices
    # that works even if disks are added and removed. See fstab(5).
    #
    # <file system> <mount point>   <type>  <options>       <dump>  <pass>
    # / was on /dev/sda2 during installation
    UUID=388726b7-1e49-429a-b9dd-7e285931159b /               ext4    errors=remount-ro 0       1
    # /boot/efi was on /dev/sda1 during installation
    UUID=5E17-7068  /boot/efi       vfat    umask=0077      0       1
    # swap was on /dev/sda3 during installation
    UUID=600bf7c2-11c2-4e94-9ee5-062cbe203afc none            swap    sw              0       0
    /mnt/swap swap swap defaults 0 0
    

    5 查看新的分区情况

    再次执行free -m查看效果

    lk@Mibook:~$ free -m
                  total        used        free      shared  buff/cache   available
    Mem:           3828        1233         254         965        2340        1352
    Swap:          4976         806        4169
    

    发现swap分区变成了4个G,扩展成功。


    参考文章阿里云ECS Linux开启swap(虚拟内存)

    展开全文
  • Linux 如何调整虚拟内存大小

    千次阅读 2015-01-28 07:00:31
    我用的CentOS, 直接贴完整的调整虚拟内存步骤,供参考: [root@1pad ~]# free -m  total used free shared buffers cached Mem: 3702 722 2979 0 149 230
  • Linux虚拟内存管理(glibc) mmap sbrk

    千次阅读 2015-01-15 14:03:18
    在使用mysql作为DB开发的兑换券系统中,随着分区表的...因此,需要深入学习 Linux虚拟内存管理方面的内容来解释这个现象; Linux虚拟内存管理有几个关键概念:  1、每个进程都有独立的虚拟地址空间,进程访问的
  • 1.1 Linux虚拟内存管理有几个关键概念: 1、每个进程都有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址; 2、虚拟地址可通过每个进程上的页表(在每个进程的内核虚拟地址空间)与物理地址进行...
  • 十问 Linux 虚拟内存管理 (glibc) (一)

    千次阅读 2016-11-07 16:28:31
    因此,需要深入学习 Linux虚拟内存管理方面的内容来解释这个现象。 Linux虚拟内存管理有几个关键概念: 每个进程有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址 虚拟地址可...
  • Linux虚拟内存系统常用参数说明

    千次阅读 2014-02-19 12:28:10
    1.admin_reserve_kbytes ...这些内存是为了给管理员登录和杀死进程恢复系统提供足够的内存。 2.block_dump  如果设置的是非零值,则会启用块I/O调试。更多的关于块I/O调试的信息可以参见Documentation/laptops/lapto
  • linux添加swap虚拟内存

    千次阅读 2017-02-23 14:45:56
    1.swap概述Swap分区,即交换区,Swap空间的作用可简单描述为:当系统的物理内存不够用的时候,就需要将物理内存中的一部分空间释放出来,以供当前运行的程序使用。那些被释放的空间可能来自一些... 其实,Swap的调整
  • Linux】设置虚拟内存

    千次阅读 2019-09-04 09:39:31
    Linux】设置虚拟内存 原文链接:https://blog.csdn.net/wr132/article/details/56837266 很多人的Linux是安装在虚拟机上,或者运行在一些嵌入式设备上,没有足够的物理内存,如果在执行一些消耗内存的任务...
  • Linux系统采用了虚拟内存管理机制,就是交换和请求分页存储管理技术。这样,当进程运行时,不必把整个进程的映像都放在内存中,只需在内存保留当前用到的那一部分页面。当进程访问到某些尚未在内存的页面时,就由...
  • 交换分区实际存在于磁盘中,不过Linux系统可以将它当作内存使用,当物理真实内存不足的时候交换分区就可以和真实内存进行数据交换。简单地说就是从磁盘里拿出一块空间当作内存的储备区。虽说磁盘被拿来当作内存使用...
  • Linux内存使用调整

    千次阅读 2017-05-09 08:26:08
    Linux内存使用调整 2014-09-17 17:44 4308人阅读 评论(0) 收藏 举报 版权声明:本文为博主原创文章,未经博主允许不得转载。 前段时间在做播放器的时候,遇到个问题,花了很长时间,做个记录,希望对有需要...
  • Linux内核虚拟内存之页表管理

    千次阅读 2017-09-15 11:51:54
    linux内核 页表管理
  • Linux SWAP分区和虚拟内存

    千次阅读 2016-05-15 22:16:31
    也好,解决办法就是把存不下的东西转移到硬盘上的“虚拟内存”中去,尽管硬盘的速度比内存慢上很多,但是至少容量要大上很多。 另外,操作系统也可以把一些很久不活动的程序转移到虚拟内存中去,留出更多的主内存给...
  • Linux性能调优之虚拟内存

    千次阅读 2016-07-08 14:49:38
    简单地说,虚拟内存就是硬盘中的一块区域,它用来存放内存里使用频率不高的页面文件。好处是为了提高内存的利用率 - 让使用频率高的页面文件活动在内存区域中,提高CPU对数据操作的速度。在Linux中,这个区域叫做...
  • tomcat虚拟内存限制调整

    千次阅读 2014-01-03 14:12:22
    windows 下 tomcat 虚拟内存配置  在tomcat的bin目录下,找到catalina.bat 文件,打开,在最上面添加这样一句:   set JAVA_OPTS=-Xms256m -Xmx512m    Eclipse中设置tomcat 虚拟内
  • linux 使用文件增加虚拟内存 swap

    千次阅读 2019-03-25 09:30:36
    之前买了个云服务器玩,不过是最低配置的1核1G,后来发现这个内存太小了,随便装几个软件就不行了,内存消耗较大的像 redis 运行起来很多问题。 这些时间了解了下 docker 容器,去尝试了下发现 mysql 就挂了,而且...
  • 进程的虚拟地址空间和内核的虚拟地址管理方法不一样,不论应用程序如何切换,内核始终是一个并且其一直驻留在内存中,而进程则不同,可以有多个进程同时驻留在内存中,并且从各个进程的角度来看,呈现的系统是一
  • Linux(ubuntu)虚拟内存的设置(转)

    千次阅读 2017-11-03 18:54:35
    耐之下只能想看能不能通过设置虚拟内存大小来解决问题。所以在网上找到了这个设置虚拟内存大小的东西。转自:http://www.2cto.com/os/201205/132352.htmlLinux(ubuntu)虚拟内存的设置 swap空间就是虚拟内存,在...
  • 为什么linux下多线程程序如此消耗虚拟内存

    万次阅读 多人点赞 2015-01-27 21:11:38
    进行服务器内存优化,发现一个非常奇妙的问题,我们的认证服务器(AuthServer)负责跟第三方渠道SDK打交道(登陆和充值),由于采用了curl阻塞的方式,所以这里开了128个线程,奇怪的是每次刚启动的时候占用的虚拟内...
  • linux内存管理理解之释放linux内存

    千次阅读 2015-11-21 16:24:47
    总有很多朋友对于Linux内存管理有疑问,之前一篇[转]理解Linux的性能日志似乎也没能清除大家的疑虑。而在新版核心中,似乎对这个问题提供了新的解决方法,特转出来给大家参考一下。最后,还附上我对这方法的意见,...
  • Linux虚拟地址空间

    千次阅读 2018-11-02 21:18:47
    Linux虚拟地址空间 注:本文来自多篇博客整理,具体博客链接在博客下方 在多任务操作系统中,每个进程都运行在属于自己的内存沙盘中。这个沙盘就是虚拟地址空间(Virtual Address Space),在32位模式下它是一个4GB的...
  • Linux内存布局、内存分配原理

    万次阅读 2017-05-21 22:09:09
    Linux虚拟内存管理有几个关键概念 1.每个进程有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址 2.虚拟地址可通过每个进程的页表与物理地址进行映射,获得真正物理地址 3.如果虚拟地址对应的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 58,137
精华内容 23,254
关键字:

linux虚拟内存调整

linux 订阅