Linux内存管理是linux操作系统的子系统之一,是一个非常重要的子系统,这是一个冗杂而又庞大的部分,和网络子系统的区别在于其和CPU架构和存储模型是息息相关的。内存管理到底是个什么意思?这里借用深入理解linux内核架构那本书对内存管理涵盖的领域概况:

l  内存中物理页的管理

l  分配大块内存的伙伴系统(buddy)

l  分配较小块内存的系统slab、slub、slob

l  分配非连续内存块vmalloc

l  进程地址空间

1.1物理内存的布局何时获得

在x86系统下,在系统还是实模式时就调用bios中断获取物理内存的布局了。在arch/x86/boot/main.c函数中的main.c函数会调用detect_memory()函数获取当前内存布局。调用BIOS的功能通常称为e820,这是因为使用该获取内存布局功能时ax寄存的十六进制值是0xe820。detect_memory()函数定义于arch/x86/boot/memory.c文件中。

<pre name="code" class="cpp">static int detect_memory_e820(void)
{int count = 0;struct biosregs ireg, oreg;struct e820entry *desc = boot_params.e820_map;static struct e820entry buf; /* static so it is zeroed */initregs(&ireg);ireg.ax  = 0xe820;  ireg.cx  = sizeof buf;ireg.edx = SMAP;ireg.di  = (size_t)&buf;do {intcall(0x15, &ireg, &oreg);ireg.ebx = oreg.ebx; /* for next iteration... *//* BIOSes which terminate the chain with CF = 1 as opposedto %ebx = 0 don't always report the SMAP signature onthe final, failing, probe. */if (oreg.eflags & X86_EFLAGS_CF)break;/* Some BIOSes stop returning SMAP in the middle ofthe search loop.  We don't know exactly how the BIOSscrewed up the map at that point, we might have apartial map, the full map, or complete garbage, sojust return failure. */if (oreg.eax != SMAP) {count = 0;break;}*desc++ = buf;count++;} while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_map));return boot_params.e820_entries = count;
}

该函数的do while语句是一个遍历语句,其作用是遍历所有可用的物理内存段,并将物理内存段的个数(count值)记录在boot_params.e820_entries,boot_params是系统启动的参数,注意这里是探测可用的物理内存。

为了从纷繁的内存管理代码细节中脱离出来,这里不会像网络子系统那部分那样逐行代码去分析,而侧重功能和管理方法的分析。图1.1是X86 32bit情况下的内存使用分布情况。

虚拟内存到物理内存到物理内存的变换是由MMU完成的。内核空间的地址内容

X86虚拟机下dmesg导出的内核虚拟地址拓扑如下:

[    0.000000] virtual kernel memory layout:
[    0.000000]     fixmap  : 0xfff14000 - 0xfffff000   ( 940 kB)
[    0.000000]     pkmap   : 0xffc00000 - 0xffe00000   (2048 kB)
[    0.000000]     vmalloc : 0xf83fe000 - 0xffbfe000   ( 120 MB)
[    0.000000]     lowmem  : 0xc0000000 - 0xf7bfe000   ( 891 MB)
[    0.000000]       .init : 0xc19b9000 - 0xc1a93000   ( 872 kB)
[    0.000000]       .data : 0xc1663132 - 0xc19b8200   (3412 kB)
[    0.000000]       .text : 0xc1000000 - 0xc1663132   (6540 kB)

由这上述信息可以看到,虚拟内存主要分为lowmen、vmalloc、pkmap(persistentkernel map)、fixmap这四个类型,对应会有它们各自的管理代码,后面会叙述。

在嵌入式情景下,有略微的差别。下面是一个arm嵌入式系统dmesg导出的内存拓扑:

[    0.000000] Virtual kernel memory layout:
[    0.000000]     vector  : 0xffff0000 - 0xffff1000   (   4 kB)
[    0.000000]     fixmap  : 0xfff00000 - 0xfffe0000   ( 896 kB)
[    0.000000]     vmalloc : 0x86800000 - 0xff000000   (1928 MB)
[    0.000000]     lowmem  : 0x80000000 - 0x86600000   ( 102 MB)
[    0.000000]     modules : 0x7f000000 - 0x80000000   (  16 MB)
[    0.000000]       .text : 0x80008000 - 0x805c9f04   (5896 kB)
[    0.000000]       .init : 0x805ca000 - 0x808cdeac   (3088 kB)
[    0.000000]       .data : 0x808ce000 - 0x8091e920   ( 323 kB)
[    0.000000]        .bss : 0x8091e920 - 0x80965828   ( 284 kB)

上述内存拓扑的一个特点是虚拟内存的地址空间可以达到4G而不需要考虑实际物理内存的大小,虚拟内存是内存管理环节中的一环,在认识linux管理模型之前,了解CPU架构对内存的影响还是挺有好处的。

1.2 CPU架构

图1.2 CPU存储架构

图1.2可知,按CPU访问开销由低到高的存储类型依次是寄存器、L1cache、L2cache、L3cache以及主存。如果没有cache,只有register和主存,这就意味着CPU取指令或者数据时,CPU将会等待数据或者指令将需要很长时间,这降低的CPU的利用率。如果和指令相关的操作或者和数据相关操作都存储在寄存器里,那么CPU取指令和数据的开销几乎可以忽略不计。

Reg-A、Reg-B:是两个寄存器组,它们的功能和地位是一样的,在实现超线程技术时,两个线程使用不同的寄存器组,这就意味着寄存器内容在线程切换时不需要保护。CPU运算核心访问它们的周期约为一个时钟周期。

L1d和L1i,分别是数据和指令cache,数据和指令分开存放,运算核心访问其约需4个时钟周期。通常它的大小分别约32K。

L2cache:第二级缓存,为L1d和L1i共享,其容量通常是L1d、L1i总和的4倍。两个CPU有其各自的L2cache。访问约12cycle。

L3cache:为第三级缓存,其容量通常是单个L2的128倍,依据情况而不同,访问周期40cycle。

Cpu运算核心访问DDR的周期约为240cycle。

由cpu运算核心访问cache和DDR的时钟周期可知,cache在一定程度上缓解了慢速的主存和快速的CPU运算核心不匹配导致的效率变低的问题,但是也带来了内存管理的复杂性,这种复杂性由芯片设计人员和软件设计人员共同解决,芯片设计人员提供了TLB和MMU(针对分页和分段,实模式不会启用MMU)两个功能模块,软件设计人员需要对这两个功能模块以及cache做些配置和管理。

MMU(memory management unit)的主要作用是虚拟地址转换成实地址。在图1.1中,虚拟内存到物理内存的转换就是由MMU完成的。对内核而言,x86的虚拟地址到物理地址的转换关系是:

虚拟地址 = 物理地址 + PAGE_OFFSET

从上述的关系也可以得到物理地址,x86上还有段地址、线性地址,段地址在arm上没有类似的概念。

