Linux namespace分析(1)

March 9th, 2015 by JasonLe's Tech 1,262 views

在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 by JasonLe's Tech 1,130 views

一、背景

在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

物理内存管理:伙伴系统数据结构分析

March 4th, 2015 by JasonLe's Tech 1,355 views

伙伴系统(buddy system)在物理内存管理中占有重要地位。

我们知道物理内存被分为三大部分:DMA、NORMAL、HIGHMEN三个区域。每个内存域都有一个struct zone的实例。而这个struct zone 的实例又被struct pglist_data管理。具体看http://www.lizhaozhong.info/archives/1184

page frame <==> struct page

struct zone 这个结构体非常庞大,总的来说分成三部分,每一部分由 ZONE_PADDING(_pad1_)这种标示分割。我们可以通过struct zone 下的unsigned long zone_start_pfn 找到某个特定的page!但是这种page frame,我们无法知道空闲页框的布局,不利于分配page frame。

具体zone定义:http://lxr.free-electrons.com/source/include/linux/mmzone.h#L327

为了尽量分配连续的page frame,避免外部碎片的产生,伙伴系统(buddy system)满足这个需求。
每个内存域(struct zone)的实例都有free_area数组,这个数组大小是11,也就是说空闲区域有0-10阶的page frame 块链表。比如free_area[3]所对应的页框块链表中,每个节点对应8个连续的页框(2的3次方)。

#ifndef CONFIG_FORCE_MAX_ZONEORDER
#define MAX_ORDER 11
#else
#define MAX_ORDER CONFIG_FORCE_MAX_ZONEORDER
#endif
struct zone{
........
476         ZONE_PADDING(_pad1_)
477
478         /* Write-intensive fields used from the page allocator */
479         spinlock_t              lock;
480
481         /* free areas of different sizes */
482         struct free_area        free_area[MAX_ORDER];
483
484         /* zone flags, see below */
485         unsigned long           flags;
486
487         ZONE_PADDING(_pad2_)
......
}

而struct free_area如下http://lxr.free-electrons.com/source/include/linux/mmzone.h#L92

 92 struct free_area {
 93         struct list_head        free_list[MIGRATE_TYPES];
 94         unsigned long           nr_free;
 95 };

通过声明看到free_list是一个链表数组。nr_free表示当前链表中空闲页框块的数目,比如free_area[3]中nr_free的值为5,表示有5个大小为8的页框块,那么总的页框数目为40。

具体概述上面buddy system结构就是:
buddy

在/proc中我们可以查看到每个阶空闲大小的PFN数量(注:本机使用的是AMD64系统,在AMD64中没有ZONE_HIGHMEN,ZONE_DMA寻值为16M,ZONE_DMA32寻值为0-4GiB,在32为机器上DMA32为0)

[root@localhost cgroup_unified]# cat /proc/buddyinfo
Node 0, zone      DMA      8      7      6      5      4      2      1      2      2      3      0
Node 0, zone    DMA32   1059   1278   9282   1868   1611    260     52     20      7      2      1

在struct list_head free_list[MIGRATE_TYPES]中,我们发现每个阶都带有一个MIGRATE_TYPES标志,通过这种方式,系统又把每个阶的空闲page更加详细的分割,具体类型有不可移动,移动,保留等。

 38 #define MIGRATE_UNMOVABLE     0
 39 #define MIGRATE_RECLAIMABLE   1
 40 #define MIGRATE_MOVABLE       2
 41 #define MIGRATE_PCPTYPES      3 /* the number of types on the pcp lists */
 42 #define MIGRATE_RESERVE       3
 43 #define MIGRATE_ISOLATE       4 /* can't allocate from here */
 44 #define MIGRATE_TYPES         5

这是为了更大限度的满足连续物理页框的需要,如果要分配一种MIGRATE_UNMOVABLE类型的页框,而两边的页框是可以移动的,这样就限制了连续大页框的分配,产生了外部碎片。
使用MIGRATE_TYPES策略后,不可移动的页面的不可移动性仅仅影响它自身的类别而不会导致一个不可移动的页面两边都是可移动的页面。这就是MIGRATE_TYPE被引入的目的。

MIGRATE_TYPE限制了内存页面的分配地点从而避免碎片,而不再仅仅寄希望于它们被释放时通过合并避免碎片[1]。

