Archive for the ‘Kernel内核分析’ category

物理内存管理:请求PFN函数主体实现(1)

March 24th, 2015

物理内存管理:请求PFN函数层次结构分析 这篇文章中,我分析了分配页框的函数结构,其中是上层页框分配的核心,这个函数比起alloc_pages()多一个参数nid,如果传入的nid < 0 ,那么在当前内存节点上分配physical frame。

这里需要阐述的是Linux的内存管理针对的就是NUMA结构,如果当前系统只有一个节点,那么默认调用numa_node_id()返回这个唯一节点。

309 static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask,
310                                                 unsigned int order)
311 {
312         /* Unknown node is current node */
313         if (nid < 0)
314                 nid = numa_node_id();
315 
316         return __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask));
317 }

而在__alloc_pages()函数中,根据nid与gfp_mask可以得到一个适当的zonelist链表,我们知道每个内存节点下面都会默认存在三个zonelist区域:ZONE_DMA/ZONE_NORMAL/ZONE_HIGHMEM ,而node_zonelist(nid, gfp_mask)就是选择合适的内存链表区域zonelist。

因为存在三个zonelist区域,联系之前的struct pglist_data结构成员struct zonelist node_zonelists[MAX_ZONELISTS],MAX_ZONELISTS最大值就是2,可以看出分配只能分配当前节点和备用节点。

581 /*
582  * The NUMA zonelists are doubled because we need zonelists that restrict the
583  * allocations to a single node for __GFP_THISNODE.
584  *
585  * [0]  : Zonelist with fallback
586  * [1]  : No fallback (__GFP_THISNODE)
587  */
588 #define MAX_ZONELISTS 2

而__alloc_pages()函数内部又封装了__alloc_pages_nodemask()函数,这个函数是页框分配的主体[2],

struct page *
2857 __alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
2858                         struct zonelist *zonelist, nodemask_t *nodemask)
2859 {
2860         enum zone_type high_zoneidx = gfp_zone(gfp_mask);
2861         struct zone *preferred_zone;
2862         struct zoneref *preferred_zoneref;
2863         struct page *page = NULL;
2864         int migratetype = gfpflags_to_migratetype(gfp_mask);
2865         unsigned int cpuset_mems_cookie;
2866         int alloc_flags = ALLOC_WMARK_LOW|ALLOC_CPUSET|ALLOC_FAIR;
2867         int classzone_idx;
2868 
2869         gfp_mask &= gfp_allowed_mask;
2870 
2871         lockdep_trace_alloc(gfp_mask);
2872 
2873         might_sleep_if(gfp_mask & __GFP_WAIT);
2874 
2875         if (should_fail_alloc_page(gfp_mask, order))
2876                 return NULL;
...
2883         if (unlikely(!zonelist->_zonerefs->zone))
2884                 return NULL;
....
2889 retry_cpuset:
2890         cpuset_mems_cookie = read_mems_allowed_begin();
2891 
2892         /* The preferred zone is used for statistics later */
2893         preferred_zoneref = first_zones_zonelist(zonelist, high_zoneidx,
2894                                 nodemask ? : &cpuset_current_mems_allowed,
2895                                 &preferred_zone);
2896         if (!preferred_zone)
2897                 goto out;
2898         classzone_idx = zonelist_zone_idx(preferred_zoneref);
2899 
2900         /* First allocation attempt */
2901         page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask, order,
2902                         zonelist, high_zoneidx, alloc_flags,
2903                         preferred_zone, classzone_idx, migratetype);
2904         if (unlikely(!page)) {
....
2910                 gfp_mask = memalloc_noio_flags(gfp_mask);
2911                 page = __alloc_pages_slowpath(gfp_mask, order,
2912                                 zonelist, high_zoneidx, nodemask,
2913                                 preferred_zone, classzone_idx, migratetype);
2914         }
2915 
2916         trace_mm_page_alloc(page, order, gfp_mask, migratetype);
2917 
2918 out:
....
2925         if (unlikely(!page && read_mems_allowed_retry(cpuset_mems_cookie)))
2926                 goto retry_cpuset;
2927 
2928         return page;
2929 }

分析代码,我们可以看到,gfp_zone()根据gfp_mask选取适当类型的zone index。然后经过几项检查,通过zonelist->_zonerefs->zone判断zonelist是否为空,在这里至少要存在一个可用的zone,然后使用上述的zone index,通过first_zones_zonelist()来分配一个内存管理区。