Cache部分的内容还挺多的,其原理可以参。考http://en.wikipedia.org/wiki/CPU_cache

第二章 物理内存管理模型

如何管理常见的4G或者更大的内存条,当前CPU倾向于不再提高CPU的主频,而朝着多核的方向发展,所以内存模型进一步被复杂化,且分为一致性内存(UMA)和非一致性内存(NUMA)两种情况。对于UMA情况,CPU访问任何一个存储空间的开销是等价的,而对于NUMA它们的开销则是不同的。

图2.1 UMA和NUMA内存示意

图2.1是UMA和NUMA的示意,左边三个CPU访问内存节点NODE0的开销是一样的,而右侧访问NODE0的开销CPU1是大于CPU2的。在后一种情况下,应该将CPU2需要的指令和数据放在NODE0中,将CPU1 需要的指令和数据放在NODE2中,这样提高系统的效率。处理器核以及内存的两种布局对应两种内存管理模型,linux将UMA这种模型作为NUMA这种模型的一个特例,当然混合型的也是NUMA的一个特例,对于混合型,只需看图2.1右侧的NUMA模型,两颗CPU访问NODE1的开销一样,而访问其它NODE节点的开销并不一样。

图2.2 内存管理模型

结合图2.2,分析一下linux内存管理模型,假设图中左上角那根内存条到各个CPU的开销是一样的且整个系统只有这一根内存条,则可以认为该系统的内存模型退化为图2.1中左侧的UMA模型,该内存条使用node表示,对应的其管理数据结构是pg_data_t,该node又分为若干类型的zone,zone的类型是有限的,根据架构不同而略有区别,对于IA-32架构,有ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM以及ZONE_MOVABLE;这对应于图的中上部。

ZONE_DMA和早期的ISA设备是有关系的;

ZONE_NORMAL指示的是可直接映射到内核段的地址空间;

ZONE_HIGH是超出内核段的物理内存;

ZONE_MOVABLE旨在防止物理内存碎片。

为了防止内存碎片,将每个zone又分为五种类型,放大镜将ZONE_DMA类型的zone进行了放大,其它类型的zone组织内存的方式和其一样,所以这里只对ZONE_DMA进行分析,每个zone采用了反碎片技术,这一技术有别于磁盘的碎片整理,后者依赖于文件系统,将零散的小块合并成连续的大块,而反碎片技术从源头上阻止碎片的产生;依据内存使用的方式,将zone分为unmovable,reclaimable, movale,reserve类型。分别对应于不可移动,可回收,可移动,保留备用(紧急情况下使用)。

ZONE_DMA有16M大小,ZONE_NORMAL上界是896M,如何管理这么多的且变化的内存,仅仅分成五种类型还是不够的,linux使用了页管理技术,每4KB作为一个进行管理,此外还有一种4MB的大页,以提高TLB命中的效率。先不考虑大页,仅仅16M的存储空间就对应4096页,对于896M,将有229376个页,如何高效的管理这些页?linux采用了伙伴系统方法,这一方法已经包含在图2.2放大镜图像里了。Buddy system的核心思想是:

根据使用有些情况需要一个页4KB就可以了,也有需要8KB,依次类推可能需要1M等等,既然如此何不将4KB页合并成8KB,8KB的页合并成16KB,16KB合并成32KB,这样管理也方便。

图2.2中的0、1…11的意识是指2的0次方、1次方…11次方,2的0次方等于1,表示该链表所有员的大小是一个页4KB,2的1次方等于2,表示该链表的所有成员大小是8KB,依次类推,这个组织也可以参看图2.3,这样需要多大的内存就到哪个内存链表上去取(还要根据zone类型和反碎片类型)。

单单只有buddy还有一个问题,就是在需要2字节的内存时,去申请一个页大小的内存4KB,实在有点浪费,所以linux引入slab管理方法,针对嵌入式和服务器又提出了slob和slub管理模型,不过到此可以知道其实内存管理的核心的数据结构是node和zone。

一个具有NUMA的X86-64系统的实例如下:

$ cat /proc/pagetypeinfo
Page block order: 9
Pages per block:  512Free pages count per migrate type at order       0      1      2      3      4      5      6      7      8      9     10
Node    0, zone      DMA, type    Unmovable      1      0      1      0      2      1      1      0      1      0      0
Node    0, zone      DMA, type  Reclaimable      0      0      0      0      0      0      0      0      0      0      0
Node    0, zone      DMA, type      Movable      0      0      0      0      0      0      0      0      0      0      3
Node    0, zone      DMA, type      Reserve      0      0      0      0      0      0      0      0      0      1      0
Node    0, zone      DMA, type      Isolate      0      0      0      0      0      0      0      0      0      0      0
Node    0, zone    DMA32, type    Unmovable    276    395    221      1      0      0      0      0      0      0      0
Node    0, zone    DMA32, type  Reclaimable  27246  21166   8671     70      0      0      0      0      0      0      0
Node    0, zone    DMA32, type      Movable      3      8     11      0      0      0      0      0      0      0      0
Node    0, zone    DMA32, type      Reserve      0      0      0     11      1      0      0      0      0      0      0
Node    0, zone    DMA32, type      Isolate      0      0      0      0      0      0      0      0      0      0      0
Node    0, zone   Normal, type    Unmovable  10635     67      0      0      0      0      0      0      0      0      0
Node    0, zone   Normal, type  Reclaimable  28157      2      0      0      0      0      0      0      0      0      0
Node    0, zone   Normal, type      Movable      0      0      0      0      0      0      0      0      0      0      0
Node    0, zone   Normal, type      Reserve      0      0      0      9      4      2      1      0      0      1      0
Node    0, zone   Normal, type      Isolate      0      0      0      0      0      0      0      0      0      0      0 Number of blocks type     Unmovable  Reclaimable      Movable      Reserve      Isolate
Node 0, zone      DMA            1            0            6            1            0
Node 0, zone    DMA32           89         1103          334            2            0
Node 0, zone   Normal          225         5583          846            2            0
Page block order: 9
Pages per block:  512Free pages count per migrate type at order       0      1      2      3      4      5      6      7      8      9     10
Node    1, zone   Normal, type    Unmovable   2266   3095    185      0      0      0      0      0      0      0      0
Node    1, zone   Normal, type  Reclaimable 442807 302949  10893      0      0      0      0      0      0      0      0
Node    1, zone   Normal, type      Movable      0      1      2      0      0      0      0      0      0      0      0
Node    1, zone   Normal, type      Reserve      0      0      0      0      1      1      1      1      1      0      0
Node    1, zone   Normal, type      Isolate      0      0      0      0      0      0      0      0      0      0      0 Number of blocks type     Unmovable  Reclaimable      Movable      Reserve      Isolate
Node 1, zone   Normal          123         7791          276            2            0

2.1 Node

Linux使用pg_data_t管理内存模型,

图2.3 内存管理数据拓扑

