操作系统是很多人每天必须打交道的东西,因为在你打开电脑的一刹那,随着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

1.linux系统基础笔记(互斥量、信号量)的更多相关文章

  1. 2.linux系统基础笔记(延时操作、实时系统中的定时器、事件)

    延时操作 延时操作是操作系统中经常遇到的一种情形.延时的原因很多,有的时候是为了等待外设芯片处理结束,有的时候是为了暂时释放cpu的使用权,有的就是为了希望在一段时间获取资源,如果没法在单位时间内获取 ...

  2. [转]一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程

    一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程 希望此文能给初学多线程编程的朋友带来帮助,也希望牛人多多指出错误. 另外感谢以下链接的作者给予,给我的学习带来了很大帮助 http ...

  3. Linux实战教学笔记06:Linux系统基础优化

    第六节 Linux系统基础优化 标签(空格分隔):Linux实战教学笔记-陈思齐 第1章 基础环境 第2章 使用网易163镜像做yum源 默认国外的yum源速度很慢,所以换成国内的. 第一步:先备份 ...

  4. Linux系统基础命令

    这是看itercast的学习笔记 Linux系统基础命令 日期时间 命令date用以查看.设置当前系统时间:格式化显示时间: +%Y--%m--%d 命令hwclock(clock)用以显示硬件时钟时 ...

  5. Linux系统学习笔记:文件I/O

    Linux支持C语言中的标准I/O函数,同时它还提供了一套SUS标准的I/O库函数.和标准I/O不同,UNIX的I/O函数是不带缓冲的,即每个读写都调用内核中的一个系统调用.本篇总结UNIX的I/O并 ...

  6. Linux系统安全笔记

    Linux系统安全笔记 https://insecure.org/https://sectools.org/SecTools.Org:排名前125的网络安全工具 http://www.ibm.com/ ...

  7. linux系统基础入门

    使用工具:源码管理,自动部署,web服务器(linux) linux系统基础入门 1.下载地址 2.本文介绍的是一个基于Ubuntu的开源操作系统 下载优麒麟 Ubuntu是一个广泛应用于个人电脑,云 ...

  8. Linu之linux系统基础优化和基本命令

    Linux系统基础优化和基本命令 网络参数设定命令 ifconfig: 查询,设置网卡和ip等参数 ifup,ifdown: 脚本命令,更简单的方式 ip: 符合指令,直接修改上述功能 编辑网卡配置文 ...

  9. (转)Linux系统基础网络配置老鸟精华篇

    Linux系统基础网络配置老鸟精华篇 原文:http://blog.51cto.com/oldboy/784625 对于linux高手看似简单的网络配置问题,也许要说出所以然来也并不轻松,因此仍然有太 ...

随机推荐

  1. Oracle数据库之七 多表查询

    七.多表查询 ​ 对于查询在之前已经学过了简单查询.限定查询.查询排序,这些都属于 SQL 的标准语句,而上一章的单行函数,主要功能是为了弥补查询的不足. ​ 而从多表查询开始就正式进入到了复杂查询部 ...

  2. Python 的整数与 Numpy 的数据溢出

    某位 A 同学发了我一张截图,问为何结果中出现了负数? 看了图,我第一感觉就是数据溢出了.数据超出能表示的最大值,就会出现奇奇怪怪的结果. 然后,他继续发了张图,内容是 print(100000*20 ...

  3. Storm 系列(六)—— Storm 项目三种打包方式对比分析

    一.简介 在将 Storm Topology 提交到服务器集群运行时,需要先将项目进行打包.本文主要对比分析各种打包方式,并将打包过程中需要注意的事项进行说明.主要打包方式有以下三种: 第一种:不加任 ...

  4. 小白专场-多项式乘法与加法运算-python语言实现

    目录 题意理解 解题思路 多项式加法 多项式乘法 完整代码 题意理解 题目: 设计函数分别求两个一元多项式的乘积与和. 输入格式: 输入分2行,每行分别先给出多项式非零项的个数,再以指数递降方式输入一 ...

  5. C# Post Get 方式发送请求

    httpPost 方式发送请求 不带参数 /// <summary> /// 没有参数的post请求 /// </summary> public void HttpPostNo ...

  6. [淘宝客技术篇005]如何取站点id和推广位id

    我们知道,生成一个用于推广的淘客链接,是需要指定对应的站点id和推广位id的,也就是siteid和adzoneid. 今天,火星来客跟大家分享两个不同的方法获取站点id和推广位id. 方法一:直接获取 ...

  7. UGUI_游戏菜单场景切换

    事件委托 GameManger(空物体)+GameManger脚本——重要的方式 public class GameManger : MonoBehaviour { public void OnSta ...

  8. JSP实例:彩色验证码

    本例使用一个JavaBean,名为Image.java,包com.zempty.bean下; 三个JSP文件,分别为image.jsp.login.jsp.check.jsp.其中login.jsp是 ...

  9. Java 教程 (Java 对象和类)

    Java 对象和类 Java作为一种面向对象语言.支持以下基本概念: 多态 继承 封装 抽象 类 对象 实例 方法 重载 本节我们重点研究对象和类的概念. 对象:对象是类的一个实例(对象不是找个女朋友 ...

  10. 多线程——Callable接口

    package pers.aaa.callable; import java.util.concurrent.Callable; public class MyCallable implements ...