如果前面分配成功,则进入get_page_from_freelist()函数,这个函数可以看成伙伴算法的前置函数,如果伙伴系统存在空位,那么利用伙伴系统进行分配内存,如果分配不成功就进入__alloc_pages_slowpath()慢速分配,这个时候内核要放宽分配的条件,回收系统内存,然后总会分配出一块page。

 

 

这里我们要说明下likely()与unlikely()的用法,这两个宏只是提高代码执行概率,是的gcc在编译时,将哪个代码段提前,哪个代码段推后,从而提高效率,不会对值有修改,例如if (unlikely(!zonelist->_zonerefs->zone))表示的就是当zonelist->_zonerefs->zone为空时,执行return NULL操作[1],虽然这个return不太可能发生。

在代码中我们还发现了cpuset_mems_cookie = read_mems_allowed_begin();语句,看到名字,我们就知道这个与cgroup有关,也就是说与cpuset子系统相关,cpuset子系统负责cpu节点与内存节点的分配,如果没有指定nodemask,则使用cpuset_current_mems_allowed允许的节点。我们看到在out域下,有一个if (unlikely(!page && read_mems_allowed_retry(cpuset_mems_cookie)))
发现目前kernel对于cgroup机制中出现page分配失败,就会怀疑是否cpuset_mems_cookie出现修改,如果出现修改,则重试。

 

 

[1] http://blog.csdn.net/npy_lp/article/details/7175517

[2]http://lxr.free-electrons.com/source/mm/page_alloc.c#L2857

物理内存管理:请求PFN函数层次结构分析

March 13th, 2015

http://www.lizhaozhong.info/archives/1254

上篇讲到的伙伴系统是alloc_pages()族函数运行的基础,下面我来简单说明下alloc_pages()的结构。

alloc_pages()函数下存在五个子函数,他们最后都会调用到alloc_pages(),唯一的区别就是传入的参数不同。

1.alloc_pages()

这个宏用来分配2的order次方个连续的页框,如果申请成功返回第一个所分配页框的描述符地址,这个地址是全局唯一的!申请失败的话返回NULL。

#define alloc_pages(gfp_mask, order) \
                alloc_pages_node(numa_node_id(), gfp_mask, order)

2.alloc_page()

这个函数是alloc_pages的特殊情况,它只分配一个页框,也就是order等于0。

#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)

3.__get_free_pages()

这个函数可以申请长为2的order次方大小的连续页框,但是它返回的是这段连续页框中第一个页所对应的线性地址(区别页框的描述符地址)。该函数内部仍然调用了alloc_pages(),并利用page_address()将页描述符地址转换为线性地址。

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
{
        struct page *page;
        VM_BUG_ON((gfp_mask & __GFP_HIGHMEM) != 0);
        page = alloc_pages(gfp_mask, order);
        if (!page)
                return 0;
        return (unsigned long) page_address(page);
}

4.__get_free_page()

该宏函数可以看作是__get_free_pages()的特殊情况,它用于申请一个单独的页框,然后返回这个单独页的线性地址

#define __get_free_page(gfp_mask) \
        __get_free_pages((gfp_mask),0)

5.get_zeroed_page()

该函数用来获取一个填满0的页框,其中__GFP_ZERO参数用来体现这一点,类似于memset()清零的效果。

unsigned long get_zeroed_page(gfp_t gfp_mask)
{
        return __get_free_pages(gfp_mask | __GFP_ZERO, 0);
}

6.__get_dma_pages()

该宏函数获得的页框用于DMA操作。

#define (gfp_mask, order) \
                __get_free_pages((gfp_mask) | GFP_DMA,(order))

请求页框的标志通过查阅手册,我可以发现有非常多的mask。
如下代码

 13 #define ___GFP_DMA              0x01u
 14 #define ___GFP_HIGHMEM          0x02u
 15 #define ___GFP_DMA32            0x04u
 16 #define ___GFP_MOVABLE          0x08u
 17 #define ___GFP_WAIT             0x10u
 18 #define ___GFP_HIGH             0x20u
 19 #define ___GFP_IO               0x40u
 20 #define ___GFP_FS               0x80u
 21 #define ___GFP_COLD             0x100u
 22 #define ___GFP_NOWARN           0x200u
 23 #define ___GFP_REPEAT           0x400u
 24 #define ___GFP_NOFAIL           0x800u
 25 #define ___GFP_NORETRY          0x1000u
 26 #define ___GFP_MEMALLOC         0x2000u
 27 #define ___GFP_COMP             0x4000u
 28 #define ___GFP_ZERO             0x8000u
 29 #define ___GFP_NOMEMALLOC       0x10000u
 30 #define ___GFP_HARDWALL         0x20000u
 31 #define ___GFP_THISNODE         0x40000u
 32 #define ___GFP_RECLAIMABLE      0x80000u
 33 #define ___GFP_NOTRACK          0x200000u
 34 #define ___GFP_NO_KSWAPD        0x400000u
 35 #define ___GFP_OTHER_NODE       0x800000u
 36 #define ___GFP_WRITE            0x1000000u

