免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 3556 | 回复: 0
打印 上一主题 下一主题

[FreeBSD] freebsd9.2-ULE线程调度-将线程添加到cpu运行队列中-sched_add函数 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2014-07-01 01:37 |只看该作者 |倒序浏览
这里分析一下最普遍的情况,就是fork系统调用创建的thread,新创建的thread最终都要添加到系统中某个cpu的运行队列中,这样才能
得到调度运行,在do_fork函数的最后阶段,会调用调度程序相关的sched_add函数将新创建的thread添加到系统中某个cpu的运行队列中。

注:
发现之前的帖子中有些用语不是很严谨,比如tdq_cpu[cpu]应该是per processor runqs and statistics,而在此之前称之为运行队列;
实际上struct tdq对象中的tdq_realtime,tdq_timeshare,tdq_idle成员才被称为运行队列。
现在为了和前面保持一致,将tdq_realtime,tdq_timeshare,tdq_idle成员标识的运行队列称为tdq_realtime队列,tdq_timeshare队列,
tdq_idle队列,不过这样并不影响理解ULE调度程序的实现原理。


[ULE线程调度-sched_add函数]:
  1. /**************************************************************************************
  2. * Select the target thread queue and add a thread to it.  Request
  3. * preemption or IPI a remote processor if required.

  4.    参数描述:
  5.    td:标识将要被添加到cpu运行队列中的线程。

  6.    flags:传递给函数sched_add的标志,相关标志需要结合上下文分析,这里暂且略过。
  7. *******************************/
  8.   2342       
  9.   2343        void
  10.   2344        sched_add(struct thread *td, int flags)
  11.   2345        {
  12.   2346                struct tdq *tdq;
  13. /***************************************************************************
  14. * 这里将局部变量更改为smp_cpu,以和下面的分析语句中的cpu区别
  15. ***********************************/
  16.   2347        #ifdef SMP
  17.   2348                int smp_cpu;
  18.   2349        #endif
  19.   2350       
  20.   2351                KTR_STATE2(KTR_SCHED, "thread", sched_tdname(td), "runq add",
  21.   2352                    "prio:%d", td->td_priority, KTR_ATTR_LINKED,
  22.   2353                    sched_tdname(curthread));
  23.   2354                KTR_POINT1(KTR_SCHED, "thread", sched_tdname(curthread), "wokeup",
  24.   2355                    KTR_ATTR_LINKED, sched_tdname(td));
  25.   2356                SDT_PROBE4(sched, , , enqueue, td, td->td_proc, NULL,
  26.   2357                    flags & SRQ_PREEMPTED);
  27.   2358                THREAD_LOCK_ASSERT(td, MA_OWNED);
  28. /*******************************************************************************
  29. * Recalculate the priority before we select the target cpu or
  30. * run-queue.
  31.    2363-2364:
  32.    struct thread对象的td_pri_class成员,即newtd的调度类:
  33.    u_char                td_pri_class;        (t) Scheduling class.  

  34.    #define PRI_TIMESHARE           3       Time sharing process.
  35.    如果new_thread的调度类为PRI_TIMESHARE,此时就要调用函数sched_priority
  36.    重新计算newtd的优先级。

  37.    一般情况下,系统中所有thread的调度类都从thread0(0号线程)继承而来,在系统
  38.    初始化时,将thread0的调度类设置为PRI_TIMESHARE。除非某些应用程序执行一个
  39.    rtprio系统调用显式修改线程的调度类,否则线程的调度类通常都为PRI_TIMESHARE。
  40.    但是这个修改也被限制为下面三种调度类:
  41.    #define PRI_REALTIME            2       /* Real time process. */
  42.    #define PRI_TIMESHARE           3       /* Time sharing process. */
  43.    #define PRI_IDLE                4       /* Idle process. */
  44. ***********************************/
  45.   2363                if (PRI_BASE(td->td_pri_class) == PRI_TIMESHARE)
  46.   2364                        sched_priority(td);
  47. /***************************************************************************************
  48. * 2365-2377:SMP系统
  49. *
  50. * Pick the destination cpu and if it isn't ours transfer to the
  51. * target cpu.

  52.    2370:函数sched_pickcpu为线程td挑选一个合适的cpu,局部变量smp_cpu保存了该
  53.          cpu的logical cpu id。

  54.    2371:函数sched_setcpu主要完成下面的工作:
  55.          将和线程td相关联的struct td_sched数据对象的ts_cpu成员设置为smp_cpu。
  56.          并返回logical cpu id为smp_cpu的运行队列,这里假设为&tdq_cpu[smp_cpu]。
  57.                   
  58.    2372:函数tdq_add将线程td添加到内嵌在tdq指向的struct tdq数据对象
  59.          中tdq_realtime队列,tdq_timeshare队列,tdq_idle队列中的一个,
  60.          见下面的简要分析。
  61. ***********************************/
  62.   2365        #ifdef SMP
  63.   2366
  64.   2367
  65.   2368
  66.   2369
  67.   2370                smp_cpu = sched_pickcpu(td, flags);
  68.   2371                tdq = sched_setcpu(td, smp_cpu, flags);
  69.   2372                tdq_add(tdq, td, flags);
  70. /*******************************************************************
  71. * 2373-2376:
  72.    如果smp_cpu和当前cpu的logical cpu id不相等,那么就调用函数
  73.    tdq_notify检查线程td是否能够抢占当前正在cpu logical id为smp_cpu
  74.    上运行的线程,并根据需要发送一个处理器间中断。
  75.   函数tdq_notify见下面的简要分析。
  76. *******************************************/
  77.   2373                if (smp_cpu != PCPU_GET(cpuid)) {
  78.   2374                        tdq_notify(tdq, td);
  79.   2375                        return;
  80.   2376                }
  81.   2377        #else
  82. /*****************************************************************
  83. * 2378-2385:单处理器系统,SMP系统的特殊情况,相关函数已经分析。
  84. ******************/
  85.   2378                tdq = TDQ_SELF();
  86.   2379                TDQ_LOCK(tdq);
  87.   2380                /*
  88.   2381                 * Now that the thread is moving to the run-queue, set the lock
  89.   2382                 * to the scheduler's lock.
  90.   2383                 */
  91.   2384                thread_lock_set(td, TDQ_LOCKPTR(tdq));
  92.   2385                tdq_add(tdq, td, flags);
  93.   2386        #endif
  94. /**************************************************************************************
  95. * 执行到这里的话,或者系统不是SMP,或者smp_cpu和当前cpu的logical id相等。

  96.    #define SRQ_YIELDING 0x0001 We are yielding (from mi_switch).

  97.    如果没有设置SRQ_YIELDING标志,那么td可能就要抢占当前正在运行的thread,此时不需要
  98.    发送处理器间中断,因为在同一个cpu上,

  99.    此时:
  100.    函数sched_setpreempt在条件满足的情况下,简单的设置正在当前cpu上运行thread的
  101.    TDF_NEEDRESCHED标志,这样当从中断或者系统调用返回时,就会调用mini_switch函数。
  102. *****************************************/
  103.   2387                if (!(flags & SRQ_YIELDING))
  104.   2388                        sched_setpreempt(td);
  105.   2389        }