在图2.1中,根据CPU访问内存的代价不一样,将每一个代价节点抽象成一个node,对于NUMA退化为一个node,需要注意的node节点数并不一定对应于内存条的个数。每一个node节点通常分为若干的域(zone)。

enum zone_type {ZONE_DMA,ZONE_DMA32,ZONE_NORMAL,ZONE_HIGHMEM,ZONE_MOVABLE,__MAX_NR_ZONES
};

因为并不是所有内存都支持DMA操作,所以这里专门开辟了一个称为ZONE_DMA类型的域以支持DMA操作,这块域大小依赖于体系结构,在i386上其小于16M,这在图1.1中物理内存有所显示。

ZONE_DMA32是针对x86架构64为处理器而准备的,因为其可以使用的DMA范围由16M拓展到了4G。

对于i386 ZONE_NORMAL的上限就是896M,是这部分内存是直接映射的。

对于i386 ZONE_HIGHMEM在ZONE_NORMAL之后,这部分的内存页是动态映射的,主要是因为页表项有限,其基本思想是需要高端内存时,申请页表进行映射,用完释放再回收页表。

<include/linux/mmzone.h>
700 typedef struct pglist_data {
/*内存管理类型,ZONE_DMA、ZONE_NORMAL、ZONE_HIGH701     struct zone node_zones[MAX_NR_ZONES];
/*每次内存申请会落到zonelist上,zonelist是zone(区)的列表,对于分配内存,第一个区是“全局”的,其它的zone是后备zone,后备zone按优先级递减排序,这里的优先级是指访问的代价,代价越大越靠后。对于UMA,node_zonelists只有一个成员,而对于NUMA,MAX_ZONELISTS的值是2,[0]是有后备zone的zonelist,而[1]没有后备zone,用于GFP_THISNODE*/702     struct zonelist node_zonelists[MAX_ZONELISTS];
/*701行实际的node_zones成员数*/703     int nr_zones;
/*非稀疏内存管理模型时,指向struct page中的第一个页面,其存放在mem_map数组中*/704 #ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */705     struct page *node_mem_map;
/*Page Cgroup,可看做mem_map的扩展,该结构体用于确定cgroup,LXC用到cgroup和命名空间*/706 #ifdef CONFIG_MEMCG707     struct page_cgroup *node_page_cgroup;708 #endif709 #endif
/*指向内存引导程序*/710 #ifndef CONFIG_NO_BOOTMEM711     struct bootmem_data *bdata;712 #endif713 #ifdef CONFIG_MEMORY_HOTPLUG714     /*715      * Must be held any time you expect node_start_pfn, node_present_pages716      * or node_spanned_pages stay constant.  Holding this will also717      * guarantee that any pfn_valid() stays that way.718      *719      * Nests above zone->lock and zone->size_seqlock.720      */721     spinlock_t node_size_lock;722 #endif
/*起始页帧号,对于UMA,该值是0,对于NUMA,该值随节点不同而不同,node_start_pfn全局唯一,由页帧号全局唯一性决定*/723     unsigned long node_start_pfn;724     unsigned long node_present_pages; /* total number of physical pages ,针对该node而言的总数*/725     unsigned long node_spanned_pages; /* total size of physical page726                          range, including holes */
//具有全局性,对于UMA,该值是1,对于NUMA从0开始计数727     int node_id;728     nodemask_t reclaim_nodes;   /* Nodes allowed to reclaim from */
/*swap dameon*/729     wait_queue_head_t kswapd_wait;730     wait_queue_head_t pfmemalloc_wait;731     struct task_struct *kswapd; /* Protected by lock_memory_hotplug() */
/*定义需要释放的区域长度*/732     int kswapd_max_order;733     enum zone_type classzone_idx;
747 } pg_data_t;

2.2 Zone结构

Zone的类型如下,对于x86, ZONE_HIGH是896M以上的空间,不能直接映射。

enum zone_type {
#ifdef CONFIG_ZONE_DMAZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32ZONE_DMA32,
#endifZONE_NORMAL,
#ifdef CONFIG_HIGHMEMZONE_HIGHMEM,
#endifZONE_MOVABLE,__MAX_NR_ZONES
};

zone结构体是

313 struct zone {
/*WMARK_MIN,WMARK_LOW,WMARK_HIGH,用于标记各阈值,影响kswap dameon的行为。
*WMARK_HIGH:内存不紧张。
*WMARK_LOW:内存有点紧张,需要将内存页换到外部存储设备(硬盘、SSD)
* WMARK_MIN :内存的最低阈值,这是回收页压力增大
由*_wmark_pages(zone)宏存取该字段
*/317     unsigned long watermark[NR_WMARK];318 319     /*320      * When free pages are below this point, additional steps are taken321      * when reading the number of free pages to avoid per-cpu counter322      * drift allowing watermarks to be breached323      */324     unsigned long percpu_drift_mark;325 326     /*每个内存区保留的内存页数。通过sysctl_lowmem_reserve_ratio sysctl 可以改变。*/334     unsigned long       lowmem_reserve[MAX_NR_ZONES];335 336     /*337      * 这是每个zone都会保留的页,本身和dirty没什么关系。339      */340     unsigned long       dirty_balance_reserve;341 /*每个CPU的冷热页帧列表,在高速缓存中的页称为“热”,不在高速缓存中的页称为“冷”*/
350     struct per_cpu_pageset __percpu *pageset;351     /*352      * 释放内存时使用该锁,防止并发353      */354     spinlock_t      lock;
/*标志是否所有也都不可回收*/355     int                     all_unreclaimable;
/*buddy系统核心数据结构,相见后面*/
368     struct free_area    free_area[MAX_ORDER];
/*对称多处理器情况下,可能对该结构的不同部分访问,ZONE_PADDING是按缓存行大小填充对其,这样并发访问该结构体时,可以通过访问两个缓存行以提高速度*/
389     ZONE_PADDING(_pad1_)390 391     /*这些是页回收扫描器使用的字段*/392     spinlock_t      lru_lock;393     struct lruvec       lruvec;394 395     unsigned long       pages_scanned;     /* since last reclaim */396     unsigned long       flags;         /* zone flags, see below */397 398     /* Zone 统计,percpu_drift_mark会和这里的统计的空闲也对比,以避免per-CPU计数器漂移*/399     atomic_long_t       vm_stat[NR_VM_ZONE_STAT_ITEMS];400
401     /*402      * 该zone的LRU链表上的活跃匿名页和非活跃匿名页比例
404      */405     unsigned int inactive_ratio;406 407 408     ZONE_PADDING(_pad2_)409     /* Rarely used or read-mostly fields */410 411     /*412      * wait_table       -- the array holding the hash table413      * wait_table_hash_nr_entries   -- the size of the hash table array414      * wait_table_bits  -- wait_table_size == (1 << wait_table_bits)
434      */435     wait_queue_head_t   * wait_table;436     unsigned long       wait_table_hash_nr_entries;437     unsigned long       wait_table_bits;438 439     /*440      * 该node节点所属的node节点441      */442     struct pglist_data  *zone_pgdat;443     /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT ,将物理地址转换为起始页帧号*/444     unsigned long       zone_start_pfn;445 446     /*447      * spanned_pages is the total pages spanned by the zone, including448      * holes, which is calculated as:449      *  spanned_pages = zone_end_pfn - zone_start_pfn;
450      *451      * present_pages is physical pages existing within the zone, which452      * is calculated as:453      *  present_pages = spanned_pages - absent_pages(pages in holes);454      *455      * managed_pages is present pages managed by the buddy system, which456      * is calculated as (reserved_pages includes pages allocated by the457      * bootmem allocator):458      *  managed_pages = present_pages - reserved_pages;459      *460      * So present_pages may be used by memory hotplug or memory power461      * management logic to figure out unmanaged pages by checking462      * (present_pages - managed_pages). And managed_pages should be used463      * by page allocator and vm scanner to calculate all kinds of watermarks464      * and thresholds.465      *466      * Locking rules:467      *468      * zone_start_pfn and spanned_pages are protected by span_seqlock.469      * It is a seqlock because it has to be read outside of zone->lock,470      * and it is done in the main allocator path.  But, it is written471      * quite infrequently.472      *473      * The span_seq lock is declared along with zone->lock because it is474      * frequently read in proximity to zone->lock.  It's good to475      * give them a chance of being in the same cacheline.476      *477      * Write access to present_pages and managed_pages at runtime should478      * be protected by lock_memory_hotplug()/unlock_memory_hotplug().479      * Any reader who can't tolerant drift of present_pages and480      * managed_pages should hold memory hotplug lock to get a stable value.481      */482     unsigned long       spanned_pages;483     unsigned long       present_pages;484     unsigned long       managed_pages;485 486     /*487      * rarely used fields:488      */489     const char      *name;490 } ____cacheline_internodealigned_in_smp;