使用最多的莫过于___GFP_DMA,___GFP_HIGHMEM,___GFP_DMA32。
当我们在写内核模块的时候,我们会用到kmalloc()函数,里面的标志位最多的应该就是GFP_KERNEL、GFP_USER和GFP_ATOMIC。这三个参数经过层层解析被系统解析为

106 #define GFP_ATOMIC      (__GFP_HIGH)
109 #define GFP_KERNEL      (__GFP_WAIT | __GFP_IO | __GFP_FS)
112 #define GFP_USER        (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL)

系统中也会按照内存zone的区域分配,总的分配顺序是HIGHMEM、NORMAL、DMA ,如果API指定分配区域,系统就按照指定区域分配。

总的函数调用关系:

get_zeroed_page()      __get_free_page()   __get_dma_pages()
      |                        |                   |
      |                        |                   |
      |----------------------------------------------
 __get_free_pages()     alloc_page()
      |                        |
      |-------------------------
alloc_pages()
      |
alloc_pages_node()
      |
__alloc_pages()
      |
__alloc_pages_nodemask()

通过上面的结构表示,alloc_pages_node()是上述API的核心,而__alloc_pages_nodemask()为页框分配的心脏!

 

参考:

http://www.cnblogs.com/hanyan225/archive/2011/07/28/2119628.html

http://lxr.free-electrons.com/source/include/linux/gfp.h

Linux PID namespace分析(2)

March 11th, 2015

PID namespace 主要完成的功能是让不同的namespace中可以有相同的PID,子namespace与root namespace之前存在映射关系。

完成这个PID功能的主要由两个结构:

struct upid,主要表示某个特定namespace中的pid实例,而struct pid可以理解为在根root namespace中存在的pid实例,而这个实例在每个子namespace中可以有不同的upid,这个时候struct pid 下的numbers成员可以表示,在声明中我们看到numbers的大小只有1,我们可以理解为一个系统只有一个全局namespace。

如果我们创建一个子namespace,numbers的大小会动态分配,然后向namespace中添加更多的upid。
默认我们可以理解为全局namespace只有一层也就是level=0 numbers也只有1个实例。

 50 struct upid {
 51         /* Try to keep pid_chain in the same cacheline as nr for find_vpid */
 52         int nr;
 53         struct pid_namespace *ns;
 54         struct hlist_node pid_chain;
 55 };
 56
 57 struct pid
 58 {
 59         atomic_t count;
 60         unsigned int level;
 61         /* lists of tasks that use this pid */
 62         struct hlist_head tasks[PIDTYPE_MAX];
 63         struct rcu_head rcu;
 64         struct upid numbers[1];
 65 };

举例,比如我们在进行kill操作时,就会首先给进程发送一个signal,然后系统调用解析signal,最后调用到kill_something_info()的函数。http://lxr.free-electrons.com/source/kernel/signal.c#L1425

这个函数中有一个子函数kill_pid_info(),里面的pid_task()函数就可以看到可以通过pid与pid_type查找某个特定的task_struct。从而通过控制PCB块达到控制该进程的目的。

上面我们看到系统存在以下几种类型的PID,而这个在struct pid中声明了几个散列表头的节点,用来链接每个namespace中的upid。
我们可以看到PIDTYPE_MAX为3,通过这种方式我们可以动态添加pid_type,非常方便。这里的pid_type中没有线程组TGID,但是在struct task_struct 中存在groups_leader的成员,可以利用这个成员访问pid。

enum pid_type
{
         PIDTYPE_PID,
         PIDTYPE_PGID,
         PIDTYPE_SID,
         PIDTYPE_MAX
};

 

PID

通过看这个图,我们基本可以大致的弄清楚PID namespace的结构。