复制代码
[函数tdq_add]:
  1. /*******************************************************************************************
  2. * Add a thread to a thread queue.  Select the appropriate runq and add the
  3. * thread to it.  This is the internal function called when the tdq is
  4. * predetermined.
  5. * 参数描述:
  6.    tdq:指向相应的cpu运行队列。
  7.    td:将要被添加到运行队列中的thread。
  8.    flags:一些标志。

  9.    函数tdq_add实际上调用函数tdq_runq_add将线程td添加到tdq_realtime队列,tdq_timeshare队列,
  10.    tdq_idle队列三个中的一个。
  11. ******************************/
  12.   2321        void
  13.   2322        tdq_add(struct tdq *tdq, struct thread *td, int flags)
  14.   2323        {
  15.   2324       
  16.   2325                TDQ_LOCK_ASSERT(tdq, MA_OWNED);
  17.   2326                KASSERT((td->td_inhibitors == 0),
  18.   2327                    ("sched_add: trying to run inhibited thread"));
  19.   2328                KASSERT((TD_CAN_RUN(td) || TD_IS_RUNNING(td)),
  20.   2329                    ("sched_add: bad thread state"));
  21.   2330                KASSERT(td->td_flags & TDF_INMEM,
  22.   2331                    ("sched_add: thread swapped out"));
  23.   2332       
  24. /***************************************************************************************
  25. * 2333-2334:
  26.    更新struct tdq对象中的tdq_lowpri成员。从这里可以看出来,tdq_lowpri成员保存的是相应
  27.    CPU运行队列中线程的最高优先级(数值小)。

  28.    2335:调用函数tdq_runq_add,见下面的简要分析。

  29.    2336:函数tdq_load_add简单的递增cpu运行队列tdq的tdq_load成员。
  30.          即struct tdq对象的tdq_load成员:
  31.          tdq_load:对应cpu的负载,sched_highest函数和函数sched_lowest使用该成员。
  32. **********************************************/
  33.   2333                if (td->td_priority < tdq->tdq_lowpri)
  34.   2334                        tdq->tdq_lowpri = td->td_priority;
  35.   2335                tdq_runq_add(tdq, td, flags);
  36.   2336                tdq_load_add(tdq, td);
  37.   2337        }