2.3 page结构

每一个物理页都有一个struct page与之关联,以跟踪页使用情况,

41 struct page {42     /* First double word block */43     unsigned long flags;    45     struct address_space *mapping;  52     /* Second double word */53     struct {54         union {55             pgoff_t index;      /* 在映射页的偏移*/56             void *freelist;     /* slub/slob 第一个空闲对象 */57             bool pfmemalloc;    /*如果该标识由页分配器设置,则ALLOC_NO_WATERMARKS也被设置并且空闲内存量不满足low水印,这意味着内存有点紧张,调用者必须确保该页用于释放其它页之用。
66         };67 68         union {
74             /*75              * Keep _count separate from slub cmpxchg_double data.76              * As the rest of the double word is protected by77              * slab_lock but _count is not.78              */79             unsigned counters;
82             struct {83 84                 union {
86                      // 页表中指向该页的页表入口项(PTE)数,也被用作复合页的尾部页引用计数
101                     atomic_t _mapcount;
102
103                     struct { /* SLUB */
104                         unsigned inuse:16; //在使用的slub对象
105                         unsigned objects:15;//slub对象数
106                         unsigned frozen:1;
107                     };
108                     int units;  /* SLOB */
109                 };
110                 atomic_t _count;        /*使用计数器,为0则说明没有被内核引用,将可能被释放*/
111             };
112         };
113     };
114
115     /* Third double word block */
116     union {
117         struct list_head lru;   /* 换出页列表,由zone->lru_lock 锁保护 */
120         struct {        /* slub per cpu partial pages */
121             struct page *next;  /* Next partial slab */
126             short int pages;
127             short int pobjects;
129         };
130
131         struct list_head list;  /* slobs list of pages */
132         struct slab *slab_page; /* slab fields */
133     };
134
135     /* Remainder is not double word aligned */
136     union {
137         unsigned long private;      /*映射私有,用途自定,通常如果设置PagePrivate 标志,则用于buffer_heads,如果设置PageSwapCache,则用于swp_entry_t,如果设置PG_buddy,则用于伙伴系统*/
147         struct kmem_cache *slab_cache;  /* SL[AU]B: Pointer to slab */
148         struct page *first_page;    /* Compound tail pages */
149     };

flags相关的定义在include/linux/page-flags.h文件中,flag用于指示该页是否被锁住、是否是脏页,是否是活动的等,该文件中还定义了一些存取该变量的宏和函数,该标志在某些情况下是可能发生异步更新的。

*mapping如果其最低比特是0,则其指向inode(信息节点,文件系统相关)地址空间或者是NULL。如果映射成匿名内存,最低比特被置位并且其指向anon_vma对象。

第三章内存初始化

在使用内存时,需要知道内存的一些信息,比如物理内存到底有多大?内存是不是分节点的(访问的代价不一样)?启用分页情况下的页表设置情况?内存相关的初始化工作在start_kernel函数完成,下列列出了内存相关的函数。

图3.0.1内存初始化相关函数调用

Build_all_zonelists的作用是处理系统node间zone的备用关系,即如果有node0,node1,node2,如果node0中的某个类型的zone申请内存,发现内存不足,去该节点的其它类型zone也没有,这是向node1还是node2申请,这就牵涉到node1 的开销和node2的开销大小以及去哪个备用zone申请,该函数即完成此工作。

Mm_init停用boot内存分配器,启用伙伴内存管理。

进入setup_arch函数,不论是arm还是x86,映入眼帘的是_text,_etext,_edata,_end,__bss_stop这些变量。这些变量定义在arch/x86/kernel/vmlinux.lds.S文件,类似的将x86=换成arm也能看到arm的连接脚本里对这些变量的定义,这些变量的值在链接时才确定,具体来看,有一个System.map文件,通常和vmlinx在同一个文件夹,在System.map文件里可以找到上述变量的定义。该文件有如下几行,随编译结果不同而有区别:

c1000000 T _text
c1000000 T startup_32
c10000e0 t bad_subarch
c10000e0 W lguest_entry
c10000e0 W xen_entry
c10000e4 T start_cpu0
c10000f8 T _stext

c1000000即为3G+16M,这从一个方面验证了内核的代码段确实是从3G+16M开始的,这里的地址是虚拟地址。对于内核在物理内存中的分布情况,可以看/proc/iomem文件,该文件的一部分内容摘录如下:

00000000-00000fff : reserved
00001000-0009efff : System RAM
0009f000-0009ffff : reserved
000a0000-000bffff : PCI Bus 0000:00000a0000-000bffff : Video RAM area
000c0000-000c7fff : Video ROM
000ca000-000cbfff : reserved000ca000-000cafff : Adapter ROM
00100000-7fedffff : System RAM01000000-01663131 : Kernel code01663132-019b81ff : Kernel data01a9b000-01b81fff : Kernel bss

从上面可以看到内核代码从1M地方开始存放,前4k作为一个单独的页,其后的640KBIOS和显卡会使用这段区域,从640K到1M是ROM区域,所以linux内核选择从1M开始处连续存放。Setup_arch主要完成以下工作:

图3.0.2 和内存相关的内存初始化

页表初始化会在setup_arch函数完成,kernel_physical_mapping_init用于虚拟地址到物理地址的映射,和页表息息相关,不过在分析页表初始化过程之前先来点linux分页基础,至于分段arm上并不存在这一概念,所以这里直接略过了。

3.1 Linux分页

分页单元将线性地址转换成物理地址,该分页单元将检查访问的物理页是否有效,无效会产生缺页异常通知操作系统。X86上当CR0的PG位被置位时,则启用分页功能,否则,线性地址就是物理地址。现在的Linux采用了四级分页模型,四级页目录分别是:

页全局目录(PageGlobal Directory)

页上级目录(PageUpper Directory)

页中间目录(PageMiddle Directory)

页表(Page Table)

X86-64采用了四级页表,启用PAE特性的x86-32也此采用了四级页表,对于未启用PAE特性的32位系统,看一个三级页表映射到物理地址的过程:

32比特按页目录项10bit,页表10bit,页内偏移12bit,总比特数是10+10+12=32,符合32位系统划分,一个页的大小是4KB,要在这4KB大小里任意寻址,则需要12bit的地址,这正是页内偏移为12bit的原因。线性地址到物理地址的转换分为两步,首先根据线性地址高10比特找到页目录项,然后根据页目录项和线性地址的中间10比特找到页表,最后的12bit用于在页表中寻址,这一过程如下:

图3.1.1 80x86处理器分页

假设一个线性地址范围是0x2000_0000到0x2003_ffff,该地址对应的空间是用户空间,内核从3G开始,这一段包括64(0x2003_ffff-0x2000_0000+1 = 0x4_0000; 0x4_0000/1024=256KB;256KB/4KB=64)个页,

页目录项是线性地址最高10比特,对应的就是0x080,和CR3(不同进程CR3里的值不同)里的基地址值相加得到页表项,同理得到的页目录项作为页表的基地址,取线性地址接下来的10比特和页表项相加得到页的基地址,根据偏移量找到对应的比特,这就是这个查找过程。四级过程类似。

内存容量的物理限制源于芯片的地址总线和数据总线的位宽,从奔腾pro开始,芯片的数据总线从32位被扩展到36比特,这样实际可以访问的物理内存由先前的4GB扩展到64GB。而这一特性正是PAE特性。另外一个PSE(Physical Address Extension)从奔三引入。对于64比特系统,IA64采用了三级分页(不包括页内偏移),而x86_64使用了四级分页技术。

对于32位系统,通过将页上级目录和页中间目录设置成0,实际上加0等于什么也没有加。但是为了兼容32位和64位系统页上级目录和页中间目录还是一直存在的。

线性地址被划分成如下部分,定义在<pgtable-3level-types.h>/<pgtable-2level-types.h>;

PAGE_SHIFT:12比特,用于找到页表起始处。

PMD_SHIFT:页内偏移和页表的总比特数,用于找到页中间目录项,当未启用PAE时,其值为22(12位页内偏移以及10位页表项),当PAE启动时其值为21(12位页内偏移以及9位页表项)。

PUD_SHIFT:用于查找页上级目录,x86上,其值等于PMD_SHIFT。

PGDIR_SHIFT:页全局目录的起始地址处,未启用PAE时PGDIR_SHIFT的值是22,启用PAE时其值时30(12比特页内偏移,9比特页表长度,9比特页中间目录长度)。

PTRS_PER_PTE、PTRS_PER_PMD、PTRS_PER_PUD、PTRS_PER_PGD用于计算各入口项的总数。当PAE未启用时,它们值为1024、1、1以及1024,而启用PAE特性时,值为512,512,1和4。

3.2 Setup_arch

接着图3.0.2,在系统启动时PAE特性并未启用,所以页表退化为两级页表,临时页全局目录存放在swapper_pg_dir里,swapper_pg_dir实际存放在bss段,在链接时才会确定其地址,但是该变量的值可以看编译生成的System.map文件。

c19ff000 B __bss_start
c19ff000 R __smp_locks_end
c19ff000 b initial_pg_pmd
c1a00000 b initial_pg_fixmap
c1a01000 B empty_zero_page
c1a02000 B swapper_pg_dir

未启用PAE时,PAGE_SHIFT:12;PMD_SHIFT:22;PGDIR_SHIFT:22;

图3.2.1 和内存相关的内存初始化

Init_mem_mapping映射低端内存区,首先映射ISA区域而不管ISA区是否存在内存空洞,然后映射剩下的内存区,max_low_pfn记录的就是低端内存区的最后一个页帧号,根据其大小,按照4MB对其的方式,循环映射完低端内存。而early_ioremap_page_table_range_init负责固定内存映射,图1.1中的fixmap部分,load_cr3操作启用分页机制,而flush操作是保持cache的一致性。

3.2.1 低端内存映射初始化

图3.2.2init_mem_mapping流程

由于ISA以及其它区域建立映射过程类似,这里剖析ISA区域的映射过程:

init_memory_mapping(0,ISA_END_ADDRESS); // #define ISA_END_ADDRESS              0x100000

建立物理内存的直接映射。

//mr[0].start = 0; mr[0].end = 256 << 12; mr[0].page_size_mask =0, 对应于该函数的三个参数。

unsigned long __init
kernel_physical_mapping_init(unsigned long start,unsigned long end,unsigned long page_size_mask)
{int use_pse = page_size_mask == (1<<PG_LEVEL_2M);unsigned long last_map_addr = end;unsigned long start_pfn, end_pfn;pgd_t *pgd_base = swapper_pg_dir;int pgd_idx, pmd_idx, pte_ofs;unsigned long pfn;pgd_t *pgd;pmd_t *pmd;pte_t *pte;unsigned pages_2m, pages_4k;int mapping_iter;start_pfn = start >> PAGE_SHIFT; //起始页帧号0end_pfn = end >> PAGE_SHIFT; //结束的页帧号256mapping_iter = 1;repeat:pages_2m = pages_4k = 0;pfn = start_pfn; //起始页帧号保存
//页全局目录索引,PAGE_OFFSET是0Xc000_0000,对应pgd_idx是0xc00即768,其意义是内核在全局目录项映射的第一个项索引是第768项。pgd_idx = pgd_index((pfn<<PAGE_SHIFT) + PAGE_OFFSET);
//页全局目录项基地址+索引pgd = pgd_base + pgd_idx;
//知道页全局目录项有1024项,这里要对这1024项进行初始化。for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) {pmd = one_md_table_init(pgd); //对于未启用PAE机制,则pmd项只有一项,pmd_idx = 0; //PAE未启用时,该pmd项的索引值是0,唯一的。for (; pmd_idx < PTRS_PER_PMD && pfn < end_pfn;pmd++, pmd_idx++) {unsigned int addr = pfn * PAGE_SIZE + PAGE_OFFSET; //该页帧对应的线性地址,偏移量是3G。pte = one_page_table_init(pmd);//获得一个页表项。pte_ofs = pte_index((pfn<<PAGE_SHIFT) + PAGE_OFFSET);//获得页表项的索引。pte += pte_ofs;
//遍历页表for (; pte_ofs < PTRS_PER_PTE && pfn < end_pfn;pte++, pfn++, pte_ofs++, addr += PAGE_SIZE) {pgprot_t prot = PAGE_KERNEL;/** first pass will use the same initial* identity mapping attribute.*/pgprot_t init_prot = __pgprot(PTE_IDENT_ATTR);if (is_kernel_text(addr)) //内核代码段,则具有可执行权限prot = PAGE_KERNEL_EXEC;pages_4k++; //记录初始化的4KB页数量
/将pte和pfn关联/if (mapping_iter == 1) {set_pte(pte, pfn_pte(pfn, init_prot));last_map_addr = (pfn << PAGE_SHIFT) + PAGE_SIZE;} elseset_pte(pte, pfn_pte(pfn, prot));}}}if (mapping_iter == 1) {/** update direct mapping page count only in the first* iteration.*/update_page_count(PG_LEVEL_2M, pages_2m);update_page_count(PG_LEVEL_4K, pages_4k);/** local global flush tlb, which will flush the previous* mappings present in both small and large page TLB's.*/__flush_tlb_all();/** Second iteration will set the actual desired PTE attributes.*/mapping_iter = 2;goto repeat;}return last_map_addr;
}

