微型操作系统内核源码详解系列三(1):任务及切换篇(任务函数定义)

系列一:微型操作系统内核源码详解系列一:rtos内核源码概论篇(以freertos为例)-CSDN博客

系列二:微型操作系统内核源码详解系列二:数据结构和对象篇(以freertos为例)-CSDN博客

系列三:微型操作系统内核源码详解系列三(0):空间存储及内存管理篇(前置篇)-CSDN博客

                微型操作系统内核源码详解系列三(1):任务及切换篇(任务函数定义)-CSDN博客

                微型操作系统内核源码详解系列三(2):任务及切换篇(任务函数定义)-CSDN博客

                微型操作系统内核源码详解系列三(3):任务及切换篇(任务函数定义)-CSDN博客

                微型操作系统内核源码详解系列三(4):arm架构篇-CSDN博客

                微型操作系统内核源码详解系列三(5):进程与线程-CSDN博客

系列四:

 ​​​​​微型操作系统内核源码详解系列四(1):操作系统调度算法(linux0.11版本内核)-CSDN博客

微型操作系统内核源码详解系列四(2):操作系统调度算法(rt-thread内核)-CSDN博客

微型操作系统内核源码详解系列四(3):操作系统调度算法(FreeRTOS内核篇上)-CSDN博客

微型操作系统内核源码详解系列四(4):操作系统调度算法(FreeRTOS内核篇下)-CSDN博客

系列五:

微型操作系统内核源码详解系列五(1):arm cortex m3架构-CSDN博客

微型操作系统内核源码详解系列五(2):cm3下栈的初始化-CSDN博客

微型操作系统内核源码详解系列五(3):cm3下调度的开启-CSDN博客

微型操作系统内核源码详解系列五(四):cm3下svc启动任务-CSDN博客

微型操作系统内核源码详解系列五(五):cm3下Pendsv切换任务上篇-CSDN博客

微型操作系统内核源码详解系列五(六):Pendsv切换任务下篇-CSDN博客

本篇笔者将会正式进入FreeRTOS这个微型操作系统最重要的一章:任务(线程)及切换。笔者会对每一个函数进行讲解,该篇预计有数个小节,每一节讲解一到两个函数或背后的操作系统知识。

如果笔者了解过操作系统进程,那大概知道操作系统是如何进行进程切换的:

       1.保存之前运行的进程上下文
  2.调用准备运行的进程的上下文
  3.CPU使用权交接

线程是进程的组成部分,在FreeRTOS这种多线程系统中,它的步骤也是一样的。任务就是线程,让我们先建立一个操作系统任务的基本框架。

可以参考这篇文章:【操作系统】进程切换到底是怎么个过程?_进程切换时,会发生什么事情-CSDN博客

如果读者没听说过上下文,可以参考这篇文章:到底什么是上下文(Context)-CSDN博客

线程和进程概念参考:进程线程(一)——基础知识,什么是进程?什么是线程?_什么是进程什么是线程-CSDN博客

大蓝图如下:

现在让我们来看我们蓝图中具体执行任务的函数,本章要做的,就是理解任务入口部分,从creat入手:

文件:tasks.c


#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

	BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
							const char * const pcName,
							const uint16_t usStackDepth,
							void * const pvParameters,
							UBaseType_t uxPriority,
							TaskHandle_t * const pxCreatedTask ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
	{
	TCB_t *pxNewTCB;
	BaseType_t xReturn;

		/* If the stack grows down then allocate the stack then the TCB so the stack
		does not grow into the TCB.  Likewise if the stack grows up then allocate
		the TCB then the stack. */
		#if( portSTACK_GROWTH > 0 )
		{
			/* Allocate space for the TCB.  Where the memory comes from depends on
			the implementation of the port malloc function and whether or not static
			allocation is being used. */
			pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

			if( pxNewTCB != NULL )
			{
				/* Allocate space for the stack used by the task being created.
				The base of the stack memory stored in the TCB so the task can
				be deleted later if required. */
				pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

				if( pxNewTCB->pxStack == NULL )
				{
					/* Could not allocate the stack.  Delete the allocated TCB. */
					vPortFree( pxNewTCB );
					pxNewTCB = NULL;
				}
			}
		}
		#else /* portSTACK_GROWTH */
		{
		StackType_t *pxStack;

			/* Allocate space for the stack used by the task being created. */
			pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

			if( pxStack != NULL )
			{
				/* Allocate space for the TCB. */
				pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exception as the casts are only redundant for some paths. */

				if( pxNewTCB != NULL )
				{
					/* Store the stack location in the TCB. */
					pxNewTCB->pxStack = pxStack;
				}
				else
				{
					/* The stack cannot be used as the TCB was not created.  Free
					it again. */
					vPortFree( pxStack );
				}
			}
			else
			{
				pxNewTCB = NULL;
			}
		}
		#endif /* portSTACK_GROWTH */

		if( pxNewTCB != NULL )
		{
			#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
			{
				/* Tasks can be created statically or dynamically, so note this
				task was created dynamically in case it is later deleted. */
				pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
			}
			#endif /* configSUPPORT_STATIC_ALLOCATION */

			prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
			prvAddNewTaskToReadyList( pxNewTCB );
			xReturn = pdPASS;
		}
		else
		{
			xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
		}

		return xReturn;
	}

