编译时出现类似的情况:把Warning当做Error,当没有加-Werror选项
1 2 3 4 5 6 7 8 |
|
修改 scripts/gcc-wrapper.py 去掉 interpret_warning 函数中的如下部分
1 2 3 4 5 6 7 |
|
若直接加 -w gcc选项,则会直接不显示Warning
编译时出现类似的情况:把Warning当做Error,当没有加-Werror选项
1 2 3 4 5 6 7 8 |
|
修改 scripts/gcc-wrapper.py 去掉 interpret_warning 函数中的如下部分
1 2 3 4 5 6 7 |
|
若直接加 -w gcc选项,则会直接不显示Warning
http://www.cnblogs.com/lisperl/archive/2012/05/02/2478817.html
cpuset子系统为cgroup 中的任务分配独立 CPU(在多核系统)和内存节点。Cpuset子系统为定义了一个叫cpuset的数据结构来管理cgroup中的任务能够使用的cpu和内存节点。Cpuset定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
其中css字段用于task或cgroup获取cpuset结构。
cpus_allowed和mems_allowed定义了该cpuset包含的cpu和内存节点。
Parent字段用于维持cpuset的树状结构,stack_list则用于遍历cpuset的层次结构。
Pn和relax_domain_level是跟Linux 调度域相关的字段,pn指定了cpuset的调度域的分区号,而relax_domain_level表示进行cpu负载均衡寻找空闲cpu的策略。
除此之外,进程的task_struct结构体里面还有一个cpumask_t cpus_allowed成员,用以存储进程的cpus_allowed信息;一个nodemask_t mems_allowed成员,用于存储进程的mems_allowed信息。
Cpuset子系统的实现是通过在内核代码加入一些hook代码。由于代码比较散,我们逐条分析。
在内核初始化代码(即start_kernel函数)中插入了对cpuset_init调用的代码,这个函数用于cpuset的初始化。
下面我们来看这个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
cpumask_setall和nodes_setall将top_cpuset能使用的cpu和内存节点设置成所有节点。紧接着,初始化fmeter,设置top_cpuset的load balance标志。最后注册cpuset文件系统,这个是为了兼容性,因为在cgroups之前就有cpuset了,不过在具体实现时,对cpuset文件系统的操作都被重定向了cgroup文件系统。
除了这些初始化工作,cpuset子系统还在do_basic_setup函数(此函数在kernel_init中被调用)中插入了对cpuset_init_smp的调用代码,用于smp相关的初始化工作。
下面我们看这个函数:
1 2 3 4 5 6 7 8 9 10 11 |
|
首先,将top_cpuset的cpu和memory节点设置成所有online的节点,之前初始化时还不知道有哪些online节点所以只是简单设成所有,在smp初始化后就可以将其设成所有online节点了。然后加入了两个hook函数,cpuset_track_online_cpus和cpuset_track_online_nodes,这个两个函数将在cpu和memory热插拔时被调用。
cpuset_track_online_cpus函数中调用scan_for_empty_cpusets函数扫描空的cpuset,并将其下的进程移到其非空的parent下,同时更新cpuset的cpus_allowed信息。cpuset_track_online_nodes的处理类似。
那cpuset又是怎么对进程的调度起作用的呢?
这个就跟task_struct中cpu_allowed字段有关了。首先,这个cpu_allowed和进程所属的cpuset的cpus_allowed保持一致;其次,在进程被fork出来的时候,进程继承了父进程的cpuset和cpus_allowed字段;最后,进程被fork出来后,除非指定CLONE_STOPPED标记,都会被调用wake_up_new_task唤醒,在wake_up_new_task中有:
1 2 |
|
即为新fork出来的进程选择运行的cpu,而select_task_rq会调用进程所属的调度器的函数,对于普通进程,其调度器是CFS,CFS对应的函数是select_task_rq_fair。在select_task_rq_fair返回选到的cpu后,select_task_rq会对结果和cpu_allowed比较:
1 2 3 |
|
这就保证了新fork出来的进程只能在cpu_allowed中的cpu上运行。
对于被wake up的进程来说,在被调度之前,也会调用select_task_rq选择可运行的cpu。
这就保证了进程任何时候都只会在cpu_allowed中的cpu上运行。
最后说一下,如何保证task_struct中的cpus_allowd和进程所属的cpuset中的cpus_allowed一致。首先,在cpu热插拔时,scan_for_empty_cpusets会更新task_struct中的cpus_allowed信息,其次对cpuset下的控制文件写入操作时也会更新task_struct中的cpus_allowed信息,最后当一个进程被attach到其他cpuset时,同样会更新task_struct中的cpus_allowed信息。
在cpuset之前,Linux内核就提供了指定进程可以运行的cpu的方法。通过调用sched_setaffinity可以指定进程可以运行的cpu。Cpuset对其进行了扩展,保证此调用设定的cpu仍然在cpu_allowed的范围内。在sched_setaffinity中,插入了这样两行代码:
1 2 |
|
其中cpuset_cpus_allowed返回进程对应的cpuset中的cpus_allowed,cpumask_and则将cpus_allowed和调用sched_setaffinity时的参数in_mask相与得出进程新的cpus_allowed。
通过以上代码的嵌入,Linux内核实现了对进程可调度的cpu的控制。下面我们来分析一下cpuset对memory节点的控制。
Linux中内核分配物理页框的函数有6个:alloc_pages,alloc_page,get_free_pages,get_free_page,get_zeroed_page,get_dma_pages,这些函数最终都通过alloc_pages实现,而alloc_pages又通过alloc_pages_nodemask实现,在__alloc_pages_nodemask中,调用get_page_from_freelist从zone list中分配一个page,在get_page_from_freelist中调用cpuset_zone_allowed_softwall判断当前节点是否属于mems_allowed。通过附加这样一个判断,保证进程从mems_allowed中的节点分配内存。
Linux在cpuset出现之前,也提供了mbind, set_mempolicy来限定进程可用的内存节点。Cpuset子系统对其做了扩展,扩展的方法跟扩展sched_setaffinity类似,通过导出cpuset_mems_allowed,返回进程所属的cupset允许的内存节点,对mbind,set_mempolicy的参数进行过滤。
最后让我们来看一下,cpuset子系统最重要的两个控制文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
通过cpus文件,我们可以指定进程可以使用的cpu节点,通过mems文件,我们可以指定进程可以使用的memory节点。
这两个文件的读写都是通过cpuset_common_file_read和cpuset_write_resmask实现的,通过private属性区分。
在cpuset_common_file_read中读出可用的cpu或memory节点;在cpuset_write_resmask中则根据文件类型分别调用update_cpumask和update_nodemask更新cpu或memory节点信息。
http://www.cnblogs.com/lisperl/archive/2012/04/28/2474872.html
memory 子系统可以设定 cgroup 中任务使用的内存限制,并自动生成由那些任务使用的内存资源报告。memory子系统是通过linux的resource counter机制实现的。下面我们就先来看一下resource counter机制。
resource counter是内核为子系统提供的一种资源管理机制。这个机制的实现包括了用于记录资源的数据结构和相关函数。Resource counter定义了一个res_counter的结构体来管理特定资源,定义如下:
1 2 3 4 5 6 7 8 9 |
|
Usage用于记录当前已使用的资源,max_usage用于记录使用过的最大资源量,limit用于设置资源的使用上限,进程组不能使用超过这个限制的资源,soft_limit用于设定一个软上限,进程组使用的资源可以超过这个限制,failcnt用于记录资源分配失败的次数,管理可以根据这个记录,调整上限值。Parent指向父节点,这个变量用于处理层次性的资源管理。
除了这个关键的数据结构,resource counter还定义了一系列相关的函数。下面我们来看几个关键的函数。
1 2 3 4 5 6 7 |
|
这个函数用于初始化一个res_counter。
第二个关键的函数是int res_counter_charge(struct res_counter *counter, unsigned long val, struct res_counter **limit_fail_at)。当资源将要被分配的时候,资源就要被记录到相应的res_counter里。这个函数作用就是记录进程组使用的资源。在这个函数中有:
1 2 3 4 5 6 7 8 9 |
|
在这个循环里,从当前res_counter开始,从下往上逐层增加资源的使用量。我们来看一下res_counter_charge_locked这个函数,这个函数顾名思义就是在加锁的情况下增加使用量。实现如下:
1 2 3 4 5 6 7 8 9 10 11 |
|
首先判断是否已经超过使用上限,如果是的话就增加失败次数,返回相关代码;否则就增加使用量的值,如果这个值已经超过历史最大值,则更新最大值。
第三个关键的函数是void res_counter_uncharge(struct res_counter *counter, unsigned long val)。当资源被归还到系统的时候,要在相应的res_counter减轻相应的使用量。这个函数作用就在于在于此。实现如下:
1 2 3 4 5 |
|
从当前counter开始,从下往上逐层减少使用量,其中调用了res_counter_uncharge_locked,这个函数的作用就是在加锁的情况下减少相应的counter的使用量。
有这些数据结构和函数,只需要在内核分配资源的时候,植入相应的charge函数,释放资源时,植入相应的uncharge函数,就能实现对资源的控制了。
介绍完resource counter,我们再来看memory子系统是利用resource counter实现对内存资源的管理的。
memory子系统定义了一个叫mem_cgroup的结构体来管理cgroup相关的内存使用信息,定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
跟其他子系统一样,mem_cgroup也包含了一个cgroup_subsys_state成员,便于task或cgroup获取mem_cgroup。
mem_cgroup中包含了两个res_counter成员,分别用于管理memory资源和memory+swap资源,如果memsw_is_minimum为true,则res.limit=memsw.limit,即当进程组使用的内存超过memory的限制时,不能通过swap来缓解。
use_hierarchy则用来标记资源控制和记录时是否是层次性的。
oom_kill_disable则表示是否使用oom-killer。
oom_notify指向一个oom notifier event fd链表。
另外memory子系统还定义了一个叫page_cgroup的结构体:
1 2 3 4 5 6 |
|
此结构体可以看作是mem_map的一个扩展,每个page_cgroup都和所有的page关联,而其中的mem_cgroup成员,则将page与特定的mem_cgroup关联起来。
我们知道在linux系统中,page结构体是用来管理物理页框的,一个物理页框对应一个page结构体,而每个进程中的task_struct中都有一个mm_struct来管理进程的内存信息。每个mm_struct知道它属于的进程,进而知道所属的mem_cgroup,而每个page都知道它属于的page_cgroup,进而也知道所属的mem_cgroup,而内存使用量的计算是按cgroup为单位的,这样以来,内存资源的管理就可以实现了。
memory子系统既然是通过resource counter实现的,那肯定会在内存分配给进程时进行charge操作的。下面我们就来看一下这些charge操作:
1.page fault发生时,有两种情况内核需要给进程分配新的页框。一种是进程请求调页(demand paging),另一种是copy on write。内核在handle_pte_fault中进行处理。其中,do_linear_fault处理pte不存在且页面线性映射了文件的情况,do_anonymous_page处理pte不存在且页面没有映射文件的情况,do_nonlinear_fault处理pte存在且页面非线性映射文件的情况,do_wp_page则处理copy on write的情况。其中do_linear_fault和do_nonlinear_fault都会调用do_fault来处理。Memory子系统则do_fault、do_anonymous_page、do_wp_page植入mem_cgroup_newpage_charge来进行charge操作。
2.内核在handle_pte_fault中进行处理时,还有一种情况是pte存在且页又没有映射文件。这种情况说明页面之前在内存中,但是后面被换出到swap空间了。内核用do_swap_page函数处理这种情况,memory子系统在do_swap_page加入了mem_cgroup_try_charge_swapin函数进行charge。mem_cgroup_try_charge_swapin是处理页面换入时的charge的,当执行swapoff系统调用(关掉swap空间),内核也会执行页面换入操作,因此mem_cgroup_try_charge_swapin也被植入到了相应的函数中。
3.当内核将page加入到page cache中时,也需要进行charge操作,mem_cgroup_cache_charge函数正是处理这种情况,它被植入到系统处理page cache的add_to_page_cache_locked函数中。
4.最后mem_cgroup_prepare_migration是用于处理内存迁移中的charge操作。
除了charge操作,memory子系统还需要处理相应的uncharge操作。下面我们来看一下uncharge操作:
1.mem_cgroup_uncharge_page用于当匿名页完全unmaped的时候。但是如果该page是swap cache的话,uncharge操作延迟到mem_cgroup_uncharge_swapcache被调用时执行。
2.mem_cgroup_uncharge_cache_page用于page cache从radix-tree删除的时候。但是如果该page是swap cache的话,uncharge操作延迟到mem_cgroup_uncharge_swapcache被调用时执行。
3.mem_cgroup_uncharge_swapcache用于swap cache从radix-tree删除的时候。Charge的资源会被算到swap_cgroup,如果mem+swap controller被禁用了,就不需要这样做了。
4.mem_cgroup_uncharge_swap用于swap_entry的引用数减到0的时候。这个函数主要在mem+swap controller可用的情况下使用的。
5.mem_cgroup_end_migration用于内存迁移结束时相关的uncharge操作。
Charge函数最终都是通过调用mem_cgroup_try_charge来实现的。在mem_cgroup_try_charge函数中,调用res_counter_charge(&mem->res, csize, &fail_res)对memory进行charge,调用res_counter_charge(&mem->memsw, csize, &fail_res)对memory+swap进行charge。
Uncharge函数最终都是通过调用do_uncharge来实现的。在do_uncharge中,分别调用res_counter_uncharge(&mem->res,PAGE_SIZE)和res_counter_uncharge(&mem->memsw, PAGE_SIZE)来uncharge memory和memory+swap。
跟其他子系统一样,memory子系统也实现了一个cgroup_subsys。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Memory子系统中重要的文件有
1 2 3 4 5 6 7 |
|
这个文件用于设定memory+swap上限值。
Limit_in_bytes
1 2 3 4 5 6 |
|
这个文件用于设定memory上限值。