这种策略在proc中也可以查看:

[root@localhost cgroup_unified]# cat /proc/pagetypeinfo
Page block order: 9
Pages per block:  512

Free pages count per migrate type at order       0      1      2      3      4      5      6      7      8      9     10
Node    0, zone      DMA, type    Unmovable      1      4      4      1      1      1      1      1      0      0      0
Node    0, zone      DMA, type  Reclaimable      5      1      1      0      0      0      0      1      1      1      0
Node    0, zone      DMA, type      Movable      2      0      2      4      3      1      0      0      1      1      0
Node    0, zone      DMA, type      Reserve      0      0      0      0      0      0      0      0      0      1      0
Node    0, zone      DMA, type      Isolate      0      0      0      0      0      0      0      0      0      0      0
Node    0, zone    DMA32, type    Unmovable      1      0      8      1     17     15      5      0      0      0      0
Node    0, zone    DMA32, type  Reclaimable   1041    162      1      0      1      1      1      1      1      0      0
Node    0, zone    DMA32, type      Movable    698     78      7   1221   1194    232     45     20      6      0      0
Node    0, zone    DMA32, type      Reserve      0      0      0      0      0      0      0      0      0      1      1
Node    0, zone    DMA32, type      Isolate      0      0      0      0      0      0      0      0      0      0      0 

Number of blocks type     Unmovable  Reclaimable      Movable      Reserve      Isolate
Node 0, zone      DMA            1            2            4            1            0
Node 0, zone    DMA32           89           93         1216            2            0

参考:

[1] http://blog.csdn.net/dog250/article/details/6108028

cgroup 介绍(2)

March 2nd, 2015 by JasonLe's Tech 1,033 views

之前在第一篇介绍cgroup的文章中,我初步使用cgroup对资源进行限制隔离http://www.lizhaozhong.info/archives/1211

但是基于层级的cgroup存在一个弊端:就是不灵活,树的深度可能是无限的,这就导致实际操作中管理非常繁琐。

基于这个原因,在kernel 3.16中正式加入了unified  hierarchy特性,这个特性目前仍然在开发,所以如果想显式开启该特性需要
mount -t cgroup -o __DEVEL__sane_behavior cgroup $MOUNT_POINT

__DEVEL__sane_behavior通过看名字,我们也能发现这个特性仍然在开发。

在之前的cgroup hierarchy中,我们知道一个hierarchy可以绑定一个子系统,也可以同时绑定12个子系统。

举例层级A绑定cpuset,层级B绑定memory,如果有一个task同时需要这两个子系统,则很多时候task在这两个层级中存在正交,非常不便。

hierarchy may be collapsed from leaf towards root when viewed from specific
controllers.  For example, a given configuration might not care about
how memory is distributed beyond a certain level while still wanting
to control how CPU cycles are distributed.

 

如果我们开启__DEVEL__sane_behavior特性,我们看到cgroup.controllers 存在的子系统,在unified hierarchy中,系统会把所有子系统都挂载到根层级下,只有leaf节点可以存在tasks,非叶子节点只进行资源控制。

# mount -t cgroup -o __DEVEL__sane_behavior cgroup /sys/fs/cgroup
# cat /sys/fs/cgroup/cgroup.controllers
cpuset cpu cpuacct memory devices freezer net_cls blkio perf_event net_prio hugetlb

 

现在我们在root cgroup下面创建parent与child,根层级的cgroup.subtree_control 控制parents的cgroup.controllers

如此往复,上级的cgroup.subtree_control控制下级的cgroup.controllers,也就是说subsystem不会有传递性!

如下面的例子,如果我指定根层级的cgroup.subtree_control 可以使能memory与cpu两个子系统,也就是说parents中可以控制memory、cpu两个子系统。而child如果没有指定子系统,是不会控制memory与cpu的。

# mkdir /sys/fs/cgroup/parent
# mkdir /sys/fs/cgroup/parent/child</pre>
# echo "+memory +cpu" > /sys/fs/cgroup/cgroup.subtree_control
# cat /sys/fs/cgroup/parent/cgroup.controllers
cpu memory

举个例子:

A(b,m) - B(b,m) - C (b)
              \ - D (b) - E

