[连载]嵌入式实时操作系统AIOS设计与实现

2019-10-14 22:23发布

本帖最后由 时飞 于 2017-2-26 18:21 编辑

目标:从零开始设计一款嵌入式实时操作系统(RTOS)
名称:AIOS - Advanced Input Output System
参考对象:ucos vxWorks eCos RTEMS等
芯片:ARM Coterx-M3内核,从STM32F1系列芯片杨帆起航……
开发平台:Keil v5
调试工具:JLink v8
代码许可:遵循GPLv2+开源许可协议,商业应用更友好,不需要公布应用源码,没有任何潜在商业风险。
源码托管:https://github.com/SenseRate/AIOS

三年前,朋友送给我一款STM32F1的开发板,从此进入嵌入式开发领域。三年多以来,做过大大小小的不同项目,在此对我的朋友进行感谢!

原子兄弟的开发板提供的教学示例给了我很大的启发,在此一并感谢,谢谢!

从零开始开发一款嵌入式实时操作系统是一件任重而道远的事情,三年多以来,不断的进行开发、修改、完善,目前已经有了雏形,现在进行重新设计,并通过连载的形式逐步呈现给大家,也欢迎论坛的朋友们加入,一起开发、完善!

当然,也欢迎朋友们来撕、来踩……

暂时发在此版块,若版主觉得不恰当,烦请移动到合适的版块……

另外,最近查找了一下,原子兄弟的STM32F1开发板没有找到,论坛中若朋友们有冗余的,可以转让给我一块,请站内短信联系。









补充内容 (2018-3-3 18:06):
此系统已经更新为嵌入式实时操作系统TINIUX
git网址:https://github.com/SenseRate/TINIUX
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
14条回答
时飞
1楼-- · 2019-10-16 02:28
本帖最后由 时飞 于 2017-2-28 07:10 编辑

描述完毕“内存管理”的整个轮廓之后,我们来查看具体的内存管理函数是怎么实现的;

