免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2368 | 回复: 0

[FreeBSD] freebsd9.2-线程切换阶段01-入口-mini_switch函数 [复制链接]

论坛徽章:
0
发表于 2014-06-23 22:13 |显示全部楼层
本帖最后由 71v5 于 2014-06-25 21:27 编辑

[当一个线程需要睡眠或者被抢占,或者需要重新调度时,内核代码调用函数mi_switch完成线程切换与硬件上下文无关的的处理]:

mi_switch函数用到的相关标志:
  1.    662        /* Types and flags for mi_switch(). */
  2.    663        #define        SW_TYPE_MASK                0xff        /* First 8 bits are switch type */
  3.    664        #define        SWT_NONE                0        /* Unspecified switch. */
  4.         /* 665:因为抢占 8/
  5.    665        #define        SWT_PREEMPT                1        /* Switching due to preemption. */
  6.         /* 666:从临界区退出时,但是此时还有其他thread访问临界区时,就会以SWT_OWEPREEMPT标志调用mini_switch函数 */
  7.    666        #define        SWT_OWEPREEMPT                2        /* Switching due to opepreempt. */
  8.         /* 667:当thread要互斥的访问某个变量,而该变量此刻忙,就会以SWT_TURNSTILE标志调用mini_switch函数 */
  9.    667        #define        SWT_TURNSTILE                3        /* Turnstile contention. */
  10.         /**************************************************************************
  11.          * 668-669:如果thread睡眠,就会以SWT_SLEEPQ或者SWT_SLEEPQTIMO标志调用
  12.                    mini_switch函数。
  13.          *************/
  14.    668        #define        SWT_SLEEPQ                4        /* Sleepq wait. */
  15.    669        #define        SWT_SLEEPQTIMO                5        /* Sleepq timeout wait. */
  16.         /**************************************************************************
  17.          * 670: 当发出一个yield系统调用时,该系统调用的内部实现会以SWT_RELINQUISH
  18.                  标志调用mini_switch函数,即主动放弃cpu。
  19.          *************/
  20.    670        #define        SWT_RELINQUISH                6        /* yield call. */
  21.         /****************************************************************
  22.          * 671:时钟中断发生时,会递减当前正在运行thread的时间片,如果
  23.                为零,先重新计算其时间片,然后设置其TDF_NEEDRESCHED标志,
  24.                在中断返回时,会检查thread是否设置了TDF_NEEDRESCHED标志,
  25.                如果设置了,就会以SWT_NEEDRESCHED标志调用mini_switch函数。
  26.          *************/
  27.    671        #define        SWT_NEEDRESCHED                7        /* NEEDRESCHED was set. */
  28.         /****************************************************************
  29.          * 672:中断线程在等待一个中断的到来的时候会以标志SWT_IDLE调用
  30.                 mini_switch函数。
  31.          *****/
  32.    672        #define        SWT_IDLE                8        /* Switching from the idle thread. */
  33.         /*********************
  34.    673        #define        SWT_IWAIT                9        /* Waiting for interrupts. */
  35.         /*******************************************************************
  36.          * 674:当thread接收到一个默认操作是top的信号时,并且相应的信号
  37.                 处理程序没有被捕获时,此时thread就会以SWT_SUSPEND标志
  38.                 调用mini_switch函数。
  39.          ************/
  40.    674        #define        SWT_SUSPEND                10        /* Thread suspended. */
  41.         /*******************************************************************
  42.          * 675-676:
  43.            在smp系统中,cpu0向cpu1发出了一个抢占的IPI中断,如果此时cpu1上
  44.            运行的是idle线程,就以SWT_REMOTEWAKEIDLE标志调用mini_switch函数。
  45.            如果此时cpu1上运行的是普通线程,就以SWT_REMOTEPREEMPT标志调用
  46.            mini_switch函数。
  47.          ********/
  48.    675        #define        SWT_REMOTEPREEMPT        11        /* Remote processor preempted. */
  49.    676        #define        SWT_REMOTEWAKEIDLE        12        /* Remote processor preempted idle. */
  50.         /*******************************************************************
  51.          * 677:sched_switch_stats数组的大小。
  52.          *********/
  53.    677        #define        SWT_COUNT                13        /* Number of switch types. */
  54.    678        /*******************************************************************
  55.          * 679: 当发出一个yield系统调用时,该系统调用的内部实现会以SW_VOL
  56.                  标志调用mini_switch函数,即主动放弃cpu。
  57.          **************/
  58.    679        #define        SW_VOL                0x0100                /* Voluntary switch. 主动进行线程切换 */
  59.         /*******************************************************************
  60.          * 680-681:
  61.            如果一个thread被抢占,从sched_preempt函数中会以SW_INVOL和
  62.            SW_PREEMPT标志调用mi_switch函数时。
  63.          **************/
  64.    680        #define        SW_INVOL        0x0200                /* Involuntary switch. 被动进行线程切换 */
  65.    681        #define SW_PREEMPT        0x0400                /* The invol switch is a preemption 因为抢占? */