3.2.2高端内区存固定映射初始化

高端内存域的初始化过程和低端内存初始化类似,但是只分配了页表,对应的PTE项并没有被初始化,初始化工作留到set_fixmap()函数建立相关页表和物理内存的关联。固定映射区分为几种索引类型,索引类型由枚举变量enum fixed_addresses定义,该初始化工作就是初始化这段区域。

图3.3.3

一个索引暂用一个4KB的页框,固定映射区的结束地址是FIXADDR_TOP,即0xfffff000(4G-4K),见图1.1。

static void __init
page_table_range_init(unsigned long start, unsigned long end, pgd_t *pgd_base)
{int pgd_idx, pmd_idx;unsigned long vaddr;pgd_t *pgd;pmd_t *pmd;pte_t *pte = NULL;unsigned long count = page_table_range_init_count(start, end);void *adr = NULL;if (count)adr = alloc_low_pages(count);vaddr = start;pgd_idx = pgd_index(vaddr);pmd_idx = pmd_index(vaddr);pgd = pgd_base + pgd_idx;
//遍历固定映射区,建立页表项for ( ; (pgd_idx < PTRS_PER_PGD) && (vaddr != end); pgd++, pgd_idx++) {pmd = one_md_table_init(pgd);pmd = pmd + pmd_index(vaddr);for (; (pmd_idx < PTRS_PER_PMD) && (vaddr != end);pmd++, pmd_idx++) {
//建立PTE项,并检查vaddr是否对应内核临时映射区,若是则重新申请一个页表来保存PTE项。pte = page_table_kmap_check(one_page_table_init(pmd),pmd, vaddr, pte, &adr);vaddr += PMD_SIZE;}pmd_idx = 0;}
}