首先我们查看一下内存初始化函数OSMemInit,这个函数很简单,主要为全局变量gpOSMemBegin,gpOSMemEnd与gpOSMemLFree分配初始数值;
[mw_shl_code=c,true]/*****************************************************************************
Function    : OSMemInit
Description : Zero the heap and initialize start, end and lowest-free pointer.
Input       : None
Output      : None
Return      : None
*****************************************************************************/
void OSMemInit(void)
{
        tOSMem_t *ptOSMemTemp;

        // align the heap
        gpOSMemBegin = (uOS8_t *)OSMEM_ALIGN_ADDR(OSRAM_HEAP_POINTER);
        
        // initialize the start of the heap
        ptOSMemTemp = (tOSMem_t *)(void *)gpOSMemBegin;
        ptOSMemTemp->NextMem = OSMEM_SIZE_ALIGNED;
        ptOSMemTemp->PrevMem = 0;
        ptOSMemTemp->Used = 0;
        
        // initialize the end of the heap
        gpOSMemEnd = (tOSMem_t *)(void *)&gpOSMemBegin[OSMEM_SIZE_ALIGNED];
        gpOSMemEnd->Used = 1;
        gpOSMemEnd->NextMem = OSMEM_SIZE_ALIGNED;
        gpOSMemEnd->PrevMem = OSMEM_SIZE_ALIGNED;

        // initialize the lowest-free pointer to the start of the heap
        gpOSMemLFree = (tOSMem_t *)(void *)gpOSMemBegin;
}
[/mw_shl_code]
接着是内存分配函数。
[mw_shl_code=c,true]/*****************************************************************************
Function    : OSMemMalloc
Description : Allocate a block of memory with a minimum of 'size' bytes.
Input       : size -- the minimum size of the requested block in bytes.
Output      : None
Return      : pointer to allocated memory or OS_NULL if no free memory was found.
              the returned value will always be aligned (as defined by OSMEM_ALIGNMENT).
*****************************************************************************/
void* OSMemMalloc(uOSMemSize_t size)
{
        uOS8_t * pResult = OS_NULL;
        uOSMemSize_t ptr, ptr2;
        tOSMem_t *ptOSMemTemp, *ptOSMemTemp2;

        if(gpOSMemEnd==OS_NULL)
        {
                OSMemInit();
                if(gpOSMemEnd==OS_NULL)
                {
                        return pResult;
                }
        }
        if (size == 0)
        {
                return pResult;
        }

        // Expand the size of the allocated memory region so that we can
        // adjust for alignment.
        size = OSMEM_ALIGN_SIZE(size);

        if(size < OSMIN_SIZE_ALIGNED)
        {
                // every data block must be at least OSMIN_SIZE_ALIGNED long
                size = OSMIN_SIZE_ALIGNED;
        }

        if (size > OSMEM_SIZE_ALIGNED)
        {
                return pResult;
        }

        // protect the heap from concurrent access
        OSIntLock();

        // Scan through the heap searching for a free block that is big enough,
        // beginning with the lowest free block.
        for (ptr = (uOSMemSize_t)((uOS8_t *)gpOSMemLFree - gpOSMemBegin); ptr < OSMEM_SIZE_ALIGNED - size;
                ptr = ((tOSMem_t *)(void *)&gpOSMemBegin[ptr])->NextMem)
        {
                ptOSMemTemp = (tOSMem_t *)(void *)&gpOSMemBegin[ptr];

                if ((!ptOSMemTemp->Used) && (ptOSMemTemp->NextMem - (ptr + SIZEOF_OSMEM_ALIGNED)) >= size)
                {
                        // ptOSMemTemp is not Used and at least perfect fit is possible:
                        // ptOSMemTemp->NextMem - (ptr + SIZEOF_OSMEM_ALIGNED) gives us the 'user data size' of ptOSMemTemp

                        if (ptOSMemTemp->NextMem - (ptr + SIZEOF_OSMEM_ALIGNED) >= (size + SIZEOF_OSMEM_ALIGNED + OSMIN_SIZE_ALIGNED))
                        {
                                // (in addition to the above, we test if another tOSMem_t (SIZEOF_OSMEM_ALIGNED) containing
                                // at least OSMIN_SIZE_ALIGNED of data also fits in the 'user data space' of 'ptOSMemTemp')
                                // -> split large block, create empty remainder,
                                // remainder must be large enough to contain OSMIN_SIZE_ALIGNED data: if
                                // ptOSMemTemp->NextMem - (ptr + (2*SIZEOF_OSMEM_ALIGNED)) == size,
                                // tOSMem_t would fit in but no data between ptOSMemTemp2 and ptOSMemTemp2->NextMem
                                ptr2 = ptr + SIZEOF_OSMEM_ALIGNED + size;
                                // create ptOSMemTemp2 struct
                                ptOSMemTemp2 = (tOSMem_t *)(void *)&gpOSMemBegin[ptr2];
                                ptOSMemTemp2->Used = 0;
                                ptOSMemTemp2->NextMem = ptOSMemTemp->NextMem;
                                ptOSMemTemp2->PrevMem = ptr;
                                // and insert it between ptOSMemTemp and ptOSMemTemp->NextMem
                                ptOSMemTemp->NextMem = ptr2;
                                ptOSMemTemp->Used = 1;

                                if (ptOSMemTemp2->NextMem != OSMEM_SIZE_ALIGNED)
                                {
                                        ((tOSMem_t *)(void *)&gpOSMemBegin[ptOSMemTemp2->NextMem])->PrevMem = ptr2;
                                }
                        }
                        else
                        {
                                // (a ptOSMemTemp2 struct does no fit into the user data space of ptOSMemTemp and ptOSMemTemp->NextMem will always
                                // be Used at this point: if not we have 2 unused structs in a row, OSMemCombine should have
                                // take care of this).
                                // -> near fit or excact fit: do not split, no ptOSMemTemp2 creation
                                // also can't move ptOSMemTemp->NextMem directly behind ptOSMemTemp, since ptOSMemTemp->NextMem
                                // will always be Used at this point!
                                ptOSMemTemp->Used = 1;
                        }

                        if (ptOSMemTemp == gpOSMemLFree)
                        {
                                // Find next free block after ptOSMemTemp and update lowest free pointer
                                while (gpOSMemLFree->Used && gpOSMemLFree != gpOSMemEnd)
                                {
                                        gpOSMemLFree = (tOSMem_t *)(void *)&gpOSMemBegin[gpOSMemLFree->NextMem];
                                }
                        }
                        pResult = (uOS8_t *)ptOSMemTemp + SIZEOF_OSMEM_ALIGNED;
                        break;
                }
        }

        OSIntUnock();

        return pResult;
}
[/mw_shl_code]
内存释放函数。
[mw_shl_code=c,true]/*****************************************************************************
Function    : OSMemFree
Description : Put a tOSMem_t back on the heap.
Input       : pMem -- the data portion of a tOSMem_t as returned by a previous
                      call to OSMemMalloc()
Output      : None
Return      : None
*****************************************************************************/
void OSMemFree(void *pMem)
{
        tOSMem_t *ptOSMemTemp;

        if (pMem == OS_NULL)
        {
                return;
        }

        if ((uOS8_t *)pMem < (uOS8_t *)gpOSMemBegin || (uOS8_t *)pMem >= (uOS8_t *)gpOSMemEnd)
        {
                return;
        }
        
        // protect the heap from concurrent access
        OSIntLock();
        // Get the corresponding tOSMem_t ...
        ptOSMemTemp = (tOSMem_t *)(void *)((uOS8_t *)pMem - SIZEOF_OSMEM_ALIGNED);
        
        //ptOSMemTemp->Used must be 1
        if( ptOSMemTemp->Used==1 )
        {
                // now set it unused.
                ptOSMemTemp->Used = 0;

                if (ptOSMemTemp < gpOSMemLFree)
                {
                        // the newly freed struct is now the lowest
                        gpOSMemLFree = ptOSMemTemp;
                }

                // finally, see if prev or next are free also
                OSMemCombine(ptOSMemTemp);               
        }
        OSIntUnock();
        
        return;
}
[/mw_shl_code]
代码已经更新到Github上了,感兴趣的朋友请移步到Github查看更多源代码。

