/* * Each physical page in the system has a struct page associated with * it to keep track of whatever it is we are using the page for at the * moment. Note that we have no way to track which tasks are using * a page. */ structpage { /** * 一组标志,也对页框所在的管理区进行编号 * 在不支持NUMA的机器上,flags中字段中管理索引占两位,节点索引占一位。 * 在支持NUMA的32位机器上,flags中管理索引占用两位。节点数目占6位。 * 在支持NUMA的64位机器上,64位的flags字段中,管理区索引占用两位,节点数目占用10位。 * page_flags_t实际上是个无符号整型数 */ page_flags_t flags; /* Atomic flags, some possibly * updated asynchronously */ /** * 页框的引用计数。当小于0表示没有人使用。 * Page_count返回_count+1表示正在使用的人数。 */ atomic_t _count; /* Usage count, see below. */ /** * 页框中的页表项数目(没有则为-1) * -1: 表示没有页表项引用该页框。 * 0: 表明页是非共享的。 * >0: 表示而是共享的。 */ atomic_t _mapcount; /* Count of ptes mapped in mms, * to show when page is mapped * & limit reverse map searches. */ /** * 可用于正在使用页的内核成分(如在缓冲页的情况下,它是一个缓冲器头指针。) * 如果页是空闲的,则该字段由伙伴系统使用。 * 当用于伙伴系统时,如果该页是一个2^k的空闲页块的第一个页,那么它的值就是k. * 这样,伙伴系统可以查找相邻的伙伴,以确定是否可以将空闲块合并成2^(k+1)大小的空闲块。 */ unsignedlong private; /* Mapping-private opaque data: * usually used for buffer_heads * if PagePrivate set; used for * swp_entry_t if PageSwapCache * When page is free, this indicates * order in the buddy system. */ /** * 当页被插入页高速缓存时使用或者当页属于匿名页时使用)。 * 如果mapping字段为空,则该页属于交换高速缓存(swap cache)。 * 如果mapping字段不为空,且最低位为1,表示该页为匿名页。同时该字段中存放的是指向anon_vma描述符的指针。 * 如果mapping字段不为空,且最低位为0,表示该页为映射页。同时该字段指向对应文件的address_space对象。 */ structaddress_space *mapping;/* If low bit clear, points to * inode address_space, or NULL. * If page mapped as anonymous * memory, low bit is set, and * it points to anon_vma object: * see PAGE_MAPPING_ANON below. */ /** * 作为不同的含义被几种内核成分使用。 * 在页磁盘映象或匿名区中表示存放在页框中的数据的位置。 * 或者它存放在一个换出页标志符。 * 表示所有者的地址空间中以页大小为单位的偏移量, * 也就是磁盘映像中页中数据的位置 * page->index是区域内的页索引或是页的线性地址除以PAGE_SIZE * * liufeng: * 不是页内偏移量,而是该页面相对于文件起始位置,以页面为大小的偏移量 * 如果减去vma->vm_pgoff,就表示该页面的虚拟地址相对于vma起始地址,以页面为大小的偏移量 */ pgoff_t index; /* Our offset within mapping. */ /** * 包含页的最近最少使用的双向链表的指针。 */ structlist_headlru;/* Pageout list, eg. active_list * protected by zone->lru_lock ! */ /* * On machines where all RAM is mapped into kernel address space, * we can simply calculate the virtual address. On machines with * highmem some memory is mapped into kernel virtual memory * dynamically, so we need a place to store that address. * Note that this field could be 16 bits on x86 ... ;) * * Architectures with slow multiplication can define * WANT_PAGE_VIRTUAL in asm/page.h */ #if defined(WANT_PAGE_VIRTUAL) /** * 如果进行了内存映射,就是内核虚拟地址。对存在高端内存的系统来说有意义。 * 否则是NULL */ void *virtual; /* Kernel virtual address (NULL if not kmapped, ie. highmem) */ #endif/* WANT_PAGE_VIRTUAL */ };
typedefenumpage_buf_flags_e {/* pb_flags values */ PBF_READ = (1 << 0), /* buffer intended for reading from device */ PBF_WRITE = (1 << 1), /* buffer intended for writing to device */ PBF_MAPPED = (1 << 2), /* buffer mapped (pb_addr valid) */ PBF_PARTIAL = (1 << 3), /* buffer partially read */ PBF_ASYNC = (1 << 4), /* initiator will not wait for completion */ PBF_NONE = (1 << 5), /* buffer not read at all */ PBF_DELWRI = (1 << 6), /* buffer has dirty pages */ PBF_STALE = (1 << 7), /* buffer has been staled, do not find it */ PBF_FS_MANAGED = (1 << 8), /* filesystem controls freeing memory */ PBF_FS_DATAIOD = (1 << 9), /* schedule IO completion on fs datad */ PBF_FORCEIO = (1 << 10), /* ignore any cache state */ PBF_FLUSH = (1 << 11), /* flush disk write cache */ PBF_READ_AHEAD = (1 << 12), /* asynchronous read-ahead */
/* flags used only as arguments to access routines */ PBF_LOCK = (1 << 14), /* lock requested */ PBF_TRYLOCK = (1 << 15), /* lock requested, but do not wait */ PBF_DONT_BLOCK = (1 << 16), /* do not block in current thread */
/* flags used only internally */ _PBF_PAGE_CACHE = (1 << 17),/* backed by pagecache */ _PBF_KMEM_ALLOC = (1 << 18),/* backed by kmem_alloc() */ _PBF_RUN_QUEUES = (1 << 19),/* run block device task queue */ } page_buf_flags_t;
/** * 内存管理区描述符 */ structzone { /* Fields commonly accessed by the page allocator */ /** * 管理区中空闲页的数目 */ unsignedlong free_pages; /** * Pages_min-管理区中保留页的数目 * Page_low-回收页框使用的下界。同时也被管理区分配器为作为阈值使用。 * pages_high-回收页框使用的上界,同时也被管理区分配器作为阈值使用。 */ unsignedlong pages_min, pages_low, pages_high; /* * We don't know if the memory that we're going to allocate will be freeable * or/and it will be released eventually, so to avoid totally wasting several * GB of ram we must reserve some of the lower zone memory (otherwise we risk * to run OOM on the lower zones despite there's tons of freeable ram * on the higher zones). This array is recalculated at runtime if the * sysctl_lowmem_reserve_ratio sysctl changes. */ /** * 为内存不足保留的页框,分别为各种内存域指定了若干页 * 用于一些无论如何都不能失败的关键性内存分配 */ unsignedlong lowmem_reserve[MAX_NR_ZONES]; /** * 用于实现单一页框的特殊高速缓存。 * 每内存管理区对每CPU都有一个。包含热高速缓存和冷高速缓存。 * 内核使用这些列表来保存可用于满足实现的“新鲜”页。 * 有些页帧很可能在CPU高速缓存中,因此可以快速访问,称之为热。 * 未缓存的页帧称之为冷的。 */ structper_cpu_pageset pageset[NR_CPUS];
/* * free areas of different sizes */ /** * 保护该描述符的自旋锁 */ spinlock_t lock; /** * 标识出管理区中的空闲页框块。 * 包含11个元素,被伙伴系统使用。分别对应大小的1,2,4,8,16,32,128,256,512,1024连续空闲块的链表。 * 第k个元素标识所有大小为2^k的空闲块。free_list字段指向双向循环链表的头。 * free_list是free_area的内部结构,是个双向环回链表节点。 */ structfree_area free_area[MAX_ORDER];
/* * prev_priority holds the scanning priority for this zone. It is * defined as the scanning priority at which we achieved our reclaim * target at the previous try_to_free_pages() or balance_pgdat() * invokation. * * We use prev_priority as a measure of how much stress page reclaim is * under - it drives the swappiness decision: whether to unmap mapped * pages. * * temp_priority is used to remember the scanning priority at which * this zone was successfully refilled to free_pages == pages_high. * * Access to both these fields is quite racy even on uniprocessor. But * it is expected to average out OK. */ /** * 临时管理区的优先级。 */ int temp_priority; /** * 管理区优先级,范围在12和0之间。 */ int prev_priority;
ZONE_PADDING(_pad2_) /* Rarely used or read-mostly fields */
/* * wait_table -- the array holding the hash table * wait_table_size -- the size of the hash table array * wait_table_bits -- wait_table_size == (1 << wait_table_bits) * * The purpose of all these is to keep track of the people * waiting for a page to become available and make them * runnable again when possible. The trouble is that this * consumes a lot of space, especially when so few things * wait on pages at a given time. So instead of using * per-page waitqueues, we use a waitqueue hash table. * * The bucket discipline is to sleep on the same queue when * colliding and wake all in that wait queue when removing. * When something wakes, it must check to be sure its page is * truly available, a la thundering herd. The cost of a * collision is great, but given the expected load of the * table, they should be so rare as to be outweighed by the * benefits from the saved space. * * __wait_on_page_locked() and unlock_page() in mm/filemap.c, are the * primary users of these fields, and in mm/page_alloc.c * free_area_init_core() performs the initialization of them. */ /** * 进程等待队列的散列表。这些进程正在等待管理区中的某页。 */ wait_queue_head_t * wait_table; /** * 等待队列散列表的大小。 */ unsignedlong wait_table_size; /** * 等待队列散列表数组的大小。值为2^order */ unsignedlong wait_table_bits;
/* * One allocation request operates on a zonelist. A zonelist * is a list of zones, the first one is the 'goal' of the * allocation, the other zones are fallback zones, in decreasing * priority. * * Right now a zonelist takes up less than a cacheline. We never * modify it apart from boot-up, and only a few indices are used, * so despite the zonelist table being relatively big, the cache * footprint of this construct is very small. */ structzonelist { structzone *zones[MAX_NUMNODES * MAX_NR_ZONES + 1]; // NULL delimited };
/* * This is the 'heart' of the zoned buddy allocator. */ /** * 请求分配一组连续页框,它是管理区分配器的核心 * gfp_mask:在内存分配请求中指定的标志 * order: 连续分配的页框数量的对数(实际分配的是2^order个连续的页框) * zonelist: zonelist数据结构的指针。该结构按优先次序描述了适于内存分配的内存管理区 */ structpage * fastcall __alloc_pages(unsignedintgfp_mask, unsignedintorder, structzonelist *zonelist) { constint wait = gfp_mask & __GFP_WAIT; structzone **zones, *z; structpage *page; structreclaim_statereclaim_state; structtask_struct *p = current; int i; int classzone_idx; int do_retry; int can_try_harder; int did_some_progress;
might_sleep_if(wait);
/* * The caller may dip into page reserves a bit more if the caller * cannot run direct reclaim, or is the caller has realtime scheduling * policy */ can_try_harder = (unlikely(rt_task(p)) && !in_interrupt()) || !wait;
zones = zonelist->zones; /* the list of zones suitable for gfp_mask */
if (unlikely(zones[0] == NULL)) { /* Should this ever happen?? */ returnNULL; }
classzone_idx = zone_idx(zones[0]);
restart: /* Go through the zonelist once, looking for a zone with enough free */ /** * 扫描包含在zonelist数据结构中的每个内存管理区 */ /* * 这里可以看到对zonelist的扫描顺序,是按索引从前向后的。 * 那么zonelist是如何布局的呢?如何为不同zone区分高低贵贱呢? * 对于UMA架构比较简单,因为只有单一node,在build_zonelists中可以一探究竟 * 简单归纳来说,HIGHMEM最廉价、NORMAL次之,DMA最昂贵。 */ for (i = 0; (z = zones[i]) != NULL; i++) { /** * 对于每个内存管理区,该函数将空闲页框的个数与一个阀值进行比较 * 该值取决于内存分配标志、当前进程的类型及管理区被函数检查的次数。 * 实际上,如果空闲内存不足,那么每个内存管理区一般会被检查几次。 * 每一次在所请求的空闲内存最低量的基础上使用更低的值进行扫描。 * 因此,这段循环代码会被复制几次,而变化很小。 */
/** * 一般来说,应当在上一次扫描时得到内存。 * 运行到此,表示内存已经紧张了(xie.baoyou注:没有连续的页框可供分配了) * 就唤醒kswapd内核线程来异步的开始回收页框。 */ for (i = 0; (z = zones[i]) != NULL; i++) wakeup_kswapd(z, order);
/* * Go through the zonelist again. Let __GFP_HIGH and allocations * coming from realtime tasks to go deeper into reserves */ /** * 执行对内存管理区的第二次扫描,将值z->pages_min作为阀值传入。这个值已经在上一步的基础上降低了(pages_low一般是pages_min的5/4,pages_high一般是pages_min的3/2)。 * 当然,实际的min值还是要由can_try_harder和gfp_high确定。z->pages_min仅仅是一个参考值而已。 */ for (i = 0; (z = zones[i]) != NULL; i++) { if (!zone_watermark_ok(z, order, z->pages_min, classzone_idx, can_try_harder, gfp_mask & __GFP_HIGH)) continue; /* * 第二次扫描后,可能因为阈值的降低也可能因为异步的kswapd内核线程回收了页框 * 此时已经可以满足分配需求了 */ page = buffered_rmqueue(z, order, gfp_mask); if (page) goto got_pg; }
/* This allocation should allow future memory freeing. */ /** * 上一步都还没有获得内存,系统内存肯定是不足了。 */
/** * 如果产生内存分配的内核控制路径不是一个中断处理程序或者可延迟函数, * 并且它试图回收页框(PF_MEMALLOC,TIF_MEMDIE标志被置位),那么才对内存管理区进行第三次扫描。 */ if (((p->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE))) && !in_interrupt()) { /* go through the zonelist yet again, ignoring mins */ for (i = 0; (z = zones[i]) != NULL; i++) { /** * 本次扫描就不调用zone_watermark_ok,它忽略阀值,这样才能从预留的页中分配页。 * 允许这样做,因为是这个进程想要归还页框,那就暂借一点给它吧(呵呵,舍不得孩子套不到狼)。 */ page = buffered_rmqueue(z, order, gfp_mask); if (page) goto got_pg; }
/** * 如果已经回收了一些页框,那么执行第二遍扫描类似的操作。 */ if (likely(did_some_progress)) { /* * 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. */ for (i = 0; (z = zones[i]) != NULL; i++) { if (!zone_watermark_ok(z, order, z->pages_min, classzone_idx, can_try_harder, gfp_mask & __GFP_HIGH)) continue;
page = buffered_rmqueue(z, order, gfp_mask); if (page) goto got_pg; } } elseif ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) { /* * 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. */ /** * 没有释放任何页框,说明内核遇到很大麻烦了。因为内存少又不能释放页框。 * 如果允许杀死进程:__GFP_FS被置位并且__GFP_NORETRY标志为0。 * 那就开始准备杀死进程吧。 */
/** * 再扫描一次内存管理区。 * 这样做有点莫名其妙,既然申请少一点的内存都不行,为什么还要传入z->pages_high??它看起来更不会成功。 * 其实这样做还是有道理的:实际上,只有另一个内核控制路径已经杀死一个进程来回收它的内存后,这步才会成功。 * 因此,这步避免了两个(而不是一个)无辜的进程被杀死。 */ for (i = 0; (z = zones[i]) != NULL; i++) { if (!zone_watermark_ok(z, order, z->pages_high, classzone_idx, 0, 0)) continue;
page = buffered_rmqueue(z, order, gfp_mask); if (page) goto got_pg; }
/** * 还是不行,就杀死一些进程再试吧。 */ out_of_memory(gfp_mask); /** * let's go on */ 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. */ /** * 如果内存分配请求不能被满足,那么函数决定是否应当继续扫描内存管理区。 * 如果__GFP_NORETRY被清除,并且内存分配请求跨越了多达8个页框或者__GFP_REPEAT被置位,或者__GFP_NOFAIL被置位。 */ 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; } /** * 要重试,就调用blk_congestion_wait 使进程休眠一会。再跳到rebalance 重试。 */ if (do_retry) { blk_congestion_wait(WRITE, HZ/50); goto rebalance; } /** * 既然不用重试,那就执行到nopage 返回NULL 了。 */ 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(); } returnNULL; got_pg: zone_statistics(zonelist, z); return page; }
/* * Return 1 if free pages are above 'mark'. This takes into account the order * of the allocation. */ /** * zone_watermark_ok辅助函数接收几个参数,它们决定内存管理区中空闲页框个数的阀值min。 * 特别的,如果满足下列两个条件,则该函数返回1: * 1、除了被分配的页框外,在内存管理区中至少还有min个空闲页框,不包括为内存不足保留的页框(zone的lowmem_reserve字段)。 * 2、除了被分配的页框外,这里在order至少为k的块中,起码还有min/2^k个空闲页框。其中对每个k,取值在1和order之间。 * * 作为参数传递的基本值可以是内存管理区界值pages_min,pages_low,pages_high中的任意一个。 */ intzone_watermark_ok(struct zone *z, int order, unsignedlong mark, int classzone_idx, int can_try_harder, int gfp_high) { /* free_pages my go negative - that's OK */ long min = mark, free_pages = z->free_pages - (1 << order) + 1; /*free_pages是除了要分配的页框(1<<order)后剩余的空闲页面*/ int o;
/** * 如果gfp_high标志被置位。则base除2。 * 注意这里不是:min /= 2; * 一般来说,如果gfp_mask的__GFP_HIGH标志被置位,那么这个标志就会为1 * 换句话说,就是指从高端内存中分配。 */ if (gfp_high) min -= min / 2; /** * 如果作为参数传递的can_try_harder标志被置位,这个值再减少1/4 * can_try_harder=1一般是当:gfp_mask中的__GFP_WAIT标志被置位,或者当前进程是一个实时进程并且在进程上下文中已经完成了内存分配。 */ if (can_try_harder) min -= min / 4;
/* * 除了被分配的页框外,在内存管理区中至少还有min个空闲页框,不包括为内存不足保留的页框(zone的lowmem_reserve字段)。 */ if (free_pages <= min + z->lowmem_reserve[classzone_idx]) return0;
/* * 除了被分配的页框外,这里在order至少为k的块中,起码还有min/2^k个空闲页框。其中对每个k,取值在1和order之间。 */ for (o = 0; o < order; o++) { /* 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;
/* * A count must never go down to zero * without a TLB flush! */ need_wakeup = 0; switch (--pkmap_count[nr]) { case0: BUG();/* 一定是逻辑错误了,多次调用了unmap */ case1: /* * Avoid an unnecessary wake_up() function call. * The common case is pkmap_count[] == 1, but * no waiters. * The tasks queued in the wait-queue are guarded * by both the lock in the wait-queue-head and by * the kmap_lock. As the kmap_lock is held here, * no need for the wait-queue-head's lock. Simply * test if the queue is empty. */ /** * 页表项可用了。need_wakeup会唤醒等待队列上阻塞的线程。 */ need_wakeup = waitqueue_active(&pkmap_map_wait); } spin_unlock(&kmap_lock);
/* do wake-up, if needed, race-free outside of the spin lock */ /** * 有等待线程,唤醒它。 */ if (need_wakeup) wake_up(&pkmap_map_wait); }
/* even !CONFIG_PREEMPT needs this, for in_atomic in do_page_fault */ inc_preempt_count(); /** * 如果被映射的页不属于高端内存,当然用不着映射。直接返回线性地址就行了。 */ if (!PageHighMem(page)) returnpage_address(page);
if (vaddr != __fix_to_virt(FIX_KMAP_BEGIN+idx)) BUG();
/* * force other mappings to Oops if they'll try to access * this pte without first remap it */ /** * 取消映射并刷新TLB */ pte_clear(kmap_pte-idx); __flush_tlb_one(vaddr); #endif /** * 允许抢占,并检查调度点。 */ dec_preempt_count(); preempt_check_resched(); }
structpage{ ... /** * 可用于正在使用页的内核成分(如在缓冲页的情况下,它是一个缓冲器头指针。) * 如果页是空闲的,则该字段由伙伴系统使用。 * 当用于伙伴系统时,如果该页是一个2^k的空闲页块的第一个页,那么它的值就是k. * 这样,伙伴系统可以查找相邻的伙伴,以确定是否可以将空闲块合并成2^(k+1)大小的空闲块。 */ unsignedlongprivate; /* Mapping-private opaque data: * usually used for buffer_heads * if PagePrivate set; used for * swp_entry_t if PageSwapCache * When page is free, this indicates * order in the buddy system. */ ... };
/** * 内存管理区页框高速缓存描述符 */ structper_cpu_pages { /** * 高速缓存中的页框个数 */ int count; /* number of pages in the list */ /** * 下界,低于此就需要补充高速缓存。 */ int low; /* low watermark, refill needed */ /** * 上界,高于此则向伙伴系统释放页框。 */ int high; /* high watermark, emptying needed */ /** * 当需要增加或者减少高速缓存页框时,操作的页框个数。 */ int batch; /* chunk size for buddy add/remove */ /** * 高速缓存中包含的页框描述符链表。 */ structlist_head list; /* the list of pages */ };