task结构
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack; 指向threadinfo
int prio, static_prio, normal_prio;
unsigned int rt_priority;
const struct sched_class *sched_class;
struct sched_entity se;
struct sched_rt_entity rt;
unsigned int policy;
int oncpu //被task_running判断死否运行(SMP),switch_context的时候会修改
struct rq:每个cpu的可运行队列中都有一个root_domain和sched_domain
struct root_domain *rd; //
struct sched_domain *sd;
static const int prio_to_weight[40] = { /* -20 */ 88761, 71755, 56483, 46273, 36291, /* -15 */ 29154, 23254, 18705, 14949, 11916, /* -10 */ 9548, 7620, 6100, 4904, 3906, /* -5 */ 3121, 2501, 1991, 1586, 1277, /* 0 */ 1024, 820, 655, 526, 423, /* 5 */ 335, 272, 215, 172, 137, /* 10 */ 110, 87, 70, 56, 45, /* 15 */ 36, 29, 23, 18, 15, };
SCHED_LOAD_SCALE == 2048
调度相关API:
sched_get_priority_min 获取实时进程的最小优先级
sched_get_priority_max 获取实时进程的最大优先级
sched_getscheduler 获取进程的调度策略policy
sched_setscheduler 设置进程的调度策略policy
nice 改变当前进程的优先级(nice值)
sched_getparam 获取实时进程的优先级
sched_setparam 设置实时进程的优先级
sched_rr_get_interval 获取进程的默认时间片
sched_getaffinity 获取一个进程的cpu亲和性
sched_setaffinity 设置一个进程的cpu亲和性
进程优先级
int prio, static_prio, normal_prio;
unsigned int rt_priority;
unsigned int policy;
rt_priority是实时进程的配置优先级 , 范围:1 ~ 99 ,越大优先级越高,非实时进程的rt_priority一定是0
static_prio是非实时进程的配置优先级,范围: 100 ~ 139 ,越大优先级越低,实时进程可以有static_prio,nice调用也可以改变static_prio的值,但不起作用。
normal_prio 是根据rt_priority 或者static_prio及调度policy计算得到的(参考函数:normal_prio) ,范围: 0 ~ 139 越大优先级越低
rt_priority static_prio 是用户使用的优先级,将被统一转化为normal_prio 以便操作系统统一处理,用户可以改变rt_prority static_prio的值
prio 是调度器使用的优先级,对于普通进程,prio等于normal_prio;对于实时进程,一般情况下prio也等于noormal_prio,但为解决优先级翻转的问题,prio根据情况被提升
进程的生命周期
进程的表示
进程的相关系统调用
调度器
----------------------------------------------- ------- | --------- ---------- | 上下文切换 | | | | 主调度器 | | 周期调度器 | |---------------- | CPU | | --------- ---------- | | | ----------------------------------------------- ------- | | 选择进程 | ------------------------------------- | _____ ____ ____ | | |_____| -> |____| -> |____| | 调度器类 | / / \ | ----/----------/------------\-------- / ____ /_____ |\ / / /\ \ | \ __/ __/ __/ \ __ \__ __| \__ |__| |__| |__| |__| |__| |__| |__| 进程
1.主调度器 :schedule
schedule() { ... preempt_disable(); /*1.关闭抢占*/ prev = rq->curr; ... clear_tsk_need_resched(prev); /*2.清除need_sched标志*/ ... deactivate_task /*3.如果主动让出cpu,那么就从可 运行队列中删除*/ ... pre_schedule(rq, prev); /*4.pre_schedule,只对使用SMP的RT调度类有效*/ ... put_prev_task(rq, prev); /*5.put当前任务*/ ... next = pick_next_task(rq); /*6.挑选下一个任务*/ ... context_switch(rq, prev, next); /*7.切换上下文*/ ... post_schedule(rq); /*8.post_schedule,只对SMP的RT调度类有效*/ ... preempt_enable_no_resched(); /*9.打开抢占*/ }
2.周期性调度器:scheduler_tick
task_tick_rt :把SCHED_RR进程的时间片减去1
task_tick_fair: 检查当前进程是否连续运行时间超时(不同优先级的进程超时时限不一样)
3.try_to_wakeup:
try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags) { this_cpu = get_cpu(); /*当前cpu*/ rq = orig_rq = task_rq_lock(p, &flags); /*被唤醒进程的cpu与rq(上次cpu)*/ cpu = task_cpu(p); orig_cpu = cpu; if (p->sched_class->task_waking) /*唤醒进程*/ p->sched_class->task_waking(rq, p); cpu = select_task_rq(p, SD_BALANCE_WAKE, wake_flags); /*为唤醒进程选择cpu*/ if (cpu != orig_cpu) set_task_cpu(p, cpu); activate_task(rq, p, 1); /*把唤醒的进程挂入到队列*/ success = 1; check_preempt_curr(rq, p, wake_flags); /*唤醒的进程挂入的队列所在的cpu是否需要抢占*/ p->state = TASK_RUNNING; /*设置唤醒进程为RUNNING状态*/ /*唤醒后做点事,如果唤醒进程是rt进程,如果没有尽快调度,push该进程*/ if(p->sched_class->task_woken) p->sched_class->task_woken(rq, p); put_cpu(); /*这里是一个调度点,如果set_needsched,就会发生一次调度*/ }
check_preempt_curr --> check_preempt_curr_rt :
如果新唤醒的进程优先级高于当前进程,则抢占当前进程。
如果新唤醒进程与当前进程的优先级相等,如果出现其他cpu可以接受当前进程但不可以接受新唤醒的进程,那么就迁移当前进程到目标cpu,并在本cpu上运行新唤醒的进程
3.RT调度类:
每一个优先级都有一个runing_queue,共99个队列:
------------------------------------------ |queue_prio1 | -> task1->task2 ->taskn | ------------------------------------------ |queue_prio2 | ->task1->task2 ->taskn | ------------------------------------------ |... | | ------------------------------------------ |queue_prio98 | ->task1->task2 ->taskn | ------------------------------------------ |queue_prio99 |->task1->task2 ->taskn | ------------------------------------------
RT调度策略比较简单,从当前最高优先级的的队列中取出第一个task即可。
如果有高优先级的RT进程被唤醒,将会抢占当前进程
RT类有2个调度policy: SCHED_FIFO SCHED_RR, SCHED_RR调度策略的进程运行一定的时间片后会被切换出。
队列array中保存着所有的可运行task,包括current
rt_nr_running中保存着所有的可运行task,包括current
pick_next_task_rt 之后不会修改array和rt_nr_running
dequeue_task_rt会从array和rt_nr_running中删除task
cpu的优先级(cpuprio):为了实时进程的调度,该cpu实时队列的最高优先级就是代表该cpu的优先级,在push_task_rt的时候就会把实时进程push到最低优先级的cpu。
select_task_rq_rt:为唤醒进程选择cpu,要考虑到最低优先级的cpu,task上次运行的cpu即cache预热过的cpu,cache距离最近的cpu。
static int select_task_rq_rt(struct task_struct *p, int sd_flag, int flags) { struct rq *rq = task_rq(p); if (sd_flag != SD_BALANCE_WAKE) return smp_processor_id(); /*rq->curr是rt进程,尽量find_lowest_rq到一个cpu,否则使用原cpu*/ if (unlikely(rt_task(rq->curr)) && (p->rt.nr_cpus_allowed > 1)) { int cpu = find_lowest_rq(p); return (cpu == -1) ? task_cpu(p) : cpu; } return task_cpu(p); }
find_lowest_rq:找到所有优先级低于被唤醒进程的cpu,如果有多个,首先尽量使用原cpu,再根据sched_domain中距离原cpu最近的cpu,以便使用cache,
RT类的强制调度:
4. CFS调度类:
CFS维持一个系统虚拟时钟,红黑树中按照等待时间(降序)存放着可运行进程队列,只需要pick第一个进程让其运行即可。
runtime -> vruntime:物理运行时间到虚拟运行时间的转化是按照方向优先级的,越高的优先级权值越小
NICE0, vruntime = delta_runtime*1
NICE +5, runtime = delta_runtime*1.1
NICE -5 ,runtime = delta_runtime*0.8
tasks_timeline.rb_node:红黑树中挂接着可运行的task,不包括current
nr_running中保存着可运行的进程数,包括current
pick_next_task_fair会从红黑树中删除current,但不会修改nr_running
dequeue_task_fair会从红黑树中删除current,并修改nr_running
select_task_rq_fair:为唤醒进程选择cpu,从smp_processor_id()所在的sched_domain开始,直到找到一个不超载的sched_domain,然后再优先选择原有cpu/原有cpu的兄弟,再其他域的最闲cpu
CFS类的强制调度:
5.idle调度类
调度
抢占的时机:
内核中不可抢占点:
ksofirqd执行do_softirq时候会首先preempt关闭
#define preempt_count() (current_thread_info()->preempt_count) _____________________________________________________ | 1 | 1 | 10 | 8 | 8 | |________________|_____|__________|_________|_________| | PREEMPT_ACTIVE | NMI | HARDIRQ | SOFTIRQ | PREEMPT | |________________|_____|__________|_________|_________|
preempt_schedule 抢占: 检查,preempt_count 不允许抢占/irqs_disabled不允许抢占
SMP调度
SMP之负载均衡
select_task_rq
load_balance
move_one_task
SMP调度之rt类
rt_overloaded:root_domain中的任何一个cpu上有超过2个rt进程
pre_schedule -> pull_task:当rt_overloaded发生时,把其他cpu上等待队列上RT prio高于本cpu等待队列最高优先级pull(拉)到本cpu上运行,当然是其他各个CPU队列中最高的那个。
post_schedule ->push_task:push当前 队列中多余的rt进程,到lowest_rq
select_task_rq 用于决定唤醒进程需要放那个CPU队列,同push_task一样。
定时负载均衡,schedule_tick会触发SCHED_SOFTIRQ软中断,调用run_rebalance_domains->rebalance_domains->load_balance ,对于RT这里不用做什么,RT只是保证实时性不用保证公平
migration_thread与cpu绑定有关
SMP调度之cfs类
定时负载均衡
void scheduler_tick(void) { int cpu = smp_processor_id(); struct rq *rq = cpu_rq(cpu); struct task_struct *curr = rq->curr; sched_clock_tick(); raw_spin_lock(&rq->lock); update_rq_clock(rq); update_cpu_load(rq); curr->sched_class->task_tick(rq, curr, 0); raw_spin_unlock(&rq->lock); perf_event_task_tick(curr, cpu); #ifdef CONFIG_SMP rq->idle_at_tick = idle_cpu(cpu); trigger_load_balance(rq, cpu); #endif }
curr->sched_class->task_tick->task_tick_fair->check_preempt_tick:根据delta_exec、sysctl_sched_min_granularity决定是否调度
schedule_tick会触发SCHED_SOFTIRQ软中断,软中断处理中调用run_rebalance_domains->rebalance_domains->load_balance ,找到最忙的cpu,并pull进程到本cpu调度
cpu负载-weight 计算方法:
static void account_entity_enqueue(struct cfs_rq *cfs_rq, struct sched_entity *se) { update_load_add(&cfs_rq->load, se->load.weight); if (!parent_entity(se)) inc_cpu_load(rq_of(cfs_rq), se->load.weight); if (entity_is_task(se)) { add_cfs_task_weight(cfs_rq, se->load.weight); list_add(&se->group_node, &cfs_rq->tasks); } cfs_rq->nr_running++; se->on_rq = 1; } static inline void inc_cpu_load(struct rq *rq, unsigned long load) { update_load_add(&rq->load, load); } static inline void update_load_add(struct load_weight *lw, unsigned long inc) { lw->weight += inc; lw->inv_weight = 0; }
域与控制组
__build_sched_domains初始化调度域 rd
CONFIG_GROUP_SCHED
CONFIG_RT_GROUP_SCHED:默认使用init_task_group(在没有CONFIG_CGROUP_SCHED和CONFIG_USER_SCHED情况下)
CONFIG_GROUP_SCHED
CONFIG_FAIR_GROUP_SCHED
CONFIG_RT_GROUP_SCHED
CONFIG_USER_SCHED
CONFIG_CGROUP_SCHED
struct cgroup_subsys cpu_cgroup_subsys = { .name = "cpu", .create = cpu_cgroup_create, .destroy = cpu_cgroup_destroy, .can_attach = cpu_cgroup_can_attach, .attach = cpu_cgroup_attach, .populate = cpu_cgroup_populate, .subsys_id = cpu_cgroup_subsys_id, .early_init = 1, }; struct cgroup_subsys mem_cgroup_subsys = { .name = "memory", .subsys_id = mem_cgroup_subsys_id, .create = mem_cgroup_create, .pre_destroy = mem_cgroup_pre_destroy, .destroy = mem_cgroup_destroy, .populate = mem_cgroup_populate, .attach = mem_cgroup_move_task, .early_init = 0, .use_id = 1, }; struct cgroup_subsys ns_subsys = { .name = "ns", .can_attach = ns_can_attach, .create = ns_create, .destroy = ns_destroy, .subsys_id = ns_subsys_id, }; struct cgroup_subsys freezer_subsys = { .name = "freezer", .create = freezer_create, .destroy = freezer_destroy, .populate = freezer_populate, .subsys_id = freezer_subsys_id, .can_attach = freezer_can_attach, .attach = NULL, .fork = freezer_fork, .exit = NULL, }; struct cgroup_subsys debug_subsys = { .name = "debug", .create = debug_create, .destroy = debug_destroy, .populate = debug_populate, .subsys_id = debug_subsys_id, }; struct cgroup_subsys cpuset_subsys = { .name = "cpuset", .create = cpuset_create, .destroy = cpuset_destroy, .can_attach = cpuset_can_attach, .attach = cpuset_attach, .populate = cpuset_populate, .post_clone = cpuset_post_clone, .subsys_id = cpuset_subsys_id, .early_init = 1, }; struct cgroup_subsys cpu_cgroup_subsys = { .name = "cpu" "task_group", .create = cpu_cgroup_create, .destroy = cpu_cgroup_destroy, .can_attach = cpu_cgroup_can_attach, .attach = cpu_cgroup_attach, .populate = cpu_cgroup_populate, .subsys_id = cpu_cgroup_subsys_id, .early_init = 1, } struct cgroup_subsys cpuacct_subsys = { .name = "cpuacct", .create = cpuacct_create, .destroy = cpuacct_destroy, .populate = cpuacct_populate, .subsys_id = cpuacct_subsys_id, }; struct cgroup_subsys blkio_subsys = { .name = "blkio", .create = blkiocg_create, .can_attach = blkiocg_can_attach, .attach = blkiocg_attach, .destroy = blkiocg_destroy, .populate = blkiocg_populate, .subsys_id = blkio_subsys_id, .use_id = 1, }; struct cgroup_subsys net_cls_subsys = { .name = "net_cls", .create = cgrp_create, .destroy = cgrp_destroy, .populate = cgrp_populate, .subsys_id = net_cls_subsys_id, };
init_task_group
cgroup_init_early -> init_task.cgroups = &init_css_set;
list_add(&init_task_group.list, &task_groups);
INIT_LIST_HEAD(&init_task_group.children);
http://hi.baidu.com/daxuelangren/item/b71f810928ec0837a2332a72
6)Processor type and features-->SMT (Hyperthreading) scheduler support
该条目为y或者n,为y时定义CONFIG_SCHED_SMT宏。定义它的目的是为了对支持超线程的CPU提供能好的调度功能。我举例说明一下:比如一 个系统有两个CPU,每个CPU又支持超线程,那么系统中有四个逻辑CPU,我们将这个四个CPU记为C00, C01, C10, C11,其中C00与C01是一个物理CPU上的两个硬件线程,而C10与C11则是另一个物理CPU上的两个硬件线程。假设某一个时刻系统中有两个线程 在执行,在没有定义CONFIG_SCHED_SMT宏的情况下内核很可能会将这两个线程分别调度到C00与C01上去,但这是不优化的,因为C00与 C01是一个物理CPU上的两个硬件线程,它们共享了许多硬件资源,导致两个线程运行时并不能充分发挥这个系统的资源优势;而定义了 CONFIG_SCHED_SMT宏的情况下,内核的调度器就会将这两个线程调度到位于不同物理CPU的逻辑CPU上,比如C00与C10上。
乍一看似乎只要你的CPU支持超线程,那么你就应该勾选这一项,但是我认为并不一定这样。比如你的系统是单CPU并且支持超线程,那么这个时候 CONFIG_SCHED_SMT宏对于提高调度效果并没有什么意义(当然前提是我对内核调度器的理解无误的话:)),而且定义 CONFIG_SCHED_SMT宏还会对调度增加额外的开销。
我的建议是:在你的CPU支持超线程的前提下,只有当你的系统有多个CPU或者支持多核的时候才有必要勾选这一项。
7)Processor type and features-->Multi-core scheduler support
该条目为y或者n,为y时定义CONFIG_SCHED_MC宏。它的作用很类似于前面提到的CONFIG_SCHED_SMT,只不过它针对多核 (multi-core)。我的建议是只有你的系统拥有多个物理CPU时才有必要勾选。比如我家里的机器CPU为单AMD Athlon 64 X2 4200+,虽然是双核,但只有一个物理CPU,我就没有必要定义这个宏。
Linux多cpu负载平衡-线程迁移
http://www.linuxidc.com/Linux/2011-04/34728.htm
http://www.ibm.com/developerworks/cn/linux/l-cn-scheduler/
[kernel] linux在多核处理器上的负载均衡原理 from 淘宝核心系统团队博客 by 董昊
http://www.starming.com/index.php?action=plugin&v=wave&tpl=union&ac=viewgrouppost&gid=33263&tid=8042
完全公平调度CFS探索之实现
http://blog.chinaunix.net/uid-25942458-id-3395887.html
context_switch( )上下文切换
http://blog.csdn.net/b02042236/article/details/6076473
优化方向:
OpenMp 多核并发 任务分解 数据分解
网络 gso 光口 udp承载 (最优原则,二层三层特点考虑)
IO 存储读写速度
cpu占用率,调度粒度 负载均衡粒度
多核监听,一般一个cpu开2个监听线程