系统中可以有多个struct task_struct 结构,并且他们的pid相同,指向同一个pid实例,这就表明系统中不只存在一个全局命名空间,多个struct task_struct中的pid(我们需要注意一点是在struct task_struct中pid是存在于struct pid_link pids[PDTYPE_MAX]中,还有一个pid_t pid仅仅是数值而已。通过这种方式每个struct task_struct被组织到struct hlist_head tasks的散列表数组上。)指向同一个struct pid实例,这个就要分类来看到底是何种类型的pid,通过上面的枚举,可以看到可以存在PID、PGID、SID。

内核只关心全局的pid,因为该pid肯定是唯一的,子namespace的pid查看,只需查找相关映射即可。

上面说到的通过pid可以查找task_struct,原理就是查找查找struct pid中task散列表数组,然后通过对应特定类型的type,查找该散列表上面的特定元素。具体查看http://lxr.free-electrons.com/source/kernel/pid.c#L434

至于那个numbers的数据,存放的就是每个命名空间的实例,虽然这个数据类型是upid,但是upid内部有成员函数pid_namespace 。

pid_namespace 包括指向直接的父namespace成员。

 24 struct pid_namespace {
...
 30         struct task_struct *child_reaper;
 31         struct kmem_cache *pid_cachep;
 32         unsigned int level;
 33         struct pid_namespace *parent;
...
 48 };

通过这个upid,我们可以找到特定pid_namespace 。依照上图,level 0的命名空间就是全局命名空间,下面的level 1 2 都是子命名空间。这种层级的组织使得父namespace可以看到子namespace的PID,反之则不行。

具体实现:http://lxr.free-electrons.com/source/kernel/pid.c#L500

除了这种树形组织结构,这些upid实例还被组织到一个pid_hash的散列表数组中。这个变量是全局的。

static struct hlist_head *pid_hash;
575 void __init pidhash_init(void)
576 {
577         unsigned int i, pidhash_size;
578
579         pid_hash = alloc_large_system_hash("PID", sizeof(*pid_hash), 0, 18,
580                                            HASH_EARLY | HASH_SMALL,
581                                            &pidhash_shift, NULL,
582                                            0, 4096);
583         pidhash_size = 1U << pidhash_shift;
584
585         for (i = 0; i < pidhash_size; i++)
586                 INIT_HLIST_HEAD(&pid_hash[i]);
587 }

每个upid又会被组织到这个pid_hash,这样可以加快搜索。

最后就是通过各种参数查找pid、task_struct、namespace的实例,理解了数据结构,使用系统提供的内核API则不难。

 

 

参考:
http://blog.chinaunix.net/uid-20687780-id-1587537.html
http://blog.chinaunix.net/uid-20687780-id-1587608.html
http://blog.chinaunix.net/uid-20687780-id-1587702.html

Linux namespace分析(1)

March 9th, 2015

在linux中实现kernel virtualization 的条件就是资源的限制与隔离,而namespace完成的系统资源的隔离。

从Linux 2.6.23开始 对于namespace的框架实现基本完成,之后的patch大多是修修补补,比较重要的一个patch是linux 3.8中一个非root用户可以创建user namespace,在这个user namespace中,该用户拥有所有的权限,包括在这个namespace中创建其他类型的namespace。

Linux namespace主要包含:

  • UTS namespace
  • Mount namespace
  • IPC namespace
  • PID namespace
  • Network namespace
  • User namespace

UTS namespace主要可以使得host拥有两套nodename与domainname:最大的区别就是如果使用lxc或者docker启动一个镜像,使用uname可以获得不同的主机名。

Mount namespace使得不同namespace中的相同进程都对文件结构有不同的视角,比起chroot有更好的安全性,使用namespace特性,可以使得系统的mount namespace产生主从的结构。

IPC namespace 使得每个IPC内的进程相互通信。

PID namespace最为神奇,他使得进程在不同的PID namespace中可以拥有相同的PID,也就是说如果系统拥有root namespace、namespace A、namespace B 。其中A、B是root的子命名空间,也就是说在A、B命名空间内部可以存在两个相同的PID,比如init 。当然这两个init的PID号从root命名空间来看是不同的,这里就有了映射的概念。

Network namespace 可以使得一个物理主机的网卡,模拟出两个虚拟网卡,每个虚拟网卡都可以绑定相同的端口,访问却不受影响。

User namespace 是在linux 3.8 才完成的部分,他可以使得一个进程的User ID和group ID相比于命名空间内外不同。举例:在root namespace中的一个非特权进程在namespace A中可以是init 0 ,可以是一个特权进程!

