1、线程时间片介绍(tx_thread_time_slice)
ThreadX内核同优先级线程之间是按时间片调度的,tx_thread_new_time_slice记录线程的时间片(一次调度的总的时间片),tx_thread_time_slice记录线程的剩余时间片(ThreadX内核每次调度线程时,并不是从tx_thread_new_time_slice,而是从上次换出cpu时的剩余时间片继续计时,只有当时间片用尽时,tx_thread_time_slice才会从tx_thread_new_time_slice开始);
ThreadX内核正在执行的线程在优先级链表表头,线程被切换出cpu时,线程并不会移动到链表末尾,如果内核线程执行还没多久就被高优先级线程抢占,把线程移动到链表末尾的话,该线程就要等很久才会得到调度,所以线程被抢占的话,线程仍在优先级链表的表头,下次该优先级成为最高优先级时,取表头线程也就是上次被切换出去的线程继续执行;另外,正是由于被换出去的线程仍在表头,如果线程以新的时间片执行的话,如果每次线程都没用尽时间片就被高优先级抢占,那么同优先级链表后面的线程就得不到调度,所以每次线程被调度都是接着上次没有用完的时间片tx_thread_time_slice继续计时,直到时间片用完才分配新的时间片tx_thread_new_time_slice(如果同优先级有其他线程就绪就将线程移动到末尾,调度下一个同优先级线程,否则接着执行当前线程)。
2、定时器中断
2.1、中断介绍
前一篇文章已经介绍过中断上下文保存、中断处理、中断上下文恢复相关内容,汇编代码具体实现参考:
ThreadX内核源码分析 - ports线程上下文相关代码分析(arm)_arm7star的博客-优快云博客1、ports源码介绍内核与cpu相关的关键代码基本都是用汇编语言实现的,c语言可能实现不了或者不好编写。ThreadX官网针对ARM9 gcc的移植代码在threadx-6.1.2_rel\ports\arm9\gnu\src目录下,ThreadX文件命名规则基本是以该文件包含的函数名命名的(函数名多了一个"_"前缀,文件名里面没有"_"前缀),每个源文件通常只实现一个函数;ports代码目录如下:tx_thread_context_restore.S是_tx_thread_context
https://blog.youkuaiyun.com/arm7star/article/details/122930850?spm=1001.2014.3001.5502
IRQ中断处理顶层代码逻辑如下,__tx_irq_handler为IRQ中断处理函数入口,__tx_irq_handler主要就是保存必要的中断上下文,调用中断处理函数,恢复中断上下文(如果有高优先级唤醒,那么要将IRQ栈里面保存的寄存器以及其他没有修改的寄存器保存到被中断线程的栈里面,_tx_thread_context_save只保存了会影响到的必要的寄存器(保存在IRQ栈里面),因为中断返回时并不一定会切换线程,保存过多的寄存器反而影响性能;如果线程没有被抢占或者切换出去,那么恢复保存在IRQ栈里面的寄存器即可):
本文只用到了定时器中断,所以中断服务程序就直接是调用_tx_timer_interrupt;另外ThreadX官网的tx_timer_interrupt是针对一类cpu的,而定时器是处理器相关的,所以官网的tx_timer_interrupt仅是内核相关的,需要自己在tx_timer_interrupt合适的地方加上硬件中断清理工作,否则定时器中断将不断触发;
针对s3c2440的定时器中断清理代码如下,保存r0, lr寄存器(BL指令会修改lr寄存器;c函数r0-r3之外的寄存器如果有用到,编译器会保护及恢复的,所以调用中断清除C函数,通用寄存器只需要考虑r0-r3,_tx_timer_interrupt调用irq_ack之后的代码很显然没有用到r0-r3的旧的值,所以r0-r3是不需要保护的,但是仿照ThreadX的其他代码,为了让栈保持8 byte对齐,还是把r0保存到栈里面了),irq_ack就是c代码清除定时器中断:

2.2、定时器中断服务程序(_tx_timer_interrupt)
_tx_timer_interrupt定时器中断服务程序主要是对正在执行的线程的时间片减1,检查当前线程时间片是否用尽,检查当前是否有timer定时器超时(应用程序定时器,非硬件定时器,线程的sleep、阻塞超时等的唤醒都是通过定时器唤醒的,例如等待某个互斥锁,如果超时了就不继续等待,那么内核就启动了一个定时器,在超时时间内等到了互斥锁,那么就取消定时器,否则定时器超时就唤醒阻塞的线程,返回获取互斥锁失败);如果有定时器超时,调用_tx_timer_expiration_process处理超时定时器,如果线程时间片用尽,调用_tx_thread_time_slice处理时间片(有同优先级的其他线程的话需要调度下一个线程,否则不需要调度,重新设置当前线程的时间片即可,新一轮开始计时),_tx_timer_interrupt代码如下:
115 _tx_timer_interrupt:
116 @
117 @ /* IRQ acknowledge. */
118 @ irq_ack();
119 @
120 STMDB sp!, {r0, lr} @ Save the lr register on the stack
121 BL irq_ack // 清除定时器中断
122 LDMIA sp!, {r0, lr} @ Recover lr register
123 @
124 @ /* Upon entry to this routine, it is assumed that context save has already
125 @ been called, and therefore the compiler scratch registers are available
126 @ for use. */
127 @
128 @ /* Increment the system clock. */
129 @ _tx_timer_system_clock++;
130 @
131 LDR r1, =_tx_timer_system_clock @ Pickup address of system clock
132 LDR r0, [r1] @ Pickup system clock
133 ADD r0, r0, #1 @ Increment system clock // _tx_timer_system_clock++,每次定时器中断,系统的时钟加1
134 STR r0, [r1] @ Store new system clock
135 @
136 @ /* Test for time-slice expiration. */
137 @ if (_tx_timer_time_slice)
138 @ {
139 @
140 LDR r3, =_tx_timer_time_slice @ Pickup address of time-slice
141 LDR r2, [r3] @ Pickup time-slice
142 CMP r2, #0 @ Is it non-active?
143 BEQ __tx_timer_no_time_slice @ Yes, skip time-slice processing // 检查_tx_timer_time_slice是否激活,_tx_timer_time_slice不为0,表示线程使用了时间片,每次定时器中断要对线程时间片计时,_tx_timer_time_slice为0,表示线程没有使用时间片,不对线程运行时间计时,只有线程被抢占或者线程自己让出cpu,否则同优先级的其他就绪线程可能就得不到调度,一般不启用时间片的线程都会有某些阻塞调用
144 @
145 @ /* Decrement the time_slice. */
146 @ _tx_timer_time_slice--;
147 @
148 SUB r2, r2, #1 @ Decrement the time-slice // 线程启用了时间片,对线程时间片减1(线程调度时,线程剩余时间片保存在_tx_timer_time_slice里面,线程被换出时,_tx_timer_time_slice保存到线程的剩余时间片tx_thread_time_slice里面)
149 STR r2, [r3] @ Store new time-slice value
150 @
151 @ /* Check for expiration. */
152 @ if (__tx_timer_time_slice == 0)
153 @
154 CMP r2, #0 @ Has it expired? // 检查是否超时(线程时间片用尽了)
155 BNE __tx_timer_no_time_slice @ No, skip expiration processing // _tx_timer_time_slice不为0表示还有时间片,跳转到__tx_timer_no_time_slice
156 @
157 @ /* Set the time-slice expired flag. */
158 @ _tx_timer_expired_time_slice = TX_TRUE;
159 @
160 LDR r3, =_tx_timer_expired_time_slice @ Pickup address of expired flag // _tx_timer_time_slice为0,线程时间片用尽了,设置_tx_timer_expired_time_slice线程时间片用尽标志
161 MOV r0, #1 @ Build expired value
162 STR r0, [r3] @ Set time-slice expiration flag
163 @
164 @ }
165 @
166 __tx_timer_no_time_slice: // 检查完了线程时间片
167 @
168 @ /* Test for timer expiration. */
169 @ if (*_tx_timer_current_ptr)
170 @ {
171 @
172 LDR r1, =_tx_timer_current_ptr @ Pickup current timer pointer address
173 LDR r0, [r1] @ Pickup current timer
174 LDR r2, [r0] @ Pickup timer list entry // *_tx_timer_current_ptr
175 CMP r2, #0 @ Is there anything in the list? // 检查*_tx_timer_current_ptr是否为空,是否有定时器,_tx_timer_current_ptr类似墙上的一个挂钟,每次定时器中断移动一格,每个格子下面挂的是超时的定时器,如果没有定时器的话,就是空的,否则有定时器超时
176 BEQ __tx_timer_no_timer @ No, just increment the timer // 没有定时器的话,跳转到__tx_timer_no_timer
177 @
178 @ /* Set expiration flag. */
179 @ _tx_timer_expired = TX_TRUE;
180 @
181 LDR r3, =_tx_timer_expired @ Pickup expiration flag address // 有定时器超时,设置_tx_timer_expired标志
182 MOV r2, #1 @ Build expired value
183 STR r2, [r3] @ Set expired flag
184 B __tx_timer_done @ Finished timer processing
185 @
186 @ }
187 @ else
188 @ {
189 __tx_timer_no_timer: // 没有定时器超时
190 @
191 @ /* No timer expired, increment the timer pointer. */
192 @ _tx_timer_current_ptr++;
193 @
194 ADD r0, r0, #4 @ Move to next timer // _tx_timer_current_ptr移动到下一个元素(_tx_timer_current_ptr指向_tx_timer_list数组的一个节点,_tx_timer_list的每个节点下是一个超时定时器链表,该链表里面的定时器全是在同一个时间点超时,_tx_timer_list每个节点间超时时间相差一个定时器中断),类似秒针走一格
195 @
196 @ /* Check for wraparound. */
197 @ if (_tx_timer_current_ptr == _tx_timer_list_end)
198 @
199 LDR r3, =_tx_timer_list_end @ Pickup address of timer list end // 因为_tx_timer_list使用数组实现链表的,_tx_timer_current_ptr指向_tx_timer_list的元素,_tx_timer_current_ptr超过_tx_timer_list的最后一个元素时,需要重新指向_tx_timer_list的第1个元素,即wrap回环,跟秒针一样,秒针走到59之后,下一次就回到0
200 LDR r2, [r3

本文详细介绍了ThreadX实时操作系统中线程时间片的概念,包括如何记录和更新线程时间片,以及当时间片用尽时如何处理。同时,阐述了定时器中断服务程序的工作流程,特别是如何检查并处理超时的线程和定时器。此外,还分析了定时器结构和超时处理函数 `_tx_timer_expiration_process` 的实现,展示了如何在中断上下文中唤醒专门处理超时的线程。
最低0.47元/天 解锁文章
1135

被折叠的 条评论
为什么被折叠?