复制代码
[函数tdq_runq_add]:
  1. /*******************************************************************************
  2. * Add a thread to the actual run-queue.  Keeps transferable counts up to
  3. * date with what is actually on the run-queue.  Selects the correct
  4. * queue position for timeshare threads.
  5. * 参数的含义和上面tdq_add函数的参数相同。

  6. ******************************/
  7.    446        static __inline void
  8.    447        tdq_runq_add(struct tdq *tdq, struct thread *td, int flags)
  9.    448        {
  10.    449                struct td_sched *ts;
  11.    450                u_char pri;
  12.    451       
  13.    452                TDQ_LOCK_ASSERT(tdq, MA_OWNED);
  14.    453                THREAD_LOCK_ASSERT(td, MA_OWNED);
  15.    454       
  16. /*******************************************************************************
  17. * 455:pri为线程td的优先级。

  18.    456:td指向和线程td相关联的struct td_sched数据对象。

  19.    457:
  20.    #define        TD_SET_RUNQ(td)                (td)->td_state = TDS_RUNQ
  21.    此时线程td将要被添加到cpu运行队列中,其状态设置为TDS_RUNQ。

  22.    458-461:

  23.    #define        THREAD_CAN_MIGRATE(td)        ((td)->td_pinned == 0)
  24.    struct thread对象的td_pinned成员,函数sched_pin会递增该成员,
  25.    如果该成员非零,就表示线程addtd不能被迁移到其它cpu的运行队列中:        
  26.    int                td_pinned;Temporary cpu pin count.

  27.    struct tdq对象的tdq_transferable成员:
  28.    tdq_transferable: 一个计数器,cpu的相应运行队列中可迁移线程的数目。

  29.    在td_pinned成员为0时,此时就表示td是可迁移线程,此时递增tdq_transferable,
  30.    同时设置一个标志,标识线程td是可以迁移的。
  31.    #define TSF_XFERABLE  0x0002 Thread was added as transferable
  32. ****************************************************/
  33.    455                pri = td->td_priority;
  34.    456                ts = td->td_sched;
  35.    457                TD_SET_RUNQ(td);
  36.    458                if (THREAD_CAN_MIGRATE(td)) {
  37.    459                        tdq->tdq_transferable++;
  38.    460                        ts->ts_flags |= TSF_XFERABLE;
  39.    461                }
  40. /*******************************************************************************
  41. * #define PRI_MIN_BATCH           152
  42.    #define PRI_MAX_BATCH           223

  43.    struct td_sched对象的ts_runq成员,即相关联的thread将被添加到ts_runq
  44.    指向的队列中:
  45.    struct runq     *ts_runq;     Run-queue we're queued on.

  46.    462-464:
  47.    如果线程td的优先级pri小于152,就将线程td添加到tdq_realtime队列中。

  48.    464-486:
  49.    如果线程td的优先级小于或者等于223,就将线程td添加到tdq_timeshare队列中。
  50.    这之间的代码还要根据条件计算一个pri等,这个不是很清楚,大家可以帮忙看看。

  51.    487-488:
  52.    如果线程td的优先级pri大于223,就将线程td添加到tdq_idle队列中。

  53.    函数runq_add_pri和函数runq_add不属于ULE线程调度实现源代码定义的,这两个
  54.    函数是简单的链表操作,大家感兴趣的话,可以参考一下:
  55.    "$FreeBSD: release/9.2.0/sys/kern/kern_switch.c"这个c源文件。   
  56. ****************************/
  57.    462                if (pri < PRI_MIN_BATCH) {
  58.    463                        ts->ts_runq = &tdq->tdq_realtime;
  59.    464                } else if (pri <= PRI_MAX_BATCH) {
  60.    465                        ts->ts_runq = &tdq->tdq_timeshare;
  61.    466                        KASSERT(pri <= PRI_MAX_BATCH && pri >= PRI_MIN_BATCH,
  62.    467                                ("Invalid priority %d on timeshare runq", pri));
  63.    468                        /*
  64.    469                         * This queue contains only priorities between MIN and MAX
  65.    470                         * realtime.  Use the whole queue to represent these values.
  66.    471                         */
  67.    472                        if ((flags & (SRQ_BORROWING|SRQ_PREEMPTED)) == 0) {
  68.    473                                pri = RQ_NQS * (pri - PRI_MIN_BATCH) / PRI_BATCH_RANGE;
  69.    474                                pri = (pri + tdq->tdq_idx) % RQ_NQS;
  70.    475                                /*
  71.    476                                 * This effectively shortens the queue by one so we
  72.    477                                 * can have a one slot difference between idx and
  73.    478                                 * ridx while we wait for threads to drain.
  74.    479                                 */
  75.    480                                if (tdq->tdq_ridx != tdq->tdq_idx &&
  76.    481                                    pri == tdq->tdq_ridx)
  77.    482                                        pri = (unsigned char)(pri - 1) % RQ_NQS;
  78.    483                        } else
  79.    484                                pri = tdq->tdq_ridx;
  80.    485                        runq_add_pri(ts->ts_runq, td, pri, flags);
  81.    486                        return;
  82.    487                } else
  83.    488                        ts->ts_runq = &tdq->tdq_idle;
  84.    489                runq_add(ts->ts_runq, td, flags);
  85.    490        }
