Zephyr学习(五)线程和调度
前面说过zephyr支持静态和动态两种方式创建线程,这里分析动态创建的方式。应用程序通过调用k_thread_create()函数创建一个线程,实际上是调用_impl_k_thread_create()函数,定义在zephyr-zephyr-v1.13.0\kernel\thread.c:
k_tid_t _impl_k_thread_create(struct k_thread *new_thread,
k_thread_stack_t *stack,
size_t stack_size, k_thread_entry_t entry,
void *p1, void *p2, void *p3,
int prio, u32_t options, s32_t delay)
{
__ASSERT(!_is_in_isr(), "Threads may not be created in ISRs"); _setup_new_thread(new_thread, stack, stack_size, entry, p1, p2, p3,
prio, options); if (delay != K_FOREVER) {
schedule_new_thread(new_thread, delay);
} return new_thread;
}
第9行,调用_setup_new_thread()函数,在开发环境搭建里已经分析过了。
第12行,传进来的最后一个参数一般为K_NO_WAIT,即马上参与调度,所以if条件成立。
第13行,调用schedule_new_thread()函数,定义在zephyr-zephyr-v1.13.0\kernel\thread.c:
static void schedule_new_thread(struct k_thread *thread, s32_t delay)
{
if (delay == ) {
k_thread_start(thread);
} else {
s32_t ticks = _TICK_ALIGN + _ms_to_ticks(delay);
int key = irq_lock(); _add_thread_timeout(thread, NULL, ticks);
irq_unlock(key);
}
}
第3行,由于K_NO_WAIT的值就为0,所以if条件成立。
第4行,调用k_thread_start()函数,定义在zephyr-zephyr-v1.13.0\kernel\thread.c:
void _impl_k_thread_start(struct k_thread *thread)
{
int key = irq_lock(); /* protect kernel queues */ if (_has_thread_started(thread)) {
irq_unlock(key);
return;
} _mark_thread_as_started(thread);
_ready_thread(thread);
_reschedule(key);
}
第5行,判断线程的状态是否不为_THREAD_PRESTART,如果是则直接返回。
第10行,清除线程的_THREAD_PRESTART状态。
第11行,前面已经分析过了。
第12行,调用_reschedule()函数,定义在zephyr-zephyr-v1.13.0\kernel\sched.c:
int _reschedule(int key)
{
if (_is_in_isr()) {
goto noswap;
} if (_get_next_ready_thread() != _current) {
return _Swap(key);
} noswap:
irq_unlock(key);
return ;
}
第3行,调用_is_in_isr()函数,判断是否处于中断上下文,在中断里是不允许线程切换的,定义在arch\arm\include\kernel_arch_func.h:
#define _is_in_isr() _IsInIsr()
实际上调用的是_IsInIsr()函数,定义在zephyr-zephyr-v1.13.0\arch\arm\include\cortex_m\exc.h:
static ALWAYS_INLINE int _IsInIsr(void)
{
u32_t vector = __get_IPSR(); /* IRQs + PendSV (14) + SYSTICK (15) are interrupts. */
return (vector > ) || (vector && !(SCB->ICSR & SCB_ICSR_RETTOBASE_Msk));
}
即IPSR的值(当前中断号)大于13则认为是处于中断上下文。
回到_reschedule()函数,第7行,调用_get_next_ready_thread()函数,定义在zephyr-zephyr-v1.13.0\kernel\include\ksched.h:
static ALWAYS_INLINE struct k_thread *_get_next_ready_thread(void)
{
return _ready_q.cache;
}
前面也说过,_ready_q.cache始终指向的是下一个要投入运行的线程。
所以,如果当前线程不是下一个要投入的线程,那么第8行,调用_Swap()函数,定义在zephyr-zephyr-v1.13.0\kernel\include\kswap.h:
static inline unsigned int _Swap(unsigned int key)
{
unsigned int ret;
_update_time_slice_before_swap(); ret = __swap(key); return ret;
}
第4行,调用_update_time_slice_before_swap()函数,定义在zephyr-zephyr-v1.13.0\kernel\sched.c:
void _update_time_slice_before_swap(void)
{
/* Restart time slice count at new thread switch */
_time_slice_elapsed = ;
}
即将时间片清0,重新开始累加。
回到_Swap()函数,第6行,调用__swap()函数,定义在zephyr-zephyr-v1.13.0\arch\arm\core\swap.c:
unsigned int __swap(int key)
{
/* store off key and return value */
_current->arch.basepri = key;
_current->arch.swap_return_value = _k_neg_eagain; /* set pending bit to make sure we will take a PendSV exception */
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; /* clear mask or enable all irqs to take a pendsv */
irq_unlock(); return _current->arch.swap_return_value;
}
第4~5行,保存key和返回值。
第8行,置位pendsv中断。
第11行,使能中断,此时就会产生pendsv中断。
下面分析pendsv中断的处理流程,定义在zephyr-zephyr-v1.13.0\arch\arm\core\swap_helper.S:
SECTION_FUNC(TEXT, __pendsv)
/* protect the kernel state while we play with the thread lists */
movs.n r0, #_EXC_IRQ_DEFAULT_PRIO
msr BASEPRI, r0
/* load _kernel into r1 and current k_thread into r2 */
ldr r1, =_kernel
ldr r2, [r1, #_kernel_offset_to_current]
/* addr of callee-saved regs in thread in r0 */
ldr r0, =_thread_offset_to_callee_saved
add r0, r2
/* save callee-saved + psp in thread */
mrs ip, PSP
stmia r0, {v1-v8, ip}
/*
22 * Prepare to clear PendSV with interrupts unlocked, but
23 * don't clear it yet. PendSV must not be cleared until
24 * the new thread is context-switched in since all decisions
25 * to pend PendSV have been taken with the current kernel
26 * state and this is what we're handling currently.
27 */
ldr v4, =_SCS_ICSR
ldr v3, =_SCS_ICSR_UNPENDSV
/* _kernel is still in r1 */
/* fetch the thread to run from the ready queue cache */
ldr r2, [r1, _kernel_offset_to_ready_q_cache]
str r2, [r1, #_kernel_offset_to_current]
/*
39 * Clear PendSV so that if another interrupt comes in and
40 * decides, with the new kernel state baseed on the new thread
41 * being context-switched in, that it needs to reschedules, it
42 * will take, but that previously pended PendSVs do not take,
43 * since they were based on the previous kernel state and this
44 * has been handled.
45 */
/* _SCS_ICSR is still in v4 and _SCS_ICSR_UNPENDSV in v3 */
str v3, [v4, #]
/* Restore previous interrupt disable state (irq_lock key) */
ldr r0, [r2, #_thread_offset_to_basepri]
movs.n r3, #
str r3, [r2, #_thread_offset_to_basepri]
/* restore BASEPRI for the incoming thread */
msr BASEPRI, r0
/* load callee-saved + psp from thread */
add r0, r2, #_thread_offset_to_callee_saved
ldmia r0, {v1-v8, ip}
msr PSP, ip
/* exc return */
bx lr
CortexM进入中断时,CPU会自动将8个寄存器(XPSR、PC、LR、R12、R3、R2、R1、R0)压栈。
第5~6行,相当于调用irq_lock()函数。
第9~19行的作用就是将剩下的其他寄存器(R4、R5、R6、R7、R8、R9、R10、R11、PSP)也压栈。
第28~29行,准备清pendsv中断标志。
第34~36行,r2指向下一个要投入运行的线程,其中第36行,将_current指向要投入运行的线程。
第48行,清pendsv中断标志。(不清也可以?)
第51~53行,清要投入运行线程的basepri变量的值。
第56行,恢复BASEPRI寄存器的值。
第59~62行,恢复R4、R5、R6、R7、R8、R9、R10、R11、PSP寄存器。
第65行,中断返回,自动将XPSR、PC、LR、R12、R3、R2、R1、R0寄存器弹出,即切换到下一个线程。
应用程序也可以调用k_yield()函数主动让出CPU,定义在zephyr-zephyr-v1.13.0\kernel\sched.c:
void _impl_k_yield(void)
{
__ASSERT(!_is_in_isr(), ""); if (!_is_idle(_current)) {
LOCKED(&sched_lock) {
_priq_run_remove(&_kernel.ready_q.runq, _current);
_priq_run_add(&_kernel.ready_q.runq, _current);
update_cache();
}
} if (_get_next_ready_thread() != _current) {
_Swap(irq_lock());
}
}
里面的函数都已经分析过了,这里不再重复。
要成功将自己切换出去(让出CPU)的前提是有优先级比自己更高的并且已经就绪的线程。
接下来看一下线程的取消过程。应用程序调用k_thread_cancel()函数取消一个线程,定义在
zephyr-zephyr-v1.13.0\kernel\thread.c:
int _impl_k_thread_cancel(k_tid_t tid)
{
struct k_thread *thread = tid; unsigned int key = irq_lock(); if (_has_thread_started(thread) ||
!_is_thread_timeout_active(thread)) {
irq_unlock(key);
return -EINVAL;
} _abort_thread_timeout(thread);
_thread_monitor_exit(thread); irq_unlock(key); return ;
}
第7~8行,如果线程都没开始运行过,则返回出错。如果线程不是在等待(延时或者休眠),也返回出错,即线程不能自己取消自己。
第13行,调用_abort_thread_timeout()函数,定义在zephyr-zephyr-v1.13.0\kernel\include\timeout_q.h:
static inline int _abort_thread_timeout(struct k_thread *thread)
{
return _abort_timeout(&thread->base.timeout);
}
实际上调用的是_abort_timeout()函数,定义在zephyr-zephyr-v1.13.0\kernel\include\timeout_q.h:
static inline int _abort_timeout(struct _timeout *timeout)
{
if (timeout->delta_ticks_from_prev == _INACTIVE) {
return _INACTIVE;
} if (!sys_dlist_is_tail(&_timeout_q, &timeout->node)) {
sys_dnode_t *next_node =
sys_dlist_peek_next(&_timeout_q, &timeout->node);
struct _timeout *next = (struct _timeout *)next_node; next->delta_ticks_from_prev += timeout->delta_ticks_from_prev;
}
sys_dlist_remove(&timeout->node);
timeout->delta_ticks_from_prev = _INACTIVE; return ;
}
第3行,如果线程没有在延时或者休眠,则返回出错。
第7行,如果线程不是在超时队列的最后,则if条件成立。
第9行,取出线程的下一个节点。
第12行,将下一个节点的延时时间加上要取消的线程剩余的延时时间。
第14行,将线程从超时队列移除。
第15行,将线程的delta_ticks_from_prev设为_INACTIVE。
好了,到这里线程的创建、取消和调度过程都分析完了。
搞明白最近这三篇随笔,也就基本搞懂了zephyr内核的核心内容了,剩下的mutex互斥锁、工作队列、信号量等内容也就比较容易理解了。
Zephyr学习(五)线程和调度的更多相关文章
- C++并发与多线程学习笔记--线程之间调度
condition_variable wait() notify_one notify_all condition_variable 条件变量的实际用途: 比如有两个线程A和B,在线程A中等待一个条件 ...
- Zephyr学习(三)启动过程
一.写在前面 最近对zephyr这个系统很感兴趣,因此业余有时间的时候都在研究它的源码,而光看代码不去动手这不是我的风格,于是乎在网上淘了一块STM32F103C8T6的核心板和一块NRF52832的 ...
- Linux的进程线程及调度
本文为宋宝华<Linux的进程.线程以及调度>学习笔记. 1 进程概念 1.1 进程与线程的定义 操作系统中的经典定义: 进程:资源分配单位. 线程:调度单位. 操作系统中用PCB(Pro ...
- [转帖]Linux的进程线程及调度
Linux的进程线程及调度 本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10393707.html 本文为宋宝华<Linux的进程 ...
- python自动化开发学习 进程, 线程, 协程
python自动化开发学习 进程, 线程, 协程 前言 在过去单核CPU也可以执行多任务,操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换任务2,任务2执行0.01秒,在切换到任务3,这 ...
- ucore操作系统学习(五) ucore lab5用户进程管理
1. ucore lab5介绍 ucore在lab4中实现了进程/线程机制,能够创建并进行内核线程的调度.通过上下文的切换令线程分时的获得CPU,使得不同线程能够并发的运行. 在lab5中需要更进一步 ...
- <<Windows via C/C++>>学习笔记 —— 线程优先级【转】
转自:http://www.cnblogs.com/wz19860913/archive/2008/08/04/1259807.html 每个线程都有一个“优先级”,范围是0-31,0为最低优先级,3 ...
- Java多线程-线程的调度(休眠)
Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率. 这里要明确的一点,不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制. ...
- Java多线程-线程的调度(合并)
线程的合并的含义就是将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时可以使用join方法. join为非静态方法,定义如下:void join(): ...
- Java多线程-线程的调度(守护线程)
本文转自http://www.cnblogs.com/linjiqin/p/3210004.html 感谢作者 守护线程与普通线程写法上基本没啥区别,调用线程对象的方法setDaemon(true), ...
随机推荐
- Django深度剖析-二
WEBserver处理过程 先写个大家熟悉的socketserver例子 #! /usr/bin/env python # encoding: utf-8 """ @Au ...
- js生成1-100不重复的随机数及生成10个1-100不重复的随机数
//生成1-100不重复的随机数 var count=100; var a=new Array(); for(var i=0;i<100;i++){ a[i]=i+1; } a.sort(fun ...
- Mac下安装pyenv
mac 用virtualenv安装py3.6的虚拟环境报错(virtualenv -p /usr/bin/python3 env36), 解决的话需要安装zlib包, 然后去掉configure配置里 ...
- Windows7下安装与破解IntelliJ IDEA2017
IDEA 全称 IntelliJ IDEA,是java语言开发的集成环境,IntelliJ在业界被公认为最好的java开发工具之一,尤其在智能代码助手.代码自动提示.重构.J2EE支持.各类版本工具( ...
- Kubernetes基础:Service
本文的试验环境为CentOS 7.3,Kubernetes集群为1.11.2,安装步骤参见kubeadm安装kubernetes V1.11.1 集群 Service 介绍 我们通过Pod.Deplo ...
- MultipartFile文件编码判断
MultipartFile文件编码判断 搜索:Java 判断文件的字符集编码 https://blog.csdn.net/top_code/article/details/8891796 但是在Mul ...
- SpringBoot项目接口第一次访问慢的问题
SpringBoot的接口第一次访问都很慢,通过日志可以发现,dispatcherServlet不是一开始就加载的,有访问才开始加载的,即懒加载. 2019-01-25 15:23:46.264 IN ...
- shell编程学习笔记(三):Shell中局部变量的使用
现在我们看一下Shell中局部变量的使用 以下蓝色字体部分为Linux命令,红色字体的内容为输出的内容: # cd /opt/scripts # vim script03.sh 开始编写script0 ...
- ionic cordova plugin 安装和使用
注意事项 ionic1需要ng-cordova plugin的使用都需要放到deviceready事件的回调中, 设备准备好了才能设备交互 plugin只有在真机上才有效果, 模拟器(部分)和brow ...
- yarn upgrade
更新一个依赖 yarn upgrade 用于更新包到基于规范范围的最新版本 yarn upgrade --latest # 忽略版本规则,升级到最新版本,并且更新 package.json .