深入理解Linux内核-内存管理
页框管理
内核对整个物理内存进行分页,每页大小为4KB或者4MB(大小无所谓,不同os都可能不一样),一般认为Linux的页大小为4KB,内核必须记录好每个页框的信息,所以linux内核把所有的页框都用struct page来描述,page也叫做页描述符,所有的page形成数组,放在mem_map变量中,mem_map的大小占用整个RAM(内存)的1%左右!记住以下两个宏定义
- virt_to_page(addr):把线性地址addr转化为页描述符地址
- pfn_to_page(pfn):把页框号转化为页描述符地址
页描述符page的字段主要包括flags、_count、index等,具体看书。
非一致内存访问NUMA和一致内存访问UMA
自己查资料,我们现在用的电脑一般为UMA。
内存管理区
Linux把整个内存分为三个区,分别为:
- ZONE_DMA:包含了低于16MB的内存页框,一般用于DMA,也可以用于别的用途。
- ZONE_NORMAL:包含了高于16MB且低于896MB的内存页框
- ZONE_HIGHMEM:包含了高于896MB的内存页框
保存的页框池
为了避免在申请页框的时候内存不够而导致的阻塞,系统会预留一些内存,这些内存被叫做保存的页框池,存放在min_free_kbytes变量中。
分区页框分配器
整个分配器如下组成:
管理区分配器可以接收动态内存的分配和释放请求,拿到请求后,可以根据具体的内存管理区去执行内存的分配和释放。每个内存管理区使用了伙伴系统来处理。下面十几个具体的函数
- 请求页框
- alloc_pages(gfp_mask,order):请求2的order次方个连续的页框,并返回第一个页框的页框描述符地址,如果失败返回NULL;
- alloc_page(gfp_mask):等价于alloc_pages(gfp_mask,0)
- __get_free_pages(gfp_mask,order):请求2的order次方个连续的页框,并返回第一个页的线性地址,如果失败返回NULL;
- get_zeroed_page(gfp_mask):请求1个初始化为0的页框,并返回线性地址,如果失败返回NULL;
- __get_dma_pages(gfp_mask,order):在DMA区域请求2的order次方个连续的页框,并返回第一个页的线性地址,如果失败返回NULL;
注意:请求的时候可以指定具体的区域,比如DMA区域,一般设置在gfp_mask中!
- 释放页框
- __free_pages(page,order):page是一个页框描述符,将其以及其后的2的order次方的描述符中count字段减一,减到0的会释放掉该页。
- free_pages(addr,order):addr是一个线形地址page,将其描述符中count字段减一,减到0的会释放掉该页。
- __free_page(page):不解释。
- free_page(addr):不解释。
高端内存页框的内核映射
针对ZONE_DMA和ZONE_NORMAL的页框请求和释放都很简单,这就是简单的线形映射,内核空间的前896MB都是这样线形映射的,所以很简单,但是高端内存(ZONE_HIGHMEM)就不一样了,因为32位的内核空间只有3-4GB,总长就1GB,前面以及占用了896MB了,所以只剩下128MB可以去映射高端内存,所以这一节着重记录一下高端内存的映射。
注意:64位的系统就没这么麻烦了,64位系统的用户空间、内核空间都远远大于物理内存,直接使用就行了!
《深入理解LINUX内核》这本书里面举了个例子,我觉得挺好:
例如,内核调用了__get_dma_pages(GFP_HIGHMEM,0)在高端内存中分配一个页框,此时如果有一个高端页框也不能反回其线性地址,因为根本不存在,所以返回NULL。
此时就要用到alloc_pages和alloc_page这两个函数了,获取到高端页框的描述符地址,然后为了让内核能访问到,必须使用内核空间的后128MB去映射。内核可以使用三种机制来映射高端内存,分别是永久内核映射、临时内核映射以及非连续内存分配。
永久内核映射
永久内核映射允许内核建立高端页框到内核地址空间的长期映射,它的页表存在于128MB之中,用变量pkmap_page_table变量保存。页表的表项数由LAST_PKMAP宏产生,一般就是512或1024项,不多,一共也就2MB或4MB。且该段页表映射的线形地址从PKMAP_BASE开始。Linux为每一个页表项都设置了一个计数器,存放在pkmap_count数组中,计数器的含义:
- 计数器为0,则代表该页表项没有映射任何高端内存,是可用的。
- 计数器为1,则代表该页表项没有映射任何高端内存,但不可用,因为从他最后一次被使用后,相应的TLB表还没有被刷新。
- 计数器大于1,由n-1个内核成分正在使用它。
此外,内核还用了page_address_htable散列表来映射物理页框和线性地址的关系,key为页框page,高端页框、低端页框都适用!函数page_address(page)就是用来查page_address_htable找出page对应的线性地址,如果page是低端页框,直接物理地址-3GB**就得到线性地址了,如果page是高端页框,就用散列表,三列表中如果不存在就返回NULL。
**kmap()**函数用来建立永久内核映射,代码等价如下:
void *kmap(struct page *page){if(!PageHighMem(page))//如果是低端内存,就直接返回映射address。return page_address(page);//上面的return kmap_high(page);
}
其中kmap_high()函数等价于:
void *kmap_high(struct page *page){unsigned long vaddr;spin_lock(&kmap_lock);vaddr = (unsigned long) page_address(page);if(!vaddr)vaddr = map_new_virtual(page);pkmap_count[(vaddr-PKMAP_BASE)>>PAGE_SHIFT]++;spin_unlock(&kmap_lock);return (void *)vaddr;
}
kmap_high使用了自旋锁,保证多处理器上的并发问题,同时它没有关中断,因为中断处理函数里不能调用kmap()。map_new_virtual函数才是真的去做映射,其代码等价于:
for(;;){int count;DECLARE_WAITQUEUE(wait, current);//将当前进程包装成一个等待结构体,wait就是该结构体指针,是个宏定义!相见之前做的笔记for(count = LAST_PKMAP;count > 0; --count){last_pkmap_nr = (last_pkmap_nr + 1) & (LAST_PKMAP-1);//这里的last_pkmap_nr是全局变量,便于从上次调用map_new_virtual产生的last_pkmap_nr接着扫描。if(!last_pkmap_nr){flush_all_zero_pkmap();//扫描,回收count=1的页表项,置count=0并删除其page对应的散列项、并刷新TLB。count = LAST_PKMAP;}if(!pkmap_count[last_pkmap_nr]){unsigned long vaddr = PKMAP_BASE + (last_pkmap_nr<<PAGE_SHIFT);set_pte(&(pkmap_page_table[last_pkmap_nr]),mk_pte(page, __pgprot(0x63)));//设置页表!!!pkmap_count[last_pkmap_nr] = 1;set_page_address(page, (void *)vaddr);//在散列表中建立散列项return vaddr;}}current->state = TASK_UNITERRUPTIBLE;//无可用页表,就去睡把!add_wait_queue(&pkmap_map_wait,&wait);spin_unlock(&kmap_lock);schedule();remove_wait_queue(&pkmap_map_wait,&wait);spin_lock(&kmap_lock);if(page_address(page)) return (unsigned long) page_address(page);//否则继续执行死循环。
}
**kunmap()**函数用来撤销映射:
void kunmap(struct page *page){spin_lock(&kmap_lock);if(--pkmap_count[((unsigned long)page_address(page)-PKMAP_BASE)>>PAGE_SHIFT]==1)if(waitqueue_active(&pkmap_map_wait))wake_up(&pkmap_map_wait);spin_unlock(&kmap_lock);
}
临时内核映射
临时内核映射更加简单,它不阻塞当前进程,可以用于中断处理程序和可延迟函数内部,但他必须保证当前没有其他的内核控制路径在使用同样的映射,所以它不能被阻塞。高端内存任何一页都可以通过一个窗口映射到内核空间,但是这些窗口非常少,每个CPU都有13个窗口,用枚举enum km_type表示如下,最后一个KM_TYPE_NR仅仅表示前面有多少个(13个),这些个窗口都是固定映射的(见第二章的“固定映射的线性地址”),他们的开始和结束都由enum fixed_addresses数据结构中的FIX_KMAP_BEGIN和FIX_KMAP_END指定,如下所示,其中KM_TYPE_NR就是13,NR_CPUS是cpu数目。
enum km_type {KM_BOUNCE_READ,KM_SKB_SUNRPC_DATA,KM_SKB_DATA_SOFTIRQ,KM_USER0,KM_USER1,KM_BIO_SRC_IRQ,KM_BIO_DST_IRQ,KM_PTE0,KM_PTE1,KM_IRQ0,KM_IRQ1,KM_SOFTIRQ0,KM_SOFTIRQ1,KM_TYPE_NR
};
enum fixed_addresses {FIX_HOLE,FIX_VDSO,...FIX_KMAP_BEGIN, /* reserved pte's for temporary kernel mappings */FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,...__end_of_fixed_addresses
};
变量kmap_pte表示可以FIX_KMAP_BEGIN的页表项地址,使用fix_to_virt(FIX_KMAP_BEGIN)调用获取并赋值给kmap_pte进行初始化。为了建立临时内核映射,内核调用**kmap_atomic()**函数实现,代码等价于:
void *kmap_atomic(struct page *page, enum km_type type){enum fixed_addresses idx;unsigned long vaddr;current_thread_info()->preempt_count++;//禁止调度、禁止强占if(!PageHighMem(page))//如果是低端内存,就直接返回映射address。return page_address(page);//上面的idx = type + KM_TYPE_NR*smp_processor_id();//通过type和cpu号 获取具体的indexvaddr = fix_to_virt(FIX_KMAP_BEGIN + idx);set_pte(kmap_pte+idx,mk_pte(page, __pgprot(0x63)));//设置页表!!!__flush_tlb_single(vaddr);//刷新tlb项return (void *)vaddr;
}
内核使用kunmap_atomic(struct page *page)来撤销临时内核映射,在该函数中会preempt_count–,这是一种禁止调度方式,所以这里直接改preempt_count,就是禁止本cpu调度,这样的话整个kmap_atomic和kunmap_atomic之间就不能被强占,保证了当前没有其他的内核控制路径在使用同样的映射,就可以避免同一个临时内核映射页表项被多个进程使用!!
伙伴系统
Linux为了解决碎片的问题,可以有两种办法:
- 利用分页,把碎片页框映射到连续的线性地址上即可。
- 使用伙伴系统。
Linux是使用伙伴系统来解决碎片问题。Linux把所有空闲页分为11个块链表,每个块链表分别包含大小为1、2、4、8、16、32、64、128、256、512、1024个连续页框。对于1024个页框就是4MB。
使用到的数据结构
Linux的伙伴系统有三种,分别针对三个内存区域,每个伙伴系统都使用了以下数据结构:
- mem_map数组,每个伙伴系统所使用的页框都是mem_map的子集,伙伴系统的第一个页框以及页框个数使用zone_mem_map和size字段指定。
- 一个数组,包含了11个(对应前面的1、2、4…一共11个)类型为free_area的元素,每个free_area里包含了一个free_list,用于链接所有的同样大小的空闲块。
free_area数组的第k个元素表示了所有大小为2的k次方的空闲快,free_area的free_list字段是一个链接空闲块的双向循环链表的头,该链表的元素实际上就是空闲块首个页框的描述符(即page的lru字段,lru在页框空闲时用于此);free_area的nr_free表示当前链表中有多少个元素(即空闲块)。此外空闲块的第一个page的private字段放了k,用于表示它后面块个数为2的k次方。
分配快
分配快使用__rmqueue()函数,需要传入两个参数:管理去描述符zone和order(即需要快的大小为2的order次方)。如果分配成功则返回第一个页框的页描述符,否则返回NULL。该函数默认调用者以及禁止了本地中断和获取了zone->lock自旋锁。该函数大概内容如下:
...struct free_area *area;unsigned int current_order;for(current_order=order;current_order<11;current_order++){//不断地向上找,找到就跳转到block_found,找不到返回NULLarea = zone->free_area + current_order;if(!list_empty(&area->free_list))goto block_found;}return NULL;
...
block_found代码如下:
block_found:page = list_entry(area->free_list.next,struct page, lru);//这是一个宏定义,用到了container_of宏list_del(&page->lru);ClearPagePrivate(page);page->private = 0;area->nr_free--;zone->free_pages -= 1UL << order;
此时如果current_order大于order的话就说明分配到了一个更大的块,所以需要讲剩余块插入到别的链表中去:
size = 1 << current_order;while(current_order>order){area--;current_order--;size >>= 1;buddy = page + size;list_add(&buddy->lru, &area->free_list);area->nr_free++;buddy->private = current_order;SetPagePrivate(buddy);}
释放块
释放块用函数__free_pages_bulk(),它需要三个参数:zone、page、order,具体含义不解释了。该函数默认调用者以及禁止了本地中断和获取了zone->lock自旋锁。该函数大概内容如下:
{struct page *base = zone->zone_mem_map;unsigned long buddy_idx, page_idx = page - base;struct page *buddy, *coalesced;int order_size = 1 << order;zone->free_pages += order_size;//增加zone的page计数器while(order<10){//不断地去找伙伴buddy_idx = page_idx ^ (1<<order);//找到伙伴的page编号buddy = base + buddy_idx;if(!page_is_buddy(buddy,order))//检查这个buddy是不是我们要找的buddybreak;list_del(&buddy->lru);zone->free_area[order].nr_free--;ClearPagePrivate(buddy);buddy->private = 0;page_idx &= buddy_idx;//和伙伴合并,拿到合并后的低indexorder++;}coalesced = base + page_idx;coalesced->private = order;SeyPagePrivate(coalesced);list_add(&coalesced->lru, &zone->free_area[order].free_list);zone->free_area[order].nr_free++;
}
每CPU页框高速缓存
为了提升系统的性能,在每个内存管理区定义了一个“每CPU”页框高速缓存,“每CPU”页框高速缓存包含了一些预先分配的页框,用于满足本地CPU发出的单一内存请求。“每CPU”页框高速缓包括了两个高速缓冲区,分别是热高速缓冲区(大概率是存在CPU的硬件高速缓冲区中)、冷高速缓冲区。
实现“每CPU”页框高速缓存的主要数据结构是存放在内存管理区描述符的pageset字段中的一个per_cpu_pageset数组数据结构,该数组为每个cpu提供一个元素,该元素又分为两个per_cpu_pages描述符,分别是热高速缓冲区、冷高速缓冲区。
per_cpu_pages的相关字段:
int count ===> 高速缓冲区的叶匡个数;
int low =====> 下界,表示需要补充页框了!
int high ====> 上界,表示高速缓冲区用尽;
int batch ===> 在高速缓冲区需要补充或删去页框时,批量操作的页框个数
struct list_head list ==> 用于链表。
当count个数低于low时,内核通过伙伴系统分配batch个页框补充给对应的高速缓冲区,当count个数高于high时,内核通过伙伴系统释放batch个。
通过每CPU页框高速缓存分配页框
**buffered_rmqueue()函数用于在指定的内存管理区中分配页框。他使用“每CPU”页框高速缓存来处理单一页框请求,注意要请求多页框时该函数使用之前的__rmqueue()**函数去申请页框。源码如下:
/** Really, prep_compound_page() should be called from __rmqueue_bulk(). But* we cheat by calling it from here, in the order > 0 path. Saves a branch* or two.*/
static struct page *buffered_rmqueue(struct zonelist *zonelist,struct zone *zone, int order, gfp_t gfp_flags)
{unsigned long flags;struct page *page;int cold = !!(gfp_flags & __GFP_COLD);int cpu;again:cpu = get_cpu();//获取cpu编号if (likely(order == 0)) {//申请单页框struct per_cpu_pages *pcp;pcp = &zone_pcp(zone, cpu)->pcp[cold];//拿到对应的热缓冲区或冷缓冲区(取决于gfp_flags)local_irq_save(flags);//关中断if (!pcp->count) {//这里判断是否需要补充页框给缓冲区pcp->count = rmqueue_bulk(zone, 0,pcp->batch, &pcp->list);//通过__rmqueue去补充if (unlikely(!pcp->count))goto failed;}page = list_entry(pcp->list.next, struct page, lru);list_del(&page->lru);pcp->count--;} else {//多页框申请spin_lock_irqsave(&zone->lock, flags);page = __rmqueue(zone, order);spin_unlock(&zone->lock);if (!page)goto failed;}__count_zone_vm_events(PGALLOC, zone, 1 << order);zone_statistics(zonelist, zone);local_irq_restore(flags);put_cpu();VM_BUG_ON(bad_range(zone, page));if (prep_new_page(page, order, gfp_flags))goto again;return page;failed:local_irq_restore(flags);put_cpu();return NULL;
}static int rmqueue_bulk(struct zone *zone, unsigned int order, unsigned long count, struct list_head *list)
{int i;spin_lock(&zone->lock);for (i = 0; i < count; ++i) {struct page *page = __rmqueue(zone, order);if (unlikely(page == NULL))break;list_add_tail(&page->lru, list);}spin_unlock(&zone->lock);return i;
}
释放每CPU页框高速缓存页框
一般使用free_hot_page和free_cold_page去释放页框。这两个函数底层都调用了**free_hot_cold_page()**来处理:
/** Free a 0-order page //这个函数只释放一个页框*/
static void fastcall free_hot_cold_page(struct page *page, int cold)
{struct zone *zone = page_zone(page);//获取zonestruct per_cpu_pages *pcp;unsigned long flags;if (PageAnon(page))page->mapping = NULL;if (free_pages_check(page))return;if (!PageHighMem(page))debug_check_no_locks_freed(page_address(page), PAGE_SIZE);arch_free_page(page, 0);kernel_map_pages(page, 1, 0);pcp = &zone_pcp(zone, get_cpu())->pcp[cold];//获取per_cpu_pageslocal_irq_save(flags);__count_vm_event(PGFREE);list_add(&page->lru, &pcp->list);//加进链表pcp->count++;//计数器++if (pcp->count >= pcp->high) {free_pages_bulk(zone, pcp->batch, &pcp->list, 0);//释放batch个块pcp->count -= pcp->batch;}local_irq_restore(flags);put_cpu();
}
注意:在当前2.6版本的内核,是没有页框释放到冷高速缓存中的,这是因为内核总是假设释放的叶匡的热的!
管理区分配器
管理区分配器是内核页框分配器的前端,它的核心函数为**__alloc_pages()**,之前的alloc_pages宏就是调用它来分配页框。该函数需要三个参数:gfp_mask标志、order、zonelist(每个内存管理区的zone的链表)
/** This is the 'heart' of the zoned buddy allocator.*/
struct page * fastcall
__alloc_pages(gfp_t gfp_mask, unsigned int order,struct zonelist *zonelist)
{const gfp_t wait = gfp_mask & __GFP_WAIT;struct zone **z;struct page *page;struct reclaim_state reclaim_state;struct task_struct *p = current;int do_retry;int alloc_flags;int did_some_progress;might_sleep_if(wait);if (should_fail_alloc_page(gfp_mask, order))return NULL;restart:z = zonelist->zones; /* the list of zones suitable for gfp_mask */if (unlikely(*z == NULL)) {/* Should this ever happen?? */return NULL;}//第一次扫描各个区,这个时候设定的阈值为z->pages_low,如果分配成功就goto got_pgpage = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, order,zonelist, ALLOC_WMARK_LOW|ALLOC_CPUSET);//关键函数,他就是扫描所有的zonelist中的zone,找到合适的块返回块的第一个page,见下if (page)goto got_pg;/** GFP_THISNODE (meaning __GFP_THISNODE, __GFP_NORETRY and* __GFP_NOWARN set) should not cause reclaim since the subsystem* (f.e. slab) using GFP_THISNODE may choose to trigger reclaim* using a larger set of nodes after it has established that the* allowed per node queues are empty and that nodes are* over allocated.*/if (NUMA_BUILD && (gfp_mask & GFP_THISNODE) == GFP_THISNODE)goto nopage;//如果第一次扫描各个区域不成功,那就唤醒kswapd内核线程来异步执行页框回收!!for (z = zonelist->zones; *z; z++)wakeup_kswapd(*z, order);/** OK, we're below the kswapd watermark and have kicked background* reclaim. Now things get more complex, so set up alloc_flags according* to how we want to proceed.** The caller may dip into page reserves a bit more if the caller* cannot run direct reclaim, or if the caller has realtime scheduling* policy or is asking for __GFP_HIGH memory. GFP_ATOMIC requests will* set both ALLOC_HARDER (!wait) and ALLOC_HIGH (__GFP_HIGH).*///设置alloc_flagsalloc_flags = ALLOC_WMARK_MIN;if ((unlikely(rt_task(p)) && !in_interrupt()) || !wait)alloc_flags |= ALLOC_HARDER;if (gfp_mask & __GFP_HIGH)alloc_flags |= ALLOC_HIGH;if (wait)alloc_flags |= ALLOC_CPUSET;/** Go through the zonelist again. Let __GFP_HIGH and allocations* coming from realtime tasks go deeper into reserves.** This is the last chance, in general, before the goto nopage.* Ignore cpuset if GFP_ATOMIC (!wait) rather than fail alloc.* See also cpuset_zone_allowed() comment in kernel/cpuset.c.*///第二次扫描,这里扫描和第一次不同的是改变了阈值大小,这次使用了较低的阈值了!也就是要求变低了!page = get_page_from_freelist(gfp_mask, order, zonelist, alloc_flags);if (page)goto got_pg;/* This allocation should allow future memory freeing. */rebalance:if (((p->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE)))&& !in_interrupt()) {if (!(gfp_mask & __GFP_NOMEMALLOC)) {nofail_alloc://第三次扫描了,这一次使用了ALLOC_NO_WATERMARKS,也就是不调用zone_watermark_ok去检查阈值,只有这种情况内核才会去使用“保存的页框”/* go through the zonelist yet again, ignoring mins */page = get_page_from_freelist(gfp_mask, order,zonelist, ALLOC_NO_WATERMARKS);if (page)goto got_pg;if (gfp_mask & __GFP_NOFAIL) {congestion_wait(WRITE, HZ/50);goto nofail_alloc;}}goto nopage;}/* Atomic allocations - we can't balance anything */if (!wait)goto nopage;//检查是否还有别的进程需要CPUcond_resched();/* We now go into synchronous reclaim */cpuset_memory_pressure_bump();p->flags |= PF_MEMALLOC;reclaim_state.reclaimed_slab = 0;p->reclaim_state = &reclaim_state;//这里就开始调用try_to_free_pages寻找一些页框来回收,可能会阻塞当前进程。did_some_progress = try_to_free_pages(zonelist->zones, gfp_mask);p->reclaim_state = NULL;p->flags &= ~PF_MEMALLOC;//检查是否还有别的进程需要CPUcond_resched();if (likely(did_some_progress)) {//如果上面try_to_free_pages有释放一些页框,那就再尝试以此获取页框page = get_page_from_freelist(gfp_mask, order,zonelist, alloc_flags);if (page)goto got_pg;} else if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) {//如果上面try_to_free_pages没有释放一些页框,那就麻烦大了,空闲叶匡已经少到危险的地步了。/** Go through the zonelist yet one more time, keep* very high watermark here, this is only to catch* a parallel oom killing, we must fail if we're still* under heavy pressure.*///再去尝试获取一次page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, order,zonelist, ALLOC_WMARK_HIGH|ALLOC_CPUSET);if (page)goto got_pg;//通过out_of_memory杀死一个进程开始释放一些内存out_of_memory(zonelist, gfp_mask, order);//跳回第一步!goto restart;}/** Don't let big-order allocations loop unless the caller explicitly* requests that. Wait for some write requests to complete then retry.** In this implementation, __GFP_REPEAT means __GFP_NOFAIL for order* <= 3, but that may not be true in other implementations.*/do_retry = 0;if (!(gfp_mask & __GFP_NORETRY)) {if ((order <= 3) || (gfp_mask & __GFP_REPEAT))do_retry = 1;if (gfp_mask & __GFP_NOFAIL)do_retry = 1;}if (do_retry) {congestion_wait(WRITE, HZ/50);goto rebalance;}nopage:if (!(gfp_mask & __GFP_NOWARN) && printk_ratelimit()) {printk(KERN_WARNING "%s: page allocation failure."" order:%d, mode:0x%x\n",p->comm, order, gfp_mask);dump_stack();show_mem();}
got_pg:return page;
}
get_page_from_freelist函数的作用就是扫描各个内存管理区,看看各个管理区是否低于阈值,如果低于就不适用该区域,如果高于那就调用buffered_rmqueue分配页框,如下:
static struct page *
get_page_from_freelist(gfp_t gfp_mask, unsigned int order,struct zonelist *zonelist, int alloc_flags)
{struct zone **z;struct page *page = NULL;int classzone_idx = zone_idx(zonelist->zones[0]);struct zone *zone;nodemask_t *allowednodes = NULL;/* zonelist_cache approximation */int zlc_active = 0; /* set if using zonelist_cache */int did_zlc_setup = 0; /* just call zlc_setup() one time */zonelist_scan:/** Scan zonelist, looking for a zone with enough free.* See also cpuset_zone_allowed() comment in kernel/cpuset.c.*/z = zonelist->zones;do {if (NUMA_BUILD && zlc_active &&!zlc_zone_worth_trying(zonelist, z, allowednodes))continue;zone = *z;if (unlikely(NUMA_BUILD && (gfp_mask & __GFP_THISNODE) &&zone->zone_pgdat != zonelist->zones[0]->zone_pgdat))break;if ((alloc_flags & ALLOC_CPUSET) &&!cpuset_zone_allowed_softwall(zone, gfp_mask))goto try_next_zone;if (!(alloc_flags & ALLOC_NO_WATERMARKS)) {//ALLOC_NO_WATERMARKS就是不去检查各个zone的阈值unsigned long mark;if (alloc_flags & ALLOC_WMARK_MIN)mark = zone->pages_min;else if (alloc_flags & ALLOC_WMARK_LOW)mark = zone->pages_low;elsemark = zone->pages_high;if (!zone_watermark_ok(zone, order, mark,classzone_idx, alloc_flags)) {//zone_watermark_ok里面确定min阈值,然后返回该zone是否有足够的内存使用,见下if (!zone_reclaim_mode ||!zone_reclaim(zone, gfp_mask, order))goto this_zone_full;//该内存管理区满了}}page = buffered_rmqueue(zonelist, zone, order, gfp_mask);//从高速缓冲区获取,这个函数见上一小节if (page)break;
this_zone_full://内存管理区满了if (NUMA_BUILD)zlc_mark_zone_full(zonelist, z);
try_next_zone:if (NUMA_BUILD && !did_zlc_setup) {/* we do zlc_setup after the first zone is tried */allowednodes = zlc_setup(zonelist, alloc_flags);zlc_active = 1;did_zlc_setup = 1;}} while (*(++z) != NULL);if (unlikely(NUMA_BUILD && page == NULL && zlc_active)) {/* Disable zlc cache for second zonelist scan */zlc_active = 0;goto zonelist_scan;}return page;
}
zone_watermark_ok函数用来确定阈值并检查区域可用空间是否高于阈值:
int zone_watermark_ok(struct zone *z, int order, unsigned long mark,int classzone_idx, int alloc_flags)
{/* free_pages my go negative - that's OK */long min = mark;long free_pages = zone_page_state(z, NR_FREE_PAGES) - (1 << order) + 1;int o;if (alloc_flags & ALLOC_HIGH)min -= min / 2;if (alloc_flags & ALLOC_HARDER)min -= min / 4;if (free_pages <= min + z->lowmem_reserve[classzone_idx])return 0;for (o = 0; o < order; o++) {//检查大于order的块链表,小于order的不需要检查了/* At the next order, this order's pages become unavailable */free_pages -= z->free_area[o].nr_free << o;/* Require fewer higher order pages to be free */min >>= 1;if (free_pages <= min)return 0;}return 1;
}
连续内存区管理
本节针对连续物理地址的内存区域管理,主要介绍slab分配机制。
slab分配器介绍
高速缓存的主内存区被分为多个slab,每个slab由一个或多个连续的页框组成,这些页框被分为多个大小相等且连续的对象,后面分配也就是这些个对象。如图所示:
接下来介绍几个关键的结构体
高速缓存描述符
高速缓存描述符结构体为kmem_cache_t类型:
类型 | 名称 | 说明 |
---|---|---|
struct array_cache * [] | array | 每CPU指针数组的本地高速缓存,后面会详细介绍 |
unsigned int | batchcount | 要转移进本地高速缓存或从本地高速缓存转出的对象数 |
unsigned int | obj_size | 本高速缓存管理下的对象大小(固定值) |
struct kmem_list3 | lists | 高速缓存中的slab链表(有三个链表,见后面) |
unsigned int | gfporder | 一个单独的slab中包含的连续页框数目的对数。 |
其余字段省略。。。 |
其中lists是一个结构体kmem_list3:
类型 | 名称 | 说明 |
---|---|---|
struct list_head | slabs_partial | 包含空闲和非空闲对象的slab描述符 |
struct list_head | slabs_full | 不包含空闲对象的slab。。 |
struct list_head | slabs_free | 只包含空闲对象的slab。。 |
unsigned long | free_objects | 高速缓冲区空闲对象的个数 |
slab描述符
类型 | 名称 | 说明 |
---|---|---|
struct list_head | list | 双向链表! |
unsigned long | colouroff | 第一个对象的偏移量(slab着色) |
void * | s_mem | slab中第一个对象的偏移 |
unsigned int | inuse | 当前正在被使用的对象个数 |
unsigned int | free | slab中下一个空闲对象的下标 |
slab描述符可以存放在两个地方:
- 外部slab描述符:存在于slab的外部。
- 内部slab描述符:存放于slab的内部。
高速缓存描述符和slab描述符之间的关系:
普通和专用高速缓存
高速缓存被分为两大类:普通和专用,普通高速缓存只由slab分配器用于自己的目的,专用高速缓存由内核的其余部分使用。
普通高速缓存是:
- 第一个高速缓存叫做kmem_cache,包含由内核使用的其余高速缓存的高速缓存描述符!
- 另外有13个不同对象大小的高速缓存,一个叫做malloc_sizes的表分别指向26个高速缓存描述符,对应对象大小为32、64、128…131072字节的dma区域分配和常规分配的高速缓存。
slab分配器与分区页框分配器的接口
当slab分配器创建新的slab时,它依靠分区页框分配器来获取一组连续的空闲页框。为了完成这一操作,系统会调用kmem_getpages函数,如下:
void * kmem_getpages(kmem_cache_t *cachep, int flags){struct page *page;int i;flags |= cachep->gfpflags;page = alloc_pages(flags, cachep->gfporder);if(!page) return NULL;i = (1 << cachep->gfporder);if(cachep->flags & SLAB_RECLAIM_ACCOUNT)atomic_add(i,&slab_reclaim_pages);while(i--){SetPageSlab(page++);}return page_address(page);
}
相反通过调用kmem_freepages函数释放slab的页框:
void kmem_freepages(kmem_cache_t *cachep, void * addr){unsigned long i = (1<<cachep->gfporder);struct page *page = virt_to_page(addr);if(current->reclaim_state)current->reclaim_state->reclaim_slab += i;while(i--)ClearPageSlab(page++);free_pages((unsigned long)addr, cachep->gfporder);if(cachep->flags & SLAB_RECLAIM_ACCOUNT)atomic_sub(1<<cachep->gfporder,&slab_reclaim_pages);
}
给高速缓存分配slab
当创建一个高速缓存没有包含任何slab和对象,当发出分配对象的请求后且高速缓存不包含任何空闲对象时,才会分配slab。此时,slab分配器会调用cache_grow()给高速缓存分配一个新的slab。这个函数首先调用kmem_getpages函数从分区页框分配器获得一组页框来存放一个单独的slab。然后调用alloc_slabmgmt()获得一个新的slab描述符,此时会看CFLGS_OFF_SLAB标志,从而实现外部slab描述符/内部slab描述符,如果是内部的,就在第一个页框中分配slab描述符。
当我们给定一个页框,内核必须确定它属于哪个高速缓存和哪个slab,所以cache_grow会扫描给新的slab分配的所有页框,把他们的lru字段的next和pre分别置为高速缓存描述符和slab描述符!相反的,当给定一个slab,也需要确定哪些页框属于它,系统通过slab描述符的s_mem字段(起始位置)和高速缓存描述符的gfporder字段(大小)来确定。
然后cache_grow调用cache_init_objs(),初始化slab的所有对象。
最后,调用list_add_tail(),把slab描述符添加到高速缓存中的全空链表上。
从高速缓存中释放slab
调用slab_destory()来撤销一个slab:
void slab_destory(kmem_cache_t *cachep, slab_t *slabp){if(cachep->dtor){int i;for(i = 0; i<cachep->num; i++){void *objp = slabp->s_mem + cachep->objsize*i; //找到对象(cachep->dtor)(objp, cachep, 0); //析构对象}}kmem_freepages(cachep, slab->s_mem - slabp->colouroff); //释放页框,这里slabp->colouroff是slab着色相关,见后面if(cachep->flags & CFLGS_OFF_SLAB) //如果是外部slab,就要手动释放描述符。kmem_cache_free(cachep->slabp_cache, slabp);
}
对象描述符
每个对象都有类型为kmem_bufctl_t的描述符,但其实他就是一个unsigned int而已,对象描述符存放在一个数组中,位于相应的slab描述符之后,因此对象描述符数组也分为外部和内部。对象描述符只不过就是一个无符号整数,只有在对象空闲的时候才有意义,它包含的是下一个空闲对象的下标,因此实现了slab内部空闲对象的简单链表。
对齐内存中的对象
对象在slab中一般存放是对齐的,存放他们的内存单元的起始物理地址是一个给定常量的倍数,通常是2的倍数,这个常量就是对齐因子。
slab着色
当不同的slab内具有相同偏移量的对象最终很可能映射在同一个高速缓存行中。高速缓存的硬件可能因此而花费内存周期在同一行高速缓存行与RAM内存单元之间来来往往传送两个对象,而其他高速缓存行没有充分利用!linux通过slab着色来解决这一问题:随机分配一个颜色(随机整数)给slab,从而让它后面的对象都错开。
如图所示:
颜色值col,那么整个slab就偏移col*aln,最终把col*aln+dsize的值存放在slab->colouroff中。
空闲slab对象的本地高速缓存
slab分配器为每个高速缓存都安排了一个每CPU数据结构,该结构由一个指向被释放对象的小指针数组组成。高速缓存描述符中的array字段是一组指向array_cache数据结构的指针,系统中每个CPU对应一个元素,array_cache是空闲对象的本地高速缓存的一个描述符:
类型 | 名称 | 说明 |
---|---|---|
unsigned int | avail | 本地高速缓存中可用的空闲对象个数 |
unsigned int | limit | 本地高速缓存中对象个数的最大值 |
unsigned int | batchcount | 本地高速缓存重新填充/腾空时操作的对象个数 |
unsigned int | touched | 标记最近有没有被使用过 |
分配slab对象
通过调用函数**kmem_cache_alloc()**函数获得新对象:
void *kmem_cache_alloc(kmem_cache_t *cachep, int flags){unsigned long save_flags;void *objp;struct array_cache *ac;local_irq_save(save_flags);ac = cachep->array[smp_processor_id()];if(ac->avail){//如果本地高速缓存还有的多。。。ac->touched = 1;objp = ((void **)(ac+1))[--ac->avail];}else //否则调用cache_alloc_refillobjp = cache_alloc_refill(cachep, flags);local_irq_restore(save_flags);return objp;
}
**cache_alloc_refill()**函数用来从slab中把空闲的对象加载到本地高速缓存,具体步骤:
- 将本地高速缓存描述符存放在局部变量中:
ac = cachep->array[smp_processor_id()];
获得cachep->spinlock。
如果slab高速缓存包含本地高速缓存,并且该共享本地高速缓存包含一些空闲对象,函数就通过从共享本地高速缓存中移动ac->batchcount个指针来填充CPU的本地高速缓存。然后跳转到第6步。
函数试图填充本地高速缓存,填充数目为ac->batchcount个指针:
查看高速缓存 描述符中slabs_partial和slabs_free链表,并获得slab描述符slabp,slabp指向的slab为部分为空或全空。如果不存在slabp,跳转到第5步.
对于slab中的每个空闲对象,函数将他们插入到本地高速缓存,增加slab描述符的inuse字段,并更新free字段使得free指向下一个空闲对象的下标:
slabp->inuse ++;
((void**)(ac + 1))[ac->avail++] = slabp->s_mem + slabp->free * cachep->objsize;
slabp->free = ((kmem_bufctl_t *)(slabp+1))[slabp->free];
将slabp插入适当的链表,可以实slabs_full或slabs_partial。
在这一步,被添加到本地高速缓存的指针个数被存放在ac->avail中,函数递减同样数量的kmem_list3结构的free_objects字段。
释放cachep->spinlock;
如果现在ac->avail大于0(相当于填充了一些指针了),函数将ac->touched设为1,并返回最后插入到本地高速缓存的空闲指针:
return ((void **)(ac + 1))[–ac->avail];
否则,上面没有发生填充操作,调用cache_grow()获取一个新的slab,如果成功再跳转到第1步,否则返回NULL。
释放slab对象
调用**kmem_cache_free()**函数释放一个对象:
void kmem_cache_free(kmem_cache_t *cachep, void *objp){unsigned long flags;struct array_cache *ac;local_irq_save(flags);ac = cachep->array[smp_processor_id()];if(ac->avail == ac->limit)//空闲的满了,要去释放了!cache_flusharray(cachep, ac);((void **)(ac+1))[ac->avail++] = objp;local_irq_restore(flags);
}
空闲的满了,要去释放就调用cache_flusharray:
获得cachep->spinlock。
如果slab高速缓存包含一个共享的本地高速缓存,并且该共享的本地缓存还没有满,函数就会通过从本CPU的本地高速缓存移动ac->batchcount个指针来填充共享的本地高速缓存。
调用free_block()函数将当前包含在本地高速缓存中的ac->batchcount个指针归还给相应的slab。对于每一个对象objp,执行如下操作:
增加高速缓存描述符的lists->free_objcts字段。
确定objp属于哪个slab(根据lru.prev确定):
slabp = (struct slab *)(virt_to_page(objp)->lru.prev);
把它从链表上摘下来。
计算slab内对象的下标:
objnr = (objp - slabp->s_mem) / cachep->objsize;
更新slabp->free的值,从而形成空闲对象链表:
((kmem_bufctl_t *)(slabp + 1))[objnr] = slabp->free;
slabp->free = objnr;
递减slabp->inuse字段。
当slabp->inuse等于0时,整个slab就是完全空闲的了。此时如果整个高速缓存的空闲对象个数大于规定值,就去释放这个slab:
if(cachep->lists.free_objects > cachep->free_limit){
cachep->lists.free_objects -= cachep->num;
slab_destory(cachep, slabp);
}
将slab插入当适当的链表。
释放cachep->spinlock。
更新ac->avail;
通用对象
本小节介绍kmalloc和kfree函数:
void *kmalloc(size_t size, int flags){struct cache_sizes *csizep = malloc_sizes;kmem_cache_t * cachep;for(; csizep->cs_size; csizep++){if(size > csizep->cs_size) continue;if(flags & __GFP_DMA)cachep = csizep->cs_dmacachep;elsecachep = csizep->cs_cachep;return kmem_cache_alloc(cachep, flags);}return NULL;
}void kfree(const void *objp){kmem_cache_t * c;unsigned long flags;if(!objp)return;local_irq_save(flags);c = (kmem_cache_t *)(virt_to_page(objp)->lru.next);kmem_cache_free(c, (void *)objp);local_irq_restore(flags);
}
非连续内存区管理
在前面一节中,内存区映射成一组连续的页框,可以充分利用高速缓存并获得较低的平均访问时间。但是如果对内存的访问不是很频繁,通过连续的线性地址来访问非连续的页框就很有意义!非连续内存区管理的优点是可以避免外碎片,但是缺点是必须打乱内核页表。非连续内存区的大小必须是4096的倍数。
非连续内存区的线性地址
要找到线性地址的一个空闲区域,可以从PAGE_OFFSET开始查找(也就是第四个GB的起始),如图所示:
- 内存区的开始部分是前896MB的物理内存的线形映射,也就是图中的物理内存映射。
- 内存区的结尾部分是固定映射的线性地址。
- 从PLMAP_BASE开始是用于高端内存页框的永久内核映射的线性地址。
- 其他的地址空间可以用于非连续内存区。在物理内存映射之后留有8MB的安全区,这是为了捕获内存越界访问。同样的原因,在每一个vmalloc区域之间都插入了4KB的安全区域。
非连续内存区保留的线性地址空间的起始地址有VMALLOC_START宏定义保存,末尾地址由VMALLOC_END宏定义保存。
非连续内存区的描述符
每个非连续内存区都对应着类型为vm_struct的描述符:
类型 | 名称 | 说明 |
---|---|---|
void * | addr | 内存区内第一个内存单元的线性地址。 |
unsigned long | size | 内存区的大小+4096(安全区大小) |
unsigned long | flags | 非连续内存区映射的内存类型 |
struct page ** | pages | 指向nr_pages数组的指针,该数组由指向页描述符的指针组成 |
unsigned int | nr_pages | 内存区填充页的个数 |
unsigned long | phys_addr | 该字段设为0,除非内存已被创建来映射一个硬件设备的io共享内存 |
struct vm_struct * | next | 指向下一个vm_struct结构的指针 |
next字段可以形成一个链表,其链表的第一个元素存放在vmlist变量中,对这个链表的访问依靠vmlist_lock读写自旋锁来保护。flags字段标识了内存类型:VM_ALLOC表示使用了vmalloc函数获取到页,VM_MAP表示vmap函数获取的页,VM_IOREMAP表示ioremap映射的物理设备。
**get_vm_area()**函数在线性地址VMALLOC_START和VMALLOC_END之间查找一个空闲区域,接收两个参数:size和flag,源码如下:
static struct vm_struct *__get_vm_area_node(unsigned long size, unsigned long flags,unsigned long start, unsigned long end,int node, gfp_t gfp_mask)
{struct vm_struct **p, *tmp, *area;unsigned long align = 1;unsigned long addr;BUG_ON(in_interrupt());if (flags & VM_IOREMAP) {int bit = fls(size);if (bit > IOREMAP_MAX_ORDER)bit = IOREMAP_MAX_ORDER;else if (bit < PAGE_SHIFT)bit = PAGE_SHIFT;align = 1ul << bit;}addr = ALIGN(start, align);size = PAGE_ALIGN(size);if (unlikely(!size))return NULL;area = kmalloc_node(sizeof(*area), gfp_mask & GFP_LEVEL_MASK, node);//kmalloc创建一个vm_struct结构体if (unlikely(!area))return NULL;/** We always allocate a guard page.*/size += PAGE_SIZE;//加上安全区4096write_lock(&vmlist_lock);//上锁for (p = &vmlist; (tmp = *p) != NULL ;p = &tmp->next) {if ((unsigned long)tmp->addr < addr) {if((unsigned long)tmp->addr + tmp->size >= addr)addr = ALIGN(tmp->size + (unsigned long)tmp->addr, align);continue;}if ((size + addr) < addr)goto out;if (size + addr <= (unsigned long)tmp->addr)//找到能容纳下size大小的区域了goto found;addr = ALIGN(tmp->size + (unsigned long)tmp->addr, align);if (addr > end - size)goto out;}found:area->next = *p;*p = area;//这里注意前面声明是**p,所以这样就可以直接插入链表了!area->flags = flags;area->addr = (void *)addr;area->size = size;area->pages = NULL;area->nr_pages = 0;area->phys_addr = 0;write_unlock(&vmlist_lock);return area;out:write_unlock(&vmlist_lock);kfree(area);if (printk_ratelimit())printk(KERN_WARNING "allocation failed: out of vmalloc space - use vmalloc=<size> to increase size.\n");return NULL;
}
分配非连续内存区
**vmalloc()**函数给内核分配一个非连续内存区:
void * vmalloc(unsigned long size){struct vm_struct *area;struct page **pages;unsigned int array_size, i;size = (size + PAGE_SIZE - 1) & PAGE_MASK; //4096的整数倍area = get_vm_area(size, VM_ALLOC);if(!area) return NULL;area->nr_pages = size >> PAGE_SHIFT;array_size = (area->nr_pages * sizeof(struct page *));area->pages = pages = kmalloc(array_size, GFP_KERNEL);//用于存放struct page **if(area->pages){remove_vm_area(area->next);kfree(area);return NULL;}memset(area->pages,0,array_size);for(i=0; i<area->nr_pages; i++){area->pages[i] = alloc_page(GFP_KERNEL | __GFP_HIGHMEM); //从高端内存申请if(!area->pages[i]){area->nr_pages = i;
fail: vfree(area->addr);return NULL;}}if(map_vm_area(area, __pgprot(0x63), &pages)) //物理地址 转化为 线性地址goto fail;return area->addr;
}
其中最重要的是map_vm_area函数,这个函数用来修改内核页表!
需要传入三个参数:
- area:指向内存区的vm_struct描述符指针。
- prot:已分配页框的保护位,它总被置为0x63。
- pages:物理页框指针数组,类型为struct page ***。
首先函数把内存区的开始和末尾的线性地址分别分配给局部变量address和end:
address = area->addr;
end = address + (area->size - PAGE_SIZE);
然后使用pgd_offset_k宏定义得到主内核页全局目录中的目录项(详细见第二张分页部分),然后获取内核页表自旋锁:
pgd = pgd_offset_k(address);
spin_lock(&init_mm.page_table_lock);
然后执行下列循环:
int ret = 0;
for(i = pgd_index(address);i<pgd_index(end-1);i++){pud_t *pud = pud_alloc(&init_mm, pgd, address); //分配一个页上级目录,并写到pgd的位置上。ret = -ENOMEM;if(!pud)break;next = (address + PGDIR_SIZE) & PGDIR_MASK;if(next < address || next > end)next = end;if(map_area_pub(pub,address,next,prot,pages)) //去处理页上级目录break;address = next;pgd ++;ret = 0;
}
spin_unlock(&init_mm.page_table_lock);
flush_cache_vmp((unsigned long)area->addr,end);
return ret;
循环结束后所有的非连续内存的页表项全被建立!
map_area_pub函数用来处理页上级目录!
do{pmd_t *pmd = pmd_alloc(&init_mm,pud,address);if(!pmd)return -ENOMEM;if(map_area_pmd(pmd,address,end-address.prot,pages))return -ENOMEM;address = (address + PUD_SIZE)&PUB_MASK;pud++;
}while(address<end);
同样的map_area_pmd去处理页中间目录:
do{pte_t *pte = pte_alloc_kernel(&init_mm,pmd,address);if(!pte)return -ENOMEM;if(map_area_pte(pte,address,end-address.prot,pages))return -ENOMEM;address = (address + PMD_SIZE)&PMD_MASK;pmd++;
}while(address<end);
最后由map_area_pte函数处理最后的页框:
do{struct page *page = **pages;set_pte(pte,mk_pte(page,prot));address += PAGE_SIZE;pte++;(*pages)++;
}while(address<end);
释放非连续内存
**vfree()用来释放vmalloc或vmalloc_32创建的非连续内存区,而vunmap()用来释放vmap创建的内存区,这两个函数都调用了__vunmap()**函数来处理。
**__vunmap()**接收两个参数:addr(起始线形地址)、deallocate_pages(标志)。
**__vunmap()**主要做了以下内容:
- 调用remove_vm_area()函数得到vm_struct,并清楚相应的页表项。
- 如果deallocate_pages被置位,函数扫描指向页描述符的area->pages指针数组;对于数组的每一个元素,调用__free_page函数释放页框。并调用kfree(area->pages)释放数组本身。
- 调用kfree(area)释放vm_struct描述符。
remove_vm_area执行如下:
write_lock(&vmlist_lock);
for(p = &vmlist;(tmp=*p);p=&tmp->next){if(tmp->addr = addr){unmap_vm_area(tmp);*p = tmp->next;break;}
}
write_unlock(&vmlist_lock);
return tmp;
内存区通过unmap_vm_area函数来释放,其执行的是map_vm_area的反向操作:
address = area->addr;
end = address + area->size;
pgd = pgd_offset_k(address);
for(i=pgd_index(address);i<=pgd_index(end);i++){next = (address + PGDIR_SIZE)&PGDIR_MASK;if(next<=address||next>end)next = end;unmap_area_pud(pgd,address,next-address);address = next;pgd++;
}
unmap_area_pud如下:
do{unmap_area_pmd(pub,address,end-address);address = (address + PUD_SIZE)&PUD_MASK;pud++;
}while(address&&(address<end));
unmap_area_pmd如下:
do{unmap_area_pte(pmd,address,end-address);address = (address + PMD_SIZE)&PMD_MASK;pmd++;
}while(address<end);
最后,unmap_area_pte如下:
do{pte_t page = ptep_get_and_clear(pte);address += PAGE_SIZE;pte++;if(!pte_none(page)&&!pte_present(page))printk("...");
}while(address<end);
ptep_get_and_clear宏将pte指向的页表项设为0。
深入理解Linux内核-内存管理相关推荐
- 深入理解Linux内核---内存管理zone
转载:https://blog.csdn.net/gatieme/article/details/52384529 https://blog.csdn.net/gatieme/article/deta ...
- Linux内核内存管理(3):kmemcheck介绍
Linux内核内存管理 kmemcheck介绍 rtoax 2021年3月 在英文原文基础上,针对中文译文增加5.10.13内核源码相关内容. 5.10.13不存在kmemcheck的概念,取代的是k ...
- Linux内核内存管理(1):内存块 - memblock
Linux内核内存管理 内存块 - memblock rtoax 2021年3月 在英文原文基础上,针对中文译文增加5.10.13内核源码相关内容. 1. 简介 内存管理是操作系统内核中最复杂的部分之 ...
- Linux内核内存管理(2):固定映射地址(fixmap)和输入输出重映射(ioremap)
Linux内核内存管理 固定映射地址(fixmap)和输入输出重映射(ioremap) rtoax 2021年3月 在英文原文基础上,针对中文译文增加5.10.13内核源码相关内容. Print ke ...
- 【Linux 内核 内存管理】虚拟地址空间布局架构 ③ ( 内存描述符 mm_struct 结构体成员分析 | mmap | mm_rb | task_size | pgd | mm_users )
文章目录 一.mm_struct 结构体成员分析 1.mmap 成员 2.mm_rb 成员 3.get_unmapped_area 函数指针 4.task_size 成员 5.pgd 成员 6.mm_ ...
- 【Linux 内核 内存管理】内存管理架构 ④ ( 内存分配系统调用过程 | 用户层 malloc free | 系统调用层 brk mmap | 内核层 kmalloc | 内存管理流程 )
文章目录 一.内存分配系统调用过程 ( 用户层 | 系统调用 | 内核层 ) 二.内存管理流程 一.内存分配系统调用过程 ( 用户层 | 系统调用 | 内核层 ) " 堆内存 " ...
- 【Linux 内核 内存管理】内存管理架构 ② ( 用户空间内存管理 | malloc | ptmalloc | 内核空间内存管理 | sys_brk | sys_mmap | sys_munmap)
文章目录 一.用户空间内存管理 ( malloc / free / ptmalloc / jemalloc / tcmalloc ) 二.内核空间内存管理 1.内核内存管理系统调用 ( sys_brk ...
- 【Linux 内核 内存管理】优化内存屏障 ③ ( 编译器屏障 | 禁止 / 开启内核抢占 与 方法保护临界区 | preempt_disable 禁止内核抢占源码 | 开启内核抢占源码 )
文章目录 一.禁止 / 开启内核抢占 与 方法保护临界区 二.编译器优化屏障 三.preempt_disable 禁止内核抢占 源码 四.preempt_enable 开启内核抢占 源码 一.禁止 / ...
- 【Linux 内核 内存管理】RCU 机制 ② ( RCU 机制适用场景 | RCU 机制特点 | 使用 RCU 机制保护链表 )
文章目录 一.RCU 机制适用场景 二.RCU 机制特点 三.使用 RCU 机制保护链表 一.RCU 机制适用场景 在上一篇博客 [Linux 内核 内存管理]RCU 机制 ① ( RCU 机制简介 ...
最新文章
- 安装完python后、还需要安装什么-安装python后
- 初识mysql学习笔记
- HDU 3530 Subsequence
- apache2+支持php7,Ubuntu14.04下配置PHP7.0+Apache2+Mysql5.7
- oracle数据库内核,深入内核:Oracle数据库里SELECT操作Hang解析
- ubuntu 20.04 DNS 设置
- 火山引擎 veStack 在企业办公场景的落地实践
- 微软欲打造开发者联盟!
- 基于OWIN WebAPI 使用OAUTH2授权服务【授权码模式(Authorization Code)】
- No package ‘dconf‘ found
- Nginx面试题整理
- 我们要不要和to B“霸王龙”企业交朋友?
- 张量基础2(张量乘法和对称)
- asp.net基于net的小美果蔬批发网-蔬菜商城系统-计算机毕业设计
- 关于SQL server 2000 在安装过程中遇到文件挂起的解决办法
- macOS VirtualBox 安装步骤
- 关于虚拟机检测技术的研究
- java入门之控制台输入人数成绩计算及格率(将成绩存入数组)与打印九九乘法表
- python文件对象
- 网络安全工程师需要考什么证吗?