以上这些空间可以使得linux建立起一个轻量级的虚拟机系统。比如lxc、docker。

 

参考:

http://lwn.net/Articles/531114/

IA32下高端内存页框(ZONE_HIGHMEM)永久内核映射的分析与实现

March 6th, 2015

一、背景

在AMD64平台下,可用的线性地址空间远远大于目前安装的RAM大小,所以在这种体系中,ZONE_HIGHMEM总是空的。

而在32位平台上,Linux设计者必须找到一种方式允许内核使用所有4GB的RAM,如果IA32支持PAE的话,则使内核可以支持64GB的RAM。

IA32架构下,之所以有这些约束,主要是为了兼容前代固有的硬件架构(i386),例如DMA直接读取,i386 内存大于900MB的情况。

ZONE_HIGHMEM属于高于896MB的内存区域,需要通过高端内存页框分配的方式映射到内核线性地址空间,从而使得内核可以对高端内存进行访问。而低于896MB直接通过(PAGE_OFFSET)偏移映射到物理内存上。详细查看http://www.lizhaozhong.info/archives/1193

二、原理

高端内存页框分配alloc_pages()函数返回的是page frame 页描述符(page description)的线性地址,因为这些页描述符地址一旦被分配,便不再改变,所以他是全局唯一的。这些页描述符在free_area_init_nodes()初始化时,已建立好。

注:free_area_init_nodes()之前的初始化与体系结构相关,当系统调用这个函数后,开始初始化每个pg_data_t的结构体,便于体系结构再无相关,http://lxr.free-electrons.com/source/mm/page_alloc.c#L5386。。

页描述符我们可以想象成系统将所有内存按照4k等分划分形成的数组下标。所有的page descriptor组成一个大的连续的数组 ,每个节点起始地址存放在 struct page *node_mem_map中,因此如果知道了page descriptor的地址pd,pd-node_mem_map就得到了pd是哪个page frame的描述符,也可以知道这个页描述符是否高于896MB。

 

已知alloc_pages()函数分配一个page descriptor的线性地址,可由它得到它所描述的物理页是整个内存的第几页:

假设是第N个物理页,那么这个物理页的物理地址是physAddr = N << PAGE_SHIFT   ,一般情况PAGE_SHIFT   为12 也就是4k。在得知该物理页的物理地址是physAddr后,就可以视physAddr的大小得到它的虚拟地址,然后对这个虚拟地址进行判断:

1.physAddr < 896M  对应虚拟地址是 physAddr + PAGE_OFFSET   (PAGE_OFFSET=3G)
2.physAddr >= 896M 对应虚拟地址不是静态映射的,通过内核的高端虚拟地址映射得到一个虚拟地址(也就是内核页表映射)。

在得到该页的虚拟地址之后,内核就可以正常访问这个物理页了。

所以,我们可以将physAddr高于896M的这128M看做成为一个内核页表,这个内核页表在paging_init()初始化完成。

三、实现

内核采取三种不同的机制将page frame映射到高端内存:永久内核映射、临时内核映射、非连续内存分配。

alloc_page()函数集返回的是全是struct page* 类型的数据,返回的也都是page descriptor的线性地址。然后系统要判断该page descriptor是否是highmem,也就是void *kmap(struct page *page)
http://lxr.free-electrons.com/source/arch/x86/mm/highmem_32.c#L6 。

 

系统调用PageHighMem(page),判断当前page是否是highmem,如果是返回kmap_high(page)。如果不是返回page_address(page)  http://lxr.free-electrons.com/source/mm/highmem.c#L412

 

void *kmap(struct page *page)
{
         might_sleep();
         if (!PageHighMem(page))
                 return page_address(page);
         return kmap_high(page);
}
void *page_address(const struct page *page)
{
         unsigned long flags;
         void *ret;
         struct page_address_slot *pas;

         if (!PageHighMem(page))
                 return lowmem_page_address(page);

         pas = page_slot(page);
         ret = NULL;
         spin_lock_irqsave(&pas->lock, flags);
         if (!list_empty(&pas->lh)) {
                 struct page_address_map *pam;

                 list_for_each_entry(pam, &pas->lh, list) {
                         if (pam->page == page) {
                                 ret = pam->virtual;
                                 goto done;
                         }
                 }
         }
 done:
         spin_unlock_irqrestore(&pas->lock, flags);
         return ret;
}

在page_address()函数中,我们要再次检查该page是否属于HIGHMEM,如果不属于,则直接计算偏移量__va(PFN_PHYS(page_to_pfn(page)))。