其中b代表blkio,m代表memory,A是根,在这个结构中ACD都拥有进程,比如C对blkio受限,那么memory则不受限,共享B,E比较特殊,如果没有指定子系统,那么blkio受D控制,memory受B控制。具体操作方式在上面parents、child已声明。

如果该cgroup中已有进程,那么只有在关联的组没有包含进程的时候,cgroup.subtree_control文件能被用来改变控制器的设置。

中间层级必须拥有子系统,如果指定E受限于blkio,那么系统不承认该操作!

Unified hierarchy implements an interface file “cgroup.populated”which can be used to monitor whether the cgroup’s subhierarchy has tasks in it or not. Its value is 0 if there is no task in the cgroup and its descendants; otherwise, 1. poll and [id]notify events are triggered when the value changes.

其他unified hierarchy 改动在document中说的很清楚,这里不再赘述。

包括tasks,cgroup.procs,cgroup.clone_children会被移除等。一旦这种层级开发明确,旧有的cgroup机制会被这种unified hierarchy代替。

 

参考:

http://lwn.net/Articles/601840/

http://events.linuxfoundation.org/sites/events/files/slides/2014-KLF.pdf

https://www.kernel.org/doc/Documentation/cgroups/unified-hierarchy.txt

http://d.hatena.ne.jp/defiant/mobile?date=20140826

CFS 调度算法

February 2nd, 2015 by JasonLe's Tech 1,588 views

之前说过CFS是Kernel中的一种调度policy,这个调度算法的核心,所有task都应该公平分配处理器,为了达成这个目标,CFS调度使用vruntime来衡量某一个进程是否值得调度。

上篇博文初步对CFS的实现有了一个说明,但是没有阐述vruntime的计算。

上篇 http://www.lizhaozhong.info/archives/1206

vruntime 是CFS算法模拟出来的一个变量,他淡化了优先级在调度中的作用,而是以vruntime的值使用struct sched_entity组织成为一棵red-black tree。

根据red-black tree的特点,值小的在tree的左边,值大的在右边,随着进程的运行,系统在timer 中断发生时会调用policy中的task_tick()方法,这个函数可以更新vruntime的值。以供CFS调度时使用。

为了维护这个red-black tree最左边的节点vruntime值最小,我们必须使得这个值单调递增,所以要比较delta_exec 与 curr->statistics.exec_max值的,并取最大值。schedstat_set(curr->statistics.exec_max,max(delta_exec, curr->statistics.exec_max));update_min_vruntime(cfs_rq);

通过这两个函数,只有最靠左的节点超过min_vruntime才会更新。

有一种情况,如果进程睡眠,则他的vruntime不变,而min_vruntime变大,则,这个进程会更加靠左!

调用路径是:

void scheduler_tick(void)
{... curr->sched_class->task_tick(rq, curr, 0); ...}
->通过函数指针,调用具体policy的函数,在CFS中是task_tick_fair,这个函数可以调用entity_tick()更新
当前调度实体sched_entity所在的cfs_rq中当前运行task的sche_entity中vruntime的值
->
3097 static void
3098 entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
3099 {
3100         /*
3101          * Update run-time statistics of the 'current'.
3102          */
3103         update_curr(cfs_rq);
3104
......
3131 }
->static void update_curr(struct cfs_rq *cfs_rq)

从entity_tick()中的update_curr()调cfs中真正更新vruntime值的函数:

694 static void update_curr(struct cfs_rq *cfs_rq)
695 {
....
697         u64 now = rq_clock_task(rq_of(cfs_rq));
....
703         delta_exec = now - curr->exec_start;
704         if (unlikely((s64)delta_exec <= 0))
705                 return;
706
707         curr->exec_start = now;
708
709         schedstat_set(curr->statistics.exec_max,
710                       max(delta_exec, curr->statistics.exec_max));
711
712         curr->sum_exec_runtime += delta_exec;
713         schedstat_add(cfs_rq, exec_clock, delta_exec);
714
715         curr->vruntime += calc_delta_fair(delta_exec, curr);
716         update_min_vruntime(cfs_rq);
717
718         if (entity_is_task(curr)) {
719                 struct task_struct *curtask = task_of(curr);
720
721                 trace_sched_stat_runtime(curtask, delta_exec, curr->vruntime);
722                 cpuacct_charge(curtask, delta_exec);
723                 account_group_exec_runtime(curtask, delta_exec);
724         }
725
726         account_cfs_rq_runtime(cfs_rq, delta_exec);
727 }

