之前在动态内存分配说提到了对应MQX的所分配的一个动态内存块,是要定义一个所有者任务的。至于为什么一定要把动态内存以任务为组织形式来管理,是因为MQX是一个非常强调任务的系统。对应MQX而言,完成功能的载体是任务,分配内存是为了完成功能,那这些的内存的拥有者就必然是任务。之前说过,动态内存分配是采用了BLOCK机制,每一个BLOCK的头都有一个stroeblock头结构体来管理这块内存。stroeblock有两个变量是和任务挂钩的,一个是TASK_NUMBER
,对应着任务的ID号(不是太完善的描述,具体看程序。),另一个就是由同一任务分配的BLOCK所组成的链表,这些在“动态内存分配”那章已经说的很明白了。通过这两个结构,MQX将动态内存与任务绑在了一起。
本章主要介绍任务是如何管理这些动态内存的,首先从动态内存的释放说起。不像ucos和rt
thread的直接释放对应的动态内存,MQX首先会对这个动态的内存进行一定的检查,判断是调用释放函数的任务为被释放动态内存块的拥有任务,如果不是,则释放失败。这样做的就可以保证只要内存块的拥有任务才可以释放自己的内存块,增加了的系统任务的安全性。
说到这里,有人一定会有疑问了:虽然MQX是基于多任务的系统,但芯片上电到任务机制启动之前,MQX是没有对应任务的,那这个时候的动态内存是属于哪一个任务呢?在这里就不得不提及SYSTEM_TD的概念了。顾名思义SYSTEM_TD指的是系统任务,难道MQX存在一个系统级别的任务来处理一些什么IPC、系统调用之类的公共服务吗?但是我找了半天也找不到这个任务的对应执行函数和启动这个任务的代码,最后我才明白,虽然它叫SYSTEM_TD,但它并不是一个任务,或者准确的来说从内容上来说它具备任务的一切属性,可惜它从来不会被执行,它只是MQX为了满足任务至上价值观所创造的一个概念,把哪些不方便放在其他任务的结构,反正SYSTEM_TD的结构之下。所以在启动阶段的所分配的动态内存的拥有者就是这个冤大头SYSTEM_TD了。具体的SYSTEM_TD初始化过程如下:
-
td_ptr = (TD_STRUCT_PTR) & kernel_data->SYSTEM_TD;
-
kernel_data->ACTIVE_PTR = td_ptr;
-
kernel_data->ACTIVE_SR = kernel_data->DISABLE_SR;
-
td_ptr->TASK_SR = kernel_data->DISABLE_SR;
-
td_ptr->TASK_ID = BUILD_TASKID(kernel_data->INIT.PROCESSOR_NUMBER, SYSTEM_TASK_NUMBER);
-
td_ptr->STATE = BLOCKED;
-
-
....
-
-
result = PSP_MINSTACKSIZE;
-
sys_td_stack_ptr = _mem_alloc_system((_mem_size) result);
-
#if MQX_CHECK_MEMORY_ALLOCATION_ERRORS
-
if (sys_td_stack_ptr == NULL) {
-
_mqx_exit(MQX_OUT_OF_MEMORY); /* RETURN TO USER */
-
} /* Endif */
-
#endif
-
sys_stack_base_ptr = (uchar_ptr) _GET_STACK_BASE(sys_td_stack_ptr, result);
-
td_ptr = SYSTEM_TD_PTR(kernel_data);
-
td_ptr->STACK_PTR = (pointer)(sys_stack_base_ptr - sizeof(PSP_STACK_START_STRUCT));
-
td_ptr->STACK_BASE = sys_stack_base_ptr;
-
#if MQX_TD_HAS_STACK_LIMIT
-
td_ptr->STACK_LIMIT = _GET_STACK_LIMIT(sys_td_stack_ptr, result);
-
#endif
-
_mqx_system_stack = td_ptr->STACK_PTR;
-
SYSTEM_TD没有task_template_struct,也没有通过_task_init_internal,更不会通过_task_ready_internal启动,它的堆栈也是最小的允许堆栈大小。MQX就把SYSTEM_TD当作了一个永远被阻塞的任务,把一切不属于其他任务的结构放在它的名下。对应动态内存则就是通过_mem_alloc_system来分配属于SYSTEM_TD的动态内存块。不过要注意的是,对应SYSTEM_TD所拥有的内存块,可是爹不疼娘不爱的,任何任务都可把它释放,对应代码如下:
-
mem_pool_ptr = (MEMPOOL_STRUCT_PTR) block_ptr->MEM_POOL_PTR;
-
td_ptr = SYSTEM_TD_PTR(kernel_data);
-
if (block_ptr->TASK_NUMBER != (TASK_NUMBER_FROM_TASKID(td_ptr->TASK_ID))) {
-
td_ptr = kernel_data->ACTIVE_PTR;
-
} /* Endif */
虽然动态内存只属于一个任务,但是这个任务是可以修改的。通过_mem_transfer就可以将一个动态内存块易主。_mem_transfer具体操作就不说明了,其核心内容就是修改内存块对应任务的ID号和移动任务的内存块链表,因为内存块与任务相关的只有这两个量,改掉它们就可以达到移花接木的效果。
由任务来管理动态内存块还有一个好处,就是我们并不用太担心内存块的释放问题了。内存泄漏是最容易出现也是最麻烦的一个问题了,而对于MQX而言,一旦一个删除一个任务,就会将这个任务所拥有的全部动态内存全部删除,在_task_destroy_internal中
-
/* Save the task number */
-
task_num = TASK_NUMBER_FROM_TASKID(victim_ptr->TASK_ID);
-
-
td_ptr = kernel_data->ACTIVE_PTR;
-
-
#if !MQX_LITE_VERSION_NUMBER
-
if ((victim_ptr->FLAGS & TASK_STACK_PREALLOCATED) == 0) {
-
pointer block_ptr = _mem_get_next_block_internal(victim_ptr, NULL);
-
while (block_ptr != NULL) {
-
pointer next_block_ptr = _mem_get_next_block_internal(victim_ptr, block_ptr);
-
if (td_ptr != victim_ptr) {
-
_mem_transfer_internal(block_ptr, SYSTEM_TD_PTR(kernel_data));
-
}
-
-
/* dealloc everything else except td and stack */
-
if ((block_ptr != victim_ptr) && (block_ptr != victim_ptr->STACK_ALLOC_BLOCK)) {
-
_mem_free(block_ptr);
-
}
-
block_ptr = next_block_ptr;
-
}
-
}
-
#endif /* MQX_LITE_VERSION_NUMBER */
可以看出,如果执行删除操作的任务不一定是这个被删除的任务,因此该任务不一定是动态内存的拥有者,在这种情况下要先修改动态内存的拥有者为执行删除操作的任务,再完成动态内存的释放。而释放的操作时沿着之前提到的任务的动态内存链表来的。这是第一部删除,保留了任务结构体和任务堆栈这两个结构体。接下来的删除分为两种情况,一种是自己删除自己
-
if (victim_ptr == td_ptr) {
-
/* We are destroying the current task */
-
_int_disable();
-
_QUEUE_UNLINK(victim_ptr); /* Remove myself from the ready queue */
-
_psp_destroy_stack_frame(victim_ptr);
-
#if !MQX_LITE_VERSION_NUMBER
-
if ((victim_ptr->FLAGS & TASK_STACK_PREALLOCATED) == 0) {
-
if (victim_ptr->STACK_ALLOC_BLOCK) {
-
_mem_transfer_internal(victim_ptr->STACK_ALLOC_BLOCK, SYSTEM_TD_PTR(kernel_data));
-
_mem_free(victim_ptr->STACK_ALLOC_BLOCK); /* Free the task's stack */
-
}
-
-
_mem_transfer_internal(victim_ptr, SYSTEM_TD_PTR(kernel_data));
-
_mem_free(victim_ptr); /* Free the task descriptor */
-
} /* Endif */
-
#endif /* MQX_LITE_VERSION_NUMBER */
-
/* Now we must jump to the scheduler */
-
_sched_run_internal(); /* WILL NEVER RETURN FROM HERE */
-
}/* Endif */
由于释放了堆栈和任务结构体,必须马上进行任务切换,不然系统就会处于非常危险的状态(堆栈溢出)。在这里调用了_sched_run_internal,这个和_sched_start_internal一样都是触发SVC
SVC_RUN_SCHED。而通过之前”从异常到任务”那章我们知道,这个系统调用用来启动任务机制的,不同于SVC_TASK_SWITCH是用来进行任务切换的。为什么用SVC_RUN_SCHED呢,是因为我们已经删除了任务和任务堆栈,之后的任务切换时是不会切换到这个任务来的,因此不需要保存这个任务的切换上下文环境。这种情况的MQX启动任务调度的情景是一样的,因此采用了同样的系统调用。通过这个调用来切换到优先级最高的任务上去。
而另一种任务删除是一个任务删除另一个任务,这个就非常简单了。
-
#if !MQX_LITE_VERSION_NUMBER
-
/* Free the task descriptor. */
-
if ((victim_ptr->FLAGS & TASK_STACK_PREALLOCATED) == 0) {
-
if (victim_ptr->STACK_ALLOC_BLOCK) {
-
_mem_free(victim_ptr->STACK_ALLOC_BLOCK); /* Free the task's stack */
-
}
-
_mem_free(victim_ptr); /* Free the task descriptor */
-
} /* Endif */
-
#endif /* MQX_LITE_VERSION_NUMBER *
就这样通过任务的删除,我们释放了任务所拥有的动态内存块。
综合上述,MQX采用任务来管理动态内存的方式是一种很好的内存管理思路,将粗犷的动态内存分配加以细分归类,虽然不能和linux的那些硬件带MMU和软件用特权级的复杂的内存管理机制相比,但它提供一个更加健壮的内存架构,能够在某些任务出现异常的情况下,也能够保持整体的稳定,是个很好的架构。
阅读(1763) | 评论(0) | 转发(0) |