uCOS系统的思考

 一:  世界潮流,浩浩汤汤,顺之者昌,逆之者亡---孙中山

      从80X86到ARM9,再从ARM9到ARM7,平台是越做越简单,但是简单并不是意味着退步,反而是种潮流趋势。。。

      在CISC道路上渐行渐远的INTEL,在移动市场无所作为,而且服务器市场也面临着ARM的潜在威胁。

      废话不多说,还是直入主题:

ARM7的特性:

        1:

        采用RISC架构的MCU最显而易见的优点就是:

        统一寻址模式,访问外设就像访问内存一样简单。而X86上则需要用特殊的IO操作指令来访问外设。

       

       2:

       差强人意的保护机制:

       ARM7只提供了2中权限:

                   Handler Mode 

                   Thread Mode:特权级/非特权级

       一般来说,OS的代码使用特权级,APP代码使用非特权级,虽然可以保护OS代码不受非法访问。

       但是没有MMU单元,APP的代码访问空间则得不到限制,线程之间可能互相破坏。所以你再也遇不到segment fault了。。转而是顷刻之间系统崩溃。。。。

 

      事情正在起变化----毛泽东

       我通读过Linux Kernel的大部分源代码,特别是关于进程调度、内存管理这部分。以为掌握了现代操作系统的设计的精髓。。。面对麻雀般的uCOS2,难免有些轻视。。。


      事情并非如此简单,原因是UCOS2的进程调度策略与现代的OS有很大的区别。

 

     时间片轮转算法:

       uCOS2的调度策略并没有采用这个算法,我想大概与uCOS2是一个实时系统有关。

       如果高优先级的进程不主动释放CPU,那么低优先级的进程永远也得不到运行。

       Linux实现了时间片轮转,只是高优先级的进程时间片长,优先运行。好处显而易见,高优先级的如果跑飞不会导致整个系统宕掉。

 

    char类型引发的血案:

      一个BUG。。。最后查了好久才发现是char类型的问题。

      因为keil编译器char类型默认被解释为unsigned char [0-255]

      而GCC/VC编译器char类型默认被解释为signed char [-128, 127]

 

      由于出现了这种代码

     char a;

      while(a-- >=0)

     { ....}

    所以程序就再也不会退出了。。。之所以查了好久,是我理所当然的认为char会被默认解释为signed char...

   之后我翻阅了C99 Doc。。。。在没有加前缀的情况下,char的具体类型由编译器决定。。。

               

     从LINUX&GCC转入UCOS2&KEIL,一切都变了。。看来我得重新审视一下这个小系统了。 

         

      uCOS2:

      uCOS2唯一值得学习的一个地方就是关于进程调度的O(1)算法

      最简单也是最愚蠢的方法是维护一个链表List。

      这种方法的问题是:当一个Thread就绪时,如果根据其优先级插入List,则算法的时间复杂度为O(n)。

      Linux采用了Bitmap,uCOS2也不例外。当然uCOS2的处理更简单,因为uCOS2必须在系统编译时配置好支持的Thread最大数量,用来分配Bitmap。

      例如:

      支持64, 则分配8字节的Bitmap,如果支持256,则分配16字节的Bitmap。

      Bitmap的每一个Bit代表thread的状态,如果Bit为1则表示就绪,0则表示挂起。

      以64为例:

    uCOS系统的思考_第1张图片

       在Bitmap中如何快速找到为1的BIT,再转换成THREAD的优先级?

       如果采用扫描的方式,按位与操作不是好方法,这里涉及循环和计数。所以操作时间和THREAD数量又成O(N)了。

       所以uCOS2采用了分组的方法:8个字节64BIT,分为8*8,如图。

       用OSRdyGrp变量的BIT来指示哪个分组里有就绪的THREAD,然后在提取出来该分组,再从该分组中找到优先级最高的THREAD。

       线程优先级Priority和分组的关系如图。也就是X,Y如何组成Priority。

  还有一个问题:如何找到优先级最高的线程?Priority值越小,Thread优先级越高。

  比如OSRdyGrp = 0x10001000,OSRdyTbl[3]=0x10001000.

       很明显OSRdyGrp的BIT4和OSRdyTbl[3]的BIT4代表的Thread 27才是我们想要的。

   

  为了解决这个问题,uCOS2又采用的了一个快速转换表OSMapTbl。

       该转换表原理如下: 0x10001000 --->转换表---->3

                                        0x00001000 --->转换表--->3

        1字节8BIT,有256种BIT组合,但是组合方式返回的值是可以提前算好,存在OSMapTbl中。

      比如OSRdyGrp=0x11111111和OSRdyGrp=0x00000001都应该返回0. [0代表第一个分组]

       所以OSMapTbl如下:

      