3.2.3 persistent 内存初始化

图1.1中只剩下的是persistent memory初始化了。这一工作由图3.2.1中的paging_init完成。该函数还完成了解除虚拟内核0地址页的映射关系,这就是NULL指针所在的区域,用于异常捕捉。

<arch/x86/mm/init_32.c>
void __init paging_init(void)
{pagetable_init();__flush_tlb_all();kmap_init();/** NOTE: at this point the bootmem allocator is fully available.*/olpc_dt_build_devicetree();sparse_memory_present_with_active_regions(MAX_NUMNODES);sparse_init();zone_sizes_init();
}

Persistent memory页表在pagetable_init函数分配,映射在kmap_init完成。

static void __init permanent_kmaps_init(pgd_t *pgd_base)
{unsigned long vaddr;pgd_t *pgd;pud_t *pud;pmd_t *pmd;pte_t *pte;vaddr = PKMAP_BASE;
//为persistent 内存分配页表page_table_range_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base);pgd = swapper_pg_dir + pgd_index(vaddr);pud = pud_offset(pgd, vaddr);pmd = pmd_offset(pud, vaddr);pte = pte_offset_kernel(pmd, vaddr);pkmap_page_table = pte; //persistent页表项保存
}static void __init kmap_init(void)
{unsigned long kmap_vstart;/** Cache the first kmap pte:*/kmap_vstart = __fix_to_virt(FIX_KMAP_BEGIN);//persistent memory的物理地址到虚拟地址转换kmap_pte = kmap_get_fixmap_pte(kmap_vstart); //获得固定映射区的临时映射页表项的起始项kmap_prot = PAGE_KERNEL;
}

3.3 per-CPU area 初始化

在图3.0.1中,setup_per_cpu_areas用于初始化per-CPU区域,将.data.percpu中的数据拷贝到每个cpu的数据段,在SMP情况下,per-CPU可以提高并发性,是免锁算法的一种,有利于提高系统性能,一个经典的应用是:

网卡接收到数据包存在若干个队列中,队列个数和CPU的个数是一样的,这样多个CPU可以并发访问各自的接收队列而不需要锁。

3.4 节点(node)和域(zone)初始化

在图3.2.1中,提到了native_pagetable_init函数,该函数将剩下页表的映射工作完成。

图3.3 节点初始化

对于node、zone和page的关系,从图2.2可以看出,这里涉及带代码分析如何完成这种内存管理拓扑的。zone_sizes_init函数还是很好理解的。

573 void __init zone_sizes_init(void)
574 {
575     unsigned long max_zone_pfns[MAX_NR_ZONES];
576
577     memset(max_zone_pfns, 0, sizeof(max_zone_pfns));
578
579 #ifdef CONFIG_ZONE_DMA
580     max_zone_pfns[ZONE_DMA]     = MAX_DMA_PFN; // MAX_DMA_PFN=16M
581 #endif
582 #ifdef CONFIG_ZONE_DMA32//ia32未定义
583     max_zone_pfns[ZONE_DMA32]   = MAX_DMA32_PFN;
584 #endif
585     max_zone_pfns[ZONE_NORMAL]  = max_low_pfn; //max_low_pfn低端页帧的上限
586 #ifdef CONFIG_HIGHMEM
587     max_zone_pfns[ZONE_HIGHMEM] = max_pfn; //最大页帧号,用于高端内存
588 #endif
589
590     free_area_init_nodes(max_zone_pfns);
591 }

free_area_init_nodes的参数是zone数组,其作用是初始化每一个node节点,