复制代码
[函数tdq_notify]:
  1. /*******************************************************************************
  2. * Notify a remote cpu of new work.  Sends an IPI if criteria are met.

  3.    和上下文联系起来分析函数tdq_notify会更清晰。

  4.    对于logical cpu id为x的cpu,此时参数描述及相应成员的取值如下:

  5.    参数描述:
  6.    tdq:为&tdq_cpu[x]。
  7.    td:已经被添加到运行队列tdq_cpu[x]的tdq_realtime队列,tdq_timeshare队列,
  8.        tdq_idle队列三个队列之一中的线程。

  9.    成员取值描述:
  10.    
  11.    td->td_sched->ts_cpu的值为x。
  12. *****************************************/           
  13.   1010         */
  14.   1011        static void
  15.   1012        tdq_notify(struct tdq *tdq, struct thread *td)
  16.   1013        {
  17.   1014                struct thread *ctd;
  18.   1015                int pri;
  19.   1016                int cpu;
  20.   1017                
  21. /************************************************************
  22. * 1018-1019:
  23.    如果struct tdq对象的tdq_ipipending成员非零,就表示已经
  24.    有一个挂起的处理器间中断,此时直接返回。
  25. *****************************/
  26.   1018                if (tdq->tdq_ipipending)
  27.   1019                        return;
  28. /**********************************************************************
  29. * 1020: 根据假设,这里cpu的值为x。
  30.    1021:pri设置为线程td的优先级。
  31.    1022:ctd标识当前正在cpu x上运行的线程。
  32.                
  33.    1023-1024:
  34.    sched_shouldpreempt函数根据线程td的优先级和线程ctd的优先级
  35.    检查线程td是否能够抢占线程ctd,返回值1表示可以抢占;
  36.    返回值0表示不能抢占,见下面的简要分析。
  37. *************************************/
  38.   1020                cpu = td->td_sched->ts_cpu;
  39.   1021                pri = td->td_priority;
  40.   1022                ctd = pcpu_find(cpu)->pc_curthread;
  41.   1023                if (!sched_shouldpreempt(pri, ctd->td_priority, 1))
  42.   1024                        return;
  43. /***********************************************************************
  44. * #define TD_IS_IDLETHREAD(td)        ((td)->td_flags & TDF_IDLETD)
  45.    #define        TDF_IDLETD  0x00000020 This is a per-CPU idle thread.

  46.    1025-1032: 如果ctd为per-CPU idle thread就进行相关的处理。
  47.               这里暂时忽略。
  48. **********************************/
  49.   1025                if (TD_IS_IDLETHREAD(ctd)) {
  50.   1026                        /*
  51.   1027                         * If the MD code has an idle wakeup routine try that before
  52.   1028                         * falling back to IPI.
  53.   1029                         */
  54.   1030                        if (!tdq->tdq_cpu_idle || cpu_idle_wakeup(cpu))
  55.   1031                                return;
  56.   1032                }
  57. /***************************************************************
  58. * 如果执行到这里,就表示线程td将抢占线程ctd。
  59. * 1033:将struct tdq对象的tdq_ipipending成员设置为1。
  60.    1034:发送类型为IPI_PREEMPT的处理器间中断。

  61.    又碰到处理器间中断了,在SMP系统中,处理器间中断非常
  62.    重要,打算下次跟大家分享一下在intel平台下,处理器
  63.    间中断的发送及处理过程。
  64. ********************************/
  65.   1033                tdq->tdq_ipipending = 1;
  66.   1034                ipi_cpu(cpu, IPI_PREEMPT);
  67.   1035        }
