unsigned int sysctl_sched_rt_period = 1000000; int sysctl_sched_rt_runtime = 950000; // 参考: // SMP负载均衡 // http://soft.chinabyte.com/os/22/12359522.shtml // linux组调度浅析 // http://hi.baidu.com/_kouu/item/0fe32610e493314be75e06d1 // 进程调度和组调度 // http://blog.chinaunix.net/uid-27052262-id-3239263.html // CF调度器 // http://blog.csdn.net/wudongxu/article/details/8574749 // FIFO调度器 // http://lwn.net/Articles/296419/ // 调度初始化 // 函数任务: // 1.初始化rootdomain // rootdomain指示rq可运行的cpu集合 // 2.初始化real-time task对cpu的占有率 // sysctl_sched_rt_period代表rt进程的调度周期 // sysctl_sched_rt_runtime代表rt进程在调度周期中可运行的时间 // 3.初始化per-cpu rq // 3.1 初始化公平调度队列,实时调度队列 // 3.2 初始化cpu负载记录数组 // 3.3 初始化cpu使用的tick hrtimer // 4.初始化current(init_task)为idle task // 4.1 设置current由公平调度管理 1.1 void __init sched_init(void) { int i, j; #ifdef CONFIG_SMP //初始化默认的调度域 init_defrootdomain(); #endif //rt_bandwidth表示实时进程对cpu的占有率 init_rt_bandwidth(&def_rt_bandwidth, global_rt_period(), global_rt_runtime()); //初始化per-cpu rq for_each_possible_cpu(i) { struct rq *rq; //per-cpu 运行队列 rq = cpu_rq(i); raw_spin_lock_init(&rq->lock); rq->nr_running = 0; rq->calc_load_active = 0; rq->calc_load_update = jiffies + LOAD_FREQ; //初始化公平调度策略、实时调度策略队列 init_cfs_rq(&rq->cfs, rq); init_rt_rq(&rq->rt, rq); //调度队列中,实时进程对cpu的占有率 rq->rt.rt_runtime = def_rt_bandwidth.rt_runtime; //分5个等级记录cpu的负载情况 for (j = 0; j < CPU_LOAD_IDX_MAX; j++) rq->cpu_load[j] = 0; #ifdef CONFIG_SMP rq->sd = NULL; rq->rd = NULL; rq->post_schedule = 0; rq->active_balance = 0; rq->next_balance = jiffies; rq->push_cpu = 0; //rq运行的cpu rq->cpu = i; rq->online = 0; rq->migration_thread = NULL; rq->idle_stamp = 0; rq->avg_idle = 2*sysctl_sched_migration_cost; INIT_LIST_HEAD(&rq->migration_queue); rq_attach_root(rq, &def_root_domain); #endif //初始化rq使用的hrtimer init_rq_hrtick(rq); atomic_set(&rq->nr_iowait, 0); } set_load_weight(&init_task); //load balancing软中断 #ifdef CONFIG_SMP open_softirq(SCHED_SOFTIRQ, run_rebalance_domains); #endif atomic_inc(&init_mm.mm_count); //通知底层体系结构不需要切换虚拟地址空间的用户空间部分 enter_lazy_tlb(&init_mm, current); //将当前进程,即init_task更新为idle thread init_idle(current, smp_processor_id()); //下次进行load balancing的时间戳 calc_load_update = jiffies + LOAD_FREQ; //当前进程关联的调度累 current->sched_class = &fair_sched_class; perf_event_init(); //标识调度器开始运行 scheduler_running = 1; } // rq结构分析 // 参考 http://blog.csdn.net/bullbat/article/details/7160246 1.2 struct rq { raw_spinlock_t lock; //就绪队列中进程数 unsigned long nr_running; #define CPU_LOAD_IDX_MAX 5 //分5个等级记录就绪队列的负载情况 //在系统初始化的时候sched_init把rq的cpu_load array初始化为0,之后通过函数update_cpu_load //公式如下: // cpu_load[0]等于rq中load.weight的值 // cpu_load[1]=(cpu_load[1]*(2-1)+cpu_load[0])/2 // cpu_load[2]=(cpu_load[2]*(4-1)+cpu_load[0])/4 // cpu_load[3]=(cpu_load[3]*(8-1)+cpu_load[0])/8 // cpu_load[4]=(cpu_load[4]*(16-1)+cpu_load[0]/16 //通过this_cpu_load返回的cpu load值是cpu_load[0] //进行cpu blance或migration时,通过调用source_load target_load取得对该处理器cpu_load index值。 unsigned long cpu_load[CPU_LOAD_IDX_MAX]; //本schedule entity的load->weight的总和 struct load_weight load; //scheduler tick中调用update_cpu_load时,这个值就增加一,可以用来反馈目前cpu load更新的次数。 unsigned long nr_load_updates; //统计处理器context switch次数 // schedule进行累加,并可以通过函数nr_context_switches统计目前所有处理器总共的context switch次数 // 或是可以查看/proc/stat中的ctxt获取目前整个系统触发context switch的次数。 u64 nr_switches; //公平调度队列 struct cfs_rq cfs; //实时调度队列 struct rt_rq rt; //支持group cfs tasks的机制 #ifdef CONFIG_FAIR_GROUP_SCHED //1.fair group scheduling, // 将cfs rq中若干task组织成若干task group,即子cfs_rq,属于这group的task所使用到的处理器时间就会以个group总共所分的的时间为上限。 //2.基于cgroup的fair group scheduling 架构 // 2.1 可以创造出有阶层性的task组织,根据不同task的功能群组化。 // 2.2 在配置给该群组对应的处理器资源,让属于该群组下的task可以通过rq机制使用属于该群组下的资源。 //3.加入、退出、遍历leaf_cfs_rq_list // 3.1 加入:list_add_leaf_cfs_rq把一个group cfs rq加入。 // 3.2 退出:list_del_leaf_cfs_rq把一个group cfs rq退出。 // 3.3 遍历:for_each_leaf_cfs_rq遍历rq上得所有leaf cfs_rq struct list_head leaf_cfs_rq_list; #endif #ifdef CONFIG_RT_GROUP_SCHED //类似leaf_cfs_rq_list,只是这里是针对属于real-time的task,对应的操作函数有list_add_leaf_rt_rq, //list_del_leaf_rt_rq, for_each_leaf_rt_rq. struct list_head leaf_rt_rq_list; #endif //统计目前rq中有多少task属于TASK_UNINTERRUPTIBLE的状态. unsigned long nr_uninterruptible; //指向目前处理器正在执行的task; 没有可运行task时,运行idle task. struct task_struct *curr, *idle; //基于处理器的jiffies值,用以记录下次进行处理器balancing的时间点. unsigned long next_balance; //用以存储context-switch发生时,前一个task的memory management结构. struct mm_struct *prev_mm; //记录目前rq的clock值 // 该值等于调用sched_clock_cpu(cpu_of(rq))的返回值,在scheduler_tick中通过函数update_rq_clock更新. u64 clock; //记录目前rq中处于等待i/o状态的task数。 // 例如当driver接受来自task的调用,但处于等待i/o阶段时,为了充分利用处理器的执行资源, // 这时就可以在driver中调用函数io_schedule,此时就会把目前rq中的nr_iowait加一, // 并设定目前task的io_wait为1, 然后触发scheduling 让其他task有机会可以得到处理器执行时间。 atomic_t nr_iowait; #ifdef CONFIG_SMP //root domain是基于多核心架构下的机制, //其中包括了: // cpu mask(包括span,online,rt overload), // reference count // cpupri //1.当root domain被rq引用时,refcount加一,反之就减一。 //2.cpu mask span表示rq可运行的cpu mask、 noline为已经被rq安排了进程的cpu //3.当rq中real-time的task执行完毕时,会调用函数pull_rt_task从该rq rto_mask中标识的cpu上, // 查找是否有处理器有大于一个以上的real-time task,若有就会迁移到本cpu执行。 //4.cpupri不同于task的优先级,cpupri本身有102个优先级: // -1 invalid, // 0 idle // 1 normal // 2-101对应real-time priority 0-99 //参考convert_prio, task priority如果是140就会对应到cpu idle,如果是大于等于100就会对应到cpu normal, // 若是task priority介于0-99之间,就会对应到cpu real-time priority 101-2 之间。 //在实际的操作上, 可以调用cpupri_find传入一个real-time task结构,此时就会依据cpupri中pri_to_cpu选择 // 一个目前执行real-time task且该task的优先级比目前要插入的task更低的处理器, 并通过cpu mask(lowest_mask) // 返回目前可以选择的处理器mask。 //参考 kernel/sched_cpupri.c. //在初始化的过程中,通过sched_init调用init_defrootdomain对root domain与cpu priority机制进行初始化。 struct root_domain *rd; //schedule domain是基于多核心架构下的机制。 //每个处理器都会有一个默认的scheduling domain. // 1.scheduling domain的层次结构: // 通过parent找到上一层的domain。 // 通过child找到下一层的domain(NULL表示结尾)。 // 2.scheduling domain覆盖的处理器: // 通过span掩码,表示这个domain所覆盖的处理器范围。 // 3.scheduling domain的根: // base domain涵盖系统中所有处理器。 // 4.scheduling doma的cpu group: // 4.1 schedule domain都会包括一个或一个以上的cpu groups(结构为struct sched_group),并通过next指针把cpu groups串联在一起(成为一个单向的circular linked list)。 // 4.2 cpu group都通过cpumask来定义其所涵盖的处理器,并且cpu group所包括的处理器范围必需涵盖在所属的schedule domain处理器范围中。 // 4.3 当scheduling domain在balancing时,会以其下的cpu groups为单位,通过cpu_power(该group所涵盖的处理器的tasks loading // 总和)来比较不同的cpu groups的负荷来进行tasks的移动,达到balancing的目的。 // 5.loadbalance时机: // 5.1 在sched_init中, 通过open_softirq注册SCHED_SOFTIRQ软中断\ // 5.2 在scheduler_tick中,通过trigger_load_balance确认目前的jiffies值是否大于要触发load balance的时间戳,并通过raise_softirq触发SCHED_SOFTIRQ。 // 5.3 在load balance软中断中,通过run_rebalance_domains进行scheduling domain load balance. // 有关scheduling domain进一步的內容,参考 // Documentation/scheduler/sched-domains.txt. struct sched_domain *sd; //为1表示目前cpu rq中执行的为idle task //为0表示执行非idle task unsigned char idle_at_tick; int post_schedule; //为1表示这个rq正在运行fair scheduling的load balance,此时会调用stop_one_cpu_nowait暂停该cpu的进程 //然后通过调用active_load_balance_cpu_stop把tasks从最忙碌的处理器移到idle的处理器上执行 int active_balance; //用以存储目前进入idle状态并且进行load balance流程的处理器id。 //整个流程为 // 进程调用schedule时, 若该处理器rq的nr_running为0(也就是目前没有正在执行的task),就会调用idle_balance并 // 触发后续load balance流程 int push_cpu; //用以存储目前运作这个rq的处理器id int cpu; int online; //如果rq中目前有task正在执行,这个值会等于目前该rq的load weight除以目前rq中task数目的均值 unsigned long avg_load_per_task; struct task_struct *migration_thread; struct list_head migration_queue; //统计目前real-time task执行时间的均值, 反应目前系统中real-time task平均被分配到的执行时间值 u64 rt_avg; u64 age_stamp; //表示cpu进入idle状态的时间 u64 idle_stamp; u64 avg_idle; #endif //用以记录下一次计算cpu load的时间,初始值为当前的jiffies加上五秒与1次的scheduling tick的间隔 unsigned long calc_load_update; long calc_load_active; #ifdef CONFIG_SCHED_HRTICK #ifdef CONFIG_SMP int hrtick_csd_pending; struct call_single_data hrtick_csd; #endif struct hrtimer hrtick_timer; #endif };