Zephys OS nano 内核篇:信号量 semaphore
2016-10-07 17:18
183 查看
Zephyr OS 所有的学习笔记已托管到 Github,CSDN 博客里的内容只是 Github 里内容的拷贝,因此链接会有错误,请谅解。
最新的学习笔记请移步 GitHub:https://github.com/tidyjiang8/zephyr-inside
信号量是 Zephyr OS 提供的用于不同线程间同步的机制。目前,Zephyr OS 只提供了信号量这一种同步机制。
信号量的类型定义
信号量的初始化
获取信号
_sem_take
nano_task_sem_take
释放(发送)信号
_sem_give_non_preemptible
nano_task_sem_give
不考虑 microkernel 的 task_q 和用于调试的 __next,信号量包含两个成员:
- nsig:信号量的值,即信号量的信号。当该值为 0,表示没有有效信号;当该值大于 0,表示有信号可以获取。
- wait_q:该信号量维护的等待队列。当一个线程试图获取无效的信号量(即信号量的值 nsig 为零)时,它可能会加入到这个等待队列中,然后陷入阻塞状态。
初始化信号量结构体中各成员:
- 将信号量的值初始化为 0。
- 初始化信号量的等待队列。
先看看函数的入参:
- sem:希望获取信号的信号量。
- timeout:获取信号的超时等待时间,以滴答为单位。函数内部会根据该变量的值来做相应的处理。
再看看函数的返回值:
- 1 - 表示获取信号成功
- 0 - 表示获取信号失败
nano_sem_take 会根据当前上下文的类型,调用对应的获取信号的函数。其中,nano_isr_sem_take() 和 nano_fiber_sem_take() 是函数 _sem_take() 的别名。
likely 是编译器内嵌的关键字,编译器会根据这个关键字对代码进行相应的优化,阅读代码时完全可以忽略。
当线程尝试从信号量中获取信号时,有两种可能:
- 信号量中有有效信号:即 nsig 的值大于 0,将 nsig 递减,获取信号成功。
- 信号量中没有有效信号:即 nsig 的值等于 0,无法获取信号,此时会根据入参 timeout_in_ticks 的值来做对应的处理:
- 等于 TICKS_NONE,表示当信号失败时,不等待,立即返回
- 不等于 TICKS_NONE,表示获取该信号的线程将陷入阻塞状态。在它陷入阻塞后,有两种可能
- 在 timeout_in_ticks 期间内,有另外一个线程向该信号量中添加了一个信号,等待线程将被添加信号的线程唤醒,获取信号成功。
- 在 timeout_in_ticks 期间内,没有线程向该信号量中添加值,那么等待将超时,定时器会将该线程唤醒,获取信号失败。
_NANO_OBJECT_WAIT() 是一个宏,对于 nanokernel,它的原型如下:
_NANO_TIMEOUT_SET_TASK_TIMEOUT()是设置内核大总管 _nanokernel 中 task_timeout 的值,目前还没发现这个有什么用。
nano_cpu_atomic_idle() 是用汇编实现的一个函数,它将使 CPU 进入睡眠状态,并在接收到中断后被唤醒。其原型如下:
根据当前上下文的类型,调用对应的添加信号量的函数。其中,nano_isr_sem_give() 和 nano_fiber_sem_give() 是函数 _sem_give_non_preemptible()的别名。
我之所以将这个函数叫做释放信号或发送信号,是因为信号量的两种应用场景:
- 当一个线程对某个临界资源访问前,它先要获取信号,访问完后,还需要释放信号
- 当一个线程获取信号时,被陷入阻塞状态。此时如果有另一个线程释放了信号,阻塞的线程将被唤醒。我们可以将其利皆为该线程对阻塞的线程发送了一个唤醒信号。
前面在从信号量中获取信号时,如果线程获取信号失败,做了两件事儿:
- 将线程阻塞(加入等待队列中)
- 将线程加入超时链表中
对应的,在往信号量中添加信号时,如果判断出有线程处于等待状态,也做了两件事儿:
- 将阻塞的线程添加到就绪链表中
- 将线程从超时链表中删除
最新的学习笔记请移步 GitHub:https://github.com/tidyjiang8/zephyr-inside
信号量是 Zephyr OS 提供的用于不同线程间同步的机制。目前,Zephyr OS 只提供了信号量这一种同步机制。
信号量的类型定义
信号量的初始化
获取信号
_sem_take
nano_task_sem_take
释放(发送)信号
_sem_give_non_preemptible
nano_task_sem_give
信号量的类型定义
struct nano_sem { struct _nano_queue wait_q; int nsig; #ifdef CONFIG_MICROKERNEL struct _nano_queue task_q; /* waiting tasks */ #endif #ifdef CONFIG_DEBUG_TRACING_KERNEL_OBJECTS struct nano_sem *__next; #endif };
不考虑 microkernel 的 task_q 和用于调试的 __next,信号量包含两个成员:
- nsig:信号量的值,即信号量的信号。当该值为 0,表示没有有效信号;当该值大于 0,表示有信号可以获取。
- wait_q:该信号量维护的等待队列。当一个线程试图获取无效的信号量(即信号量的值 nsig 为零)时,它可能会加入到这个等待队列中,然后陷入阻塞状态。
信号量的初始化
void nano_sem_init(struct nano_sem *sem) { sem->nsig = 0; _nano_wait_q_init(&sem->wait_q); SYS_TRACING_OBJ_INIT(nano_sem, sem); // 用于调试 _TASK_PENDQ_INIT(&sem->task_q); // microkernel 才使用的 }
初始化信号量结构体中各成员:
- 将信号量的值初始化为 0。
- 初始化信号量的等待队列。
获取信号
int nano_sem_take(struct nano_sem *sem, int32_t timeout) { static int (*func[3])(struct nano_sem *, int32_t) = { nano_isr_sem_take, nano_fiber_sem_take, nano_task_sem_take }; return func[sys_execution_context_type_get()](sem, timeout); }
先看看函数的入参:
- sem:希望获取信号的信号量。
- timeout:获取信号的超时等待时间,以滴答为单位。函数内部会根据该变量的值来做相应的处理。
再看看函数的返回值:
- 1 - 表示获取信号成功
- 0 - 表示获取信号失败
nano_sem_take 会根据当前上下文的类型,调用对应的获取信号的函数。其中,nano_isr_sem_take() 和 nano_fiber_sem_take() 是函数 _sem_take() 的别名。
_sem_take
int _sem_take(struct nano_sem *sem, int32_t timeout_in_ticks) { unsigned int key = irq_lock(); if (likely(sem->nsig > 0)) { // nsig > 0,说明可以获取信号 // 然后将信号量的值 nsig 递减 // 然后返回 1,表示获取信号成功 sem->nsig--; irq_unlock(key); return 1; } // 代码走到这里,说明当前没有有效的信号 if (timeout_in_ticks != TICKS_NONE) { // 将当前线程加入内核的超时链表中,并绑定该超时节点的等待队列为 // 当前信号量所维护的等待队列 _NANO_TIMEOUT_ADD(&sem->wait_q, timeout_in_ticks); // 将当前线程加入到该信号量维护的等待队列 _nano_wait_q_put(&sem->wait_q); // return _Swap(key); /* 原文是 return _Swap(key),但是为了分析说明,我们将其拆分为下面两句话 */ int value = _Swap(key); // 执行完 _Swap() 函数后,将会切换到其它上下文。如果该函数返回了,有两种可能: // 1. 有线程向该信号量中添加了限号,唤醒了本线程,且在唤醒前设置了本线程的返回值, // 具体信息可参考函数 _sem_give_non_preemptible // 2. 由于等待超时,超时服务唤醒了本线程,本线程的返回值没有被设置,返回默认值 0 reutrn value; } // 代码走到这里,说明获取信号失败,且立即返回 irq_unlock(key); return 0; }
likely 是编译器内嵌的关键字,编译器会根据这个关键字对代码进行相应的优化,阅读代码时完全可以忽略。
当线程尝试从信号量中获取信号时,有两种可能:
- 信号量中有有效信号:即 nsig 的值大于 0,将 nsig 递减,获取信号成功。
- 信号量中没有有效信号:即 nsig 的值等于 0,无法获取信号,此时会根据入参 timeout_in_ticks 的值来做对应的处理:
- 等于 TICKS_NONE,表示当信号失败时,不等待,立即返回
- 不等于 TICKS_NONE,表示获取该信号的线程将陷入阻塞状态。在它陷入阻塞后,有两种可能
- 在 timeout_in_ticks 期间内,有另外一个线程向该信号量中添加了一个信号,等待线程将被添加信号的线程唤醒,获取信号成功。
- 在 timeout_in_ticks 期间内,没有线程向该信号量中添加值,那么等待将超时,定时器会将该线程唤醒,获取信号失败。
nano_task_sem_take
int nano_task_sem_take(struct nano_sem *sem, int32_t timeout_in_ticks) { int64_t cur_ticks; int64_t limit = 0x7fffffffffffffffll; // 为什么这样初始化,不解 unsigned int key; key = irq_lock(); // 获取当前的系统滴答数 cur_ticks = _NANO_TIMEOUT_TICK_GET(); if (timeout_in_ticks != TICKS_UNLIMITED) { // 计算等待时间到期后的滴答数 limit = cur_ticks + timeout_in_ticks; } do { if (likely(sem->nsig > 0)) { // nsig > 0,说明可以获取信号 // 然后将信号量的值 nsig 递减 // 然后返回 1,表示获取信号成功 sem->nsig--; irq_unlock(key); return 1; } if (timeout_in_ticks != TICKS_NONE) { // 让 task 等待,具体请见后面的分析 _NANO_OBJECT_WAIT(&sem->task_q, &sem->nsig, timeout_in_ticks, key); // 获取并更新当前的滴答数,用作循环的判断条件,判断是否跳出循环 cur_ticks = _NANO_TIMEOUT_TICK_GET(); _NANO_TIMEOUT_UPDATE(timeout_in_ticks, limit, cur_ticks); } // 如果 timeout_in_ticks 等于 TICKS_NONE,那么 cur_ticks 等于 limit // 循环判断的条件将失败,跳出循环,本函数立即返回,获取信号失败 } while (cur_ticks < limit); irq_unlock(key); return 0; }
_NANO_OBJECT_WAIT() 是一个宏,对于 nanokernel,它的原型如下:
#define _NANO_OBJECT_WAIT(queue, data, timeout, key) \ do { \ _NANO_TIMEOUT_SET_TASK_TIMEOUT(timeout); \ nano_cpu_atomic_idle(key); \ key = irq_lock(); \ } while (0)
_NANO_TIMEOUT_SET_TASK_TIMEOUT()是设置内核大总管 _nanokernel 中 task_timeout 的值,目前还没发现这个有什么用。
nano_cpu_atomic_idle() 是用汇编实现的一个函数,它将使 CPU 进入睡眠状态,并在接收到中断后被唤醒。其原型如下:
SECTION_FUNC(TEXT, nano_cpu_atomic_idle) // 本函数使用了两个通用寄存器,r0 和 r1。 // r0:调用者传递进来的中断的 mask // r1:用于设置 BASEPRI 的值 // 异或操作,将 r1 与 r1,进行异或操作,将结果保存到 r1 中,所以 r1 里面的值为 0 // .n 用于告诉编译器使用 16 位的指令进行汇编 eors.n r1, r1 // 锁定 PRIMASK, 此时系统不会处理中断 cpsid i // 将 0 写入寄存器 BASEPRI,此时系统将能够接收**所有**中断 msr BASEPRI, r1 // 此时的状态:能接收中断,但是先不处理中断 // wfe,Wait For Event,等待事件的到来。执行这个指令后,CPU 将进入睡眠状态,进入低功 // 耗模式。更具体地说,执行 wfe 指令后,CPU core 将会发送一个信号,pmu 收到这个信号后, // 将关闭 core 的 clock,甚至进入 retention 模式(降低电压,但是保存寄存器和cache 的值)。 wfe // 此时 cpu 陷入睡眠了,不再取指执行。 // 当有外部中断到来时,将会唤醒 CPU,然后继续执行下面的指令. // 恢复睡眠之前的中断优先级 msr BASEPRI, r0 // 允许执行之前检测到的中断信号的 ISR cpsie i // 可能会在这里执行 ISR. // 返回调用函数 bx lr
释放(发送)信号
void nano_sem_give(struct nano_sem *sem) { static void (*func[3])(struct nano_sem *sem) = { nano_isr_sem_give, nano_fiber_sem_give, nano_task_sem_give }; func[sys_execution_context_type_get()](sem); }
根据当前上下文的类型,调用对应的添加信号量的函数。其中,nano_isr_sem_give() 和 nano_fiber_sem_give() 是函数 _sem_give_non_preemptible()的别名。
我之所以将这个函数叫做释放信号或发送信号,是因为信号量的两种应用场景:
- 当一个线程对某个临界资源访问前,它先要获取信号,访问完后,还需要释放信号
- 当一个线程获取信号时,被陷入阻塞状态。此时如果有另一个线程释放了信号,阻塞的线程将被唤醒。我们可以将其利皆为该线程对阻塞的线程发送了一个唤醒信号。
_sem_give_non_preemptible
void _sem_give_non_preemptible(struct nano_sem *sem) { struct tcs *tcs; unsigned int imask; imask = irq_lock(); // 取出信号量的等待队列的队首数据,判断有没有线程在等待信号 tcs = _nano_wait_q_remove(&sem->wait_q); if (!tcs) { // 如果没有线程在等待信号,直接将信号量的值递增 // 此外,_nano_wait_q_remove 内部会将队首的线程加入到就绪队列 // 即该线程的状态由阻塞态变为了就绪态 sem->nsig++; _NANO_UNPEND_TASKS(&sem->task_q); } else { // 如果有线程在等待信号,将该线程从超时队列中删除 _nano_timeout_abort(tcs); // 此时不设置信号量的值,直接设置等待线程的返回值为 1 set_sem_available(tcs); } irq_unlock(imask); }
前面在从信号量中获取信号时,如果线程获取信号失败,做了两件事儿:
- 将线程阻塞(加入等待队列中)
- 将线程加入超时链表中
对应的,在往信号量中添加信号时,如果判断出有线程处于等待状态,也做了两件事儿:
- 将阻塞的线程添加到就绪链表中
- 将线程从超时链表中删除
nano_task_sem_give
void nano_task_sem_give(struct nano_sem *sem) { struct tcs *tcs; unsigned int imask; imask = irq_lock(); // 取出信号量的等待队列的队首数据,判断有没有线程在等待信号 tcs = _nano_wait_q_remove(&sem->wait_q); if (tcs) { // 如果有线程在等待信号,将该线程从超时队列中删除 // 此外,_nano_wait_q_remove 内部会将队首的线程加入到就绪队列 _nano_timeout_abort(tcs); // 此时不设置信号量的值,直接设置等待线程的返回值为 1 set_sem_available(tcs); 由于 wait_q 中保存的线程是 fiber,而当前的线程是 task,所以让出 cpu,让该 fiber 先运行。 _Swap(imask); return; } // 代码走到这里,说明之前没有线程在等待信号 // 将信号量的值递增 sem->nsig++; _TASK_NANO_UNPEND_TASKS(&sem->task_q); irq_unlock(imask); }
相关文章推荐
- Zephys OS nano 内核篇:fiber 服务 - 基础
- Zephys OS nano 内核篇:isr 服务
- Zephys OS nano 内核篇:fiber 服务
- Zephys OS nano 内核篇:FIFO
- Zephys OS nano 内核篇:LIFO
- Zephys OS nano 内核篇:栈 stack
- Zephys OS nano 内核篇:环形缓冲 Ring Buffer
- Zephys OS nano 内核篇:前言
- Zephys OS nano 内核篇:task 服务 - 基础
- Windows进程同步之信号量内核对象(Semaphore)
- Linux内核参数-共享内存信号量semaphore设置
- 第9章 用内核对象进行线程同步(3)_信号量(semaphore)、互斥对象(mutex)
- Windows 临界区CRITICAL_SECTION,内核事件Event,互斥量Mutex,信号量Semaphore
- 线程同步 信号量 Semaphore 内核对象 CreateSemaphore
- Linux 内核信号量(semaphore) __down() 函数浅析
- Zephyr OS nano 内核篇: 等待队列 wait_q
- OS__信号量(semaphore)PV操作
- 内核同步机制-读写信号量(rw_semaphore)
- 内核同步机制-信号量(semaphore)
- Windows进程同步之信号量内核对象(Semaphore)