[连载]嵌入式实时操作系统TINIUX设计与实现
本帖最后由 时飞 于 2018-3-3 20:23 编辑嵌入式实时操作系统TINIUX设计与实现
名称:TINIUX - A tiny and efficient embedded real time operating system (RTOS)
芯片MCU:ARM Coterx-M3内核与MCS-51 8051内核,具体型号选用STM32F1系列与STC8系列。
开发平台:Keil v5 与 Code::Blocks+SDCC
代码许可:遵循MIT开源许可协议,可以免费在商业产品中使用,不需要公布应用源码,没有任何潜在商业风险。
源码托管网址:github.com/SenseRate/TINIUX
以微型嵌入式实时操作系统TINIUX V2.1.0为基础,逐步展开对系统的讲解,揭开嵌入式实时操作系统的神秘面纱。在讲解的过程中,我们尽量做到避免芯片型号之间的差异性,力争做到同系列芯片下的普适性。
欢迎论坛的朋友们加入,一起开发、完善。当然,也欢迎朋友们来撕、来踩……
围观 本帖最后由 时飞 于 2017-2-25 15:20 编辑
为了方便嵌入式操作系统的跨平台移植,我们先为操作系统定义一些必要的数据类型,同时这些数据类型要尽量避免与用户应用程序的数据类型相冲突。
在此我们给系统定义基本的数据类型如下:
typedef unsigned char uOS8_t;
typedef char sOS8_t;
typedef unsigned short uOS16_t;
typedef signed short sOS16_t;
typedef unsigned int uOS32_t;
typedef signed int sOS32_t;
typedef uOS32_t uOSStack_t;
typedef sOS32_t sOSBase_t;
typedef uOS32_t uOSBase_t;
typedef uOS32_t uOSTick_t;
typedef enum {OS_FALSE = 0, OS_TRUE = !OS_FALSE} uOSBool_t;
typedef enum {OS_SUCESS = 0, OS_ERROR = !OS_SUCESS} uOSStatus_t;
数据类型中的下划线 _t代表Type
下面我们定义一些和具体平台相关的一些参数,也就是和STM32F1相关的参数
#define FITSTACK_GROWTH ( -1 )
#define FITBYTE_ALIGNMENT ( 8 )
因为是和平台相关的,我们定义相关参数的前缀为FIT,代表需要根据不同的芯片类型进行调整。用此前缀也便于提醒用户,在进行系统移植时,一定要注意带有前缀fit相关的参数、变量和函数;
上述宏定义的参数中FITSTACK_GROWTH 代表栈增长的方向, 我们用-1代表从高位置向低位置增长,用1代表从低位置向高位值增长;宏定义参数FITBYTE_ALIGNMENT代表数据对齐方式;在Cortex-M3系列的STM32F1芯片中,栈增长方向为从高到低,我们定义为-1,芯片为32位宽的,我们定义8字节对齐;这两个参数对嵌入式操作系统的内存布局影响非常大,后面我们会具体分析!
本帖最后由 时飞 于 2017-2-25 14:13 编辑
dirtwillfly 发表于 2017-2-25 10:34
围观
感谢版主来捧场!{:smile:}
我们在此种下一颗种子,至于能否发芽、长大、开花、结果,那就看“造化”了,呵呵!
最近一直在给这个系统想个名称,身边的朋友们也有推荐,论坛的朋友们有好的想法也请站内短信给我,在此先表示感谢了!
嵌入式实时操作系统一般都运行在资源非常有限的芯片上,对内存管理要求比较严格,若使用方式比较粗放,很可能导致内存紧张。在此,我们先提出对内存管理的几点要求,后面再按照这个要求逐步实现。有兴趣的朋友也可以一起思考,共同实现!
对内存管理的相关要求如下:
1、在嵌入式操作系统未使用之前,为其分配的为一块连续的内存空间;
2、该内存空间可以由用户自由指定,即内存的起始位置可以进行配置,方便应用开发,也便于对内存的安全管理;
3、在用户使用时,可以从连续的内存块中自由取出指定大小的内存区域,以供专门的使用,比如为系统任务、消息队列、信号量、互斥锁等分配内存空间;
4、在用户释放内存空间后(如任务生命期结束,释放该任务占用的内存空间),可以由系统进行回收。若出现内存碎片,则相邻的内存碎片可以合并成一个较大的内存区域,供下次内存分配使用;
内存管理永远是一个操作系统中比较核心的内容,即使考虑的再周到也难以避免内存碎片的问题。尤其是动态内存的频繁分配与释放,往往会导致某些“破碎”的内存不可再次使用,尤其是嵌入式实时操作系统,内存资源更加宝贵。因此,在使用时,我们尽量把内存使用方式静态化——内存专用,即分给某一任务、消息队列、信号量或者互斥锁等的内存尽量固定下来,避免对内存的频繁分配与释放;
跟上学习。。加油 小板凳坐好,开始学习^_^ 欢迎 zhiqingdianzi和infofans前来捧场,欢迎欢迎~
为嵌入式实时操作系统分配的内存大小可由用户进行配置。
#define OSMEM_SIZE OSTOTAL_HEAP_SIZE
用户可以通过上面的宏定义变量OSTOTAL_HEAP_SIZE进行配置,为系统分配特定大小的内存空间。上面我们对此进行再次定义,主要目的是与用户的配置隔离开,以便内存管理相关的函数操作不受用户配置的影响。下面内存管理相关的操作变量均采用宏定义变量OSMEM_SIZE实现;
由于嵌入式实时操作系统内存资源比较宝贵,在进行设计的时候,我们需要进行“量体裁衣”,尽量避免资源浪费的情况出现;接下来我们根据用户分配的内存大小设置内存控制变量的类型;
#if OSMEM_SIZE > 64000L
typedef uOS32_t uOSMemSize_t;
#else
typedef uOS16_t uOSMemSize_t;
#endif
上述代码比较简单明了,若用户定义的内存长度超出64000,我们使用uOS32_t作为内存管理的变量类型,若长度小于64000,则使用uOS16_t作为内存管理的变量类型;细心的朋友可能发现了,uOS16_t的表示范围到65535了,这里怎么使用64000作为分界线呢?是的,因为内存管理自身的相关操作也需要占用内存,这句话读起来好像有点拗口啊。在内存管理时,我们为内存块设置了若干个信息记录表(表的数量与内存块的数量是一一对应的),信息记录表用于记录该内存块的使用情况,如该内存块的前一个相邻内存块位置,后一个相邻内存块位置,以及该内存块是否已经被分配使用等等;冗余的1535个位置即为内存块记录信息使用,若系统中分配的内存块较多,则会占用的较多,一般情况下,1535的大小足够内存信息记录表使用了;
下面我们为内存定义最小可分配的内存块大小,默认为12个字节长;
#ifndef MIN_SIZE
#define MIN_SIZE 12
#endif
定义此最小内存块大小的目的也是为了避免内存区出现琐碎的碎片,从而导致碎片不可再次回收利用;
在内存定义实现之前,我们再来看一个内存对齐的宏定义;
#ifndef OSMEM_ALIGN_SIZE
#define OSMEM_ALIGN_SIZE(size) (((size) + OSMEM_ALIGNMENT - 1) & ~(OSMEM_ALIGNMENT-1))
#endif
在STM32F1系列的芯片中,我们定义OSMEM_ALIGNMENT为4字节对齐方式;通过OSMEM_ALIGN_SIZE的宏定义,我们可以获取4字节的整数倍数值,例如,若指定size大小为13或者15,则返回数值为16;
内存表信息结构体定义如下;
typedef struct _tOSMem
{
uOSMemSize_t NextMem; /*相邻的后一内存块地址 */
uOSMemSize_t PrevMem; /*相邻的前一内存块地址*/
uOS8_t used; /*内存块的使用标志1: 该内存块已使用; 0: 未使用*/
}tOSMem_t;
对齐后的内存区块数值大小由如下宏定义实现:
#define SIZEOF_OSMEM_ALIGNED OSMEM_ALIGN_SIZE(sizeof(tOSMem_t))
#define OSMEM_SIZE_ALIGNED OSMEM_ALIGN_SIZE(OSMEM_SIZE)
下面就是操作系统内存区域具体的实现方式了:
#ifndef OSRAM_HEAP_POINTER
uOS8_t OSRamHeap;
#define OSRAM_HEAP_POINTER OSRamHeap
#endif
上述OSRAM_HEAP_POINTER宏定义变量为系统所需的内存指针,该指针可由用户自行配置,指向具体的内存区。如若未配置,则通过数组的形式进行分配。
数组的长度=用户指定的长度+内存信息表的长度*2+内存对齐最小长度;
上述长度数值均是按照内存对齐方式处理过之后的长度数值;从上面的宏定义我们可以看出,若用户没有配置OSRAM_HEAP_POINTER的具体数值,系统则会把该变量指向数组OSRamHeap的首地址;
到现在,我们已经实现了上面提到的内存管理方面的第1和第2两项要求了,接着就是内存管理的具体实现方式了;
一些朋友看到我上面的描述可能会头晕目眩,不知所云。犹如苏轼那首经典古诗——题西林壁描述的一样:横看成岭侧成峰,远近高低各不同。不识庐山真面目,只缘身在此山中。这时,朋友们需要跳出这个圈子,从整体上对系统的内存管理进行重新认识。下面我们从整体上给内存管理功能“画一个轮廓”。
在刚刚为系统分配内存区域之后,这块内存区域一定是一个连续的空间。为了便于管理,我们在这块连续的内存空间中插入内存块信息头,记录内存块的基本信息:即前面提到的与该内存块相邻的前一块内存地址,后一块内存地址,当前内存块是否已经被分配使用等等;下图即为刚刚分配完的内存分布示意图;
图示:刚刚分配完毕时的内存分布示意图
为了便于管理,我们采用全局变量gpOSMemBegin记录第一块内存地址,用全局变量gpOSMemEnd记录最后一块的内存地址;同时为了方便更快速的分配内存,我们用全局变量gpOSMemLFree记录第一块未被分配使用的内存地址;
从上图中我们可以看出,对于刚刚分配好的内存区域,我们对其初始化为两个内存块;第一个内存块标记为未被使用的内存块,信息参数Used设置为0。其信息参数NextMem初始化为OSMEM_SIZE_ALIGNED,即指向最后一个内存块信息头。因为其自身就是第一个内存块,前面已经没有可用内存块了,我们把其参数PrevMem初始化为0。我们把第二个内存块设置为最后一个内存块,永远不能被系统分配使用,仅仅用其记录内存区域的尾部,因此其信息参数Used设置为1,表示已经使用,无法被系统再次分配使用。该内存块的信息参数NextMem与PrevMem均初始化为OSMEM_SIZE_ALIGNED,即永远指向自己。
至此,系统的内存区域已经初始化完毕了,下面请朋友们猜想一下,系统具体是如何分配使用内存的呢?没错,从第一个内存块切分!是的,系统若需要使用某一大小的内存块,则需要从上述初始化完毕的第一个内存块中切分出一块使用。为了方便管理,系统需要把切分出的内存块信息记录下来,并标记上已经在使用了;若系统运行一段时间后,某一块具体内存块不再需要了,就需要把该内存块释放掉。所谓内存释放也比较简单,只需要把内存块的使用标志清除掉即可。为了便于检测,在清除内存时,也可以把内存区设置为默认值。
图示:系统运行一段时间之后的内存分布示意图
上图表示运行一段时间之后系统的内存分布示意图;上图为了画图方便,指针的指示位置有所出入;在实际使用中,内存块信息参数中的NextMem指向下一个内存块信息的首地址,内存块信息参数PrevMem指向前一个内存块信息的首地址。
为了防止内存出现碎片的情况,我们在内存释放时还需要对相邻的前后内存使用情况进行检测,若相邻区域的内存块未使用,则需要把这两个相邻的未被使用的内存块合并起来;
本帖最后由 时飞 于 2018-3-3 20:27 编辑
经过几天的筛选,决定给这个嵌入式实时操作系统命名为“TINIUX”。
TiniUX相当于Tiny Unix的简称,小巧的类Unix系统吧,但是更突出在实时性,即RTOS。字母组合的谐音就是"蹄牛"操作系统了!
目前系统在8位51单片机及32位ARM Cortex平台均运行正常!系统中的变量使用自身定义的类型,相信在其它平台的移植会非常方便。
欢迎朋友们在项目中使用!
时飞 发表于 2017-2-25 23:52
欢迎 zhiqingdianzi和infofans前来捧场,欢迎欢迎~
为嵌入式实时操作系统分配的内存大小可由用户进行配置 ...
#define SIZEOF_OSMEM_ALIGNED OSMEM_ALIGN_SIZE(sizeof(tOSMem_t))// 这是用户指定的长度
#define OSMEM_SIZE_ALIGNED OSMEM_ALIGN_SIZE(OSMEM_SIZE)//内存信息表的长度
对吧
还有上面
#define OSMEM_ALIGN_SIZE(size) (((size) + OSMEM_ALIGNMENT - 1) & ~(OSMEM_ALIGNMENT-1))
还没明白过来。 zhiqingdianzi 发表于 2017-2-26 21:21
#define SIZEOF_OSMEM_ALIGNED OSMEM_ALIGN_SIZE(sizeof(tOSMem_t))// 这是用户指定的长度
#defin ...
#define SIZEOF_OSMEM_ALIGNED OSMEM_ALIGN_SIZE(sizeof(tOSMem_t))// 内存信息表的长度
#define OSMEM_SIZE_ALIGNED OSMEM_ALIGN_SIZE(OSMEM_SIZE)//用户设置的长度
#define OSMEM_ALIGN_SIZE(size) (((size) + OSMEM_ALIGNMENT - 1) & ~(OSMEM_ALIGNMENT-1))
这个宏定义的作用是获取传入的size数值,并按照内存对齐的方式进行处理,得到的最终结果是不小于size的数值,并且这个数值满足内存对齐字节的整数倍;
举个例子来讲,若内存按照4字节对齐,输入的size取值为6、7或者8,则经过宏定义处理后,返回的都是8 想学习,请问有GitHub链接吗,这样方便学习 本帖最后由 时飞 于 2018-3-3 20:34 编辑
ianhom 发表于 2017-2-26 23:48
想学习,请问有GitHub链接吗,这样方便学习
感谢 ianhom前来捧场!
GitHub我们刚刚搭建起来,还没有来得及更新,地址如下:
github.com/SenseRate/TINIUX
时飞 发表于 2017-2-27 00:17
感谢 ianhom前来捧场!
GitHub我们刚刚搭建起来,还没有来得及更新,地址如下:
已关注,期待文档更新{:smile:} 时飞 发表于 2017-2-26 22:59
#define SIZEOF_OSMEM_ALIGNED OSMEM_ALIGN_SIZE(sizeof(tOSMem_t))// 内存信息表的长度
#define...
谢谢,一讲解马上就懂了。 本帖最后由 时飞 于 2017-2-28 07:15 编辑
描述完毕“内存管理”的整个轮廓之后,我们来查看具体的内存管理函数是怎么实现的;
首先我们查看一下内存初始化函数OSMemInit,这个函数很简单,主要为全局变量gpOSMemBegin,gpOSMemEnd与gpOSMemLFree分配初始数值;
/*****************************************************************************
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;
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;
}
接着是内存分配函数。
/*****************************************************************************
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)->NextMem)
{
ptOSMemTemp = (tOSMem_t *)(void *)&gpOSMemBegin;
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;
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)->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;
}
}
pResult = (uOS8_t *)ptOSMemTemp + SIZEOF_OSMEM_ALIGNED;
break;
}
}
OSIntUnlock();
return pResult;
}
内存释放函数。
/*****************************************************************************
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);
}
OSIntUnlock();
return;
}
代码已经更新到Github上了,感兴趣的朋友请移步到Github查看更多源代码。
到现在,我们已经实现了上面提到的内存管理方面的第3和第4两项要求了。这样,嵌入式实时操作系统AIOS的内存管理已经基本实现了;
由于大部分的微控制器芯片资源非常有限,部分嵌入式操作系统在设计时没有单独的内存管理模块,而是在使用时临时分配。例如ucos嵌入式操作系统,在创建任务时,总是通过一个数组的形式分配一段内存空间,然后把数组的首地址及长度传递给待创建的任务使用。这样相当于把内存管理分散化,但是如果要整体设置系统的内存位置就不那么方便了。
内存管理已经基本完成了,在进行下一步开发之前,我们先熟悉一下ARM汇编编程规则:
1. 基本概念
• ATPCS (ARM-Thumb Procedure Call Standard)
规定了一些子程序间调用的基本规则,这些规则包括子程序调用过程中寄存器的使用规则,数据栈的使用规则,参数的传递规则。有了这些规则之后,单独编译的C语言程序就可以和汇编程序相互调用。
使用ADS的C语言编译器编译的C语言子程序满足用户指定的ATPCS类型。而对于汇编语言来说,则需要用户来保证各个子程序满足ATPCS的要求。
• AAPCS (ARM Archtecture Procedure Call Standard)
2007年ARM公司正式推出了AAPCS标准,AAPCS是ATPCS的改进版,目前, AAPCS和ATPCS都是可用的标准。
2. 寄存器使用规则
• 子程序间通过寄存器R0~R3来传递参数。这时,寄存器R0~R3可记作a0~a3。被调用的子程序在返回前无需恢复寄存器R0~R3的内容。
• 在子程序中,使用寄存器R4~R11来保存局部变量。这时,寄存器R4~R11可以记作v1~v8。如果在子程序中使用了寄存器v1~v8中的某些寄存器,则子程序进入时必须保存这些寄存器的值,在返回前必须恢复这些寄存器的值。在Thumb程序中,通常只能使用寄存器R4~R7来保存局部变量。
• 寄存器R12用作过程调用中间临时寄存器,记作IP。在子程序之间的连接代码段中常常有这种使用规则。
• 寄存器R13用作堆栈指针,记作SP。在子程序中寄存器R13不能用作其他用途。寄存器SP在进入子程序时的值和退出子程序时的值必须相等。
• 寄存器R14称为连接寄存器,记作LR。它用于保存子程序的返回地址。如果在子程序中保存了返回地址,寄存器R14则可以用作其他用途。
• 寄存器R15是程序计数器,记作PC。它不能用作其它用途。
3. 堆栈使用规则
• ATPCS规定堆栈为FD(Full Descending: sp指向最后一个压入的值,数据栈由高地址向低地址生长)类型,即满递减堆栈,并且对堆栈的操作是8字节对齐。所以经常使用的指令就有STMFD和LDMFD。
•对于汇编程序来说,如果目标文件中包含了外部调用,则必须满足下列条件:
(1)外部接口的堆栈必须是8字节对齐的。
(2)在汇编程序中使用PRESERVE8伪指令告诉连接器,本汇编程序数据是8字节对齐的。
4. 参数传递规则
• 根据参数个数是否固定,可以将子程序分为参数个数固定的子程序和参数个数可变化的子程序。
• 这两种子程序的参数传递规则是不一样的。
4.1 参数个数可变子程序参数传递规则
• 对于参数个数可变的子程序,当参数个数不超过4个时,可以使用寄存器R0~R3来传递参数;当参数超过4个时,还可以使用堆栈来传递参数。
• 在传递参数时,将所有参数看作是存放在连续的内存字单元的字数据。然后,依次将各字数据传递到寄存器R0,R1,R2和R3中。如果参数多于4个,则将剩余的字数据传递到堆栈中。入栈的顺序与参数传递顺序相反,即最后一个字数据先入栈。
4.2 参数个数固定子程序参数传递规则
• 如果系统不包含浮点运算的硬件部件,浮点参数会通过相应的规则转换成整数参数(若没有浮点参数,此步省略),然后依次将各字数据传送到寄存器R0~R3中。如果参数多于4个,将剩余的字数据传送堆栈中,入栈的顺序与参数顺序相反,即最后一个字数据先入栈。在参数传递时,将所有参数看作是存放在连续的内存字单元的字数据。
5. 子程序结果返回规则
子程序中结果返回的规则如下:
• 结果为一个32位整数时,可以通过寄存器R0返回;
• 结果为一个64位整数时,可以通过寄存器R0和R1返回;
• 结果为一个浮点数时,可以通过浮点运算部件的寄存器f0、d0或s0来返回;
• 结果为复合型浮点数(如复数)时,可以通过寄存器f0~fn或d0~dn来返回;
• 对于位数更多的结果,需要通过内存来传递。
太棒了!跟着学习下,期待更新! 小巧稳定的系统,支持
页:
[1]
2