首先获取当前rq的时间,使用delta_exec获取当前进程运行的实际时间,然后将exec_start再次更新为now以便下一次使用。

并将该值加到sum_exec_runtime中时间中,对于vruntime 时间则需要calc_delta_fair(delta_exec, curr);进行处理。

通过下表我们可以看出当nice值为0,weight值为1024。另外我们需要明确nice值【-20,+19】映射到整个系统中是100~139,也就是说nice值每增加一个nice值,获得cpu时间减少10%,反之增加10%!而0~99则属于实时进程专用!nice值越高权值越小!

1046 static const int prio_to_weight[40] = {
1047  /* -20 */     88761,     71755,     56483,     46273,     36291,
1048  /* -15 */     29154,     23254,     18705,     14949,     11916,
1049  /* -10 */      9548,      7620,      6100,      4904,      3906,
1050  /*  -5 */      3121,      2501,      1991,      1586,      1277,
1051  /*   0 */      1024,       820,       655,       526,       423,
1052  /*   5 */       335,       272,       215,       172,       137,
1053  /*  10 */       110,        87,        70,        56,        45,
1054  /*  15 */        36,        29,        23,        18,        15,
1055 };

在calc_delta_fair()函数中会比较当前权重与nice值为0的权重(NICE_0_LOAD),如果等于则直接返回加权后的vruntime,如果不同则需要对该权值加权。
struct sched_entity *se 存在着当前进程的权重,就是上面那个array里面数字!

601 static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
602 {
603         if (unlikely(se->load.weight != NICE_0_LOAD))
604                 delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
605
606         return delta;
607 }

如果当前进程的nice值不等于nice 0 ,进入下面的函数:

214 static u64 __calc_delta(u64 delta_exec, unsigned long weight, struct load_weight *lw)
215 {
216         u64 fact = scale_load_down(weight);
217         int shift = WMULT_SHIFT;
218
219         __update_inv_weight(lw);
220
221         if (unlikely(fact >> 32)) {
222                 while (fact >> 32) {
223                         fact >>= 1;
224                         shift--;
225                 }
226         }
227
228         /* hint to use a 32x32->64 mul */
229         fact = (u64)(u32)fact * lw->inv_weight;
230
231         while (fact >> 32) {
232                 fact >>= 1;
233                 shift--;
234         }
235
236         return mul_u64_u32_shr(delta_exec, fact, shift);
237 }

这个函数有些复杂,我现在理解这个加权公式就是delta_exec = delta_exec * (weight / lw.weight)

我们可以绘制出不同nice下,加权后vruntime与真实的delta_exec值的关系。我们可以对照上面那个数组发现nice值越高,权值越小,在这里我们比较的是1024/lw.weight的值,权值越小的,商越大,vruntime越大!在CFS中,vruntime值越小,越容易调度!
105514854

 

mul_u64_u32_shr()函数应该是32位与64位转换的,具体没研究清楚,改天再来。
具体这个的解释:
/*
203  * delta_exec * weight / lw.weight
204  *   OR
205  * (delta_exec * (weight * lw->inv_weight)) >> WMULT_SHIFT
206  *
207  * Either weight := NICE_0_LOAD and lw \e prio_to_wmult[], in which case
208  * we're guaranteed shift stays positive because inv_weight is guaranteed to
209  * fit 32 bits, and NICE_0_LOAD gives another 10 bits; therefore shift >= 22.
210  *
211  * Or, weight =< lw.weight (because lw.weight is the runqueue weight), thus
212  * weight/lw.weight <= 1, and therefore our shift will also be positive.
213  */

CFS总结:

1)不再区分进程类型,不使用nice值判断优先级,而是使用vruntime衡量一个进程的重要性。

2)对于IO类型的进程,随着睡眠时间正常,仍然可以得到公平的时间片

3)对于优先级高的进程,可以获得更多的CPU时间。

参考:
http://lxr.free-electrons.com/source/kernel/sched/fair.c#L214
http://lxr.free-electrons.com/source/kernel/sched/sched.h#L1046