Dispatch and scheduling

Wikipedia,自由的百科全书

目录

调度器概述

内核的调度器是负责在调度队列中放置可运行线程、选择处理机运行的下一个线程以及管理线程前往或离开处理机的切换的代码。线程的优先级决定它将运行多久,内核实现全局优先级安排,这样最高优先级线程可以在任何时候被选中。每个线程属于几个优先级类型中的一种,这决定了线程优先级的范围以及线程状态转移使用的类型相关调度算法。

调度器子系统可以分为核心调度器函数和调度类型相关函数。两者是紧密集成在一起的。调度器支持多种调度类型。不同调度类型决定线程优先级范围,并且在线程相关算法应用期间变化。

调度器进行线程管理、队列管理和调度任务要用到许多数据结构和它们的变量。这些任务非常复杂,尤其在有成百上千活跃线程工作量的多处理机系统上。处理机和绑定的资源管理设施、动态重新配置能力和处理机状态转换,都促使调度器必须具有高度工程技巧,以便在提供优秀的性能和可伸缩性的同时保持正确性。

调度队列(运行队列)是多个含有可运行内核线程的链表。这些线程等待调度器的选择以便在一个处理机上执行。Solaris实现了处理机独有调度队列,就是说,每个运行Solaris系统的处理机在初始化时会拥有它独有的调度队列集合(per-processor dispatch queue)。这些队列被组织成以队列为元素的数组,每个队列为每一个全局优先级维护着一个线程的链表。处理机独有队列组织(per-processor queue arrangement)提供了改进的可伸缩性,排除了高度竞争互斥锁的可能,而这种互斥锁需要全局的、跨系统的队列。另外,处理机独有队列简便地管理线程到处理机的绑定,因为要启动的线程只需放到相应处理机的调度队列中。对多核处理机来说,每个处理机都有一个处理机独有队列。

每个处理机独有队列包含除了实时线程(realtime thread)之外的所有调度类型的线程。实时调度类为特殊需求的应用提供了一些独有的特性。为优化对实时性的支持,实时线程放在一个特殊的调度队列集合中,这些队列被称为内核抢占队列(kernel preemption queues)或kp队列(kp queues)。一旦一个线程进入kp队列,就会发生内核抢占,强制处理机进入调度程序。

接口功能描述

调度器初始化(dispatcher Initialization)函数

调度器初始化在启动时开始,这时核心操作系统启动代码调用dispinit()。dispinit()执行的基础任务有设置默认CPU分区(cpupart_initialize_default()),和为所有预装载调度类型调用调度类型相关初始化函数。调用disp_setup()来建立实际调度队列以及初始化队列变量。

函数	                描述
disp_setup()	  分配调度器数据结构和变量
dispinit()	  初始化已装载的调度类型和调度器框架
disp_add()	  初始化新装载的调度类型
cpu_dispalloc()   分配处理机独有调度队列
disp_dq_alloc()   支持函数cpu_dispalloc(),为队列分配内核内存并设置指针
disp_dq_assign()  为调度队列关联已分配的内存
disp_dq_free()	  释放调度队列资源(内核内存)
disp_cpu_init()   为处理机初始化调度队列
disp_kp_alloc()   分配内核抢占(kp)队列
disp_kp_free()	  释放先前分配的kp队列

调度类型(Scheduling Class)函数

下面列出内核调度类型相关函数极其描述。函数名称中的“cl” 代表 ts, ia, rt, fx 或 fss中的一个。

下面9个函数属于类型管理范畴,通常支持priocntl(2)系统调用。

cl_admin — 找回或替换该类调度表中的变量。

cl_getclinfo — 获取调度类型的信息。目前,只返回最高用户优先级的值(xx_maxupri)。

cl_parmsin —确认用户提供的优先级值来保证它们在有效范围中。还检查调用权限来保证请求的操作是允许的。对RT类型来说,没有用户优先级的概念,所以对最大RT优先级检查范围。该函数支持priocntl(2)的PC_SETPARMS命令。

cl_parmsout — 支持priocntl(2)中的PC_GETPARMS 命令。找回类型相关调度参数。

cl_vaparmsin, cl_vaparmsout — 函数parmsin/parmsout 的变体,在变量参数列表中得到一个添加的参数。

cl_getclpri — 得到类型优先级范围。返回每个优先级的最小和最大全局优先级。

cl_alloc, cl_free — 分配或释放一个类型相关结构(xxproc_t)。

下面的函数与线程的支持和管理有关。

cl_enterclass —为线程分配所需资源来进入一个调度类型— xxproc_t 结构。初始化域和链接。

cl_exitclass — 从链表中删除释放类型相关数据结构(xxproc_t) 。

cl_fork —执行支持 fork 的代码。分配一个类型相关数据结构(tsproc 或rtproc) ,用父线程数据始化,并且添加进程到链表中。在作为fork(2)系统调用的一部分的内核函数lwpcreate()和 lwpfork()中被调用。

cl_forkret — 在支持fork(2)系统调用的cfork() 中被调用。它是fork(2)返回到父线程和子线程之前最后要做的事。 子线程放在调度队列最后,父线程放弃处理机。

cl_parmsget — 获取线程的当前用户的优先级和最大用户优先级。

cl_parmsset — 在穿过来的参数基础上设置线程的优先级。每个调度类型定义了一个参数数据结构(xxparms)。

cl_stop — 为线程转移到停止状态作准备。

cl_exit —控制一个离开的线程。

cl_active, cl_inactive — 这两个函数只被FSS 调度类型实现。

cl_swapin — 计算线程的有效优先级来在交换进来时决定它关联的LWP的有效性。

cl_swapout — 计算线程有效优先级来交换出去它的LWP。被内存调度器(sched())调用。

cl_trapret — 返回陷阱的代码,在从系统调用或陷阱返回到用户模式时调用,为重新调整线程优先级而设计。

cl_preempt — 当线程要被强占必须放在一个调度队列时被调用。内核模式的线程终端被赋予SYS类型优先级以便更快的重新执行。

cl_setrun — 将一个内核线程设成可运行的,典型调用发生在当线程从休眠队列中移除时。把线程放在调度队列。对多数线程,如果线程等待(休眠)了较长时间,就重新调整全局调度优先级。

cl_sleep —为线程休眠作准备。以等待时间或是否要求内核优先级为基础来设置线程优先级。如果线程把持一个内存页上的排它锁或RW写锁就设置内核优先级(SYS类型优先级)。

cl_tick —线程的Tick处理,被中断控制器调用。

cl_wakeup —线程唤醒包括从休眠队列移动线程到调度队列,重新设置一些线程和类型变量。

cl_donice —当一个nice(1)命令发出到线程来替换优先级时被调用。为目标线程根据nice值调整优先级。nice(1)不被RT和SYS类型支持。

cl_globpri —为给定的用户模式优先级返回全局调度优先级赋给线程,线程的实际调度优先级的计算基于一些包含用户优先级在内的因素。

cl_set_process_group —为IA类型的线程建立窗口会话关联的处理群组。

cl_yield —当线程获取(交出)处理机时被yield(2)系统调用调用。内核线程被放在调度队列的后部。

调度器和其他部分,实际上通过VFS/Vnode子系统使用的相同方法调用特定合适的调度类型函数。一个宏基和通过以当前内核线程指针或系统类型数组为下标确定类型相关函数。某些函数用在为调度类型设置线程,这些函数还未放在类型操作数组中的线程中,所以只能通过系统类型数组确定。

#define CL_ENTERCLASS(t, cid, clparmsp, credp, bufp) \
(sclass[cid].cl_funcs->thread.cl_enterclass) (t, cid, \
(void *)clparmsp, credp, bufp)
#define CL_EXITCLASS(cid, clprocp)\
(sclass[cid].cl_funcs->thread.cl_exitclass) ((void *)clprocp)
#define CL_CANEXIT(t, cr) (*(t)->t_clfuncs->cl_canexit)(t, cr)
#define CL_FORK(tp, ct, bufp) (*(tp)->t_clfuncs->cl_fork)(tp, ct, bufp)
#define CL_FORKRET(t, ct) (*(t)->t_clfuncs->cl_forkret)(t, ct)
#define CL_GETCLINFO(clp, clinfop) \
(*(clp)->cl_funcs->sclass.cl_getclinfo)((void *)clinfop)
. . .
See usr/src/uts/common/sys/class.h

例如,CL_ENTERCLASS,是通过系统类型数组来进入的,以类型ID(cid)作下标。CL_CANEXIT, CL_FORK,等,使通过线程的t_clfuncs指针来调进入的。可以在usr/src/uts/common/sys/class.h中找到类型操作宏的完全列表。

调度器(dispatcher)函数

调度器函数总述

调度器的主要功能是决定下次执行哪个可运行线程、管理线程上下文切换以及提供成为可运行kthread的插入调度队列的机制。其他调度器功能主持初始化和调度类型装载、加锁、抢占,以及对一些用户和管理员命令的支持,这些命令监控或改变调度相关行为,比如dispadmin(1M)和priocntl(1)命令。

进入调度器的主要接口是调用swtch(),它查找最高优先级可运行线程,并且在目标CPU上对它进行上下文切换。一些调度子系统,以及内核,通过swtch()进入调度器来初始化线程切换。很多工作是swtch()调用的disp()的核心代码完成的。函数setfrontdq()和setbackdq()控制处理机独有调度队列的插入;setkpdq()控制内核抢占队列(kernel preempt (kp) queue)。这些函数会按照线程优先级把线程放到一个调度队列中。在队列插入函数调用之前,线程放在队列前面还是后面已经决定好了。我们只要观察一个线程插入函数,来查看swtch()。

调度队列管理

调度队列函数控制从合适的调度队列中插入和删除线程。

函数	         描述
setbackdq()	插入一个线程到调度队列后部
setfrontdq()	插入一个线程到调度队列前部
setkpdq()	插入一个实时线程到内核抢占队列
dispdeq()	将一个线程从所在队列删除

下图说明了一个典型内核线程在移入移出调度队列和休眠队列期间的执行状态。

Image:solaris_thread_state.JPG

含有xx_ 前缀的函数调用时调度类型相关函数。线程获取(xx_yield)只在一个应用程序通过thr_yield(3T)发出获取调用(yield call)时发生。抢占意味着一个线程自动地进行上下文切换离开处理机,让给更高优先级的线程,或者,时间片终止用抢占机制强制上下文切换。

下一个运行线程选择CPU是在队列插入函数中。选择插入到哪一个CPU的调度队列中,受以下因素影响:

a) 线程的优先级

b) 线程的home lgroup

c) 线程是否被绑定(处理机集合或pbind)

d) 动态装载被调度器代码配平

队列插入函数在调度器的多处被调用,但绝大部分队列插入初始化发生在以下情况:

a) 线程创建:线程第一次分派插入到一个调度队列发生在它被创建时。线程调度类型从调用thread_create()的线程继承过来,在设置线程优先级(也是继承过来的)后进入一个类型相关的函数setrun()。新创建的线程继承当前CPU。就是说,新线程的t_cpu被设置为执行thread_create()代码的CPU。

b) 线程休眠:一个发生系统阻塞的线程将自动从CPU切换到一个休眠队列。

c) 线程唤醒:唤醒时,调度类型相关的唤醒函数调用调度器来插入新唤醒的线程,将它从休眠队列移到调度队列。

d) 线程抢占:一个抢占线程进行上下文切换,离开它的CPU,插入到一个调度队列。

e) 线程优先级改变。典型的,一个线程的优先级将在它的运行周期频繁发生,优先级改变意味着调度队列的改变(因为队列以CPU和优先级来组织)。

队列插入函数负责选择线程将要在哪个CPU上运行。这种选择处理线程绑定、优先级、分区、lgroup和装载平衡中的诸多因素。CPU的选择总结如下:

a) 新创建的线程把自己的CPU设为创建它的CPU,除非那个CPU不是默认分区,这种情况下用disp_lowpri_cpu()重新选择。

b) 如果线程被绑定,就在那个处理机集合或分区中选择CPU。

c) 如果离开CPU时间不长,就使用上次使用的CPU。

d) 如果较长时间没有运行,但是在家lgroup中,仍使用上次使用的CPU。

e) 否则,在当前lgroup中查找运行更低优先级线程的CPU,若找不到,就在远处的lgroup中查找。若所有运行的线程有更高的优先级,就在当前lgroup中选择一个CPU。

f) 查看芯片装载平衡,如果需要平衡,就选择装载较少的CPU。

g) 查看运行队列长度平衡。如果长度相差超过2,就选择有较短队列的CPU。

disp(),idle()及其相关函数

disp():查找当前处理机最高优先级线程来运行,并且设置线程到TS_ONPROC状态以便可以调用resume()来运行它。

Idle():CPU空闲循环。

swtch_to():切换到指定线程,与switch类似。

disp_getbest():把最高优先级的未绑定可运行线程移出队列。返回在onproc状态的锁住的线程。传递过来的参数是一个指向没有关联到当前CPU的调度队列。

disp_ratify():提交批准一个调度决策。

disp_getwork():察看调度器队列中是否存在其他CPU的工作,如果存在,让最优线程出列。

disp_fix_unbound_pri():决定队列中最大优先级的为绑定线程。这个优先级被队列保持,只增不减,除非有CPU干预队列。

调度器的核心:swtch()

swtch()初始化离开处理机线程的上下文切换,决定下一个运行的线程,并且把选择的线程上下文切换到处理机执行。在操作系统中,该函数被多次调用:fork函数(新创建线程);空闲线程(处理机调度队列空时自动执行);中断控制器(重新进入调度器);线程休眠管理;内核同步机制(互斥、读写锁、环境变量等);还有preempt()函数。诸多调用点请见下表。

内核子系统	内核函数	描述
Dispatcher	idle	处理机独有空闲线程
	         preempt	抢占的最后状态
Kthread	release_interrupt	被中断线程调用
TS/IA class	ts_forkret	在kthread创建后
Sleep/wakeup	cv_xxxx	诸多函数
CPU	force_migrate	线程转移到另一个处理机
	cpu_pause	处理机转到暂停状态
Mutex	mutex_vector_enter	获得互斥锁
RWlock	rw_enter_sleep	获得RW锁
Memory scheduler	sched	PID 0
Semaphore	sema_p	P原语
Signal	stop	线程停止函数
Sleep/wakeup	slp_cv_wait	线程休眠
Interrupt	intr_thread_exit	离开中断控制器

其他函数

cpu_choose(): 为将要运行的线程选择CPU,除了以下情况,选择线程当前CPU: --线程t_cpu不在线程分派的lgrp; --线程离开t_cpu时间超过了重选CPU时间(如果线程是当前线程并且t_disp_time不准确,则忽略此项); --线程t_cpu刚接受了离线(offline)或部分转移(partition move)的请求。

disp_lowpri_cpu(): 寻找运行最低优先级线程的CPU。一个传进来的参数是线程上次运行的CPU,即hint。将从hint开始查找CPU。查找时利用线程的lgroup和优先级。优先选择lgroup中最低优先级线程的CPU。也就是说,如果lgroup中的CPU都在运行高优先级的线程,就在其他lgroup中寻找运行较低优先级的CPU;如果所有的CPU都在运行高优先级的线程,就选择家lgroup中的一个CPU。

cpu_surrender(): 使线程交出处理机。查找线程运行的处理机,让该处理机被强占。

数据结构分析

cpu_t(处理机)

在usr/src/uts/common/sys/cpuvar.h中定义。每个系统中的cpu都有一个cpu_t。除了本身具有的一些调度器相关变量,cpu_t还有一个到disp_t的连接,disp_t维护处理机独有调度器信息和一个到处理机实际调度队列的链接。

/*
* Scheduling variables.
*/
disp_t *cpu_disp; /* dispatch queue data */
/*
* Note that cpu_disp is set before the CPU is added to the system
* and is never modified. Hence, no additional locking is needed
* beyond what's necessary to access the cpu_t structure.
*/
char cpu_runrun; /* scheduling flag - set to preempt */
char cpu_kprunrun; /* force kernel preemption */
pri_t cpu_chosen_level; /* priority at which cpu */
/* was chosen for scheduling */
kthread_t *cpu_dispthread; /* thread selected for dispatch */
disp_lock_t cpu_thread_lock; /* dispatcher lock on current thread */
uint8_t cpu_disp_flags; /* flags used by dispatcher */
/*
* The following field is updated when ever the cpu_dispthread
* changes. Also in places, where the current thread(cpu_dispthread)
* priority changes. This is used in disp_lowpri_cpu()
*/
pri_t cpu_dispatch_pri; /* priority of cpu_dispthread */
clock_t cpu_last_swtch; /* last time switched to new thread */
See usr/src/uts/common/sys/cpuvar.h

cpupart_t(处理机分区)

在usr/src/uts/common/sys/cpupart.h中定义。该数据结构是处理机分区的部分框架。处理机分区是处理机集合内部定义的方式。处理机集合是在一个多处理机系统上聚集一个或多个处理机成为一个面向用户的集合的设施。给定一个多任务分享处理机资源的方法,进程和线程就可以明确地绑定到集合中的处理机。

typedef struct cpupart {
disp_t cp_kp_queue; /* partition-wide kpreempt queue */
cpupartid_t cp_id; /* partition ID */
int cp_ncpus; /* number of online processors */
struct cpupart *cp_next; /* next partition in list */
struct cpupart *cp_prev; /* previous partition in list */
struct cpu *cp_cpulist; /* processor list */
struct kstat *cp_kstat; /* per-partition statistics */
/*
* cp_nrunnable and cp_nrunning are used to calculate load average.
*/
uint_t cp_nrunnable; /* current # of runnable threads */
uint_t cp_nrunning; /* current # of running threads */
/*
* cp_updates, cp_nrunnable_cum, cp_nwaiting_cum, and cp_hp_avenrun
* are used to generate kstat information on an as-needed basis.
*/
uint64_t cp_updates; /* number of statistics updates */
uint64_t cp_nrunnable_cum; /* cum. # of runnable threads */
uint64_t cp_nwaiting_cum; /* cum. # of waiting threads */
struct loadavg_s cp_loadavg; /* cpupart loadavg */
klgrpset_t cp_lgrpset; /* set of lgroups on which this */
/* partition has cpus */
lpl_t cp_lgrploads[NLGRPS_MAX];
/* table of load averages for this */
/* partition, indexed by lgrp ID */
uint64_t cp_hp_avenrun[3]; /* high-precision load average */
uint_t cp_attr; /* bitmask of attributes */
lgrp_gen_t cp_gen; /* generation number */
#if defined(_MACHDEP)
/*
* These guarded members must reside at the end of the structure
*/
cpuset_t cp_haltset; /* bitmask of halted cpus */
chip_set_t cp_chipset; /* set of chips spanned by this part */
#endif /* _MACHDEP */
} cpupart_t;
See usr/src/uts/common/sys/cpupart.h

dispq_t(调度队列)

在usr/src/uts/common/sys/disp.h中定义。调度队列,含有到一个队列的首部和尾部的链接,以及队列中可运行线程的个数。

* The following is the format of a dispatcher queue entry.
*/
typedef struct dispq {
kthread_t *dq_first; /* first thread on queue or NULL */
kthread_t *dq_last; /* last thread on queue or NULL */
int dq_sruncnt; /* number of loaded, runnable */
/* threads on queue */
} dispq_t;
See usr/src/uts/common/sys/disp.h

disp_t (调度器)

在usr/src/uts/common/sys/disp.h中定义。含有处理机调度队列所需变量,包括一个到调度队列的链接。

typedef struct _disp {
disp_lock_t disp_lock; /* protects dispatching fields */
pri_t disp_npri; /* # of priority levels in queue */
dispq_t *disp_q; /* the dispatch queue */
dispq_t *disp_q_limit; /* ptr past end of dispatch queue */
ulong_t *disp_qactmap; /* bitmap of active dispatch queues */
/*
* Priorities:
* disp_maxrunpri is the maximum run priority of runnable threads
* on this queue. It is -1 if nothing is runnable.
*
* disp_max_unbound_pri is the maximum run priority of threads on
* this dispatch queue but runnable by any CPU. This may be left
* artificially high, then corrected when some CPU tries to take
* an unbound thread. It is -1 if nothing is runnable.
*/
pri_t disp_maxrunpri; /* maximum run priority */
pri_t disp_max_unbound_pri; /* max pri of unbound threads */
volatile int disp_nrunnable; /* runnable threads in cpu dispq */
struct cpu *disp_cpu; /* cpu owning this queue or NULL */
} disp_t;
See usr/src/uts/common/sys/disp.h 

disp_queue_info(调度队列信息)

在usr/src/uts/common/disp/disp.c中定义。每个处理机有一个队列信息数据结构。队列信息变量为队列数据提供临时占位符(temporary placeholders)。队列信息将在调度器事件常规流程中改变。disp_queue_info在一个数组中维护,内核通过disp_mem指针引用它。

/* Dispatch queue allocation structure and functions */
struct disp_queue_info {
disp_t *dp;
dispq_t *olddispq;
dispq_t *newdispq;
ulong_t *olddqactmap;
ulong_t *newdqactmap;
int oldnglobpris;
};
See usr/src/uts/common/disp/disp.c

kthread_t(内核线程)

在usr/src/uts/common/sys/thread.h中定义。该数据结构定义了一个内核线程。kthread_t参与维护线程的分配和继承优先级、一个到调度类型相关结构(例如xxproc_t)的链接、以及用来追踪执行和等待时间的时间标记。

typedef struct _kthread {
...
pri_t t_pri; /* assigned thread priority */
pri_t t_epri; /* inherited thread priority */
...
struct thread_ops *t_clfuncs; /* scheduling class ops vector */
void *t_cldata; /* per scheduling class specific data */
...
See usr/src/uts/common/sys/thread.h

xxproc_t(xx调度类型的进程)

这里xx代表 ts, ia, rt, fx 或 fss中的一个,对应内核线程的调度类型(比如,一个时间共享(timeshare (ts)))类型的线程链接到一个tsproc_t数据结构,一个公平共享(fair share (fss))的线程链接到一个fssproc数据结构,依次类推)。这些数据结构在usr/src/uts/common/sys/ts.h, ia.h, rt.h, fx.h 和 fss.h中定义,它们维护时间片信息,和其他类型相关信息。下面展示的是时间共享类型的数据结构。

/*
* time-sharing class specific thread structure
*/
typedef struct tsproc {
int ts_timeleft; /* time remaining in procs quantum */
uint_t ts_dispwait; /* wall clock seconds since start */
/* of quantum (not reset upon preemption */
pri_t ts_cpupri; /* system controlled component of ts_umdpri */
pri_t ts_uprilim; /* user priority limit */
pri_t ts_upri; /* user priority */
pri_t ts_umdpri; /* user mode priority within ts class */
char ts_nice; /* nice value for compatibility */
char ts_boost; /* interactive priority offset */
unsigned char ts_flags; /* flags defined below */
kthread_t *ts_tp; /* pointer to thread */
struct tsproc *ts_next; /* link to next tsproc on list */
struct tsproc *ts_prev; /* link to previous tsproc on list */
} tsproc_t;
See usr/src/uts/common/sys/ts.h

调度相关数据结构的组织

上述数据结构和它们间的链接可用下图来表示。为了清晰,链接到线程t_cldata指针的类型相关数据没有在图中显示。

Solaris dispatcher data structures

图中显示了处理机独有调度队列的组织,用来排列出了实时调度类型的所有运行线程。每个全局优先级有一个dispq_t,以数值降序排列。也就是说,第一个 dispq_t是所有线程的根,在最高全局优先级(109或169);下一个链接到次高优先级的线程,等等。

调度队列(运行队列)是多个含有可运行内核线程的链表。这些线程等待调度器的选择以便在一个处理机上执行。Solaris实现了处理机独有调度队列,就是说,每个运行Solaris系统的处理机在初始化时会拥有它独有的调度队列集合(per-processor dispatch queue)。这些队列被组织成以队列为元素的数组,每个队列为每一个全局优先级维护着一个线程的链表。处理机独有队列组织(per-processor queue arrangement)提供了改进的可伸缩性,排除了高度竞争互斥锁的可能,而这种互斥锁需要全局的、跨系统的队列。另外,处理机独有队列简便地管理线程到处理机的绑定,因为要启动的线程只需放到相应处理机的调度队列中。对多核处理机来说,每个处理机都有一个处理机独有队列。

每个处理机独有队列包含除了实时线程(realtime thread)之外的所有调度类型的线程。实时调度类为特殊需求的应用提供了一些独有的特性。为优化对实时性的支持,实时线程放在一个特殊的调度队列集合中,这些队列被称为内核抢占队列(kernel preemption queues)或kp队列(kp queues)。一旦一个线程进入kp队列,就会发生内核抢占,强制处理机进入调度程序。

实时线程的调度队列(kp队列)管理方法稍微有些不同。优先级独有队列安排方式是一样的(60个实时优先级0-59),但是实际队列的数量不是处理机独有,而是处理机分区独有(per-processor partition)或者处理机集合(processor set)。Solaris系统没有创建用户定义处理机集合,默认有一个为实时线程准备的跨系统(system-wide)的kp_preempt队列。创建处理机集合后,kp_queue的数量将等于配置的处理机集合的数量加一。也就是说,每个处理机集合有一个kp_queue,另外还有一个默认集合的kp_queue。还有,disp_t是嵌入在cpupart_t中的(而不是一个链接指针)。

调度器结构在启动时创建和初始化,基于系统安装的处理机的数量。初始化程序可以在系统运行时被再次调用,以便支持一些Sun服务器系统得动态重配置功能。另外,调度类型被实现成动态可装载的内核模块,调度类型的装载需要调用调度器初始化程序。

主要函数算法分析以及流程图

调度器初始化(dispatcher Initialization)函数

下图是初始化流程图,

Image:solaris_dq_init.JPG

cpupart_initialize_default()是CPU分区支持代码的一部分。一个CPU分区是用户定义的处理机集合的内核抽象。处理机集合和CPU分区是不同的抽象,但是它们是相关的。用户创建处理机集合并且明确地绑定线程到它们上,内核担保只有绑定到集合的线程才会被集合中的处理机执行。在内核内部,定义了一个处理机分区,它表示一个含有一个或多个处理机的组,还有一个全局调度队列——实时线程的kp队列对分区是全局的;每个分区中的处理机仍有一个处理机独有的调度队列集合来调度其他类型的线程。作为调度初始化程序的一部分,默认CPU分区被创建和初始化。

每个调度类型有一个类相关初始化函数,它们为装载的调度类型调用。调度类型初始化函数相似,比如创建优先级限制变量,设置维护每个类型中线程链表的列表数组,以及初始化类型相关参数。

调度队列设置代码控制内核内存的分配,以及CPU独有队列和kp队列的链表、指针和结构变量的设置。当调度队列初始化完成后,最后一步是计算中断线程的全局基础优先级(global base priority)(参见图4.2调度器全局优先级)。全局优先级的数量,以及中断线程的基础优先级,由当前的或缺失的实时调度类型来决定。如果实时类型没有加载(默认),就会有100个非中断线程的全局优先级(0-99),中断线程优先级是100-109。如果加载了实时类型,则全局优先级数量增加到160 (0-159),此时中断线程占据160-169。

Image:solaris_thread_globle_priority.JPG

实时调度类型(Real Time Scheduling Class)函数

实时应用需要一个提供快速、整体和一致的调度延迟的系统。调度延迟参考在线程变成可运行到他进行上下文切换到处理机的过程中消逝的时间量,也就是从可运行到开始运行之间的时间。Solaris通过一些特性支持快速上下文切换:

a) 全局优先级安排。实时线程有系统最高优先级。只有中断线程有高于实时线程的优先级。

b)内核强占队列。实时线程在单独得调度队列中管理。

c)内核强占。当实时线程可运行,一个内核强占被触发,强迫CPU切换出当前线程到实时线程。

实时Tick处理的实现比较简单,因为没有必要测试强占控制或SYS优先级。实时优先级比SYS高,强占控制是多余的,因为实时线程获取并保持处理机,除非一个更高优先级的实时线程出现或者中断发生。

Image:solaris_rt_tick.JPG

调度器(dispatcher)函数

调度队列管理

setbackdq()把根据优先级所给线程放入调度队列后端。该函数在线程转移、运行或停止状态时被调用,返回一个TS_RUN状态仍然被锁住的线程。其他调度队列管理函数根setbackdq()类似。下面是setbackdq()流程图:

Image:solaris_setbackdq.JPG

idle()

Idle() 是CPU空闲循环,流程图如下:

Image:solaris_idle.JPG

调度器的核心:swtch()

swtch()找到最优可运行线程并且运行它。流程图如下:

Image:solaris_switch.JPG

Personal tools