Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1748506
  • 博文数量: 199
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 6186
  • 用 户 组: 普通用户
  • 注册时间: 2012-10-30 11:01
个人简介

Linuxer.

文章存档

2015年(4)

2014年(28)

2013年(167)

分类: LINUX

2013-07-15 15:21:18


  1. 一、调度策略
  2. 每个linux进程总是按照下边的调度类型被调度:
  3. SCHED_FIFO:先进先出的实时进程。适用于时间性要求比较强,但每次运行所需的时间比较短的进程,实时的应用大都具有这样的特点。
  4. SCHED_RR:时间片轮流的实时进程。轮流,适合比较大,每次运行需时较长的进程。
  5. SCHED_OTHER:普通的分时进程。传统的调度政策,适合于交互式分时应用。

  6. Linux 根据policy从整体上区分实时进程和普通进程,因为实时进程和普通进程度调度是不同的,它们两者之间,实时进程应该先于普通进程而运行,然后,对于同一类型的不同进程,采用不同的标准来选择进程:
  7. 1.普通进程调度
  8. (1)静态优先级
  9. 每个普通进程都有它自己的静态优先级。内核从100~139表示。
  10. 静态优先级本质上决定了进程的基本时间片,即进程用完了以前的时间片时,系统分配给进程的时间片长度。
  11. 不随时间而改变,只能由用户进行修改。它指明了在被迫和其他进程竞争CPU之前,该进程所应该被允许的时间片的最大值。
  12. 若静态优先级<120:基本时间片(ms)=(140-静态优先级)*20
  13. 若静态优先级>=120:基本时间片(ms)=(140-静态优先级)*5
  14. (2)动态优先级
  15. 普通进程除了静态优先级还有动态优先级,范围是100~139.    
  16. 动态优先级是调度程序在选择新程序来运行的时候选择的数。只要进程拥有CPU,它就随着时间不断减小;当它小于0时,标记进程重新调度。它指明了在这个时间片中所剩余的时间量。    
  17. 动态优先级=max(100,min(静态优先级-bonus+5,139))    
  18.     
  19. 2.实时进程的调度
  20. 每个实时进程都与一个实时优先级相关,范围从1~99表示。
  21. 与普通进程相反,实时进程总是被当成活动进程。    
  22. 调度程序总是让优先级高的进程运行,发生以下情况实时进程才会被另外一个进程取代:
  23. (1)进程被另外一个具有更高实时优先级的实时进程抢占。
  24. (2)进程执行了阻塞操作并进入睡眠。
  25. (3)进程停止或被杀死。
  26. (4)进程通过系统调用自动放弃CPU。
  27. (5)进程是基于时间片轮转的实时进程(SCHED_RR),而且用完了时间片。
  28.     
  29. 二、调度程序使用的数据结构
  30. 数据结构runqueue是linux2.6调度程序最重要的数据结构。
  31. 系统中每个cpu都有它自己的运行队列,所有的runqueue结构存放在runqueues每cpu变量中。
  32. struct runqueue {
  33.     spinlock_t lock;//自旋锁,用于锁定运行队列
  34.     unsigned long nr_running;//可运行的的任务数目
  35.     unsigned long cpu_load[3];//基于运行队列中进程的平均数量的cpu负载因子
  36.     unsigned long long nr_switches;//上下文切换数目
  37.     unsigned long nr_uninterruptible;//处于不可中断或者睡眠状态的任务数目
  38.     unsigned long expired_timestamp;//队列最后被换出的时间
  39.     unsigned long long timestamp_last_tick;//最后一个调度程序的节拍
  40.     task_t *curr, *idle;//当前运行的任务和空闲的任务
  41.     struct mm_struct *prev_mm;//最后运行任务的mm_struct结构体。
  42.     prio_array_t *active, *expired, arrays[2];//1.活动优先级队列2.超时优先级队列,3.实际优先级数组
  43.     int best_expired_prio;//最适宜换出的优先级队列
  44.     atomic_t nr_iowait;//等待I/O操作的任务数目
  45.     
  46.     struct sched_domain *sd;//多处理器相关
  47.     int active_balance;//活动的需要负载平衡进程个数
  48.     int push_cpu;//哪个CPU需要进行负载平衡?
  49.     task_t *migration_thread;//换出任务
  50.     struct list_head migration_queue;//换出队列
  51.     /* latency stats */
  52.     struct sched_info rq_sched_info;
  53.     /* sys_sched_yield() stats */
  54.     unsigned long yld_exp_empty;
  55.     unsigned long yld_act_empty;
  56.     unsigned long yld_both_empty;
  57.     unsigned long yld_cnt;
  58.     /* schedule() stats */
  59.     unsigned long sched_switch;
  60.     unsigned long sched_cnt;
  61.     unsigned long sched_goidle;
  62.     /* try_to_wake_up() stats */
  63.     unsigned long ttwu_cnt;
  64.     unsigned long ttwu_local;
  65. };

  66. 三、实际代码分析
  67. //schedule只能由进程在内核中主动调用,或者在当前进程从系统空间返回到用户空间的前夕被动的调用。而不能在一个中断服务程序中调用
  68. asmlinkage void schedule(void)
  69. {
  70.     struct schedule_data * sched_data;
  71.     struct task_struct *prev, *next, *p;
  72.     struct list_head *tmp;
  73.     int this_cpu, c;
  74.     
  75.     if (!current->active_mm) BUG();//内存描述符不为空
  76. need_resched_back:
  77.     prev = current;
  78.     this_cpu = prev->processor;//当前进程所在的处理器
  79.     
  80.     if (in_interrupt())//调度不能发生在中断服务程序中
  81.         goto scheduling_in_interrupt;
  82.     
  83.     release_kernel_lock(prev, this_cpu);//空语句
  84.     //检查是否有内核软中断服务请求在等待
  85.     if (softirq_active(this_cpu) & softirq_mask(this_cpu))
  86.         goto handle_softirq;//有就转去执行软中断服务请求
  87. handle_softirq_back:
  88.     //将当前cpu的schedule_data信息保存
  89.     sched_data = & aligned_data[this_cpu].schedule_data;
  90.     //锁住队列
  91.     spin_lock_irq(&runqueue_lock);
  92.     
  93.     if (prev->policy == SCHED_RR)//若调度政策为SCHED_RR,即轮流政策
  94.         goto move_rr_last;//转去检查时间配额是否为0,为0就重新分配,然后再跳转回来
  95. move_rr_back:
  96.     switch (prev->state) {//查询当前进程状态
  97.         case TASK_INTERRUPTIBLE://可中断的睡眠状态
  98.             if (signal_pending(prev)) {//有信号到来
  99.                 prev->state = TASK_RUNNING;//设置为可运行状态
  100.                 break;
  101.             }
  102.             default://深度睡眠
  103.                 del_from_runqueue(prev);//将当前进程从可执行队列中删除
  104.             case TASK_RUNNING:
  105.     }
  106.     prev->need_resched = 0;//调度标志清0

  107. repeat_schedule://挑选一个进程来运行
  108.     next = idle_task(this_cpu);//从0号进程开始
  109.     c = -1000;//权值设为-1000
  110.     if (prev->state == TASK_RUNNING)//当前进程状态为可运行状态
  111.         goto still_running;//转去计算当前进程的权值,并把最优进程设置为当前进程,然后跳转回来接着往下找是否还有更优的进程
  112.     
  113. still_running_back:
  114.     list_for_each(tmp, &runqueue_head) {//遍历可执行队列中的所有进程
  115.         p = list_entry(tmp, struct task_struct, run_list);//从队列中依次取出进程的task_struct结构
  116.         if (can_schedule(p, this_cpu)) {//取出的进程是否允许调度
  117.             int weight = goodness(p, this_cpu, prev->active_mm);//计算出取出进程的权值
  118.             if (weight > c)//是否大于C
  119.                 c = weight, next = p;//最优进程next改成权值最大的进程
  120.         }
  121.     }
  122.     if (!c)//若c为0,表明可运行队列中的所有进程的权值都是0,且都是SCHED_OTHER的进程
  123.         goto recalculate;//转去重新计算各个进程的时间配额
  124.     sched_data->curr = next;
  125. #ifdef CONFIG_SMP
  126.     next->has_cpu = 1;//表明已经在某一个cpu上开始运行
  127.     next->processor = this_cpu;//最优进程运行的处理器号
  128. #endif
  129.     spin_unlock_irq(&runqueue_lock);
  130.     
  131.     if (prev == next)//若挑选出来的进程和当前进程一致
  132.         goto same_process;//不需切换,退出
  133.     
  134.     kstat.context_swtch++;
  135.     prepare_to_switch();//空语句
  136.     struct mm_struct *mm = next->mm;
  137.     struct mm_struct *oldmm = prev->active_mm;
  138.     if (!mm) {//最优进程若是内核线程,不需要切换进程的虚拟空间,
  139.         if (next->active_mm) BUG();
  140.             next->active_mm = oldmm;//内核线程使用当前进程的内核空间
  141.         atomic_inc(&oldmm->mm_count);//当前进程的内核空间使用计数递加
  142.         enter_lazy_tlb(oldmm, next, this_cpu);
  143.     } else {//最优进程非内核线程
  144.         if (next->active_mm != mm) BUG();
  145.             switch_mm(oldmm, mm, next, this_cpu);//虚拟空间的切换
  146.     }
  147.     
  148.     if (!prev->mm) {//当前进程是内核线程
  149.         prev->active_mm = NULL;//借来的内存描述符清空
  150.         mmdrop(oldmm);//递减共享计数
  151.      }
  152.     
  153.     switch_to(prev, next, prev);//进程的切换,主要是堆栈的切换
  154.     __schedule_tail(prev);//将当前进程的SCHED_YIELD标志清0
  155. 650
  156. same_process:
  157.     reacquire_kernel_lock(current);
  158.     if (current->need_resched)//调度标志又为1
  159.         goto need_resched_back;//转回重新调度
  160.     
  161.     return;//成功返回
  162.     
  163. recalculate:
  164.     {
  165.         struct task_struct *p;
  166.         spin_unlock_irq(&runqueue_lock);
  167.         read_lock(&tasklist_lock);
  168.         for_each_task(p)//遍历所有进程
  169.             p->counter = (p->counter >> 1) + NICE_TO_TICKS(p->nice);//根据静态优先级nice重新分配时间配额
  170.         read_unlock(&tasklist_lock);
  171.         spin_lock_irq(&runqueue_lock);
  172.     }
  173.     goto repeat_schedule;
  174.     
  175. still_running:
  176.     c = goodness(prev, this_cpu, prev->active_mm);//计算出当前进程的权值
  177.     next = prev;//将当前进程暂时设置为最优进程,也就是说下边的查找会以当前进程的权值做起点
  178.     goto still_running_back;
  179.     
  180. handle_softirq:
  181.     do_softirq();//处理软中断请求
  182.     goto handle_softirq_back;
  183. move_rr_last:
  184.     if (!prev->counter) {//当前进程的时间配额为0
  185.         prev->counter = NICE_TO_TICKS(prev->nice);//重新分配时间按配额
  186.         move_last_runqueue(prev);//加入到可执行进程队列末尾
  187.     }
  188.     goto move_rr_back;
  189.     
  190. scheduling_in_interrupt:
  191.     printk("Scheduling in interrupt\n");
  192.     BUG();
  193.     return;
  194. }

  195. #if HZ < 200
  196.     #define TICK_SCALE(x) ((x) >> 2)
  197. #elif HZ < 400
  198.     #define TICK_SCALE(x) ((x) >> 1)
  199. #elif HZ < 800
  200.     #define TICK_SCALE(x) (x)
  201. #elif HZ < 1600
  202.     #define TICK_SCALE(x) ((x) << 1)
  203. #else
  204.     #define TICK_SCALE(x) ((x) << 2)
  205. #endif
  206.     
  207. #define NICE_TO_TICKS(nice) (TICK_SCALE(20-(nice))+1)

  208. static inline int goodness(struct task_struct * p, int this_cpu, struct mm_struct *this_mm)
  209. {
  210.     int weight;
  211.     weight = -1;
  212.     if (p->policy & SCHED_YIELD)//若进程设置了”礼让“,则权值设为weight = -1返回
  213.         goto out;
  214.     
  215.     if (p->policy == SCHED_OTHER) {//对于没有实时要求的进程
  216.         weight = p->counter;//权值设为剩下的时间配额
  217.         if (!weight)//若剩下的时间配额为0,则权值weight = 0返回
  218.             goto out;

  219. #ifdef CONFIG_SMP
  220.     if (p->processor == this_cpu)
  221.         weight += PROC_CHANGE_PENALTY;
  222. #endif
  223.     if (p->mm == this_mm || !p->mm)//若是内核线程且其用户空间与当前进程相同,无需切换用户空间
  224.         weight += 1;//得到一点奖励
  225.         weight += 20-p->nice;//20-p->nice范围为1-40,nice为谦让程度,nice为非实时进程的静态优先级
  226.         goto out;//返回weight
  227.     }
  228.     //对于有实时要求的进程,即调度政策为SCHED_FIFO 或者SCHED_RR的进程
  229.     weight = 1000 + p->rt_priority;//加上实时优先级(动态优先级)
  230. out:
  231.     return weight;
  232. }

  233. static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, struct task_struct *tsk,unsigned cpu)
  234. {
  235.     if (prev != next) {
  236.         clear_bit(cpu, &prev->cpu_vm_mask);
  237.         if (prev->context.segments != next->context.segments)
  238.             load_LDT(next);
  239. #ifdef CONFIG_SMP
  240.         cpu_tlbstate[cpu].state = TLBSTATE_OK;
  241.         cpu_tlbstate[cpu].active_mm = next;
  242. #endif
  243.         set_bit(cpu, &next->cpu_vm_mask);
  244.         //将新进程的页面目录的起始地址装入cr3中
  245.         asm volatile("movl %0,%%cr3": :"r" (__pa(next->pgd)));
  246.     }
  247. }

  248. #define switch_to(prev,next,last) do { \
  249. 16    asm volatile("pushl %%esi\n\t" \//当前进程prev的寄存器入栈
  250. 17    "pushl %%edi\n\t" \
  251. 18    "pushl %%ebp\n\t" \
  252. 19    "movl %%esp,%0\n\t" /* save ESP */ \//将当前进程prev系统空间堆栈指针存入prev->thread.esp
  253. 20    "movl %3,%%esp\n\t" /* restore ESP */ \//将新受到调度要进入运行的进程next的系统空间堆栈指针next->thread.esp存入ESP
  254.                                           //从21行开始,已经切换了堆栈,现在使用的是next进程的堆栈
  255. 21    "movl $1f,%1\n\t" /* save EIP */ \//将25行pop指令所在的地址保存在prev->thread.eip
  256. 22    "pushl %4\n\t" /* restore EIP */ \//将next->thread.eip压入堆栈,next->thread.eip正是进程next在上一次被调离时在21行保存的,指向1
  257. 23    "jmp __switch_to\n" \//从__switch_to返回时,执行那里的ret指令,由于是jmp指令转过去的,最后进入堆栈的
  258.                         //next->thread.eip变成了返回地址,也就是标号1所在的地址,也就是25行指令所在的地址
  259. 24    "1:\t" \
  260. 25    "popl %%ebp\n\t" \//恢复新切入的进程next在上一次被调离时push进堆栈的内容,前边堆栈已经切换了
  261. 26    "popl %%edi\n\t" \
  262. 27    "popl %%esi\n\t" \
  263. 28    :"=m" (prev->thread.esp),"=m" (prev->thread.eip), \
  264. 29    "=b" (last) \
  265. 30    :"m" (next->thread.esp),"m" (next->thread.eip), \
  266. 31    "a" (prev), "d" (next), \
  267. 32    "b" (prev)); \
  268. } while (0)

  269. void __switch_to(struct task_struct *prev_p, struct task_struct *next_p)
  270. {
  271.     struct thread_struct *prev = &prev_p->thread,
  272.     *next = &next_p->thread;
  273.     struct tss_struct *tss = init_tss + smp_processor_id();
  274.     
  275.     unlazy_fpu(prev_p);
  276.     
  277.     tss->esp0 = next->esp0;//TSS内核的内核空间堆栈指针设为要调度运行的进程的堆栈指针
  278.     
  279.     asm volatile("movl %%fs,%0":"=m" (*(int *)&prev->fs));
  280.     asm volatile("movl %%gs,%0":"=m" (*(int *)&prev->gs));
  281.     
  282.     loadsegment(fs, next->fs);
  283.     loadsegment(gs, next->gs);
  284.     
  285.     if (next->debugreg[7]){
  286.         loaddebug(next, 0);
  287.         loaddebug(next, 1);
  288.         loaddebug(next, 2);
  289.         loaddebug(next, 3);
  290.         loaddebug(next, 6);
  291.         loaddebug(next, 7);
  292.     }
  293.     
  294.     if (prev->ioperm || next->ioperm) {//有I/O位图
  295.         if (next->ioperm) {
  296.             memcpy(tss->io_bitmap, next->io_bitmap,IO_BITMAP_SIZE*sizeof(unsigned long));//拷贝要调度运行的进程的IO位位图
  297.             tss->bitmap = IO_BITMAP_OFFSET;
  298.         } else
  299.             tss->bitmap = INVALID_IO_BITMAP_OFFSET;
  300.     }
  301. }


  302. //////////////////////////////////////////////////////////////////////////////////////////////
  303. /*************************************强制性调度*********************************************/
  304. //内核中的强制性调度,也就是非自愿的、被动的剥夺式的调度,主要是由时间引起的,这种调度发生在
  305. //进程从系统空间返回到用户空间的前夕。如下三种情况:
  306. //1.在时钟中断的服务程序中,发现当前进程运行时间过长
  307. //2.当唤醒一个睡眠中的进程时,发现被唤醒的进程比当前进程更有资格运行。
  308. //3.一个进程通过系统调用改变调度政策或礼让。

  309. //第一种情况:
  310. [do_timer_interrupt()>do_timer()>update_process_times()]
  311. void update_process_times(int user_tick)//调整当前进程与时间有关的一些参数
  312. {
  313. 581 struct task_struct *p = current;
  314. 582 int cpu = smp_processor_id(), system = user_tick ^ 1;
  315. 583
  316. 584 update_one_process(p, user_tick, system, cpu);//统计信息
  317. 585 if (p->pid) {//0号进程,时间配额不能递减
  318. 586 if (--p->counter <= 0) {//时间配额减为0或小于0
  319. 587 p->counter = 0;//设为0
  320. 588 p->need_resched = 1;//设置调度标志
  321. 589 }
  322. 590 if (p->nice > 0)
  323. 591 kstat.per_cpu_nice[cpu] += user_tick;
  324. 592 else
  325. 593 kstat.per_cpu_user[cpu] += user_tick;
  326. 594 kstat.per_cpu_system[cpu] += system;
  327. 595 } else if (local_bh_count(cpu) || local_irq_count(cpu) > 1)
  328. 596 kstat.per_cpu_system[cpu] += system;
  329. }

  330. //第二种情况:
  331. inline void wake_up_process(struct task_struct * p)
  332. {
  333. 331 unsigned long flags;
  334. 332
  335. 336 spin_lock_irqsave(&runqueue_lock, flags);
  336. 337 p->state = TASK_RUNNING;
  337. 338 if (task_on_runqueue(p))//当前唤醒进程是否已经在cpu的可运行队列runqueue
  338. 339 goto out;
  339. 340 add_to_runqueue(p);//挂入cpu的可运行队列runqueue
  340. 341 reschedule_idle(p);
  341. out:
  342. 343 spin_unlock_irqrestore(&runqueue_lock, flags);
  343. }

  344. static void reschedule_idle(struct task_struct * p)
  345. {
  346. #ifdef CONFIG_SMP

  347. #else /* UP */
  348. 287 int this_cpu = smp_processor_id();
  349. 288 struct task_struct *tsk;
  350. 289 //#define cpu_curr(cpu) aligned_data[(cpu)].schedule_data.curr
  351. 290 tsk = cpu_curr(this_cpu);
  352. 291 if (preemption_goodness(tsk, p, this_cpu) > 1)//将当前运行进程和唤醒进程比较
  353. 292 tsk->need_resched = 1;//唤醒的进程权值高,就设置调度标志
  354. #endif
  355. }

  356. static inline int preemption_goodness(struct task_struct * prev, struct task_struct * p, int cpu)
  357. {
  358.     return goodness(p, cpu, prev->active_mm)-goodness(prev, cpu, prev->active_mm);
  359. }

  360. //第三种情况:自愿让出
  361. asmlinkage long sys_sched_setscheduler(pid_t pid, int policy,struct sched_param *param)
  362. {
  363.     return setscheduler(pid, policy, param);
  364. }

  365. asmlinkage long sys_sched_setparam(pid_t pid, struct sched_param *param)
  366. {
  367.     return setscheduler(pid, -1,param);
  368. }
  369. //改变进程的调度政策
  370. static int setscheduler(pid_t pid, int policy,struct sched_param *param)
  371. {
  372. 890 struct sched_param lp;
  373. 891 struct task_struct *p;
  374. 892 int retval;
  375. 893
  376. 894 retval = -EINVAL;
  377. 895 if (!param || pid < 0)//检查参数和PID
  378. 896 goto out_nounlock;
  379. 897
  380. 898 retval = -EFAULT;
  381. 899 if (copy_from_user(&lp, param, sizeof(struct sched_param)))//从用户空间拷贝到内核
  382. 900 goto out_nounlock;
  383. 901
  384. 905 read_lock_irq(&tasklist_lock);
  385. 906 spin_lock(&runqueue_lock);
  386. 907 //根据PID找到进程的task_struct结构
  387. 908 p = find_process_by_pid(pid);
  388. 909
  389. 910 retval = -ESRCH;
  390. 911 if (!p)
  391. 912 goto out_unlock;
  392. 913
  393. 914 if (policy < 0)
  394. 915 policy = p->policy;
  395. 916 else {
  396. 917 retval = -EINVAL;
  397. 918 if (policy != SCHED_FIFO && policy != SCHED_RR &&policy != SCHED_OTHER)//调度政策必须为这三种
  398. 920 goto out_unlock;
  399. 921 }
  400. 922
  401. 927 retval = -EINVAL;
  402. 928 if (lp.sched_priority < 0 || lp.sched_priority > 99)
  403. 929 goto out_unlock;
  404. 930 if ((policy == SCHED_OTHER) != (lp.sched_priority == 0))
  405. 931 goto out_unlock;
  406. 932
  407. 933 retval = -EPERM;
  408. 934 if ((policy == SCHED_FIFO || policy == SCHED_RR) &&!capable(CAP_SYS_NICE))//是否有权限
  409. 936 goto out_unlock;
  410. 937 if ((current->euid != p->euid) && (current->euid != p->uid) &&!capable(CAP_SYS_NICE))
  411. 939 goto out_unlock;
  412. 940
  413. 941 retval = 0;
  414. 942 p->policy = policy;//设置新的调度政策
  415. 943 p->rt_priority = lp.sched_priority;
  416. 944 if (task_on_runqueue(p))//检查是否在可运行队列中
  417. 945 move_first_runqueue(p);//在队列中的话,移到队列前部
  418. 946
  419. 947 current->need_resched = 1;//重新设置调度标志
  420. 948
  421. out_unlock:
  422. 950 spin_unlock(&runqueue_lock);
  423. 951 read_unlock_irq(&tasklist_lock);
  424. 952
  425. out_nounlock:
  426. 954 return retval;
  427. }
  428. //为其它进程让路,但并不进入睡眠
  429. asmlinkage long sys_sched_yield(void)
  430. {
  431. 1028
  432. 1029 int nr_pending = nr_running;//正在等待运行的进程数量
  433. 1030
  434. #if CONFIG_SMP
  435. 1032 int i;
  436. 1035 for (i = 0; i < smp_num_cpus; i++)
  437. 1036 if (aligned_data[i].schedule_data.curr != idle_task(i))
  438. 1037 nr_pending--;
  439. #else
  440. 1040 nr_pending--;
  441. #endif
  442. 1042 if (nr_pending) {//若还有等待运行的进程
  443. 1047 if (current->policy == SCHED_OTHER)
  444. 1048 current->policy |= SCHED_YIELD;//调度政策设为“自动礼让”
  445. 1049 current->need_resched = 1;//重新设置调度标志
  446. 1050 }
  447. 1051 return 0;
  448. }

阅读(5164) | 评论(2) | 转发(3) |
0

上一篇:进程(二):进程创建

下一篇:软中断机制

给主人留下些什么吧!~~

wangbaolin7192013-08-01 08:40:42

_lele:这都是代码啊!!!!下次谈谈cfs与O(1)调度的特点与优缺点吧。。。注释不错!

研究不深,不敢妄谈。

回复 | 举报

_lele2013-07-31 17:29:22

这都是代码啊!!!!下次谈谈cfs与O(1)调度的特点与优缺点吧。。。注释不错!