到现在,我们已经实现了上面提到的内存管理方面的第3和第4两项要求了。这样,嵌入式实时操作系统AIOS的内存管理已经基本实现了;

由于大部分的微控制器芯片资源非常有限,部分嵌入式操作系统在设计时没有单独的内存管理模块,而是在使用时临时分配。例如ucos嵌入式操作系统,在创建任务时,总是通过一个数组的形式分配一段内存空间,然后把数组的首地址及长度传递给待创建的任务使用。这样相当于把内存管理分散化,但是如果要整体设置系统的内存位置就不那么方便了。



时飞
2楼-- · 2019-10-16 07:14
内存管理已经基本完成了,在进行下一步开发之前,我们先熟悉一下ARM汇编编程规则:

1. 基本概念
   &#8226; ATPCS (ARM-Thumb Procedure Call Standard)

    规定了一些子程序间调用的基本规则,这些规则包括子程序调用过程中寄存器的使用规则,数据栈的使用规则,参数的传递规则。有了这些规则之后,单独编译的C语言程序就可以和汇编程序相互调用。
    使用ADS的C语言编译器编译的C语言子程序满足用户指定的ATPCS类型。而对于汇编语言来说,则需要用户来保证各个子程序满足ATPCS的要求。
   &#8226; AAPCS (ARM Archtecture Procedure Call Standard)

     2007年ARM公司正式推出了AAPCS标准,AAPCS是ATPCS的改进版,目前, AAPCS和ATPCS都是可用的标准。

2. 寄存器使用规则
    &#8226; 子程序间通过寄存器R0~R3来传递参数。这时,寄存器R0~R3可记作a0~a3。被调用的子程序在返回前无需恢复寄存器R0~R3的内容。

    &#8226; 在子程序中,使用寄存器R4~R11来保存局部变量。这时,寄存器R4~R11可以记作v1~v8。如果在子程序中使用了寄存器v1~v8中的某些寄存器,则子程序进入时必须保存这些寄存器的值,在返回前必须恢复这些寄存器的值。在Thumb程序中,通常只能使用寄存器R4~R7来保存局部变量。

   &#8226; 寄存器R12用作过程调用中间临时寄存器,记作IP。在子程序之间的连接代码段中常常有这种使用规则。

   &#8226; 寄存器R13用作堆栈指针,记作SP。在子程序中寄存器R13不能用作其他用途。寄存器SP在进入子程序时的值和退出子程序时的值必须相等。
   &#8226; 寄存器R14称为连接寄存器,记作LR。它用于保存子程序的返回地址。如果在子程序中保存了返回地址,寄存器R14则可以用作其他用途。
   &#8226; 寄存器R15是程序计数器,记作PC。它不能用作其它用途。

3. 堆栈使用规则

   &#8226; ATPCS规定堆栈为FD(Full Descending: sp指向最后一个压入的值,数据栈由高地址向低地址生长)类型,即满递减堆栈,并且对堆栈的操作是8字节对齐。所以经常使用的指令就有STMFD和LDMFD。
   &#8226;对于汇编程序来说,如果目标文件中包含了外部调用,则必须满足下列条件:
   (1)外部接口的堆栈必须是8字节对齐的。   
   (2)在汇编程序中使用PRESERVE8伪指令告诉连接器,本汇编程序数据是8字节对齐的。

4. 参数传递规则

    &#8226; 根据参数个数是否固定,可以将子程序分为参数个数固定的子程序和参数个数可变化的子程序。
    &#8226; 这两种子程序的参数传递规则是不一样的。

4.1 参数个数可变子程序参数传递规则

    &#8226; 对于参数个数可变的子程序,当参数个数不超过4个时,可以使用寄存器R0~R3来传递参数;当参数超过4个时,还可以使用堆栈来传递参数。
    &#8226; 在传递参数时,将所有参数看作是存放在连续的内存字单元的字数据。然后,依次将各字数据传递到寄存器R0,R1,R2和R3中。如果参数多于4个,则将剩余的字数据传递到堆栈中。入栈的顺序与参数传递顺序相反,即最后一个字数据先入栈。

4.2 参数个数固定子程序参数传递规则

    &#8226; 如果系统不包含浮点运算的硬件部件,浮点参数会通过相应的规则转换成整数参数(若没有浮点参数,此步省略),然后依次将各字数据传送到寄存器R0~R3中。如果参数多于4个,将剩余的字数据传送堆栈中,入栈的顺序与参数顺序相反,即最后一个字数据先入栈。在参数传递时,将所有参数看作是存放在连续的内存字单元的字数据。

5. 子程序结果返回规则
    子程序中结果返回的规则如下:
    &#8226; 结果为一个32位整数时,可以通过寄存器R0返回;
    &#8226; 结果为一个64位整数时,可以通过寄存器R0和R1返回;
    &#8226; 结果为一个浮点数时,可以通过浮点运算部件的寄存器f0、d0或s0来返回;
    &#8226; 结果为复合型浮点数(如复数)时,可以通过寄存器f0~fn或d0~dn来返回;
    &#8226; 对于位数更多的结果,需要通过内存来传递。

一周热门 更多>