操作系统是很多人每天必须打交道的东西,因为在你打开电脑的一刹那,随着bios自检结束,你的windows系统已经开始运行了。如果问大家操作系统是什么?可能有的人会说操作系统就是windows,就是那些可以放大、缩小、移动的窗口。对曾经是计算机专业的朋友来说,这个答案还要稍微复杂一些,操作系统可能还有linux、unix、ios、sun solaris、aix等。如果再细化一点,对嵌入式工具比较解的朋友还会有新的补充,因为在他们看来,vxworks、eCos、ucos也都是操作系统,虽然它们好多系统连界面都没有。
既然操作系统称之为一个系统,那么它必然是由好多的部件组成的。有过linux嵌入式开发经验的朋友都知道,要想使一个linux在arm芯片上真正跑起来,它必须有三个部分组成,即boot + 内核 + 文件系统。而真正内核的东西其实很少,也就是cpu初始化、线程调度、内存分配、文件系统、网络协议栈、驱动这些部分组成。那么是不是所有的芯片都需要跑操作系统呢?我们可以举个例子。
现在有一个简单的温度测量电路,它由三部分组成:1、单片机;2、温度传感器模块;3、无线发射模块。我们设计这么一个温度测量电路其实就是一个目的,那就是为了实时获取当前的温度信息。那么,这么一个简单的电路应该怎么设计程序呢?其实很简单。
void sleep(int value) { int outer; int inner; for(; outer < value; outer++) { for(inner = 0; inner < 1000; inner++) ; } } void main() { while(1) { /* read temperature from port*/ sleep(1000); /* send temperature to wireless module */ sleep(1000); } }
如果我们需要cpu干的事情很少,甚至极端一点说只有一件事情,那么根本没有设计操作系统的必要。我们设计出操作系统,主要是想在单位时间内完成几件事情。打个比方来说,你完全可以在工作的时候一遍写文档、一遍收发电子邮件,偶尔还能开个小差休息一会。 所以操作系统就是为了共享资源而存在的。
认识操作系统的用途不难,关键是如何把操作系统用代码写出来。也许有人会跟你说,免费的代码一大堆,Linux就不错,你下载下来直接读就好了。但是我告诉你,最新的Linux内核版本已经轻松的越过了3.0,整个代码的长度远在千万行之上,你可能从哪看起都不知道。可能此时又有人不同意了,看不懂高版本的linux,可以看看linux低版本的代码,0.11版本的代码就不错,因为赵炯就是怎么推荐的。我要说的是,0.11的代码固然好,但是怎么编译版本、怎么修改代码、怎么构造文件系统、怎么跑起来是我们绕不过的一道难题。对于很多朋友来说,阅读linux代码尚且困难,更不要说后面还需要完成的一大摊子烂事了。
说了这么多,我们需要的的内核代码是什么样的?其实在我看来,很简单。它只要满足下面两个条件就可以了,
(1)像用户软件一样可以运行;
(2)像用户软件一样可以单步调试。
要解决这些问题,对linux系统来说上不难解决。要解决os的运行和调试问题,关键就在于如何仿真中断和实现os的任务切换。至于任务的开启、运行和挂起,内存分配,互斥量,信号量,文件系统,tcp/ip协议栈,GUI操作,这些其实都是可以在linux上进行仿真和操作的,朋友们可以尽请放心。这部分的内容,我们会在以后的博客中陆续展开。
为了能够更好地阅读后面发表的博文,我建议你巩固一下下面这些知识,这样会对你的理解有很大的裨益。
(1)cpu 结构,了解中断流程就行;
(2)linux 汇编语言;
(3)函数堆栈格式和内容;
(4)互斥量、信号量的使用方法;
(5)调度的基本策略;
(6)内存分配的基本方法;
(7)tcp/ip socket编程;
(8)gui编程方法,可以参考windows的方法;
(9)系统中的内存布局、编译原理等等。
互斥量
学过操作系统课程的朋友对这个词汇肯定不会很陌生。和信号量相比,互斥保护的资源一般是唯一的。也就是说,资源就一份,你占有了,我就没有办法占有;当然如果你释放了,此时我就有机会占有了。
一切的一切看上去没有什么问题。但是,我们都知道在实时嵌入式系统当中,线程之间的调度是严格按照优先级来进行调度。比方说,优先级为10的任务必须比优先级为11的任务优先得到调度。那么,有同学会问了,那优先级为11的任务什么时候才能得到调度呢,其实这个要求还是蛮苛刻的。要想优先级为11的任务得到调度,此时必须没有优先级10的任务、或者任务pend到资源上了、或者自身delay、或者被人suspend了。否则,优先级为10的任务会这么一直运行下去。那,这和我们的互斥量有什么关系呢?请听我一一讲来。
我们假设现在有两个任务都准备运行,分别人任务A、B,优先级依次是10、11。某一段时间后,优先级为10和优先级为11的任务都在尝试获取某个资源。本来按照优先级的先后顺序,优先级为10的任务应该率先获取资源,这都没问题。但是,假设在尝试获取资源前,优先级为10的任务开了个小差,sleep一会,那么这个时候优先级为11的任务就可以开始运行了。等到优先级为10的任务苏醒过来,想重新获取资源的时候,惊讶地发现资源早就被别人给占了。因为资源目前只有一份,所以它只好把自己pend到等待队列里面,慢慢等待好心人能快点把资源释放出来。一切的一切看上去没有什么问题,但是这却和实时系统设计的初衷是相违背的。前面我们规定高优先级的任务必须优先得到运行的机会,而目前这种情况和我们的设计原则是背道而驰的。
当然这个问题很早就被大家发现了,大家也在尝试不同的方法来解决。目前使用的比较多的就是两种方法,一种是给互斥量设定一个优先级,另外一种就是对优先级进行继承处理。看上去是两种方法,其实目的只有一个,就是让那些占有互斥量的thread提高优先级,赶快运行结束,把资源还给后面真正需要的人。看上去一切解决得都很完美,但是大家有没有考虑过这样一个问题,如果线程连续占有多个互斥量,优先级又该怎么处理?如果pend的任务被修改了优先级该怎么处理?如果这两种方法一起被使用,那又该怎么处理?我想,这就是作者在后期对互斥量代码进行重构的原因吧。当然了,上面讨论的内容已经是比较深的了,大家可以看看早期互斥量是怎么设计的,慢慢来,这样才会对作者的设计意图更加了解一些。
老规矩,我们首先看看互斥量是怎么设计的,
typedef struct RAW_MUTEX { RAW_COMMON_BLOCK_OBJECT common_block_obj; RAW_U8 count; /*ponit to occupy task*/ RAW_TASK_OBJ *occupy; /*occupy task original priority*/ RAW_U8 occupy_original_priority; } RAW_MUTEX;
看上去互斥量的东西多一点,其实也还可以,只要大家明白了互斥量处理逻辑再回头来看看这些东西的时候,认识就会更加深刻。我们看看,数据结构里面都有什么,
(1)通用互斥结构,这在前面信号量的时候已经介绍过一遍;
(2)计数,判断资源是否还在;
(3)当前所属的任务;
(4)该任务原来的优先级。
说好了基本结构,我们看看互斥量的构造、申请、释放、删除函数是怎么设计的,首先当然还是初始化函数,
RAW_U16 raw_mutex_create(RAW_MUTEX *mutex_ptr, RAW_U8 *name_ptr) { #if (RAW_MUTEX_FUNCTION_CHECK > 0) if (mutex_ptr == 0) return RAW_NULL_OBJECT; #endif /*Init the list*/ list_init(&mutex_ptr->common_block_obj.block_list); mutex_ptr->common_block_obj.block_way = 0; mutex_ptr->common_block_obj.name = name_ptr; /*No one occupy mutex yet*/ mutex_ptr->occupy = 0; /*resource is available at init state*/ mutex_ptr->count = 1; mutex_ptr->occupy_original_priority = 0; return RAW_SUCCESS; }
初始化的函数还是比较简单的,主要做了下面的流程,
(1)初始化互斥结构的公共属性,比如名字、阻塞方式等等;
(2)初始化当前资源数量;
(3)初始化占有资源的线程指针,还有就是线程的优先级。
创建了互斥量之后,我们就要看看互斥量是怎么申请的?代码有点长,同学们可以心理调整一下了,
RAW_U16 raw_mutex_get(RAW_MUTEX *mutex_ptr, RAW_U32 wait_option) { RAW_U16 error_status; RAW_SR_ALLOC(); #if (RAW_MUTEX_FUNCTION_CHECK > 0) if (mutex_ptr == 0) { return RAW_NULL_OBJECT; } if (raw_int_nesting) { return RAW_NOT_CALLED_BY_ISR; } #endif RAW_CRITICAL_ENTER(); /* mutex is available */ if (mutex_ptr->count) { mutex_ptr->occupy = raw_task_active; mutex_ptr->occupy_original_priority = raw_task_active->priority; mutex_ptr->count = 0; RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } /*if the same task get the same mutex again, it causes deadlock*/ if (raw_task_active == mutex_ptr->occupy) { #if (CONFIG_RAW_ASSERT > 0) RAW_ASSERT(0); #endif RAW_CRITICAL_EXIT(); return RAW_MUTEX_DEADLOCK; } /*Cann't get mutex, and return immediately if wait_option is RAW_NO_WAIT*/ if (wait_option == RAW_NO_WAIT) { RAW_CRITICAL_EXIT(); return RAW_NO_PEND_WAIT; } /*system is locked so task can not be blocked just return immediately*/ if (raw_sched_lock) { RAW_CRITICAL_EXIT(); return RAW_SCHED_DISABLE; } /*if current task is a higher priority task and block on the mutex *priority inverse condition happened, priority inherit method is used here*/ if (raw_task_active->priority < mutex_ptr->occupy->priority) { switch (mutex_ptr->occupy->task_state) { case RAW_RDY: /*remove from the ready list*/ remove_ready_list(&raw_ready_queue, mutex_ptr->occupy); /*raise the occupy task priority*/ mutex_ptr->occupy->priority = raw_task_active->priority; /*readd to the ready list head*/ add_ready_list_head(&raw_ready_queue, mutex_ptr->occupy); break; case RAW_DLY: case RAW_DLY_SUSPENDED: case RAW_SUSPENDED: /*occupy task is not on any list, so just change the priority*/ mutex_ptr->occupy->priority = raw_task_active->priority; break; case RAW_PEND: /* Change the position of the task in the wait list */ case RAW_PEND_TIMEOUT: case RAW_PEND_SUSPENDED: case RAW_PEND_TIMEOUT_SUSPENDED: /*occupy task is on the block list so change the priority on the block list*/ mutex_ptr->occupy->priority = raw_task_active->priority; change_pend_list_priority(mutex_ptr->occupy); break; default: RAW_CRITICAL_EXIT(); return RAW_INVALID_STATE; } } /*Any way block the current task*/ raw_pend_object(&mutex_ptr->common_block_obj, raw_task_active, wait_option); RAW_CRITICAL_EXIT(); /*find the next highest priority task ready to run*/ raw_sched(); /*So the task is waked up, need know which reason cause wake up.*/ error_status = block_state_post_process(raw_task_active, 0); return error_status; }
这段代码其实开头都还好,关键是末尾要结束的时候有一段代码比较费解。我想,这就是我前面说过的优先级反转问题。为了解决这一问题,在rawos版本中采取了优先级继承的方法。我们还是详细看一下逻辑本身是怎么样的,
(1)判断参数合法性;
(2)判断资源是否可取,如果可取,则在记录当前线程和优先级后返回;
(3)如果资源被自己重复申请,返回;
(4)如果线程不愿等待,返回;
(5)如果此时禁止调度,返回;
(6)如果此时优先级大于互斥量占有者的优先级,分情况处理
a)占有者处于ready的状态,那么修改它的优先级,重新加入调度队列;
b)占有者处于sleep的状态,直接修改优先级即可;
c)占有者也被pend到别的资源上面了,那么修改那个资源的pend列表,可能设计到调度顺序问题。
(7)线程把自己pend到互斥量等待队列上面;
(8)线程调用系统调度函数,切换到其他线程运行;
(9)线程再次得到运行的机会,从task获取结果后返回。
基本上上面的介绍算得上是很详细了,那么互斥量的释放基本上是一个逆操作的过程,朋友也可以思考一下应该怎么解决才好,
RAW_U16 raw_mutex_put(RAW_MUTEX *mutex_ptr) { LIST *block_list_head; RAW_SR_ALLOC(); #if (RAW_MUTEX_FUNCTION_CHECK > 0) if (mutex_ptr == 0) { return RAW_NULL_OBJECT; } #endif block_list_head = &mutex_ptr->common_block_obj.block_list; RAW_CRITICAL_ENTER(); /*Must release the mutex by self*/ if (raw_task_active != mutex_ptr->occupy) { RAW_CRITICAL_EXIT(); return RAW_MUTEX_NOT_RELEASE_BY_OCCYPY; } /*if no block task on this list just return*/ if (is_list_empty(block_list_head)) { mutex_ptr->count = 1; RAW_CRITICAL_EXIT(); return RAW_SUCCESS; } /*if priority was changed, just change it back to original priority*/ if (raw_task_active->priority != mutex_ptr->occupy_original_priority) { remove_ready_list(&raw_ready_queue, raw_task_active); raw_task_active->priority = mutex_ptr->occupy_original_priority; add_ready_list_end(&raw_ready_queue, raw_task_active); } /* there must have task blocked on this mutex object*/ mutex_ptr->occupy = list_entry(block_list_head->next, RAW_TASK_OBJ, task_list); /*the first blocked task became the occupy task*/ mutex_ptr->occupy_original_priority = mutex_ptr->occupy->priority; /*mutex resource is occupied*/ mutex_ptr->count = 0; /*Wake up the occupy task, which is the highst priority task on the list*/ raw_wake_object(mutex_ptr->occupy); RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; }
和之前的信号量释放相比,互斥量的释放要复杂一切,关键就在于修改优先级的问题。我们来梳理一下,
(1)判断参数合法性;
(2)判断线程是否为互斥量的占有线程,不是则返回;
(3)判断等待队列是否为空,为空的话则返回;
(4)判断占有任务的优先级有没有发生变化,如果有则需要重新修改优先级,重新加入调度队列中;
(5)选择下一个可以调度的线程;
(6)函数返回。
说了这么些,就剩下最后一个删除互斥量了,大家再接再厉,一起去学习。
RAW_U16 raw_mutex_delete(RAW_MUTEX *mutex_ptr) { LIST *block_list_head; RAW_TASK_OBJ *mutex_occupy; RAW_SR_ALLOC(); #if (RAW_MUTEX_FUNCTION_CHECK > 0) if (mutex_ptr == 0) { return RAW_NULL_OBJECT; } #endif block_list_head = &mutex_ptr->common_block_obj.block_list; RAW_CRITICAL_ENTER(); mutex_occupy = mutex_ptr->occupy; /*if mutex is occupied and occupy priority is not the original priority*/ if ((mutex_occupy) && (mutex_occupy->priority != mutex_ptr->occupy_original_priority)) { switch (mutex_occupy->task_state) { case RAW_RDY: /*remove from the ready list*/ remove_ready_list(&raw_ready_queue, mutex_ptr->occupy); /*raise the occupy task priority*/ mutex_occupy->priority = mutex_ptr->occupy_original_priority; /*readd to the ready list head*/ add_ready_list_end(&raw_ready_queue, mutex_ptr->occupy); break; case RAW_DLY: case RAW_SUSPENDED: case RAW_DLY_SUSPENDED: /*occupy task is not on any list, so just change the priority*/ mutex_occupy->priority = mutex_ptr->occupy_original_priority; break; case RAW_PEND: case RAW_PEND_TIMEOUT: case RAW_PEND_SUSPENDED: case RAW_PEND_TIMEOUT_SUSPENDED: /*occupy task is on the block list so change the priority on the block list*/ mutex_occupy->priority = mutex_ptr->occupy_original_priority; change_pend_list_priority(mutex_occupy); break; default: RAW_CRITICAL_EXIT(); return RAW_STATE_UNKNOWN; } } /*All task blocked on this queue is waken up*/ while (!is_list_empty(block_list_head)) { delete_pend_obj(list_entry(block_list_head->next, RAW_TASK_OBJ, task_list)); } RAW_CRITICAL_EXIT(); raw_sched(); return RAW_SUCCESS; }
互斥量的操作在实际情形下未必是存在的,所以作者在设计的时候添加了一个编译宏。不过删除所做的工作也不难理解,一个是处理好当前占有者的关系,一个是处理好等待队列的关系。我们来细看一下流程,
(1)判断当前参数合法性;
(2)判断占有者的情况,修改任务优先级,这里的情形和上面申请互斥量的处理方式是一样的,不再赘述;
(3)唤醒所有的等待线程,如果线程已经suspend掉了,那么继续suspend;
(4)调度到其他线程,防止有优先级高的任务已经被释放出来了;
(5)函数返回,结束。
信号量
之前因为工作的原因,操作系统这块一直没有继续写下去。一方面是自己没有这方面的经历,另外一方面就是操作系统比较复杂和琐碎,调试起来比较麻烦。目前在实际项目中,使用的实时操作系统很多,很多国内的朋友也写过操作系统,有些项目现在还在维护和修改中,这是十分难得的。就我知道和熟悉的就有三个系统,比如
(1)RT-THREAD
(2)RAW-OS
(3)ClearRTOS
这里有比较介绍一下,这三个系统是国内的三位朋友开发的。其中rt-thread时间比较久一点,模块也比较全,bsp、cpu、fs、lwip、gui等辅助的代码也比较多,有兴趣的朋友可以到网站上面下载代码看一看。raw-os是我今年才发现的一个实时系统,从网站的注册时间和软件版本号上来看,系统开发的时间不是很长,不过整个系统代码的结构非常清晰,是我重点推荐阅读的代码。如果朋友们自己download下来,好好看一下其中的代码,肯定会有不少的收获。最后一个代码是作者李云在编写《专业嵌入式软件开发》这本书的时候,为了说明os的基本原理而开发的软件,前后设计了线程、互斥、内存、定时器、驱动框架等内容,值得一读。
当然有了这么多优秀的代码,我觉得现在自己的工作就不是重新造一个车轮了,而是和大家分享这些优秀的代码是如何设计的。理解代码本身不是目的,关键是理解代码背后的基本思路。就我个人看过来,rt-thread和raw-os都可以用来学习,不过raw-os更好一些,主要是因为作者将raw-os移植到的vc上面,学起来也十分方便,要是个人在使用过程中有什么疑问,可以通过邮件和作者及时交流。不过由于raw-os的版本在一直在update之中,所以部分代码在前后稍微有点差别,不过这些都不是重点,暂时不了解的内容可以通过后面的了解和学习逐步掌握,不会成为太大的障碍。
就像今天的题目一样,我们重点介绍一下信号量的设计原理。首先看一下信号量的数据结构是怎么样的,
typedef struct RAW_SEMAPHORE
{
RAW_COMMON_BLOCK_OBJECT common_block_obj;
RAW_U32 count;
} RAW_SEMAPHORE;
这些代码都是从raw-os上面摘抄下来的,这个版本是0.94版本,和最新的0.96c版本有点差别。首先分析一下信号量的基本结构,其实非常简单,就两个变量,其中comm_block_obj是一个通用类型,记录了当前结构的名称、类型和阻塞队列,而count就是计数,判断是否还有释放的资源。
说到了信号量的操作,无非就是信号量的创建、获取、释放、删除操作,当然这里作者考虑的比较详细,在信号量释放的时候还分成了 WAKE_ONE_SEM和WAKE_ALL_SEM两种类型。意思很简单,就是当信号量来临的时候是唤醒一个等待线程呢,还是唤醒所有的等待线程呢,就是这么回事。下面,我们就按照顺序介绍这几个函数,首先是创建函数,
RAW_U16 raw_semaphore_create(RAW_SEMAPHORE *semaphore_ptr, RAW_U8 *name_ptr, RAW_U32 initial_count)
{
#if (RAW_SEMA_FUNCTION_CHECK > 0)
if (semaphore_ptr == 0) {
return RAW_NULL_OBJECT;
}
if (initial_count == 0xffffffff) {
return RAW_SEMOPHORE_OVERFLOW;
}
#endif
/*Init the list*/
list_init(&semaphore_ptr->common_block_obj.block_list);
/*Init resource*/
semaphore_ptr->count = initial_count;
semaphore_ptr->common_block_obj.name = name_ptr;
semaphore_ptr->common_block_obj.block_way = 0;
return RAW_SUCCESS;
}
看着初始化函数,我们发现信号量的初始化其实也非常简单,基本工作主要有:
(1)判断参数合法性;
(2)初始化阻塞队列、名称等;
(3)初始化信号量的计数。
说完了这些,我们看看信号量的获取是怎么完成的,代码可能长度稍微长一些,不过也不用太紧张,
RAW_U16 raw_semaphore_get(RAW_SEMAPHORE *semaphore_ptr, RAW_U32 wait_option)
{
RAW_U16 error_status;
RAW_SR_ALLOC();
#if (RAW_SEMA_FUNCTION_CHECK > 0)
if (semaphore_ptr == 0) {
return RAW_NULL_OBJECT;
}
if (raw_int_nesting) {
return RAW_NOT_CALLED_BY_ISR;
}
#endif
RAW_CRITICAL_ENTER();
if (semaphore_ptr->count) {
semaphore_ptr->count--;
RAW_CRITICAL_EXIT();
return RAW_SUCCESS;
}
/*Cann't get semphore, and return immediately if wait_option is RAW_NO_WAIT*/
if (wait_option == RAW_NO_WAIT) {
RAW_CRITICAL_EXIT();
return RAW_NO_PEND_WAIT;
}
if (raw_sched_lock) {
RAW_CRITICAL_EXIT();
return RAW_SCHED_DISABLE;
}
raw_pend_object(&semaphore_ptr->common_block_obj, raw_task_active, wait_option);
RAW_CRITICAL_EXIT();
raw_sched();
error_status = block_state_post_process(raw_task_active, 0);
return error_status;
}
信号量的获取情况比较复杂一些,这在长度上也体现出来了。不过没关系,我们一步一步看函数做了什么,
(1)判断参数合法性;
(2)判断当前函数是否处于中断处理的流程中,如果是选择返回;
(3)判断当前count是否为0,如果不为 0,则减1返回;
(4)如果当前count是0,且线程不愿意等待,那么选择返回;
(5)如果当前禁止调度,那么依然选择返回;
(6)当前线程将自己挂起,从ready队列中删除,把自己pend到信号量的阻塞队列中;
(7)阻塞的线程再次获得了运行的机会,我们从task数据结构获得返回结果,此时也不一定是因为获得了资源的缘故哦。
上面的get函数看上去比较复杂,但是所有的同步函数基本上都是这样设计的,看多了反而有一种八股文的感觉。刚开始看的同学可能觉得不是很习惯。不要紧,每天多看两眼,时间长了就ok了。好了,接着我们继续去看看信号量的释放函数是怎么处理的,大家做好心理准备哦,
static RAW_U16 internal_semaphore_put(RAW_SEMAPHORE *semaphore_ptr, RAW_U8 opt_wake_all)
{
LIST *block_list_head;
RAW_SR_ALLOC();
#if (RAW_SEMA_FUNCTION_CHECK > 0)
if (semaphore_ptr == 0) {
return RAW_NULL_OBJECT;
}
#endif
block_list_head = &semaphore_ptr->common_block_obj.block_list;
RAW_CRITICAL_ENTER();
/*if no block task on this list just return*/
if (is_list_empty(block_list_head)) {
if (semaphore_ptr->count == 0xffffffff) {
RAW_CRITICAL_EXIT();
return RAW_SEMOPHORE_OVERFLOW;
}
/*increase resource*/
semaphore_ptr->count++;
RAW_CRITICAL_EXIT();
return RAW_SUCCESS;
}
/*wake all the task blocked on this semphore*/
if (opt_wake_all) {
while (!is_list_empty(block_list_head)) {
raw_wake_object(list_entry(block_list_head->next, RAW_TASK_OBJ, task_list));
}
}
else {
/*Wake up the highest priority task block on the semaphore*/
raw_wake_object(list_entry(block_list_head->next, RAW_TASK_OBJ, task_list));
}
RAW_CRITICAL_EXIT();
raw_sched();
return RAW_SUCCESS;
}
看上去,信号量的释放函数也比较长,不过只要有耐心,都是可以看明白的,我们就来具体分析一下,
(1)判断参数的合法性;
(2)判断当前是否有等待队列,如果没有,则count自增,函数返回,当然如果count达到了0xffffffff也要返回,不过概率极低;
(3) 当前存在等待队列,根据opt_wake_all的要求是唤醒一个线程还是唤醒所有的线程;
(4)调用系统调度函数,让高优先级任务及时得到运行的机会;
(5)当前线程再次得到运行的机会,函数返回。
有了上面的讲解,我们发现os的代码其实也没有那么恐怖。所以,请大家一鼓作气,看看信号量是怎么删除的吧,
RAW_U16 raw_semaphore_delete(RAW_SEMAPHORE *semaphore_ptr)
{
LIST *block_list_head;
RAW_SR_ALLOC();
#if (RAW_SEMA_FUNCTION_CHECK > 0)
if (semaphore_ptr == 0) {
return RAW_NULL_OBJECT;
}
#endif
block_list_head = &semaphore_ptr->common_block_obj.block_list;
RAW_CRITICAL_ENTER();
/*All task blocked on this queue is waken up*/
while (!is_list_empty(block_list_head)) {
delete_pend_obj(list_entry(block_list_head->next, RAW_TASK_OBJ, task_list));
}
RAW_CRITICAL_EXIT();
raw_sched();
return RAW_SUCCESS;
}
信号量删除的工作其实很少,也很简单,同样我们也来梳理一下,
(1)判断参数合法性;
(2)唤醒阻塞队列中的每一个线程;
(3)调用系统调度函数,因为高优先级的任务很有可能刚刚从阻塞队列中释放出来;
(4)当前线程再次运行,函数返回。
通过上面几个函数的讲解,我们发现关于os互斥部分的代码其实也不复杂。只要对系统本身和中断有一些了解,其实代码都是可以看懂的。当然,上面的代码我们还是讲的比较粗糙,所以有些细节还是要补充一下,
(1)全局变量操作的函数必须在关中断的情况下进行操作;
(2)实时系统的抢占是每时每刻都在进行的,比如中断返回时、信号量释放时、调用延时函数、调用调度函数的时候,所以大家心中要有抢占的概念;
(3)互斥函数中大量使用了链表的结构,建议大家好好掌握链表的相关算法;
(4)关于os的代码一定要多看、多思考、多练习才会有进步和提高,纸上得来终觉浅、绝知此事要躬行。
参考blog:https://blog.csdn.net/feixiaoxing/article/details/7976857