在mm/page_alloc.c文件定义了两个局部全局变量:

static unsigned long __meminitdata arch_zone_lowest_possible_pfn[MAX_NR_ZONES];
static unsigned long __meminitdata arch_zone_highest_possible_pfn[MAX_NR_ZONES];

这两个变量用于标记每一个zone的起止边界页帧号。

5043 void __init free_area_init_nodes(unsigned long *max_zone_pfn)
5044 {
5045     unsigned long start_pfn, end_pfn;
5046     int i, nid;
5047
5048     /* 5048~5068确定每一个zone类型的起止页帧号 */
5049     memset(arch_zone_lowest_possible_pfn, 0,
5050                 sizeof(arch_zone_lowest_possible_pfn));
5051     memset(arch_zone_highest_possible_pfn, 0,
5052                 sizeof(arch_zone_highest_possible_pfn));
5053     arch_zone_lowest_possible_pfn[0] = find_min_pfn_with_active_regions();
5054     arch_zone_highest_possible_pfn[0] = max_zone_pfn[0];
5055     for (i = 1; i < MAX_NR_ZONES; i++) {
5056         if (i == ZONE_MOVABLE)
5057             continue;
5058         arch_zone_lowest_possible_pfn[i] =
5059             arch_zone_highest_possible_pfn[i-1];
5060         arch_zone_highest_possible_pfn[i] =
5061             max(max_zone_pfn[i], arch_zone_lowest_possible_pfn[i]);
5062     }
5063     arch_zone_lowest_possible_pfn[ZONE_MOVABLE] = 0;
5064     arch_zone_highest_possible_pfn[ZONE_MOVABLE] = 0;
5065
5066     /* Find the PFNs that ZONE_MOVABLE begins at in each node */
5067     memset(zone_movable_pfn, 0, sizeof(zone_movable_pfn));
5068     find_zone_movable_pfns_for_nodes();
5100     /*初始化每一个node */
//遍历每一个node
5103     for_each_online_node(nid) {
/**定义于include/linux/mmzone.h,对于单一节点,NODE_DATA定义如下:
#define NODE_DATA(nid)      (&contig_page_data), 5104行的node节点
*/
5104         pg_data_t *pgdat = NODE_DATA(nid);
5105         free_area_init_node(nid, NULL,
5106                 find_min_pfn_for_node(nid), NULL);
5107
5108         /* 如果node上存在页,则将其设置该node有内存状态为N_MEMORY,表示该node上有可用的内存。 */
5109         if (pgdat->node_present_pages)
5110             node_set_state(nid, N_MEMORY);
5111         check_for_memory(pgdat, nid);
5112     }
5113 }

5015行的free_area_init_node第一个参数是node(节点)ID,第二个参数在zone的大小,第三个参数的是该zone的其实页帧号,最后一个参数是该zone的空洞大小,在3.10版本。Node的核心初始化函数是free_area_init_core;

4592 static void __paginginit free_area_init_core(struct pglist_data *pgdat,
4593         unsigned long *zones_size, unsigned long *zholes_size)
4594 {
4595     enum zone_type j;
4596     int nid = pgdat->node_id; //节点ID号
4597     unsigned long zone_start_pfn = pgdat->node_start_pfn; //该node的起始页帧号
4598     int ret;
4599
4600     pgdat_resize_init(pgdat); //热插拔情况内存量可能会变化,重新计算
4606     init_waitqueue_head(&pgdat->kswapd_wait); //换页守护进程队列头初始化。
4607     init_waitqueue_head(&pgdat->pfmemalloc_wait);//尽最大努力分配内存队列头初始化。
4608     pgdat_page_cgroup_init(pgdat); //node节点页cgroup初始化
4609 //迭代每一个zone
4610     for (j = 0; j < MAX_NR_ZONES; j++) {
4611         struct zone *zone = pgdat->node_zones + j;
4612         unsigned long size, realsize, freesize, memmap_pages;
4613 //j类型的zone的页总数,可能包含空洞
4614         size = zone_spanned_pages_in_node(nid, j, zones_size);
4615         realsize = freesize = size - zone_absent_pages_in_node(nid, j, //realsize是真实长度,减去了hole
4616                                 zholes_size);
4617
4618         /*调整页,将其按4KB边界对其,影响水印值。
4623         memmap_pages = calc_memmap_size(size, realsize);
4635         /*DMA预留内存保留,不会计入到zone里 */
4636         if (j == 0 && freesize > dma_reserve) {
4637             freesize -= dma_reserve;
4638             printk(KERN_DEBUG "  %s zone: %lu pages reserved\n",
4639                     zone_names[0], dma_reserve);
4640         }
// nr_kernel_pages记录了DMA和NORMAL类型的页数,对不是高端内存情况需要加上。如果内核页足够多,需要进行调整,内核页的物理地址范围小于896M。
4642         if (!is_highmem_idx(j))
4643             nr_kernel_pages += freesize;
4644         /* Charge for highmem memmap if there are enough kernel pages */
4645         else if (nr_kernel_pages > memmap_pages * 2)
4646             nr_kernel_pages -= memmap_pages;
4647         nr_all_pages += freesize;
4648 //4649到4667初始化zone的相关成员
4649         zone->spanned_pages = size;
4650         zone->present_pages = realsize;
4656         zone->managed_pages = is_highmem_idx(j) ? realsize : freesize;
4663         zone->name = zone_names[j];
4664         spin_lock_init(&zone->lock);
4665         spin_lock_init(&zone->lru_lock);
4666         zone_seqlock_init(zone);
4667         zone->zone_pgdat = pgdat;
4668
4669         zone_pcp_init(zone); //zone的per-CPU成员初始化
4670         lruvec_init(&zone->lruvec);
4671         if (!size)
4672             continue;
4673
//伙伴系统的各阶初始化再次完成,从0阶到11阶
4676         ret = init_currently_empty_zone(zone, zone_start_pfn,
4677                         size, MEMMAP_EARLY);
4678         BUG_ON(ret);
4679         memmap_init(size, nid, j, zone_start_pfn); //处理内存域中的page实例,将其标记为可移动的。
4680         zone_start_pfn += size;
4681     }
4682 }

3.5 启用内核内存分配器

依然还是图3.0.1,这次是mm_init函数。

