经过这段代码能够看出,它调用了kmem_cache_alloc_node函数,在task_struct的缓存区域task_struct分配了一块内存node
static struct kmem_cache *task_struct_cachep; task_struct_cachep = kmem_cache_create("task_struct", arch_task_struct_size, align, SLAB_PANIC|SLAB_NOTRACK|SLAB_ACCOUNT, NULL); static inline struct task_struct *alloc_task_struct_node(int node) { return kmem_cache_alloc_node(task_struct_cachep, GFP_KERNEL, node); } static inline void free_task_struct(struct task_struct *tsk) { kmem_cache_free(task_struct_cachep, tsk); }
一、在系统初始化的时候,task_struct_cachep 会被 kmem_cache_create 函数建立。算法
二、这个函数也比较容易看懂、专门用于分配 task_struct 对象的缓存。这个缓存区的名字就叫 task_struct。数组
三、缓存区中每一块的大小正好等于 task_struct 的大小,也即 arch_task_struct_size。缓存
一、kmem_cache_alloc_node函数的做用?数据结构
一、有了这个缓存区,每次建立task_struct的时候,咱们就不用到内存里面去分配,先在缓存里面看看有没有直接可用的,这就是kmem_cache_alloc_node的做用app
二、kmem_cache_free的做用less
当一个进程结束,task_struct 也不用直接被销毁,而是放回到缓存中,这就是kmem_cache_free的做用,async
这样,新进程建立的时候,咱们就能够直接用现成的缓存中的task_struct了函数
struct kmem_cache { struct kmem_cache_cpu __percpu *cpu_slab; /* Used for retriving partial slabs etc */ unsigned long flags; unsigned long min_partial; int size; /* The size of an object including meta data */ int object_size; /* The size of an object without meta data */ int offset; /* Free pointer offset. */ #ifdef CONFIG_SLUB_CPU_PARTIAL int cpu_partial; /* Number of per cpu partial objects to keep around */ #endif struct kmem_cache_order_objects oo; /* Allocation and freeing of slabs */ struct kmem_cache_order_objects max; struct kmem_cache_order_objects min; gfp_t allocflags; /* gfp flags to use on each alloc */ int refcount; /* Refcount for slab cache destroy */ void (*ctor)(void *); ...... const char *name; /* Name (only for display!) */ struct list_head list; /* List of slab caches */ ...... struct kmem_cache_node *node[MAX_NUMNODES]; };
一、在 struct kemem_cache里面,有个变量struct list_head list,这个结构咱们已经看到过屡次了ui
二、咱们能够想象一下,对于操做系统来说,要建立和管理的缓存绝对不止task_struct,难道mm_struct就不须要吗?
三、fs_struct就不须要吗?都须要,所以全部的缓存最后都会放在一个链表里面这就是LIST_HEAD(slab_caches)
对于缓存来来说,其实就是分配了连续几页的答内存块,而后根据缓存对象的大小,切成小内存块因此咱们这里有三个kmem_cache_order_objects 类型的变量:
一、这里面有order,就是2的order次方个页面的答内存块,
二、objects就是可以存放的缓存对象的数量
最终,咱们讲答内存块切分红小内存块,样子就像下面这样
每一项的结构都是缓存对象后面跟一个下一个空闲对象的指针,这样很是方便将全部的空闲对象链成一个链,其实这就至关于我们数据结构
里面学的,用数组实现一个可随机插入和删除的链表
因此,这里面有三个变量:size是包含这个指针的大小,object_size是纯的大小,offset就是把下一个空闲对象的指针存放在这一项里的偏移量
那这些缓存对象那些被分配了,那些在空着,什么状况下整个大内存块被分配完,须要向伙伴系统申请几个页造成新的大内存块?这些信息该由谁来维护呢?
接下来就是最重要的两个成员变量出场的时候了kmem_cache_cpu和kmem_cache_node,它们是每一个NUMA节点上有一个,咱们只须要看一个节点里面的状况
咱们来看一下,kemem_cache_cpu里面是如何存放缓存块的
struct kmem_cache_cpu { void **freelist; /* Pointer to next available object */ unsigned long tid; /* Globally unique transaction id */ struct page *page; /* The slab from which we are allocating */ #ifdef CONFIG_SLUB_CPU_PARTIAL struct page *partial; /* Partially allocated frozen slabs */ #endif ...... };
在这里,page指向的答内存块的第一个页,缓存块就是从里面分配的,freelist指向大内存块里面第一个空闲的项按照上面说的,这一项会有指针指向下一个空闲的项,最终全部空闲的项会造成一个链表
partial指向的页是大内存块的第一个页,之因此明叫partial(部分),就是由于它里面部分被分配出去了,部分是空的,这是一个备用列表当page满了,就会从这里找
咱们来看一下,kemem_cache_node这的定义
struct kmem_cache_node { spinlock_t list_lock; ...... #ifdef CONFIG_SLUB unsigned long nr_partial; struct list_head partial; ...... #endif };
这里面也有一个 partial,是一个链表。这个链表里存放的是部分空闲的大内存块。这是 kmem_cache_cpu 里面的 partial的备用列表,若是那里没有,就到这里来找。
下面咱们就来看看这个分配过程。kmem_cache_alloc_node 会调用 slab_alloc_node。你仍是先重点看这里面的注释,这里面说的就是快速通道和普统统道的概念
/* * Inlined fastpath so that allocation functions (kmalloc, kmem_cache_alloc) * have the fastpath folded into their functions. So no function call * overhead for requests that can be satisfied on the fastpath. * * The fastpath works by first checking if the lockless freelist can be used. * If not then __slab_alloc is called for slow processing. * * Otherwise we can simply pick the next object from the lockless free list. */ static __always_inline void *slab_alloc_node(struct kmem_cache *s, gfp_t gfpflags, int node, unsigned long addr) { void *object; struct kmem_cache_cpu *c; struct page *page; unsigned long tid; ...... tid = this_cpu_read(s->cpu_slab->tid); c = raw_cpu_ptr(s->cpu_slab); ...... object = c->freelist; page = c->page; if (unlikely(!object || !node_match(page, node))) { object = __slab_alloc(s, gfpflags, node, addr, c); stat(s, ALLOC_SLOWPATH); } ...... return object; }
快速通道很简单,取出 cpu_slab 也即kmem_cache_cpu 的 freelist,这就是第一个空闲的项,能够直接返回了。若是没有空闲的了,则只好进入普统统道,调用 __slab_alloc。
static void *___slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node, unsigned long addr, struct kmem_cache_cpu *c) { void *freelist; struct page *page; ...... redo: ...... /* must check again c->freelist in case of cpu migration or IRQ */ freelist = c->freelist; if (freelist) goto load_freelist; freelist = get_freelist(s, page); if (!freelist) { c->page = NULL; stat(s, DEACTIVATE_BYPASS); goto new_slab; } load_freelist: c->freelist = get_freepointer(s, freelist); c->tid = next_tid(c->tid); return freelist; new_slab: if (slub_percpu_partial(c)) { page = c->page = slub_percpu_partial(c); slub_set_percpu_partial(c, page); stat(s, CPU_PARTIAL_ALLOC); goto redo; } freelist = new_slab_objects(s, gfpflags, node, &c); ...... return freeli
若是真的还不行,那就要到 new_slab_objects 了
static inline void *new_slab_objects(struct kmem_cache *s, gfp_t flags, int node, struct kmem_cache_cpu **pc) { void *freelist; struct kmem_cache_cpu *c = *pc; struct page *page; freelist = get_partial(s, flags, node, c); if (freelist) return freelist; page = new_slab(s, flags, node); if (page) { c = raw_cpu_ptr(s->cpu_slab); if (c->page) flush_slab(s, c); freelist = page->freelist; page->freelist = NULL; stat(s, ALLOC_SLAB); c->page = page; *pc = c; } else freelist = NULL; return freelis
在这里面,get_partial 会根据 node id找到相应的 kmem_cache_node,而后调用 get_partial_node,开始在这个节点进行分配
/* * Try to allocate a partial slab from a specific node. */ static void *get_partial_node(struct kmem_cache *s, struct kmem_cache_node *n, struct kmem_cache_cpu *c, gfp_t flags) { struct page *page, *page2; void *object = NULL; int available = 0; int objects; ...... list_for_each_entry_safe(page, page2, &n->partial, lru) { void *t; t = acquire_slab(s, n, page, object == NULL, &objects); if (!t) break; available += objects; if (!object) { c->page = page; stat(s, ALLOC_FROM_PARTIAL); object = t; } else { put_cpu_partial(s, page, 0); stat(s, CPU_PARTIAL_NODE); } if (!kmem_cache_has_cpu_partial(s) || available > slub_cpu_partial(s) / 2) break; } ...... return object;
acquire_slab 会从 kmem_cache_node的partial 链表中拿下一大块内存来,而且将 freelist也就是第一块空闲的缓存块,赋值给t
而且当第一轮循环的时候,将kmem_cache_cpu的page指向去下来的这一大块内存,返回的object就是这块内存里面的第一个缓存t
若是kmem_cache_cpu也有一个partial,就会进行第二轮,再次取下一大块内存来,此次调用put_cpu_partial,放到 kmem_cache_cpu的 partial 里面。
若是kmem_cache_node里面也没有空闲的内存,这就说明原来分配的页里面都放满了,就要回到 new_slab_objects 函数,里面new_slab 函数会调用 allocate_slab。
static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node) { struct page *page; struct kmem_cache_order_objects oo = s->oo; gfp_t alloc_gfp; void *start, *p; int idx, order; bool shuffle; flags &= gfp_allowed_mask; ...... page = alloc_slab_page(s, alloc_gfp, node, oo); if (unlikely(!page)) { oo = s->min; alloc_gfp = flags; /* * Allocation may have failed due to fragmentation. * Try a lower order alloc if possible */ page = alloc_slab_page(s, alloc_gfp, node, oo); if (unlikely(!page)) goto out; stat(s, ORDER_FALLBACK); } ...... return page; }
在这里,咱们看到了alloc_slab_page 分配页面。分配的时候,要按kmem_cache_order_objects 里面的 order来。若是第一次分配不成功、说明内存已经很紧张了,那就换成min版本的kmem_cache_order_objects
好了,这个复杂的层层分配机制,咱们就讲到这里,你理解到这里也就够用了
虚拟地址空间很是大、物理内存不可能有这么多的空间放得下,因此通常状况下,页面只有在被使用的时候,才会放在物理内存
若是过一段时间不被使用,即使用户进程并无释放,物理内存管理也有责任作必定的干预,
例如这些物理内存中的页面换出到硬盘上去;将空出的物理内存,交给活跃的进程去使用
一、分配内存的时候发现没有地方了,就试图回收一下
二、内存管理系统主动去作,而不是等真的出事再作,这就是内核线程kswapd
/* * The background pageout daemon, started as a kernel thread * from the init process. * * This basically trickles out pages so that we have _some_ * free memory available even if there is no other activity * that frees anything up. This is needed for things like routing * etc, where we otherwise might have all activity going on in * asynchronous contexts that cannot page things out. * * If there are applications that are active memory-allocators * (most normal use), this basically shouldn't matter. */ static int kswapd(void *p) { unsigned int alloc_order, reclaim_order; unsigned int classzone_idx = MAX_NR_ZONES - 1; pg_data_t *pgdat = (pg_data_t*)p; struct task_struct *tsk = current; for ( ; ; ) { ...... kswapd_try_to_sleep(pgdat, alloc_order, reclaim_order, classzone_idx); ...... reclaim_order = balance_pgdat(pgdat, alloc_order, classzone_idx); ...... } }
例如,我们解析申请一个页面的时候,会调用get_page_from_freelist,接下来的调用链
经过这个调用链,能够看出,页面换出也是之内存节点为单位的
这里的调用链是 balance_pgdat kswapd_shrink_node->shrink_node是之内存节点为单位的,最后也调用shrink_node会调用 shrink_node_memcg。
这里面有一个循环处理页面的列表,看这个函数的注释,其实和上面咱们想表达的内存换出是同样的
/* * This is a basic per-node page freer. Used by both kswapd and direct reclaim. */ static void shrink_node_memcg(struct pglist_data *pgdat, struct mem_cgroup *memcg, struct scan_control *sc, unsigned long *lru_pages) { ...... unsigned long nr[NR_LRU_LISTS]; enum lru_list lru; ...... while (nr[LRU_INACTIVE_ANON] || nr[LRU_ACTIVE_FILE] || nr[LRU_INACTIVE_FILE]) { unsigned long nr_anon, nr_file, percentage; unsigned long nr_scanned; for_each_evictable_lru(lru) { if (nr[lru]) { nr_to_scan = min(nr[lru], SWAP_CLUSTER_MAX); nr[lru] -= nr_to_scan; nr_reclaimed += shrink_list(lru, nr_to_scan, lruvec, memcg, sc); } } ...... } ......
enum lru_list { LRU_INACTIVE_ANON = LRU_BASE, LRU_ACTIVE_ANON = LRU_BASE + LRU_ACTIVE, LRU_INACTIVE_FILE = LRU_BASE + LRU_FILE, LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE, LRU_UNEVICTABLE, NR_LRU_LISTS }; #define for_each_evictable_lru(lru) for (lru = 0; lru <= LRU_ACTIVE_FILE; lru++) static unsigned long shrink_list(enum lru_list lru, unsigned long nr_to_scan, struct lruvec *lruvec, struct mem_cgroup *memcg, struct scan_control *sc) { if (is_active_lru(lru)) { if (inactive_list_is_low(lruvec, is_file_lru(lru), memcg, sc, true)) shrink_active_list(nr_to_scan, lruvec, sc, lru); return 0; } return shrink_inactive_list(nr_to_scan, lruvec, sc, lru);
从上面的代码能够看出:
一、shrink_list会先缩减毁约页面列表,再压缩不毁约的页面列表,
二、对于不活跃的缩减,shrink_inactive_list就须要对页面进行回收;
三、对于匿名页来说,须要分配swap,将内存页写入文件系统;
四、对于内存映射关联了文件的,咱们须要将在内存中对于文件的修改写回到文件中
好了,对于物理内存的管理就讲到这里,咱们来总结一下,对于物理内存来说,从下层到上层的关系及分配模式如何:
一、物理内存分NUMA节点,分别进行管理
二、每一个 NUMA 节点分红多个内存区域;
三、每一个内存区域分红多个物理页面;
四、伙伴系统将多个连续的页面做为一个大的内存块分配给上层;
五、kswapd 负责物理页面的换入换出;
六、Slub Allocator 将从伙伴系统申请的大内存块切成小内存,分配给其余系统