嵌入式实时操作系统的设计与开发(调度机制学习)

创建线程

用户创建一个线程时须指定用户希望采用的调度策略。

//周期策略数据控制块
typedef struct{
	unsigned char prio;
	unsigned char prio_type;
	unsigned int time; 
}acoral_period_policy_data_t;

普通线程

普通线程是指用户需要用通用调度策略进行调度的线程,例如,用户希望自己创建的线程采用FIFS的方式进行调度。

int acoral_create_thread(void (*route)(void *args), unsigned int stack_size, void *args, char *name,void *stack, unsigned int sched_policy, void *data){
	acoral_thread_t *thread;
	thread = acoral_alloc_thread();
	if(NULL==thread){
		acoral_print("Alloc thread:%s fail\n",name);
		acoral_print("No Mem Space or Beyond the max thread\n");
		return -1;
	}
	thread->name = name;
	stack_size = stack_size & (~3);
	thread->stack_size = stack_size;
	if(stack != NULL)
		thread->stack_buttom = (unsigned int *)stack;
	else
		thread->stack_buttom = NULL;
	thread->policy = sched_policy;
	return acoral_policy_thread_init(sched_policy,thread,route,args,data);
}

stack_size = stack_size & (~3);
这个代码通常用于将一个整数stack_size舍入到最接近的较小的能被4整除的值。
这可能出现在与线程或内存分配相关的代码中,以确保栈大小或内存块的大小是4的倍数。

  1. ~3 是二进制表示中的 11111111111111111111111111111100。这是一个32位的整数,除了最后两位为0,其他位都是1。
  2. 将stack_size的最后两位设置为0,而其他位保持不变。因此被舍入到最接近的较小的4的倍数。

创建线程需要做的第一项工作是为该线程分配内存空间,线程是通过tcb描述的,为线程分配内存空间就是为TCB分配空间,其返回值是刚分配的TCB的指针。

acoral_thread_t *acoral_alloc_thread(){
	return (acoral_thread_t *)acoral_get_res(&acoral_thread_pool_ctrl);
}

资源池控制块

typedef struct{
	unsigned int type;//资源类型
	unsigned int size;//资源大小,一般就是结构体的大小,如线程控制块的大小,用sizeof(acoral_thread_t)这种形式赋值
	unsigned int num_per_pool; //每个资源池对象的数目。
	unsigned int num; //已经分配的资源池的个数
	unsigned int max_pools; //最多可以分配多少个资源池
	acoral_list_t *free_pools;
	acoral_list_t *pools,list[2];
	unsigned char *name;
}acoral_pool_ctrl_t;

资源池管理的资源内存是从第一级内存系统(伙伴系统)分配的,为了最大限度使用内存,减少内存碎片,对象的个数、最大值、可分配内存等都是通过计算后由用户指定的。
例如,伙伴算法设定基本内存块的大小为1KB,资源的大小为1KB,用户一个资源池包含20个资源,这样计算下来要分配20KB的空间,而由于伙伴系统只能分配2i个基本内存块的大小,故会分配32KB,32KB包含32个资源对象,故每个资源池的对象的个数更改为32。

acoral_res_t *acoral_get_res(acoral_pool_ctrl_t *pool_ctrl)
{
	acoral_list_t *first;
	acoral_res_t *res;
	acoral_pool_t *pool;
	acoral_enter_critical();
	first = pool_ctrl->free_pools->next;
	if(acoral_list_empty(first))
	{
		
	}
}

线程初始化

int comm_policy_thread_init(acoral_thread_t *thread, void (*route)(void *args), void *args, void *data){
	unsigned int prio;
	acoral_comm_policy_data_t *policy_data;
	policy_data = (acoral_comm_policy_data_t *)data;
	prio = policy_data->prio;
	if(policy_data->prio_type == ACORAL_NOHARD_PRIO)
	{
		prio += ACORAL_NOHARD_RT_PRIO_MAX;
		if(prio >= ACORAL_NOHRAD_RT_PRIO_MIN)
			prio = ACORAL_NOHRAD_RT_PRIO_MIN;
	}
	thread->prio = prio;
	if(acoral_thread_init(thread, route, acoral_thread_exit, args) != 0)
	{
		acoral_print("No comm thread stack:%s\r\n",thread->name);
		acoral_enter_critical();
		acoral_release_res((acoral_res_t *)thread);
		acoral_exit_critical();
		return -1;
	}
	acoral_resume_thread(thread);
	return thread->res.id;
}
unsigned int acoral_thread_init(acoral_thread_t *thread, void (*route)(void *args), void (*exit)(void), void *args)
{
	unsigned int stack_size = thread->stack_size;
	if(thread->stack_buttom == NULL) //判断堆栈指针是否为NULL,如果为NULL,则需要动态分配
	{
		if(stack_size<CFG_MIN_STACK_SIZE)
			stack_size=CFG_MIN_STACK_SIZE;
		thread->stack_buttom = (unsigned int *)acoral_malloc(stack_size);
		if(thread->stack_buttom == NULL)
			return ACORAL_ERR_THREAD_NO_STACK;
		thread->stack_size = stack_size;
	}
	thread->stack = (unsigned int *)((char *)thread->stack_buttom_stack_size-4);
	HAL_STACK_INIT(&thread->stack, route, exit, args); //模拟线程创建时的堆栈环境
	thread->data = NULL;
	thread->state = SUSPEND;
	acoral_init_list(&thread->waiting);
	acoral_init_list(&thread->ready);
	acoral_init_list(&thread->timeout);
	acoral_init_list(&thread->global_list);
	acoral_enter_critical();
	acoral_list_add2_tail(&thread->global_list, &acoral_threads_queue);
	acoral_exit_critical();
	return 0;
}
#define HAL_STACK_INIT(stack,route,exit,args) hal_stack_init(stack, route, exit, args);

HAL_STACK_INIT是与硬件相关的函数,不同的处理器有不同的寄存器[寄存器个数、寄存器功能分配(程序指针、程序当前状态寄存器)、连接寄存器、通用寄存器等],这些寄存器体现了当前线程的运行环境**,如果当前线程被其中断或线程所抢占**,将会发生上下文切换。

HAL_STACK_INIT()就是用来规定寄存器保存顺序的。

ARM9 S3C2410的线程环境是通过R0~R15及CPSR来保存的,即发生上下文切换时,要保存这16个寄存器的值(除R13(SP)外)。
故在堆栈初始化时就得压入这么多寄存器来模拟线程的环境(以方便在不知道具体针对某一硬件平台的时候,模拟堆栈的压栈),未来方便修改和操作,用一个数据结构表示环境。

typedef struct{
	unsigned int primask;
	unsigned int r4; ///<通用寄存器
	unsigned int r5; ///<通用寄存器
	unsigned int r6; ///<通用寄存器
	unsigned int r7; ///<通用寄存器
	unsigned int r8; ///<通用寄存器
	unsigned int r9; ///<通用寄存器
	unsigned int r10; ///<通用寄存器
	unsigned int r11; ///<通用寄存器
	unsigned int r0; ///<通用寄存器
	unsigned int r1; ///<通用寄存器
	unsigned int r2; ///<通用寄存器
	unsigned int r3; ///<通用寄存器
	unsigned int r12; ///<通用寄存器
	unsigned int lr; ///<链接寄存器
	unsigned int pc; ///<程序计数器
	unsigned int cpsr;
}hal_ctx_t;

由于是用C语言来模拟线程创建时的堆栈环境,所以用宏转换定义hal_stack_init()来实现HAL_STACK_INIT()。

其中,R0R7是通用寄存器,R8R12是影子寄存器,R14(LR)是链接寄存器,R15(PC)是程序指针。

void hal_stack_init(unsigned int **stk, void (*route)(), void (*exit)(), void args)
{
	hal_ctx_t *ctx = (hal_ctx_t *)*stk; 
	ctx--; //由于堆栈是向下生长的,所以用ctx--。
	ctx = (hal_ctx_t *)((unisgned int *)ctx + 1); //调整了4个字节
	ctx->r0 = (unsigned int)args;
	ctx->r1=0;
	ctx->r2=0;
	ctx->r3=0;
	ctx->r4=0;
	ctx->r5=0;
	ctx->r6=0;
	ctx->r7=0;
	ctx->r8=0;
	ctx->r9=0;
	ctx->r10=0;
	ctx->r11=0;
	ctx->r12=0;
	ctx->lr = (unsigned int)exit;
	ctx->pc = (unsigned int)route;
	ctx->cpsr = 0x01000000;
	ctx->primask = 0;
	*stk = (unsigned int *)ctx;
}

挂载线程到就绪队列

恢复线程,将新创建的线程挂载到一个就绪队列上。

线程恢复有两个接口:
acoral_resume_thread()和acroal_ready_thread()
前者比后者多了一个acoral_sched调度函数和一个判断。
acoral_resume_thread可由用户调用,acoral_rdy_thread用户不能直接调用,只能是内核内部调用。
acoral_resume_thread是可能立即导致当前线程挂起的调用,而rdy_thread不会,必须显示调用acoral_sched后才能挂起。

void acoral_resume_thread(acoral_thread_t *thread){
	acoral_u8 CPU;
	if(!(thread->state & ACORAL_THREAD_STATE_SUSPEND)) //如果线程不处于suspend状态,则不需要唤醒
		return;
	#ifdef CFG_CMP
		CPU = thread->CPU;
		if(CPU != acoral_current_CPU){
			acoral_ipi_cmd_send(CPU,ACORAL_IPI_THREAD_RESUME,thread->res.id, NULL);
			return;
		}
	#endif
	HAL_ENTER_CRITICAL();
	acoral_rdyqueue_add(thread);
	acoral_exit_critical();
	acoral_sched();
}
//将线程挂到就绪队列上
void acoral_rdyqueue_add(acoral_thread_t *thread)
{
	acoral_rdy_queue_t *rdy_queue;
	rdy_queue = &acoral_ready_queues;
	acoral_prio_queue_add(rdy_queue, thread->prio, &thread->ready);
	thread->state &= ~ACORAL_THREAD_STATE_SUSPEND;
	thread->state |= ACORAL_THREAD_STATE_READY;
	acoral_set_need_sched(true);
}
void acoral_prio_queue_add(acoral_rdy_queue_t *array, unsigned char prio, acoral_list_t *list)
{
	acoral_list_t *queue;
	acoral_list_t *head;
	array->num++;
	queue = array->queue + prio; //根据线程的优先级找到线程所在的优先级链表
	head = queue;
	acoral_list_add2_tail(list, head);
	acoral_set_bit(prio, array->bitmap);
}

调用acoral_sched()

一个普通线程创建的最后一步是调用内核函数acoral_sched(),由内核根据调度算法安排线程执行,即从就绪队列中取出符合调度算法的线程依次执行。

调用调度程序的具体位置又被称为是一个调度点(Scheduling Point)。由于调度通常是由外部事件的中断来出发,或者周期性的时钟信号来触发,因此调度点通常处于以下位置。

  1. 中断服务程序结束的位置。例如,当用户通过按键向系统提出新的请求,系统首先以中断服务程序ISR响应用户请求,然后,在中断服务程序结束时创建新的任务,并将新的任务挂载到就绪队列尾部。接下来,RTOS就会进入一个调度点,调用调度程序,执行相应的调度策略。又如,当I/O中断发生的时候,如果I/O事件是一个或多个任务正在等待的事件,则在I/O中断结束时,会进入一个调度点,调用调度程序,调度程序将根据调度策略确定是否继续执行当前处于运行状态的任务,或是让高优先级就绪任务抢占该任务。
  2. 运行任务因缺乏资源而被阻塞的时刻。例如,使用串口UART传输数据,如果UART正在被其它任务使用,这将导致当前任务从就绪状态转换成等待状态,不能继续执行,此时RTOS会进入一个调度点,调用调度程序。
  3. 任务周期或者结束的时刻。一些嵌入式实时系统往往将任务设计成周期性的,如空调控制器、雷达探测系统等,这样,在每个任务的周期开始或者结束时刻,都将进入调度点。
  4. 高优先级任务就绪的时刻。当高优先级任务处于就绪状态时,如果采用基于优先级的抢占式调度策略,将导致当前任务暂停运行,使更高优先级任务处于运行状态。

普通线程创建流程

  1. 首先为线程分配空间
  2. 然后根据创建线程的调度策略对线程TCB进行相关初始化
  3. 然后对线程的堆栈进行初始化
  4. 最后将创建的线程挂到就绪队列上,供内核进行调度。

你可能感兴趣的:(嵌入式实时操作系统的设计与开发,学习,算法,jvm,嵌入式实时操作系统,嵌入式实时操作系统的设计与开发)