static void __init mm_init(void)
{/** page_cgroup requires contiguous pages,* bigger than MAX_ORDER unless SPARSEMEM.*/page_cgroup_init_flatmem();mem_init();kmem_cache_init();percpu_init_late();pgtable_cache_init();vmalloc_init();
}740 void __init mem_init(void)
741 {
742     int codesize, reservedpages, datasize, initsize;
743     int tmp;
761     /* this will put all low memory onto the freelists */
762     totalram_pages += free_all_bootmem(); //释放boot内存分配器
763
764     reservedpages = 0;
765     for (tmp = 0; tmp < max_low_pfn; tmp++)
769         if (page_is_ram(tmp) && PageReserved(pfn_to_page(tmp)))
770             reservedpages++;
771
772     after_bootmem = 1;
773
774     codesize =  (unsigned long) &_etext - (unsigned long) &_text; //内核代码段
775     datasize =  (unsigned long) &_edata - (unsigned long) &_etext; //
776     initsize =  (unsigned long) &__init_end - (unsigned long) &__init_begin;

下列dmesg出来的信息,就在该函数打印的。

[    0.000000] Memory: 2044624K/2096632K available (6539K kernel code, 640K rwda
ta, 2764K rodata, 872K init, 924K bss, 52008K reserved, 1183624K highmem)
[    0.000000] virtual kernel memory layout:
[    0.000000]     fixmap  : 0xfff14000 - 0xfffff000   ( 940 kB)
[    0.000000]     pkmap   : 0xffc00000 - 0xffe00000   (2048 kB)
[    0.000000]     vmalloc : 0xf83fe000 - 0xffbfe000   ( 120 MB)
[    0.000000]     lowmem  : 0xc0000000 - 0xf7bfe000   ( 891 MB)
[    0.000000]       .init : 0xc19b9000 - 0xc1a93000   ( 872 kB)
[    0.000000]       .data : 0xc1663132 - 0xc19b8200   (3412 kB)
[    0.000000]       .text : 0xc1000000 - 0xc1663132   (6540 kB)

kmem_cache_init初始化cache,vmalloc内存池初始化。

至此内存初始化流程结束。

内存管理-之启动-基于linux3.10相关推荐

  1. linux内存管理子系统采用基于内存区域,Linux 内存管理之highmem简介

    一.Linux内核地址空间 一般来说Linux 内核按照 3:1 的比率来划分虚拟内存(X86等):3 GB 的虚拟内存用于用户空间,1GB 的内存用于内核空间.当然有些体系结构如MIPS使用2:2 ...

  2. linux定时器tinner,第三章 套接字相关数据结构--基于Linux3.10

    本章是对socket通信过程中使用到的比较重要的据结构罗列和意义的阐述,在阅读其它层的代码前,先来看几个重要的数据结构,这几个数据结构贯串四层模型. 3.1 socket对应的内核结构体 在用户空间使 ...

  3. 别再说你不懂Linux内存管理了,10张图给你安排的明明白白!

    来自:后端技术学堂 过去的一周有点魔幻,有印象的有三个新闻:天猫总裁绯闻事件,蘑菇街裁员,不可能打工的周某也放出来了.三件事,两件和互联网行业相关,好像外面的世界很是精彩啊!吃瓜归吃瓜,学习还是不能落 ...

  4. 一个基于链表的内存管理方案

    在Open×××中,一种很不错的内存管理方案是基于链表的,该方案的实现使用了一个gc_arena结构体,该结构体的作用就是将所有的动态分配的内存块收集汇集起来,然后就可以在一个地方统一释放,c语言对动 ...

  5. Unity内存管理你应该知道的底层原理

    本文首发公众号洪流学堂.洪流学堂,让你快人几步. 本文主要是Unity官方川哥的视频<浅谈Unity内存管理>的笔记及相关知识点补充,如果有时间强烈建议学习原视频: https://www ...

  6. Linux内存管理(经典)

    今天来带大家研究一下Linux内存管理.对于精通 CURD 的业务同学,内存管理好像离我们很远,但这个知识点虽然冷门(估计很多人学完根本就没机会用上)但绝对是基础中的基础,这就像武侠中的内功修炼,学完 ...

  7. iOS夯实:内存管理

    最近的学习计划是将iOS的机制原理好好重新打磨学习一下,总结和加入自己的思考. 有不正确的地方,多多指正. 基本信息 Objective-C 提供了两种内存管理方式. MRR (manual reta ...

  8. 详细讲解Linux内核源码内存管理(值得收藏)

    Linux的内存管理是一个非常复杂的过程,主要分成两个大的部分:内核的内存管理和进程虚拟内存.内核的内存管理是Linux内存管理的核心,所以我们先对内核的内存管理进行简介. 一.物理内存模型 物理内存 ...

  9. [iOS]Advanced Memory Management Programming Guide 高级内存管理编程指南(官方文档翻译)

    Advanced Memory Management Programming Guide - 高级内存管理编程指南(官方文档翻译) 版权声明:本文为博主原创翻译,如需转载请注明出处. 新博客文章地址: ...

  10. 全网独一无二值得收藏的,Linux的内存管理

    Linux的内存管理 Linux的内存管理是一个非常复杂的过程,主要分成两个大的部分:内核的内存管理和进程虚拟内存.内核的内存管理是Linux内存管理的核心,所以我们先对内核的内存管理进行简介. 一. ...

最新文章

  1. [詹兴致矩阵论习题参考解答]习题6.6
  2. 摘要:ASP.NET的路由
  3. ML之K-means:基于K-means算法利用电影数据集实现对top 100 电影进行文档分类
  4. Java8 LinkedHashMap 源码阅读
  5. BZOJ1146[CTSC2008]网络管理——出栈入栈序+树状数组套主席树
  6. oVirt Reports Portal 3.4 added to ovirt-engine
  7. 2011东北地区赛G题(二分-网络流判可行性)
  8. VMware vSAN 7.0 发行说明
  9. mac分区数据恢复|mac分区后数据丢失怎么恢复?
  10. 什么叫反向链接?什么是死链接?什么是错误链接?
  11. php实现前后端完全分离
  12. 矩阵可逆与行列式关系的一个直观推导
  13. 前后端交互流程,如何进行交互
  14. 简单几步让WinUSB设备变为多端点设备
  15. MATLAB颜色映像
  16. 点评Hack易支付 - 免签约支付平台 -彩虹易支付,1分钟快速接入支付功能
  17. php 判断是否是机器人,PHP_php实现判断访问来路是否为搜索引擎机器人的方法,本文实例讲述了php实现判断访 - phpStudy...
  18. matlab非线性数值方程的求解
  19. 光纤通信技术期末复习提纲
  20. 芯片商洗牌战拉开序幕 指纹识别技术独霸市场

热门文章

  1. RocketMQ 延迟队列
  2. 利用css构建三角形(正三角,倒三角,左/右三角)
  3. 支付宝网关支付模式详细解答
  4. Pandas:利用Styler对象设置Series、Dataframe在Jupyter Notebook中的输出样式(2)——内置样式
  5. Android 时区中英文显示对照
  6. OpenERP 模块动态加载原理及启动代码分析
  7. OSChina 周日乱弹 —— 每天叫醒我的是Bug
  8. java多线程聊天室_JAVA多线程网络聊天室代码
  9. 【渝粤教育】电大中专中药炮制学作业 题库
  10. 全国计算机王牌专业的一本大学,全国大学最牛专业排行,报考必读!