#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

栈帧是在函数被调用时,系统为该函数在栈区域中开辟的一段存储空间,如果你看过前一篇,应该知道栈和堆分别存放的是什么,简单来说,栈里面是函数有关的各种生命周期短的数据,堆里面是贯穿程序生命周期的数据,你也知道pvPortMalloc是heap文件的函数,所以TCB和STACK申请的内存空间都在堆中(个人认为FreeRTOS这个stack的命名有点不妥)。

通过观察你会发现,portSTACK_GROWTH是决定栈生长的判断。if有两个分支,它们的区别在于顺序不同,前者先申请TCB的内存,再申请STACK,后者相反。

让我来解释一下,通过前一篇博客,我们知道堆的位置在低地址,方向是向高地址增长的。操作系统默认内存高地址在上,低地址在下,portSTACK_GROWTH默认为-1。笔者认为在内存这里使用grown down/up这种向上向下的说法非常误导人,内存增长的方向只有向高地址或低地址,不存在向下或者向上。那么为什么要设置这个分支选项呢?笔者认为与开辟空间分配内存时的方式有关:

如果分配内存的顺序是从低地址往高地址依次分配,就是向上生长。
如果分配内存的顺序是从高地址往低地址依次分配,则是向下生长。

说实话,笔者只听过栈有这种性质,但是把这块在堆中开辟的空间理解为栈的话,就能理解为什么命名为stack了,当然,这只是一块数据结构为栈的空间,并不是存储结构中的栈。(此处结论是根据笔者个人分析得出的结果,如有问题,请多多包涵)

我们通过前一章知识可得,堆是向高地址方向增长,先分配的在低地址,后分配的在高地址,FreeRTOS默认是向下生长,当stack在低地址,而TCB在高地址时,此时stack继续向低地址生长,不会干扰到TCB。也就是说,我们需要先分配stack,再分配TCB。如果stack在高地址,TCB在低地址,那么stack向下生长时势必会占用TCB的内存。

现在让我们理解任务创建函数,以下图为例:

xReturn=xTaskCreate(
							(TaskFunction_t )	AppTaskCreate,
							(const char*		) "AppTaskCreate",
							(uint16_t				)AppTaskCreate_size,
							(void*          ) pvParameters,
							(UBaseType_t    ) START_TASK_PRIO,
							(TaskHandle_t* )&AppTaskCreate_Handle
						);

AppTaskCreate是创建任务的函数的地址,xTaskCreate函数的代码会保存这个地址,方便随时执行这个任务,"AppTaskCreate"是任务的名字,是为了方便我们调试时辨别任务的,AppTaskCreate_size就是栈了,它需要我们自己估计任务的大小并分配,任务大小的估计可以根据三个方面:函数嵌套(临时变量和状态值等)、任务切换、中断配置。它的单位是字,

在32位架构下,一个字的大小就是32。

因为stm32是32位的,所以实际的字节大小为你输入的数字×4;这个栈会保存任务被切换之前的所有情况,方便还原。pvParameters一般默认是NULL,这是一个用来传递参数的指针,根据笔者的看法,它是一个独立在任务中的指针,可能会被用来调试等操作。START_TASK_PRIO是优先级, AppTaskCreate_Handle是用于传出任务的句柄。这个句柄将在API 调用中对该创建出来的任务进行引用,如改变任务优先级,删除任务等。pxCreatedTask 一般被设为NULL。

理解了这个之后,让我们来到下文,也就是:

让我们关注这两个函数:

prvInitialiseNewTask函数主要负责完成栈溢出检查、获取栈顶地址、任务名存储到TCB、设置链表节点和初始化任务栈等操作。

prvAddNewTaskToReadyList的作用是添加任务到就绪列表。

笔者决定在下一章重点讲解prvInitialiseNewTask函数和prvAddNewTaskToReadyList函数。在这之后,笔者将重点讲解栈的开辟和使用,以及arm架构汇编和操作系统调度,只有理解了这些,才能理解FreeRTOS最难的部分:任务的保存与切换。

  • 29
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值