复制代码
  1.    
  2.    419       
  3.    420
  4. /**********************************************************
  5.   * The machine independent parts of context switching.
  6.            参数描述:
  7.            flags:传递给mi_switch函数的一些标志,如上所示。
  8.            newtd:将要被调度运行的新线程对应的struct thread对象。

  9.            volatile int        ticks; 系统启动以来经过的节拍数,
  10.                                由函数hardclock_cnt维护
  11. **************************/
  12.    421         
  13.    422        void
  14.    423        mi_switch(int flags, struct thread *newtd)
  15.    424        {     
  16. /*******************************************************************
  17.   * runtime:线程的运行时间,使用intel cpu中的TIME-STAMP COUNTER
  18.                             来描述。

  19.                    new_switchtime:执行线程切换时的时间戳,使用intel cpu中的
  20.                                    TIME-STAMP COUNTER来描述。

  21.                    td:将要被替换的thread对应的struct thread对象,这里为curthread。

  22.                    p:和td相关联的struct proc对象。

  23.                    为了描述方便,这里将td,p简单记为old_td,old_p。
  24.   *********************************/
  25.    425                uint64_t runtime, new_switchtime;
  26.    426                struct thread *td;
  27.    427                struct proc *p;
  28.    428       
  29.    429                td = curthread;                        /* XXX */
  30.    430                THREAD_LOCK_ASSERT(td, MA_OWNED | MA_NOTRECURSED);
  31.    431                p = td->td_proc;                /* XXX */
  32.    432                KASSERT(!TD_ON_RUNQ(td), ("mi_switch: called by old code"));
  33.    433        #ifdef INVARIANTS
  34.    434                if (!TD_ON_LOCK(td) && !TD_IS_RUNNING(td))
  35.    435                        mtx_assert(&Giant, MA_NOTOWNED);
  36.    436        #endif
  37.    437                KASSERT(td->td_critnest == 1 || panicstr,
  38.    438                    ("mi_switch: switch in a critical section"));
  39.    439                KASSERT((flags & (SW_INVOL | SW_VOL)) != 0,
  40.    440                    ("mi_switch: switch must be voluntary or involuntary"));
  41.    441                KASSERT(newtd != curthread, ("mi_switch: preempting back to ourself"));
  42.    442       
  43.    443                /*
  44.    444                 * Don't perform context switches from the debugger.
  45.                    446-447:debugger被激活?
  46.    445                 */
  47.    446                if (kdb_active)
  48.    447                        kdb_switch();
  49. /**************************************************************************
  50.   * td_stopsched:如果非零,就表示调度程序被暂停,此时将不会发生线程切换,
  51.                                  函数panic会将该成员设置为1.
  52.   * #define  SCHEDULER_STOPPED() __predict_false(curthread->td_stopsched)
  53.   ****************/
  54.    448                if (SCHEDULER_STOPPED())
  55.    449                        return;
  56. /****************************************************************
  57.   * 450-454:
  58.                    递增相应的计数器。

  59.                    #define  SW_VOL 0x0100 Voluntary switch. 主动进行线程切换
  60.                   
  61.                    td_swvoltick:当以标志SW_VOL调用函数mi_switch时,该成员被设置为
  62.                                  当时的ticks。

  63.                    long        ru_nvcsw;    voluntary context switches
  64.                    long        ru_nivcsw;   involuntary
  65.   **********************************/
  66.    450                if (flags & SW_VOL) {
  67.    451                        td->td_ru.ru_nvcsw++;
  68.    452                        td->td_swvoltick = ticks;
  69.    453                } else
  70.    454                        td->td_ru.ru_nivcsw++;
  71.    455        #ifdef SCHED_STATS
  72.    456                SCHED_STAT_INC(sched_switch_stats[flags & SW_TYPE_MASK]);
  73.    457        #endif
  74.    458               
  75.    459
  76.    460
  77. /*****************************************************************
  78.    * Compute the amount of time during which the current
  79.    * thread was running, and add that to its total so far.

  80.                    462:函数cpu_ticks读取intel cpu的TIME-STAMP COUNTER,用该
  81.                         计数器的值初始化new_switchtime。

  82.                    463:runtime记录了线程的运行时间,cpu_ticks描述。

  83.                    464-465:将runtime累积到td_runtime和td_incruntime上。

  84.                    466:更新struct pcpu对象的pc_switchtime成员。
  85. *****************************/
  86.    461                 
  87.    462                new_switchtime = cpu_ticks();
  88.    463                runtime = new_switchtime - PCPU_GET(switchtime);
  89.    464                td->td_runtime += runtime;
  90.    465                td->td_incruntime += runtime;
  91.    466                PCPU_SET(switchtime, new_switchtime);
  92.    467                td->td_generation++;        /* bump preempt-detect counter */
  93. /******************************************************
  94. * 468:递增struct vmmeter对象的v_swtch成员,该成员
  95.                         记录了系统中执行线程切换的次数。

  96.                    469:更新struct pcpu对象的pc_switchticks成员,以
  97.                         全局的ticks来描述。
  98. *************************/
  99.    468                PCPU_INC(cnt.v_swtch);
  100.    469                PCPU_SET(switchticks, ticks);
  101.    470                CTR4(KTR_PROC, "mi_switch: old thread %ld (td_sched %p, pid %ld, %s)",
  102.    471                    td->td_tid, td->td_sched, p->p_pid, td->td_name);
  103.    472        #if (KTR_COMPILE & KTR_SCHED) != 0
  104.    473                if (TD_IS_IDLETHREAD(td))
  105.    474                        KTR_STATE1(KTR_SCHED, "thread", sched_tdname(td), "idle",
  106.    475                            "prio:%d", td->td_priority);
  107.    476                else
  108.    477                        KTR_STATE3(KTR_SCHED, "thread", sched_tdname(td), KTDSTATE(td),
  109.    478                            "prio:%d", td->td_priority, "wmesg:\"%s\"", td->td_wmesg,
  110.    479                            "lockname:\"%s\"", td->td_lockname);
  111.    480        #endif
  112.    481                SDT_PROBE0(sched, , , preempt);
  113.    482        #ifdef XEN
  114.    483                PT_UPDATES_FLUSH();
  115.    484        #endif
  116. /* 485:调用调度程序相关的sched_switch函数 */
  117.    485                sched_switch(td, newtd, flags);
  118.    486                KTR_STATE1(KTR_SCHED, "thread", sched_tdname(td), "running",
  119.    487                    "prio:%d", td->td_priority);
  120.    488       
  121.    489                CTR4(KTR_PROC, "mi_switch: new thread %ld (td_sched %p, pid %ld, %s)",
  122.    490                    td->td_tid, td->td_sched, p->p_pid, td->td_name);
  123.    491       
  124.    492                /*
  125.    493                 * If the last thread was exiting, finish cleaning it up.
  126.    494                 */
  127.    495                if ((td = PCPU_GET(deadthread))) {
  128.    496                        PCPU_SET(deadthread, NULL);
  129.    497                        thread_stash(td);
  130.    498                }
  131.    499        }




  132. [ULE调度程序-sched_switch函数]:

  133. / ********************************************************************
  134.   * Switch threads.  This function has to handle threads coming in while
  135.   * blocked for some reason, running, or idle.  It also must deal with
  136.   * migrating a thread from one queue to another as running threads may
  137.   * be assigned elsewhere via binding.

  138.            函数sched_switch完成线程切换,在SMP系统中,还要将线程在系统中cpu
  139.            的运行队列来回迁移,以保持运行队列的平衡。
  140.    
  141.            参数描述:
  142.            td:将要被替换的thread。
  143.            newtd:将要被调度运行的thread。
  144.            flags:一些标志。        
  145. *********************************/
  146.   1829        void
  147.   1830        sched_switch(struct thread *td, struct thread *newtd, int flags)
  148.   1831        {
  149. /*********************************************************
  150. * 局部变量描述:
  151.                    tdq:指向当前cpu对应的struct tdq对象。
  152.                    ts:指向和td相关联的struct td_sched数据对象。
  153.                    mtx:指向防止并发访问的互斥数据结构。
  154.                    cpuid:当前cpu的logical id。
  155.                    preempted:如果preempted非零,就表示是由于被抢占而进行
  156.                               的线程切换;如果preempted为零,就表示是因为
  157.                               线程的时间片用完而进行的线程切换。
  158. *************************************/  
  159.   1832                struct tdq *tdq;
  160.   1833                struct td_sched *ts;
  161.   1834                struct mtx *mtx;
  162.   1835                int srqflag;
  163.   1836                int cpuid, preempted;
  164.   1837                
  165.   1838                THREAD_LOCK_ASSERT(td, MA_OWNED);
  166.   1839                KASSERT(newtd == NULL, ("sched_switch: Unsupported newtd argument"));
  167.   1840                
  168. /*************************************************************
  169. * 1841:获取当前cpu的logical id。
  170.                    1842:tdq指向数组元素tdq_cpu[cpuid]。
  171.                    1843:ts指向和td相关联的struct td_sched数据对象。
  172.                    1845:函数sched_pctcpu_update更新struct td_sched对象的
  173.                          相关成员。
  174.                    1846:将struct td_sched对象的ts_rlick成员设置为ticks。
  175. *************************************/
  176.   1841                cpuid = PCPU_GET(cpuid);
  177.   1842                tdq = TDQ_CPU(cpuid);
  178.   1843                ts = td->td_sched;
  179.   1844                mtx = td->td_lock;
  180.   1845                sched_pctcpu_update(ts, 1);
  181.   1846                ts->ts_rltick = ticks;
  182. /****************************************************************
  183. * 1847-1848:
  184.                    #define        NOCPU        0xff        For when we aren't on a CPU.

  185.                    u_char                td_lastcpu; Last cpu we were on.
  186.                    u_char                td_oncpu;   Which cpu we are on.

  187.                    td_oncpu:负责执行线程td的cpu的logical id,在线程切换时,
  188.                              td_lastcpu成员会被更新为td_oncpu的值,当线程
  189.                              td重新被调度运行时,td_oncpu会被更新。
  190. **************************************/
  191.   1847                td->td_lastcpu = td->td_oncpu;
  192.   1848                td->td_oncpu = NOCPU;
  193. /***********************************************************************
  194. * #define TDF_SCHED2        0x04000000 Reserved for scheduler private use
  195. * #define TDF_SLICEEND    TDF_SCHED2     Thread time slice is over.
  196.                    #define TD_IS_IDLETHREAD(td)        ((td)->td_flags & TDF_IDLETD)
  197.                    #define TDF_NEEDRESCHED        0x00010000  Thread needs to yield.

  198.                    1849:线程切换是被抢占还是自身时间片用完?
  199.                    1850:清TDF_NEEDRESCHED和TDF_SLICEEND标志。

  200.                    1851:暂时还不清楚其作用。
  201.                    volatile u_char td_owepreempt; (k*) Preempt on last critical_exit。

  202.                    1852-1853:如果td不是idle线程,就递增相应的计数器。
  203.                    short           tdq_switchcnt;           Switches this tick.
  204.   ********************************/
  205.   1849                preempted = !(td->td_flags & TDF_SLICEEND);
  206.   1850                td->td_flags &= ~(TDF_NEEDRESCHED | TDF_SLICEEND);
  207.   1851                td->td_owepreempt = 0;
  208.   1852                if (!TD_IS_IDLETHREAD(td))
  209.   1853                        tdq->tdq_switchcnt++;
  210. /**********************************************************************
  211.   * The lock pointer in an idle thread should never change.  Reset it
  212.                    * to CAN_RUN as well.
  213.                    1858-1883:如何处理td(将要被替换的线程thread)。
  214.                   1858-1861:td为idle线程->

  215.                    #define TD_SET_CAN_RUN(td) (td)->td_state = TDS_CAN_RUN
  216.                    此时宏TD_SET_CAN_RUN将td的状态设置为TDS_CAN_RUN。
  217. ********************************/
  218.   1858                if (TD_IS_IDLETHREAD(td)) {
  219.   1859                        MPASS(td->td_lock == TDQ_LOCKPTR(tdq));
  220.   1860                        TD_SET_CAN_RUN(td);
  221.   1861                } else if (TD_IS_RUNNING(td)) {
  222. /***********************************************************************************
  223. * 1861-1878:
  224.                    线程td此时处于运行状态,或者被抢占,或者自身的时间片用完,进而执行
  225.                    了线程切换。
  226.                
  227.                    1863-1865:确定srqflag,该变量包含了一些标志,这些标志标识了进行
  228.                               线程切换的reason。

  229.                    1866-1869:
  230.                    SMP支持,判断是否将td迁移到系统中另一个cpu的运行队列上。函数sched_pickcpu的任务
  231.                    如下:
  232.                    选择一个负载最小,并且该cpu在线程td的CPU affinity sets(td_cpuset)集合中,并返回
  233.                    该cpu 的logical id。
  234.             
  235.                    #define        THREAD_CAN_MIGRATE(td)        ((td)->td_pinned == 0)
  236.                    #define THREAD_CAN_SCHED(td, cpu)  CPU_ISSET((cpu), &(td)->td_cpuset->cs_mask)
  237.   ****************************************/  
  238.   1862                        MPASS(td->td_lock == TDQ_LOCKPTR(tdq));
  239.   1863                        srqflag = preempted ?
  240.   1864                            SRQ_OURSELF|SRQ_YIELDING|SRQ_PREEMPTED :
  241.   1865                            SRQ_OURSELF|SRQ_YIELDING;
  242.   1866        #ifdef SMP
  243.   1867                        if (THREAD_CAN_MIGRATE(td) && !THREAD_CAN_SCHED(td, ts->ts_cpu))
  244.   1868                                ts->ts_cpu = sched_pickcpu(td, 0);
  245.   1869        #endif
  246. /************************************************************
  247.   * 1870-1872:将在同一个cpu上运行?
  248.                            根据srqflag参数中的标志,将td重新连接到cpu的运行队列中。

  249.                            1872-1877:不在同一个cpu上运行。函数sched_switch_migrate
  250.                                       将td迁移到相应cpu的运行队列中。
  251. ******************************/
  252.   1870                        if (ts->ts_cpu == cpuid)
  253.   1871                                tdq_runq_add(tdq, td, srqflag);
  254.   1872                        else {
  255.   1873                                KASSERT(THREAD_CAN_MIGRATE(td) ||
  256.   1874                                    (ts->ts_flags & TSF_BOUND) != 0,
  257.   1875                                    ("Thread %p shouldn't migrate", td));
  258.   1876                                mtx = sched_switch_migrate(tdq, td, srqflag);
  259.   1877                        }
  260.   1878                } else {
  261.   1879                       
  262. /********************************************************
  263.   * This thread must be going to sleep.
  264.                            1880-1882:线程将要睡眠。函数tdq_load_rem更新相应cpu
  265.                                       的负载等。

  266. *********************/
  267.   1880                        TDQ_LOCK(tdq);
  268.   1881                        mtx = thread_lock_block(td);
  269.   1882                        tdq_load_rem(tdq, td);
  270.   1883                }
  271. /**********************************************************************
  272.   * We enter here with the thread blocked and assigned to the
  273.   * appropriate cpu run-queue or sleep-queue and with the current
  274.   * thread-queue locked.
  275.                    1890:函数choosethread从当前cpu的运行队列中选择一个优先级最高的
  276.                          thread带来运行,并返回该thread对应的struct thread对象。
  277. **********************************/
  278.   1889                TDQ_LOCK_ASSERT(tdq, MA_OWNED | MA_NOTRECURSED);
  279.   1890                newtd = choosethread();
  280. /********************************************************
  281. * Call the MD code to switch contexts if necessary.
  282.                    1894-1930:只有当td和newtd不是同一个thread时,才调用
  283.                               cpu_switch函数完成线程硬件上下文的切换。
  284. **********************************/
  285.   1894                if (td != newtd) {
  286.   1895        #ifdef        HWPMC_HOOKS
  287.   1896                        if (PMC_PROC_IS_USING_PMCS(td->td_proc))
  288.   1897                                PMC_SWITCH_CONTEXT(td, PMC_FN_CSW_OUT);
  289.   1898        #endif
  290.   1899                        SDT_PROBE2(sched, , , off_cpu, newtd, newtd->td_proc);
  291.   1900                        lock_profile_release_lock(&TDQ_LOCKPTR(tdq)->lock_object);
  292.   1901                        TDQ_LOCKPTR(tdq)->mtx_lock = (uintptr_t)newtd;
  293.   1902                        sched_pctcpu_update(newtd->td_sched, 0);
  294.   1903       
  295.   1904        #ifdef KDTRACE_HOOKS
  296.   1905                        /*
  297.   1906                         * If DTrace has set the active vtime enum to anything
  298.   1907                         * other than INACTIVE (0), then it should have set the
  299.   1908                         * function to call.
  300.   1909                         */
  301.   1910                        if (dtrace_vtime_active)
  302.   1911                                (*dtrace_vtime_switch_func)(newtd);
  303.   1912        #endif
  304.   1913                        /* 1914:cpu_switch完成线程硬件上下文的切换 */
  305.   1914                        cpu_switch(td, newtd, mtx);
  306. /****************************************************************
  307.   * We may return from cpu_switch on a different cpu.  However,
  308.   * we always return with td_lock pointing to the current cpu's
  309.   * run queue lock.

  310.                            当线程(刚创建的线程,内核线程等除外)被重新调度运行时,会从
  311.                            这里开始执行。

  312.                            1920:cpuid为当前cpu的logical id。
  313.                            1921:tdq为logical id为cpuid的cpu对应的运行队列。
  314. *************************************/
  315.   1920                        cpuid = PCPU_GET(cpuid);
  316.   1921                        tdq = TDQ_CPU(cpuid);
  317.   1922                        lock_profile_obtain_lock_success(
  318.   1923                            &TDQ_LOCKPTR(tdq)->lock_object, 0, 0, __FILE__, __LINE__);
  319.   1924       
  320.   1925                        SDT_PROBE0(sched, , , on_cpu);
  321.   1926        #ifdef        HWPMC_HOOKS
  322.   1927                        if (PMC_PROC_IS_USING_PMCS(td->td_proc))
  323.   1928                                PMC_SWITCH_CONTEXT(td, PMC_FN_CSW_IN);
  324.   1929        #endif
  325.   1930                } else {
  326.   1931                        thread_unblock_switch(td, mtx);
  327.   1932                        SDT_PROBE0(sched, , , remain_cpu);
  328.   1933                }
  329. /**************************************************
  330. * Assert that all went well and return.
  331.                    1939:更新struct thread对象的td_oncpu成员。
  332. **************************/
  333.   1937                TDQ_LOCK_ASSERT(tdq, MA_OWNED|MA_NOTRECURSED);
  334.   1938                MPASS(td->td_lock == TDQ_LOCKPTR(tdq));
  335.   1939                td->td_oncpu = cpuid;
  336.   1940        }
复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP