原文连接: http://www.nces.is.nagoya-u.ac.jp/NEXCESS/blog/index.php?catid=13&blogid=4
我们马上来看看实时操作系统的内部构造.这次我们以函数wup_tsk()为例子来进行解说. 我们的目的是学习RTOS的代码的读法和处理的流程,所以我们不会一行一行的分析代码,而是把主要的代码和要点拿出来分析.
1.概要
这次我们围绕在任务之间切换调用的时候是如何处理的为主题,来看看里面的代码实现.
关于任务A和任务B的动作在下面解说.
关于任务A,内核, 任务B的动作,请看下图
【図4-1 各个任务和内核的处理流程】
现在我们来看看相对在任务A中,函数slp_tsk()调用等待后, 任务B中在函数wup_tsk调用,在回去通知任务A之前的内核的处理.也就是上图中标记颜色的部分.这次我们以掌握主要的流程为目的进行学习.
下图是wup_tsk函数在调用完返回任务A的流程的处理概要【図4-2】.
【図4-2 wup_tsk(任务A)发行后内核的处理流程】
那么就让我们来看看实际的代码把,这次我们来看图4-2的流程.
2.wup_tsk()函数调用
函数wup_tsk()的作用是:把唤醒等待状态的任务变成等待解除状态.
让我们来看看实际的代码.
132 ER
133 wup_tsk(ID tskid)
134 {
135 TCB *p_tcb;
136 ER ercd;
137
138 LOG_WUP_TSK_ENTER(tskid); //ログをとるためのマクロ
139 CHECK_TSKCTX_UNL(); //CPUロック状態でないかどうかをチェック
140 CHECK_TSKID_SELF(tskid); //タスクIDのチェック
141 p_tcb = get_tcb_self(tskid); //TCBへのポインタを取り出す ⇒ ①
142
143 t_lock_cpu(); //CPUロック状態へ ⇒ ②
144 if (TSTAT_DORMANT(p_tcb->tstat)) { //そのタスクが休止状態かどうか ⇒ ③
145 ercd = E_OBJ;
146 }
147 else if (TSTAT_WAIT_SLP(p_tcb->tstat)) { //対象タスクが起床待ち状態かどうか ⇒ ④
148 if (wait_complete(p_tcb)) { //戻り値は,ディスパッチが必要かどうか ⇒ ⑤
149 dispatch(); //ディスパッチを行う関数 ⇒ ⑥
150 }
151 ercd = E_OK;
152 }
153 else if (!(p_tcb->wupque)) {
154 p_tcb->wupque = TRUE;
155 ercd = E_OK;
156 }
157 else {
158 ercd = E_QOVR;
159 }
160 t_unlock_cpu();
161
162 error_exit:
163 LOG_WUP_TSK_LEAVE(ercd);
164 return(ercd);
165 }
在函数wup_tsk()中,对象任务的ID通过参数tskid来传入.
那么在理解了流程的基础上让我们来看看要点的说明.
①141行目
取出
对象任务(任务A)的TCB指针. 内容保存在指针p_tcb里面.
②143行目锁住CPU
.
函数wup_tsk()在调用过程中,会把唤醒等待状态的任务变成等待解除状态.如果对象任务是未登陆状态或者休止状态的话,就出现ERROR.这次,对象任务A是在唤醒等待状态,所以让我们来看看唤醒等待状态.
③144行目 在TCB中,根据保存任务的状态的地方(p_tcb->tstat)的值的不同,在这里判断是否解除等待,如果是休止状态的话,就返回ERROR.
④147行目
对象任务A是否处在唤醒等待状态下,如果是的话就返回TRUE
⑤148行目
确认wait_complete(p_tcb)的返回值,具体做了什么请参照wait_complete()函数的说明.是否调用dispatch就取决于这个函数的返回值.
⑥149行目 等待解除以后,如果有必要的话就呼出dispatch(),呼出dispatch()函数以后任务之间就进行切换.
3.wait_complete()函数
等待解除函数
107 wait_complete(TCB *p_tcb)
108 {
109 wait_dequeue_tmevtb(p_tcb);
110 p_tcb->p_winfo->wercd = E_OK; // ⇒ ①
111 return(make_non_wait(p_tcb)); // ⇒ ②
①110行目 等待解除的时候在放ErrorCode的地方放入E_OK. 关于唤醒等待的管理方法,以后在进行说明.
②111行目 调用函数make_non_wait().
4.make_non_wait()函数
在等待状态里面,其实是有很多种类的状态的.关于这些等待状态的管理将在以后进行说明.在这个函数里面,对象任务处于一个什么样的状态,不是双重等待状态,而是单纯的等待状态是否可以变成执行可能状态就在这个函数里面进行确认.
80 Inline BOOL
81 make_non_wait(TCB *p_tcb)
82 {
83 assert(TSTAT_WAITING(p_tcb->tstat)); //待ち状態もしくは,二重待ち状態か ⇒ ①
84
85 if (!TSTAT_SUSPENDED(p_tcb->tstat)) { //二重待ち状態でなければ(つまり待ち状態)⇒ ②
86 /*
87 * 待ち状態から実行できる状態への遷移
88 */
89 return(make_runnable(p_tcb)); // ⇒ ③
90 }
91 else {
92 /*
93 * 二重待ち状態から強制待ち状態への遷移
94 */
95 p_tcb->tstat = TS_SUSPENDED;
96 LOG_TSKSTAT(p_tcb);
97 return(FALSE);
98 }
99 }
①83行目 在这里确认是等待状态还是双重等待状态
②85行目 确认对象任务A 在单纯的等待状态而非强制等待状态下, 变成执行可能状态.
③89行目 转变成执行可能状态.详细请参照
make_runnable() .
5.make_runnable()
变成执行可能的状态.
232 BOOL
233 make_runnable(TCB *p_tcb)
234 {
235 uint_t pri = p_tcb->priority;
236
237 p_tcb->tstat = TS_RUNNABLE; //対象タスクTCBの現在の状態を「実行できる状態」へ ⇒ ①
238 LOG_TSKSTAT(p_tcb);
239 queue_insert_prev(&(ready_queue[pri]), &(p_tcb->task_queue));
240 primap_set(pri);
241
242 if (p_schedtsk == (TCB *) NULL || pri < p_schedtsk->priority) { //⇒ ②
243 p_schedtsk = p_tcb; //⇒ ③
244 return(dspflg); //⇒ ④
245 }
246 return(FALSE);
247 }
①237行目 把对象任务A的状态位(p_tcb->tstat)改成执行可能的状态.
②239,240行目 把这个任务插入到等待队列的最后面.关于等待队列的构造和管理方法将在以后说明.
重要!
为了妥善管理好任务队列,在等待队列中把拥有最高的优先顺序的任务通过变量:p_schedtsdk来进行管理.
判断p_schedtsdk是否需要更新.
在任务A已经变成执行可能状态以后, 在下面两个条件下,需要更新最高优先度的指针p_schedtsdk.
②242行目
1.目前没有执行可能的任务
p_schedtsk == (TCB *) NULL
2.对象任务A的优先度比现在最高优先度的任务还要高的时候
pri < p_schedtsk->priority (优先度值越小越优先)
③243行目 满足条件的时候,使p_schedtsk指向对象任务A.
④244行目 对象任务A已经变成了最高优先度(p_schedtsdk已经更新),所以就有必要进行dispatch了.在这里执行了return(dspflg)以后,就回到了wup_tsk()函数中的wait_complete()函数这里. dispatch如果是保留状态的时候dspflg的值为FALSE,反之则为TRUE.在这个情况下因为disflg等于TRUE的缘故, dispatch就被调用了.关于dispatch函数将在以后的章节中说明.
这次,以函数wup_tsk()为例子,对RTOS中的一小部分的代码完成了解析.想深入学习的还要知道一些对象结构的相关知识.接下来的一章中,我们就来看看SH3结构.