INT8U const OSUnMapTbl[256] = {
0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x00 to 0x0F */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x10 to 0x1F */
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x20 to 0x2F */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x30 to 0x3F */
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x40 to 0x4F */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x50 to 0x5F */
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x60 to 0x6F */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x70 to 0x7F */
7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x80 to 0x8F */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x90 to 0x9F */
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0xA0 to 0xAF */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0xB0 to 0xBF */
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0xC0 to 0xCF */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0xD0 to 0xDF */
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0xE0 to 0xEF */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 /* 0xF0 to 0xFF */
};  可以看出OSMapTbl[0]和OSMapTbl[255]都是0。

       

       Jean J. Labrosse,uCOS的作者,一个不懂哲学的人?

       uCOS诞生于1992年,我想想,那个时候Linus在芬兰已经设计出了Linux的第一版本Beta0.12。David Cluter在西雅图为MicrioSoft第一个现代的版本操作系统内核NT忙的焦头烂额。

        上个世纪90年代,OS设计已经进入了工业界,在学术界早已盖棺论定.

        Labrosse在设计uCOS竟然没有采用最基本C/S模型。这也是设计OS的最基本哲学之一。

        也就是Kernel为Server,App Thread为client。

       App Thread 通过Kernel 提供的系统调用发出请求,具体操作由Kernel完成。

 

       以X86平台为例,在Linux/Windows上,比如打开文件的Open函数,在libc库中的实现一定类似于:

            MOV  AX, XX

            MOV  BX, XX

            MOV  CX, XX

            MOV  DX, XX

            INT 0X80

这里并不涉及文件操作的任何代码,而是APP向OS发出一个请求:我要打开FILE,请帮我打开。

       1. BX、CX、DX用来保存Open的参数(FILE的路径,打开模式).

       2. AX用来保存系统调用号,因为有很多系统调用,比如OPEN /WRITE/READ/CLOSE,他们各自对应一个调用号,比如OPEN对应1,WRITE对应2等等。。。 

       3. INT指令用来产生软中断。

       软中断处理函数(exception handler),通过AX换算得到打开FILE操作的真正地址去执行。

 

这样的好处是:

       任何线程不能直接控制硬件资源,只能提交请求,这样最大保证了系统的稳定性。

      

       当然这个和CPU有莫大的关系,如果CPU没有提供类似于INT的软中断触发指令,和相应的保护模式,则不能实现现代OS。

       作者可能受制于MOTO的芯片,才设计一个不符合惯例的OS.

       OS Kernel / interrupt routine / app 采用一样的特权级。app任何微小的错误将导致整个系统崩溃。。。。

       怪不得有朋友说,这类似于古老的DOS。。。

       

 贡献两个技巧:

  1 如何检验栈的使用量:

      其实非常简单的操作,每次线程创建时,把栈空间全部清零。然后定期检测栈中非零的空间,就知道栈的使用量了。【只有线程运行到一定程度时,线程栈使用量才准确】

   

 2 如何判断栈是否溢出:

      在uCOS中创建任务时,在任务控制块中保存堆栈的栈底地址,并在栈底地址所对应的内存块中写入MAGIC NUMBER,  定期检查该字节是否被更改,用此来判断堆栈是否溢出。

你可能感兴趣的:(thread,linux,算法,exception,OS,编译器)