大家好,如果您还对Linux内核内存管理全解析不太了解,没有关系,今天就由本站为大家分享Linux内核内存管理全解析的知识,包括的问题都会给大家分析到,还望可以解决大家的问题,下面我们就开始吧!
• 大地址空间;• 进程保护;• 内存映射;• 公平的物理内存分配;• 共享虚拟内存。实现结构分析
(1)内存映射模块(mmap):负责将磁盘文件的逻辑地址映射到虚拟地址,并将虚拟地址映射到物理地址。
(2)交换模块(swap)负责控制内存内容的换入换出,消除最近访问过的页面并保留最近访问过的页面。
(3)core(核心内存管理模块):负责内存管理功能。
(4)结构特定模块:实现虚拟内存的物理基础
视频推荐:
【Linux内核内存管理专题训练营】火热开营!!
Linux内核内存管理特训营-学习视频教程-腾讯课堂
两天连续技术输出:
第一天:
1.物理内存映射和空间划分
2.ARM32/64页表映射过程
3.分配物理页和Slab分配器
4.实战:VMA查找/插入/合并
第二天:
5.实战:mallocap系统调用实现
6.缺页中断处理/反向映射
7.回收页面/匿名页面生命周期
8.KSM实现/脏牛内存漏洞
内核空间和用户空间
Linux简化了分段机制,使虚拟地址与线性地址相同。 Linux将虚拟地址空间(4G)划分为最高1G部分供内核使用(所有进程共享)。进程使用最低的3G。
内核直接占用最高1G的虚拟空间,但是映射是从地址0开始的,是一个非常简单的线性映射。 PAGE_OFFSET为0xc0000000(物理地址与线性地址之间的位移)
include/asm/i386/page.h中内核空间地址映射的描述和定义
/** 这处理内存映射。我们可以将其作为一个配置*选项,但是太多人搞砸了,而需要*它的人太少了。** __PAGE_OFFSET 为0xC0000000 意味着内核有*一个虚拟地址空间1 GB,这将*您可以使用的物理内存量限制为大约950MB。**如果您想要比这更多的物理内存,请参阅内核配置中的CONFIG_HIGHMEM4G* 和CONFIG_HIGHMEM64G 选项。*/#define __PAGE_OFFSET (0xC0000000) ……#define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET) #define __pa(x) ((unsigned long)(x)-PAGE_OFFSET) #define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET )) 代码注释指出:如果你的物理内存大于950MB,则需要在编译内核时添加。
CONFIG_HIGHMEM4G 和CONFIG_HIGHMEM64G 选项,我们暂时不考虑这种情况。如果物理内存小于950MB,那么对于内核空间来说,给定一个虚拟地址x,它的物理地址为“x- PAGE_OFFSET”,给定一个物理地址x,它的虚拟地址为“x+ PAGE_OFFSET”。再次强调,宏__pa() 仅将内核空间中的虚拟地址映射到物理地址,绝不适用于用户空间。用户空间中的地址映射要复杂得多。 2. 内核镜像
内核的代码和数据称为内核映像。当系统启动时,Linux内核被加载到从0x001000000开始的物理地址。 1M起始间隔。然而
正常运行时,整个内核镜像都应该在虚拟空间中,因此,所有符号地址都应该加上PAGE_OFFSET。这样,内核镜像就位于0xc01000000处
进程的页目录PGD(属于内核数据结构)位于内核空间。当切换进程时,应将寄存器CR3设置为指向新进程的页目录PGD。目录的起始地址是内核空间中的虚拟地址,但CR3需要物理地址。在这种情况下,应该使用__pa()执行地址转换。 mm_context.h中有这么一行语句:asm volatile("movl %0,%%cr3" : "r"(__pa(next-pgd)));这是一行嵌入式汇编代码,表示下一个进程的页目录next_pgd的起始地址通过__pa()转换为物理地址,存储在寄存器中,然后使用mov指令。处理完这行语句后,CR3指向新的地址。接下来进程的页目录表是PGD。
虚拟内存地址实现机制之间的关系
• 内存分配和回收机制; • 地址映射机制; • 缓存和刷新机制; • 页面请求机制; • 交换机制; • 内存共享机制
内存管理程序通过映射将用户的逻辑地址映射到物理地址。用户程序运行时,发现虚拟地址与物理地址不对应,发出(1)请求。如果有空闲内存
如果可用于分配,则请求内存分配(2),并在页缓存中记录正在使用的物理页(3)。如果内存分配不够,调用swap机制释放一些内存(4) (5)
另外,地址映射时使用TLB来选择物理页(8);交换机制还使用交换缓存(6)。并且将物理页内容交换到交换文件后,还必须修改页表来映射文件地址(7)
---------------Linux内存管理初始化------------------
当Linux启动时,它以实模式运行。然后它切换到保护模式。
,
1页表的初始初始化/* * 此处页表仅初始化为8MB - 最终页表将在稍后根据内存大小进行设置。 */.org0x2000ENTRY (pg0) //存储虚拟地址.org0x3000ENTRY (pg1)/* *empty_zero_page必须紧跟在页表后面! (*初始化循环计数直到empty_zero_page) */.org0x4000ENTRY (empty_zero_page)/* *初始化页表*/movl $pg0-__PAGE_OFFSET,%edi /*初始化页表将edi中的物理地址存储在0x1002000处*/movl $007 ,%eax /* '007'并不代表有杀权,而是PRESENT+RW+USER */2: stosl add $0x1000,%eax cmp $empty_zero_page-__PAGE_OFFSET,%edi jne 2b内核执行这段代码时,页机制还没有启动,指令还是纯物理地址。因为pg0存储的是虚拟地址,所以$pg0-__PAGE_OFFSET获取其物理地址
pg0存储在相对于内核代码起始点的0x2000处,即物理地址为0x00102000,而pg1的物理地址为0x00103000。两个页表Pg0和pg1中的条目依次设置为0x007、0x1007、0x2007等。最低三位全为1,表示这两个页面是用户页面,可写,页面内容在内存中(见图2.24)。映射的物理页的基地址为0x0、0x1000、0x2000等,分别是物理内存中的第0、1、2、3等页。总共映射了2K页,即8MB的存储空间。可见Linux内核的最小物理内存需求为8MB。接下来存储empty_zero_page页(即零页)。零页存储系统启动参数和命令行参数。
启动分页机制--------------------
代码函数:将页目录swapper_pg_dir的物理地址添加到cr3中。并将cr0的最高位置设置为1
/* * 这被初始化以在0-8M 处创建一个身份映射(用于启动* 目的),并在虚拟地址* PAGE_OFFSET 处创建另一个0-8M 区域的映射。 */.org0x1000ENTRY (swapper_pg_dir) .long0x00102007 //这两个页表是用户页表,可写,页表内容在内存中。long0x00103007 .fill BOOT_USER_PGD_PTRS-2,4,0 /* default: 766 个条目*/.long0x00102007 .long0x00103007 /* default: 254 个条目*/.fill BOOT_KERNEL_PGD_PTRS-2,4,0/* * 启用分页* /333 360 移动$swapper_pg_dir-__PAGE_OFFSET,%eax movl %eax,%cr3 /* 设置页表指针. */movl %cr0,%eax orl $0x80000000,%eax movl %eax,%cr0 /* .并设置分页( PG) bit */jmp 1f /* 刷新预取队列*/1: movl $1f,%eax jmp *%eax /* 确保eip 被重定位*/物理内存描述
Linux将物理内存分为三个层次的管理:存储节点、管理区域和页表,并使用三种相应的数据结构来描述。
页面数据结构
物理页的描述
struct page{struct list_head 列表; //通过使用它进入双向链队列中以下数据结构free_area_struct结构struct address_space *映射; //内存交换的数据结构unsigned long index; //当页面进入交换文件时,指向其目标struct page *next_hash; //指向自身的指针,这样就可以链接成链表atomic t count; //用于页面交换的计数,如果页面空闲则为0,分配赋值为1,未建立或者映射恢复一次时加1,映射断开时减1 unsigned long旗帜; //反映页面的各种状态,如active、inactive dirty、inactive clean、idle struct list_head lru;无符号长年龄; //表示页面生命周期wait_queue_head_t wait;struct page ** pprev_hash;struct buffer_head * buffers;void * virtualstruct zone_struct * zone; //指向所属的管理区域}系统的每个物理页都有一个Page结构,系统初始化内存的大小,创建一个Page Array mem_map
为了有效管理物理页,Linux将物理页分为3个区域:
• 专用于DMA 的ZONE_DMA 区域(小于16MB); • 常规ZONE_NORMAL 区域(大于16MB 且小于896MB); • 无法由内核直接映射的区域ZONE_HIGME 区域(大于896MB)。 DMA控制器不能依赖CPU的内部MMU将连续的虚拟内存页映射到物理上连续的页,因此用于DMA的物理页必须单独管理。
存储节点数据结构
typedef struct pglist_data { zone_t node_zones[MAX_NR_ZONES];//节点最大3个页面管理区域zonelist_t node_zonelists[GFP_ZONEMASK+1];//指向上述管理区域的管理区域指针数组int nr_zones; struct page *node_mem_map;//指向特定节点的page结构数组unsigned long *valid_addr_bitmap;结构bootmem_data *bdata;无符号长node_start_paddr;无符号长node_start_mapnr;无符号长节点大小; int 节点ID; struct pglist_data *node_next;//形成单链表节点队列} pg_data_t;typedef struct zonelist_struct { zone_t *zone[MAX_NR_ZONE+1]; //NULL分隔管理区域Int gfp_mast; zonelist_tzone[] 是一个指针数组。每个元素以特定的顺序指向特定的页面管理区域,表示分配页面时首先尝试zone[。 0指向的管理区域]。如果不能满足要求,则尝试zone[1]指向的管理区域。
内存管理区
typedef struct zone_struct { /* * 常用字段: */spinlock_t lock;用于串行访问该结构体中的其他字段unsigned long free_pages;//该区域中现有的空闲页数unsigned long pages_min,pages_low,pages_high;//该区域中最小、次小、最大页数的描述int need_balance;//与kswapd集成/* *不同大小的空闲区域*/free_area_t free_area[MAX_ORDER];/伙伴分配系统中的位图数组和页链表/* * Discontig内存支持字段。 */struct pglist_data *zone_pgdat;//此管理区域所在的存储节点struct page *zone_mem_map;//此管理区域的内存映射表unsigned long zone_start_paddr;//此管理区域的物理地址unsigned long zone_start_mapnr; //mem_map索引/* *很少使用的字段: */char *name;无符号长尺寸; } zone_t;free_area[MAX_ORDER] 是一组队列的链表。每个队列保存1 2 4.各一个用于分配物理大小的块
type struct free_area_struct { struct list_head free_list unsigned int *map } free_area_t 物理页分配和释放Linux 还将不连续存储空间归类为非均匀存储结构(NUMA)。这是因为不连续存储空间本质上是广义的NUMA,因为它意味着最低物理地址和最高物理地址之间存在空洞,有空洞的空间当然是“不一致”的。因此,一个地址不连续的物理空间必须被划分为若干个连续的、统一的“节点”,就像不同结构的物理空间一样。因此,在存储结构不连续的系统中,每个模块都有多个节点,因此有一个pg_data_t数据结构队列。我们首先看一下mm/numa.c中的alloc_page()函数:
//表示采用哪种分配策略,order表示需要的物理块的大小,1,2,4.struct page * _alloc_pages(unsigned int gfp_mask, unsigned int order) { struct page *ret=0; pg_data_t *开始,*临时; #ifndef CONFIG_NUMA 无符号长标志;静态pg_data_t *next=0; #endif if (order=MAX_ORDER) 返回NULL; #ifdef CONFIG_NUMA temp=NODE_DATA(numa_node_id()); //通过NUMA_DATA()找到cpu节点的数据结构队列,保存在temp中#else spin_lock_irqsave(node_lock, flags); if (!next) next=pgdat_list;温度=下一个;下一个=下一个节点_下一个; spin_unlock_irqrestore (node_lock, 标志); #endif开始=临时; while (temp) { if ((ret=alloc_pages_pgdat (temp, gfp_mask, order))) //从当前节点扫描到最后一个节点,是否可以满足分配的内存return (ret); temp=temp-node_next; } 临时=pgdat_list; while (temp !=start) {//从头节点扫描到当前节点,view分配内存if ((ret=alloc_pages_pgdat(temp, gfp_mask, order))) return (ret); temp=temp-node_next;返回(0); }为一致存储结构(UMA)中的页面分配连续的空间。 UMA结构体的alloc_page()定义在include/linux/mm.h中:
#ifndef CONFIG_DISCONTIGMEM static inline struct page * alloc_pages(unsigned int gfp_mask, unsigned int order){ /* * 被编译器优化掉。 */if (order=MAX_ORDER) return NULL; return __alloc_pages(gfp_mask, order, contig_page_data.node_zonelists+ (gfp_mask GFP_ZONEMASK)); #endi_alloc_pages() 在管理区域列表zonelist中依次搜索各个区域,找到符合要求的区域,然后使用伙伴算法从这个区域中分配给定大小(2阶)的页块。如果所有区域都没有足够的空闲页,则调用交换器或bdflush内核线程将脏页写入磁盘以释放一些页。并且映射的页面只能被标记。
仅在当前需要分配时分配
struct page * __alloc_pages (unsigned int gfp_mask, unsigned int order, zonelist_t *zonelist) { unsigned long min; zone_t **区域,*类区域;结构页*页; int 已释放;区域=区域列表区域;类区=*区;最小=1UL 订单; for (;) {//遍历各个zone的空闲页总量zone_t *z=* (zone++); if (!z) 中断; min +=z-pages_low; if (z-free_pages min ) {//如果素数大于最低水平线与请求页数之和,则调用rmqueue() 尝试分配page=rmqueue(z, order); if (page) return page;//赋值成功,返回第一页指针} } 如果发现管理区的空闲页总量已经下降到最低点,则需要重新平衡zone_t结构
标志(need_balance)设置为1,如果内核线程kswapd正在等待队列中休眠,则将其唤醒并让其回收一些页面以供使用(可见need_balance是与kswapd配合使用的)。
类区-need_balance=1; mb();如果(waitqueue_active(kswapd_wait))wake_up_interruptible(kswapd_wait);如果给定的分配策略中的所有页管理区都分配失败,则将原来的最低水位除以4并降低。看看是否可以是否满足要求,通过调用rmqueue分配并返回即可满足
区域=区域列表区域;最小=1UL 订单; for (;) { unsigned long local_min; zone_t *z=*(zone++); if (!z) 中断; local_min=z-pages_min; if (!(gfp_mask __GFP_WAIT) ) local_min=2; min +=local_min; if (z-free_pages min) { page=rmqueue(z, order); if (page) 返回页面;如果分配不成功,那么需要查看什么样的进程正在请求分配内存页面,PF_MEMALLOC表示正在分配页面的进程。
PF_MEMDIE为1,表示进程因内存溢出而被杀死。这两种类型必须为进程分配页面。
if (当前标志(PF_MEMALLOC | PF_MEMDIE)) { zone=zonelist-zones; for (;) { zone_t *z=*(zone++); if (!z) 中断;页面=rmqueue(z, 顺序); if (page) 返回页面;返回NULL;分配的页面不能被等待,也不能被调度。不分配页面直接返回。
/* 原子分配- 我们无法平衡任何东西*/if (! (gfp_mask __GFP_WAIT)) return NULL;如果必须获取页面的进程尚未分配页面,则必须调用它
Balance_classzone()函数释放当前进程占用的本地页面。如果释放成功,则返回一个页结构指针,指向页块中第一页的起始地址。
page=Balance_classzone (classzone, gfp_mask, order, freed); if (page) 返回页面;从而继续分配页面
上面重复调用了requeue()函数,它尝试从一个页管理区域分配几个连续的内存页。这是最基本的分配操作,其具体代码如下:
//指向要分配page的管理区域,order代表2个分配的page的顺序次幂static struct page * rmqueue(zone_t *zone, unsigned int order) {//area指向free_area的order元素free_area_t *面积=区域-
free_area + order; unsigned int curr_order = order; struct list_head *head, *curr; unsigned long flags; struct page *page; spin_lock_irqsave(&zone->lock, flags); do { head = &area->free_list; curr = memlist_next(head); if (curr != head) { unsigned int index; //获得空闲块的第 1 个页面的地址,如果这个地址是个无效的地址,就陷入 BUG()page = memlist_entry(curr, struct page, list); if (BAD_RANGE(zone,page)) BUG();//从队列中摘除分配出去的页面块。 memlist_del(curr); index = page - zone->zone_mem_map; if (curr_order != MAX_ORDER-1) //如果某个页面块被分配出去,就要在 frea_area 的位图中进行标记,这是通过调用 MARK_USED()宏来完成的。 MARK_USED(index, curr_order, area); zone->free_pages -= 1UL<< order; //如果分配出去后还有剩余块,就通过 expand()获得所分配的页块,而把剩余块链入适当的空闲队列中。 page = expand(zone, page, index, order, curr_order, area); spin_unlock_irqrestore(&zone->lock, flags); set_page_count(page, 1); if (BAD_RANGE(zone,page)) BUG(); if (PageLRU(page)) BUG(); if (PageActive(page)) BUG(); return page; } curr_order++; area++; } while (curr_order< MAX_ORDER);//如果当前空闲队列没有空闲块,就从更大的空闲块队列中找。 spin_unlock_irqrestore(&zone->lock, flags); return NULL; }expand体现了伙伴算法 /*zone指向已分配页块所在的管理区page指向一分配的页块index为一分配的页块在mem_map中的下标;low表示所需页面块的大小为2的low次方high表示从实际空闲队列中实际分配的页面块大小为2的high次方area指向要实际分配的页块 */static inline struct page * expand (zone_t *zone, struct page *page, unsigned long index, int low, int high, free_area_t * area) { unsigned long size = 1<< high;//初始化为分配块的页面数 while (high >low) { if (BAD_RANGE(zone,page)) BUG(); area--; high--; size >>= 1; memlist_add_head(&(page)->list, &(area)->free_list); /*然后调用 memlist_add_head()把刚分配出去的页面块又加入到低一档(物理块减半)的空闲队列中准备从剩下的一半空闲块中重新进行分配*/ //MARK_USED()设置位图 MARK_USED(index, high, area); index += size; page += size; } if (BAD_RANGE(zone,page)) BUG(); return page; }Slab的数据结构 对于预期频繁使用的内存区,可以创建一组特定大小的专用缓冲区进行处理,以避免内碎片的产生。对于较少使用的内存区,可以创建一组通用缓冲区(如 Linux 2.0 中所使用的 2 的幂次方)来处理,即使这种处理模式产生碎片,也对整个系统的性能影响不大。 slab有2种数据结构:描述缓冲区的结构keme_cache_t,描述Slab的结构kmem_slab_t slab是slab管理最基本的结构 typedef struct slab_s { struct list_head list; unsigned long colouroff;//slab上着色区的大小 void *s_mem; /*指向对象区的起点 */ unsigned int inuse; /* 分配对象的个数 */ kmem_bufctl_t free;//空闲对象链的第一个对象 } slab_t;每个缓冲区管理一个Slab链表.Slab按序分3组,第一组全满的Slab(没空闲的对象). 第二组只有部分对象被分配,部分对象还空闲,最后一组slab的对象则完全空闲. 为了对slab有效管理,每个缓冲区都有一个轮转锁,在对链表进行修改时用这锁进行同步 struct kmem_cache_s {/* 1) each alloc & free */ /* full, partial first, then free */ struct list_head slabs_full; struct list_head slabs_partial; struct list_head slabs_free; unsigned int objsize;原始的数据结构的大小.初始化为kemem_cache_t的大小 unsigned int flags; /* constant flags */ unsigned int num; //每个slabobj的个数 spinlock_t spinlock;#ifdef CONFIG_SMP unsigned int batchcount;#endif/* 2) slab additions /removals */ /* order of pgs per slab (2^n) */ unsigned int gfporder;//gfporder 则表示每个 Slab 大小的对数,即每个 Slab 由 2 gfporder 个页面构成。 /* force GFP flags, e.g. GFP_DMA */ unsigned int gfpflags; size_t colour; /* 颜色数目 */ unsigned int colour_off; /*颜色的偏移量 */ unsigned int colour_next; /* 下一个slab将要使用的颜色 */ kmem_cache_t *slabp_cache; unsigned int growing; unsigned int dflags; /* dynamic flags */ /* constructor func */ void (*ctor)(void *, kmem_cache_t *, unsigned long); /* de-constructor func */ void (*dtor)(void *, kmem_cache_t *, unsigned long); unsigned long failures;/* 3) cache creation/removal */ char name[CACHE_NAMELEN]; struct list_head next;#ifdef CONFIG_SMP/* 4) per-cpu data */ cpucache_t *cpudata[NR_CPUS];#endif…..};定义了 kmem_cache_t并给部分域赋予了初值,该结构有3个队列分别指向满slab,半满slab,空闲slab.另外一next把所有的专用缓冲区连成一个链表 kmem_cache_t 相当于 Slab 的总控结构, static kmem_cache_t cache_cache = { slabs_full: LIST_HEAD_INIT(cache_cache.slabs_full) , slabs_partial: LIST_HEAD_INIT(cache_cache.slabs_partial), slabs_free: LIST_HEAD_INIT(cache_cache.slabs_free) ,objsize: sizeof(kmem_cache_t),//原始的数据结构的大小.初始化为kemem_cache_t的大小 flags: SLAB_NO_REAP, spinlock: SPIN_LOCK_UNLOCKED, colour_off: L1_CACHE_BYTES, name: "kmem_cache",};专用缓冲区的建立与撤销 //缓冲区名 对象大小 所请求的着色偏移量kmem_cache_t *kmem_cache_create(const char *name, size_t size, size_t offset,unsigned long c_flags,//对缓冲区的设置标志,SLAB_HWCACHE_ALIGN:表示与第一个高速缓冲中的行边界对齐 //指向对象指针 ,指向缓冲区void (*ctor) (void *objp, kmem_cache_t *cachep, unsigned long flags),//构造函数,一般为NULLvoid (*dtor) (void *objp, kmem_cache_t *cachep, unsigned long flags))//析构函数一般为NULLkmem_cache_create()函数要进行一系列的计算,以确定最佳的 Slab 构成。包括:每个 Slab 由几个页面组成,划分为多少个对象;Slab 的描述结构 slab_t 应该放在 Slab 的外面还是放在 Slab 的尾部;还有“颜色”的数量等等。并根据调用参数和计算结果设置kmem_cache_t 结构中的各个域,包括两个函数指针 ctor 和 dtor。最后,将 kmem_cache_t结构插入到 cache_cache 的 next 队列中。kmem_cache_create()所创建的缓冲区中还没有包含任何 Slab,因此,也没有空闲的对象。只有以下两个条件都为真时,才给缓冲区分配 Slab:(1)已发出一个分配新对象的请求;(2)缓冲区不包含任何空闲对象。当这两个条件都成立时,Slab 分配模式就调用 kmem_cache_grow()函数给缓冲区分配一个新的 Slab。其中,该函数调用 kmem_gatepages()从伙伴系统获得一组页面;然后又调用kmem_cache_slabgmt()获得一个新的 Slab 结构; 还要调用 kmem_cache_init_objs()为新 Slab中的所有对象申请构造方法(如果定义的话); 最后, 调用 kmem_slab_link_end()把这个 Slab结构插入到缓冲区中 Slab 链表的末尾。 通用缓冲区在内核中初始化开销不大的数据结构可以合用一个通用的缓冲区。通用缓冲区非常类似于物理页面分配中的大小分区,最小的为 32,然后依次为 64、128、……直至 128KB(即 32个页面),但是,对通用缓冲区的管理又采用的是 Slab 方式。从通用缓冲区中分配和释放缓冲区的函数为: void *kmalloc(size_t size, int flags);Void kree(const void *objp);因此,当一个数据结构的使用根本不频繁时,或其大小不足一个页面时,就没有必要给其分配专用缓冲区,而应该调用 kmallo()进行分配。如果数据结构的大小接近一个页面,则干脆通过 alloc_page()为之分配一个页面。事实上,在内核中,尤其是驱动程序中,有大量的数据结构仅仅是一次性使用,而且所占内存只有几十个字节, 因此, 一般情况下调用 kmallo()给内核数据结构分配内存就足够了。另外,因为,在 Linux 2.0 以前的版本一般都调用 kmallo()给内核数据结构分配内存,因此,调用该函数的一个优点是(让你开发的驱动程序)能保持向后兼容。 内核空间非连续内存区的管理 非连续内存处于3G到4G(虚拟地址) PAGE_OFFSET为3G.high_memory表示保存物理地址的最高值的变量.VMALLOC_START为非连续区的起始地址 在物理地址的末尾与第一个内存区之间插入了一个 8MB(VMALLOC_OFFSET)的区间这是一个安全区,目的是为了“捕获”对非连续区的非法访问。出于同样的理由,在其他非连续的内存区之间也插入了 4KB 大小的安全区。每个非连续内存区的大小都是 4096 的倍数。描述非连续区的数据结构
用户评论
这篇博客对Linux 内核之内存管理写的太详细了!对一个初学者来说,能清晰地了解到各种机制运作原理真是太好了,我已经开始着手实践了,希望可以深刻理解这些概念。
有16位网友表示赞同!
终于找到一篇深入浅出讲解Linux内核内存管理的文章!以前总是被这些复杂的概念绕得晕头转向,现在终于有点思路了。 还是要好好研读一遍。
有9位网友表示赞同!
说的太官方了! 我想更多一些实际案例和应用场景的分析,这样才能更好地理解这些理论知识的意义和价值。
有7位网友表示赞同!
作为一个长期使用Linux系统的程序员,我对内存管理机制一直感兴趣。这篇文章解释得非常清晰,帮助我更深入地了解内核是如何运作的。 希望能看到更多的博文分享类似内容。
有10位网友表示赞同!
感觉文章中对vmalloc和gmalloc 的描述不够详细,这两个功能机制对我来说还是比较模糊。希望作者能够进一步展开讲解,提供更多实用的案例说明。
有13位网友表示赞同!
这篇博客确实讲得很仔细,特别是对页表结构的解释非常到位,让我对Linux内核的多层内存管理机制有了更清晰的认知。感谢作者的辛勤付出!
有5位网友表示赞同!
这篇文章虽然很专业,但对我这种刚入门Linux的菜鸟来说有些难度,很多概念还是不太理解。 可能需要再看一些基础知识才能更好地掌握这些内容。
有17位网友表示赞同!
我曾经在项目中遇到过内存泄漏的问题,现在回头来看这篇博文,发现很多机制与当时的报错信息是关联的。如果早点看了这篇文章,也许能帮我更快找到解决方案。
有13位网友表示赞同!
关于Slab allocator的文章写的太浅了,希望能深入介绍slab对象分配和回收的过程细节, 并结合代码示例进行分析。
有17位网友表示赞同!
对于内存碎片管理的讲解还不如其他部分详细,希望作者能介绍一些常用的碎片整理算法,并对比它们的优缺点。这样才能更好地理解Linux内核如何在内存使用过程中控制碎片化现象
有20位网友表示赞同!
内核内存管理确实很复杂,这篇文章写的虽然清晰,但还是需要多次阅读和思考才能真正掌握。我打算把它收藏起来,慢慢消化吸收。
有14位网友表示赞同!
对这个标题感兴趣很久了,终于找到了这么好的文章! 现在要好好刷一遍 Linux 内核相关知识了。 这篇博客是学习的很好的开端!
有13位网友表示赞同!
作者写的很不错,但我觉得对于一些高级的概念和实现细节,可以提供更多深入的分析和讨论。例如,对NUMA内存管理机制的讲解比较浅显,希望能更加全面地介绍相关机制。
有11位网友表示赞同!
Linux 内核之内存管理完全剖析确实是一个非常有价值的话题,我一直在探索如何优化程序的内存使用情况。这篇文章让我对内核的工作原理有了更深入的了解,并给我一些启发。
有15位网友表示赞同!
学习 Linux 内核相关知识不容易,这篇文章虽然很难理解,但它却让我明白了很多东西。
有17位网友表示赞同!