而pas = page_slot(page);之后的代码,是在永久映射中,系统为了方便查找,建立了一个page_address_htable散列表,系统可以很快的对于已经存放在散列表中的永久映射的page descriptor的线性地址进行查找,如果找到就返回内核线性地址,否则为NULL。

如果判断该page属于HIGHMEM,进入kmap_high(page) http://lxr.free-electrons.com/source/mm/highmem.c#L279

void *kmap_high(struct page *page)
{
         unsigned long vaddr;

/*
* For highmem pages, we can't trust "virtual" until
* after we have the lock.
*/
         lock_kmap();
         vaddr = (unsigned long)page_address(page);
         if (!vaddr)
                 vaddr = map_new_virtual(page);
         pkmap_count[PKMAP_NR(vaddr)]++;
         BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
         unlock_kmap();
         return (void*) vaddr;
}

虽然判断了HIGHMEM,调用这个函数,但是我们不能信任,仍需要再次检查,顺便检查是否在page_address_htable散列表中存在该page,如果不存在,则进行映射( map_new_virtual(page)),然后将pkmap_count数组中,这个page引用+1,然会内核就可以正常使用这个高端内存的线性地址了。

map_new_virtual()函数是进行HIGHMEM映射的核心函数,其中for(;;)循环是寻找可用的内核页表项进行高端内存页框映射,这里涉及到了页表color问题,主要为了提速查找效率。

当系统找到可用的页表项时,从line261~line268就是核心

  • 以PKMAP_BASE为基址last_pkmap_nr为偏移在永久映射区创建内核线性地址vaddr
  • 将vaddr加入pkmap_page_table
  • 引用赋值为1,之后有可能会++
  • 然后将该内核线性地址vaddr加入page,并返回内核线性地址
</pre>
<pre>217 static inline unsigned long map_new_virtual(struct page *page)
218 {
...
224 start:
225         count = get_pkmap_entries_count(color);
226         /* Find an empty entry */
227         for (;;) {
228                 last_pkmap_nr = get_next_pkmap_nr(color);
229                 if (no_more_pkmaps(last_pkmap_nr, color)) {
230                         flush_all_zero_pkmaps();
231                         count = get_pkmap_entries_count(color);
232                 }
233                 if (!pkmap_count[last_pkmap_nr])
234                         break;  /* Found a usable entry */
235                 if (--count)
236                         continue;
237
238                 /*
239                  * Sleep for somebody else to unmap their entries
240                  */
241                 {
242                         DECLARE_WAITQUEUE(wait, current);
243                         wait_queue_head_t *pkmap_map_wait =
244                                 get_pkmap_wait_queue_head(color);
246                         __set_current_state(TASK_UNINTERRUPTIBLE);
247                         add_wait_queue(pkmap_map_wait, &wait);
248                         unlock_kmap();
249                         schedule();
250                         remove_wait_queue(pkmap_map_wait, &wait);
251                         lock_kmap();
253                         /* Somebody else might have mapped it while we slept */
254                         if (page_address(page))
255                                 return (unsigned long)page_address(page);
256
257                         /* Re-start */
258                         goto start;
259                 }
260         }
261         vaddr = PKMAP_ADDR(last_pkmap_nr);
262         set_pte_at(&init_mm, vaddr,
263                    &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));
264
265         pkmap_count[last_pkmap_nr] = 1;
266         set_page_address(page, (void *)vaddr);
268         return vaddr;

中间line241~259 内核考虑一种情况:如果这个PKMAP都已经被映射,最少的count都是>=1的,那么需要将当前的映射操作阻塞,然后加入等待队列,然后主动让出cpu被调度,直到PKMAP中存在count=0的页表项存在,唤醒,重新执行一次。

pkmap_count引用的可能值:0/1/>1。

0意味着该内核页表项未被映射高端内存,是可用的

1意味着虽然未被映射高端内存,但是需要unmapped,如果没有任何可用的页表项了,需要调用flush_all_zero_pkmaps()刷新pkmap_count,并置零。

>1意味着该页表项被使用。

 

参考:

http://repo.hackerzvoice.net/depot_madchat/ebooks/Mem_virtuelle/linux-mm/zonealloc.html#INITIALIZE

http://blog.csdn.net/lcw_202/article/details/5955783

https://www.kernel.org/doc/gorman/html/understand/understand006.html#sec: Mapping addresses to struct pages