复制代码
[函数sched_shouldpreempt]:
  1. /***************************************************************************
  2. * sched_shouldpreempt根据优先级判断是否能够抢占,返回值1表示可以抢占;返回
  3.    值0表示不能抢占。

  4.    参数描述:
  5.    pri:将要抢占的thread的优先级,这里假设为td1。
  6.    cpri:将要被抢占的thread的优先级,这里假设为td2。
  7.    remote:标识是否是同一个cpu。
  8. *************************************/           
  9.    408        static inline int
  10.    409        sched_shouldpreempt(int pri, int cpri, int remote)
  11.    410        {
  12. /***************************************************************************
  13. * If the new priority is not better than the current priority there is
  14. * nothing to do.
  15.    415-416:
  16.    如果415行的if条件为真,就表示td1的优先级比td2的优先级低,此时不需要
  17.    抢占,返回值为0.数值越大,优先级越低。
  18. ***********************************/
  19.    414                 
  20.    415                if (pri >= cpri)
  21.    416                        return (0);
  22. /************************************************************************
  23. * Always preempt idle.

  24.    420-421:
  25.    #define PRI_MIN_IDLE            (224)
  26.    如果420行的if条件为真,就表示td2为idle线程,这种情况下总是抢占td2.
  27.    返回值为1。
  28. *****************************/
  29.    419                 
  30.    420                if (cpri >= PRI_MIN_IDLE)
  31.    421                        return (1);
  32.    422               
  33.    423
  34. /***************************************************************
  35. * If preemption is disabled don't preempt others.

  36.    425-431:
  37.    #define PRI_MAX_IDLE            (PRI_MAX) 255
  38.    #define PRI_MIN_KERN            (80)

  39.    开启或禁用kernel thread preemption。

  40.    [1]:对应430-431行。
  41.    只有当编译了内核选项PREEMPTION时,preempt_thresh的值
  42.    才非零,此时kernel thread preemption才有效,分两种情况:

  43.    编译了FULL_PREEMPTION选项时:
  44.    preempt_thresh被设置为PRI_MAX_IDLE,表示始终进行抢占。
  45.            
  46.    没有编译FULL_PREEMPTION选项时:
  47.    preempt_thresh被设置为PRI_MIN_KERN,只有当线程优先级
  48.    小于或者等于PRI_MIN_KERN时,才进行抢占。此时,
  49.    preempt_thresh相当于一个抢占阈值,只有当pri低于这个阈值时,
  50.    才进行抢占。
  51.                   
  52.    [2]:对应425-426行。
  53.    当没有编译内核选项PREEMPTION时,preempt_thresh的值被
  54.    设置为0,此时禁止kernel thread preemption。
  55. ***************************************/
  56.    425                if (preempt_thresh == 0)
  57.    426                        return (0);
  58.    427                /*
  59.    428                 * Preempt if we exceed the threshold.
  60.    429                 */
  61.    430                if (pri <= preempt_thresh)
  62.    431                        return (1);
  63. /****************************************************************
  64. * If we're interactive or better and there is non-interactive
  65. * or worse running preempt only remote processors.
  66.    436-437:
  67.    #define PRI_MAX_INTERACT        151
  68.    436行if语句条件全为真就表示线程td1的交互性程度比线程td2的
  69.    交互性程度高,此时需要抢占td2.
  70. ***************************************/
  71.    436                if (remote && pri <= PRI_MAX_INTERACT && cpri > PRI_MAX_INTERACT)
  72.    437                        return (1);
  73.                 /* 如果执行到这里,就表示不需要抢占,函数返回值为0 */
  74.    438                return (0);
  75.    439        }
复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP