AliOS-Things+STM32 (八) 进程管理(下)

上一章,我们已经分析过一些基本的task初始化,以及让第一个task调度后运行起来了。这只是一个开始,我们还需要关注三个问题:

  1. 在SCHED_RR模式下,进程时间片消耗完毕了,怎么切换给同一优先级的另一个待运行的进程呢?
  2. 当进程主动自己调度,调度器如何切换呢?
  3. 当进程因为sleep/等待同步时间被block住,调度器怎么调度呢?
    带着这三个问题,我们往下看

  1. 先看下当时间片用完,也就是触发了systick的时候,是怎么处理的:
/*设置每秒出发100次tick*/
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/RHINO_CONFIG_TICKS_PER_SECOND);

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
  if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
  {
    return (1UL);                                                   /* Reload value impossible */
  }

  SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
  SysTick->VAL   = 0UL;                                             /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                   SysTick_CTRL_TICKINT_Msk   |
                   SysTick_CTRL_ENABLE_Msk;                         /* Enable SysTick IRQ and SysTick Timer */
  return (0UL);                                                     /* Function successful */
}

完成之后,每隔10ms,就会出发一次系统tick,在这个里面,可以触发一次任务切换的尝试,如下:

void SysTick_Handler(void)
{
  krhino_intrpt_enter();  //进入中断处理
  HAL_IncTick();     //uwTick记录的tick+1
  krhino_tick_proc();  /*更新定时器相关信息*/
  krhino_intrpt_exit(); //退出中断处理
}

kstat_t krhino_intrpt_enter(void)
{
    CPSR_ALLOC();

#if (RHINO_CONFIG_INTRPT_STACK_OVF_CHECK > 0)
    krhino_intrpt_stack_ovf_check();
#endif

	/*禁止中断*/
    RHINO_CPU_INTRPT_DISABLE();
    /*g_intrpt_nested_level[0]++,记录中断正在处理*/
    g_intrpt_nested_level[cpu_cur_get()]++;
    /*开启中断*/
    RHINO_CPU_INTRPT_ENABLE();
	
#if (RHINO_CONFIG_PWRMGMT > 0)
    cpu_pwr_up();
#endif

    return RHINO_SUCCESS;
}
#define RHINO_CPU_INTRPT_DISABLE()  do{cpsr = cpu_intrpt_save();}while(0)
#define RHINO_CPU_INTRPT_ENABLE()   do{cpu_intrpt_restore(cpsr);}while(0)
cpu_intrpt_save
	/*加载特殊寄存器的值(当前中断状态)到通用寄存器R0保存*/
    MRS     R0, PRIMASK
    /*关中断*/
    CPSID   I  
    BX      LR

cpu_intrpt_restore
	/*将通用寄存器R0(之前的中断状态)恢复到特殊寄存器*/
    MSR     PRIMASK, R0
    /*之所以这边没有CPSIE I来开启中断,是因为这里要的是中断状态恢复原状,
    由于R0存的是之前的中断状态,如果之前是开启的,那么现在恢复到开启状态,
    如果之前是关闭的,那么现在恢复后依然关闭*/
    BX      LR

void krhino_tick_proc(void)
{
#if (RHINO_CONFIG_USER_HOOK > 0)
    krhino_tick_hook();
#endif
	//更新等待到期的进程
    tick_list_update(1);

#if (RHINO_CONFIG_SCHED_RR > 0)
	//当使能SCHED_RR模式, 更新时间片,将当前时间片用完的task放到队列尾部
    time_slice_update();
#endif
}

void tick_list_update(tick_i_t ticks)
{
    CPSR_ALLOC();

    klist_t  *tick_head_ptr;
    ktask_t  *p_tcb;
    klist_t  *iter;
    klist_t  *iter_temp;
    tick_i_t  delta;

    RHINO_CRITICAL_ENTER();
	/*g_tick_count +1*/
    g_tick_count += ticks;
	
	/*g_tick_head的链表记录着所有需要定时器唤醒的任务*/
    tick_head_ptr = &g_tick_head;
    iter          =  tick_head_ptr->next;

	/*从g_tick_head中,遍历所有等待到时的task,
	如果当前时间已经到达task等待的时间,那么就重新把task放到就绪队列*/
    while (RHINO_TRUE) {
    	//遍历所有g_tick_head上的任务,检查已经到时的任务
        /* search all the time list if possible */
        if (iter != tick_head_ptr) {
            iter_temp = iter->next;
           	//取出一个任务
            p_tcb     = krhino_list_entry(iter, ktask_t, tick_list);
            //这个任务要到期的tick与当前tick的差值
            delta = (tick_i_t)p_tcb->tick_match - (tick_i_t)g_tick_count;
            /* since time list is sorted by remain time, so just campare  the absolute time */
            /*delta<=0,说明该task等待的时间已经到达*/
            if (delta <= 0) {
                switch (p_tcb->task_state) {
                /*如果这个task在sleep状态(执行krhino_task_sleep等会sleep的操作)*/
                    case K_SLEEP:
                    	//阻塞状态设置成BLK_FINISH,表示该任务等待到了正常的定时器到期时间
                        p_tcb->blk_state  = BLK_FINISH;
                        //将该任务状态设置成为就绪
                        p_tcb->task_state = K_RDY;
                        //将该任务从定时器任务链表上删除
                        tick_list_rm(p_tcb);
                        //将该任务放到就绪队列上,之后给调度器调度执行
                        ready_list_add(&g_ready_queue, p_tcb);
                        break;
                    /*如果这个task在Pend状态(之前执行krhino_buf_queue_recv, 
                    krhino_mutex_lock等会block的操作)*/
                    case K_PEND:
                    	//将该任务从定时器任务链表上删除
                        tick_list_rm(p_tcb);
                        //将该任务从阻塞队列上删除
                        /* remove task on the block list because task is timeout */
                        klist_rm(&p_tcb->task_list);
                        //将该任务放到就绪队列上,之后给调度器调度执行
                        ready_list_add(&g_ready_queue, p_tcb);
                        /*阻塞状态设置成BLK_TIMEOUT,表示该任务由于等待某个block操作,
                        已经超时了也没有等待到预先等待的事件*/
                        p_tcb->blk_state  = BLK_TIMEOUT;
                        //将该任务状态设置成为就绪
                        p_tcb->task_state = K_RDY;
                        //下面我们会单独分析下mutex_task_pri_reset
                        mutex_task_pri_reset(p_tcb);
                        p_tcb->blk_obj    = NULL;
                        break;
                     /*如果这个task在PEND_SUSPENDED状态(之前在PEND状态时又被挂起)*/
                    case K_PEND_SUSPENDED:
                   		//将该任务从定时器任务链表上删除
                        tick_list_rm(p_tcb);
                        //将该任务从阻塞队列上删除
                        /* remove task on the block list because task is timeout */
                        klist_rm(&p_tcb->task_list);
                        //将该任务的阻塞状态设为超时
                        p_tcb->blk_state  = BLK_TIMEOUT;
                        //将该任务的任务状态设置为挂起
                        p_tcb->task_state = K_SUSPENDED;
                        //下面我们会单独分析下mutex_task_pri_reset
                        mutex_task_pri_reset(p_tcb);
                        p_tcb->blk_obj    = NULL;
                        break;
                     /*如果这个task在SLEEP_SUSPENDED状态(之前在Sleep状态时又被挂起)*/
                    case K_SLEEP_SUSPENDED:
                    	/*sleep到期,设置成挂起状态*/
                        p_tcb->task_state = K_SUSPENDED;
                        //将该任务的阻塞状态设为到期结束
                        p_tcb->blk_state  = BLK_FINISH;
                        //将该任务从定时器列表上删除
                        tick_list_rm(p_tcb);
                        break;
                    default:
                        k_err_proc(RHINO_SYS_FATAL_ERR);
                        break;
                }

                iter = iter_temp;
            } else {
                break;
            }
        } else {
            break;
        }
    }
    RHINO_CRITICAL_EXIT();
}


void mutex_task_pri_reset(ktask_t *task)
{
#if (RHINO_CONFIG_MUTEX_INHERIT > 0)
    kmutex_t *mutex_tmp;
    ktask_t  *mutex_task;
	//如果导致阻塞的类型为mutex
    if (task->blk_obj->obj_type == RHINO_MUTEX_OBJ_TYPE) {
    	//mutex_tmp为导致该task被阻塞的mutex
        mutex_tmp = (kmutex_t *)(task->blk_obj);
        //mutex_task为获取到该mutex_tmp的任务,也就是导致task被阻塞的任务
        mutex_task = mutex_tmp->mutex_task;

		/*如果这两个优先级是一样的,那么可以怀疑,mutex_task有可能由于阻塞过task,
		被合理提升过优先级,所以做一次mutex_relase,将mutex_task的优先级降到一个原来的优先级上。*/
        /* the new highest prio task blocked on this mutex may
        decrease prio than before so reset the mutex task prio */
        if (mutex_task->prio == task->prio) {
        	/*这里名字虽然叫mutex_release,但是和释放锁没太大关系,
			只是将mutex_task任务重置到一个合理的优先级,这个我们在mutex章节分析过,
			详见之前的 AliOS-Things+STM32(三) 同步机制(mutex)。*/
            mutex_release(mutex_task, NULL);
        }
    }
#endif
}

所以经过分析,可以发现mutex_task_pri_reset(p_tcb)实际上是将之前导致p_tcb阻塞的另一个任务的优先级重置,因为p_tcb这个任务由于超时或者说到期将不再被阻塞了,所以,之前为了规避mutex优先级反转,而被提升过的那个任务优先级需要重新判断,回归到原先状态。
接下来,我们再看time_slice_update这个函数,这个函数是要在设置RHINO_CONFIG_SCHED_RR后,才会执行的,具体RHINO_CONFIG_SCHED_RR代表了什么意思,请先看本章最后的关于调度策略的补充介绍。在Alios-Things系统中,若是不设置RHINO_CONFIG_SCHED_RR,就遵从SCHED_FIFO策略了。

void time_slice_update(void)
{
    CPSR_ALLOC();

    ktask_t *task;
    klist_t *head;
    uint8_t  task_pri;

    RHINO_CRITICAL_ENTER();
	//获得当前CPU目前正在运行的任务的优先级task_pri
    task_pri = g_active_task[cpu_cur_get()]->prio;
	//获得task_pri同一优先级的任务链表
    head = g_ready_queue.cur_list_item[task_pri];
	//查一下就绪队列里,同一优先级的任务链表是否为空?
	// 如果为空,则表明没有其他任务需要和当前task共享时间片,退出。
    /* if ready list is empty then just return because nothing is to be caculated */
    if (is_ready_list_empty(task_pri)) {
        RHINO_CRITICAL_EXIT();
        return;
    }
	
	//程序执行到这里,说明有任务需要共享时间片
    /* Always look at the first task on the ready list */
    task = krhino_list_entry(head, ktask_t, task_list);
	//确认下当前任务task有否手动设置为SCHED_FIFO,有的话,也不需要和其他任务共享时间片,并退出
    if (task->sched_policy == KSCHED_FIFO) {
        RHINO_CRITICAL_EXIT();
        return;
    }

	//如果就绪队列中,只有一个任务,那么也退出。这是因为没有必要更新当前任务的time_slice
    /* there is only one task on this ready list, so do not need to caculate time slice */
    /* idle task must satisfy this condition */
    if (head->next == head) {
        RHINO_CRITICAL_EXIT();
        return;
    }
	
	//如果当前任务的time_slice>0,那么--
    if (task->time_slice > 0u) {
        task->time_slice--;
    }
	
	//如果当前任务的time_slice>0,表明当前任务本次time_slice还没用完,那么不需要调度
    /* if current active task has time_slice, just return */
    if (task->time_slice > 0u) {
        RHINO_CRITICAL_EXIT();
        return;
    }
	/*程序运行到这里,说明当前任务的time_slice已经运行完了,
	那么就将当前任务放置到就绪队列的尾部。以便下次调度的时候切换任务*/
    /* move current active task to the end of ready list for the same prio */
    ready_list_head_to_tail(&g_ready_queue, task);
	
	//重置当前任务的time_slice,这样下次再被调度到时,还能从完整的时间片开始消耗。
    /* restore the task time slice */
    task->time_slice = task->time_total;

    RHINO_CRITICAL_EXIT();
}

接着我们要看重头戏了,krhino_intrpt_exit:

void krhino_intrpt_exit(void)
{
    CPSR_ALLOC();
    uint8_t  cur_cpu_num;
    ktask_t *preferred_task;

	//这里是用来检测堆栈溢出的
#if (RHINO_CONFIG_INTRPT_STACK_OVF_CHECK > 0)
    krhino_intrpt_stack_ovf_check();
#endif
	//关闭当前CPU中断
    RHINO_CPU_INTRPT_DISABLE();
	//获得当前CPU号
    cur_cpu_num = cpu_cur_get();
	//当前CPU中断嵌套计数-1
    g_intrpt_nested_level[cur_cpu_num]--;

	/*如果当前CPU中断嵌套计数还是>0,表明还有中断嵌套,
	需要执行更多次krhino_intrpt_exit才能执行后续调度,所以打开中断并退出。*/
    if (g_intrpt_nested_level[cur_cpu_num] > 0u) {
        RHINO_CPU_INTRPT_ENABLE();
        return;
    }
	
	/*如果当前任务不允许调度,则退出*/
    if (g_sched_lock[cur_cpu_num] > 0u) {
        RHINO_CPU_INTRPT_ENABLE();
        return;
    }
	
	/*从就绪列表里,找出最应该被调度运行的任务*/
    preferred_task = preferred_cpu_ready_task_get(&g_ready_queue, cur_cpu_num);
	/*如果该任务就是当前运行的任务,那么也不需要切换任务了,退出*/
    if (preferred_task == g_active_task[cur_cpu_num]) {
        RHINO_CPU_INTRPT_ENABLE();
        return;
    }
	
    TRACE_INTRPT_TASK_SWITCH(g_active_task[cur_cpu_num], preferred_task);

	/*如果系统设定,不允许抢占,那么只有当当前任务为idle任务时,才允许cpu出让给其他任务,否则不切换*/
#if (RHINO_SCHED_NONE_PREEMPT > 0)
    if (g_active_task[cur_cpu_num] == &g_idle_task[cur_cpu_num]) {
#endif
#if (RHINO_CONFIG_CPU_NUM > 1)
        g_active_task[cur_cpu_num]->cur_exc = 0;
        preferred_task->cpu_num             = cur_cpu_num;
        preferred_task->cur_exc             = 1;
#endif
		/*当前cpu将要执行的任务指向之前选出的最应该被调度运行的任务*/
        g_preferred_ready_task[cur_cpu_num] = preferred_task;
        /*当前CPU切换任务*/
        cpu_intrpt_switch();
#if (RHINO_SCHED_NONE_PREEMPT > 0)
    }
#endif
    RHINO_CPU_INTRPT_ENABLE();
}

ktask_t *preferred_cpu_ready_task_get(runqueue_t *rq, uint8_t cpu_num)
{
	/*从当前最高优先级的任务链表中,取出链表头的任务*/
    klist_t *node = rq->cur_list_item[rq->highest_pri];
    /* get the highest prio task object */
    return krhino_list_entry(node, ktask_t, task_list);
}
;******************************************************************************
; Functions:
;     void cpu_intrpt_switch(void);
;     void cpu_task_switch(void);
;******************************************************************************
cpu_task_switch:
    LDR     R0, =SCB_ICSR
    LDR     R1, =ICSR_PENDSVSET
    STR     R1, [R0]
    BX      LR

/*这一部分在上一章cpu_first_task_start中分析过,是为了触发pv中断:PendSV_Handler
最后会在_pendsv_handler_nosave中处理切换任务的具体实现*/
cpu_intrpt_switch:
    LDR     R0, =SCB_ICSR		/*0xE000ED04*/
    LDR     R1, =ICSR_PENDSVSET	/*0x10000000*/
    STR     R1, [R0]			/*执行PendSV 中断*/
    BX      LR

PendSV_Handler:
    CPSID   I
    /*读取PSP寄存器的值,如果是0,表示之前是在cpu_first_task_start初始化的,
    跳转到_first_task_restore, 只有初始化调度器的时候才会走这里*/
    MRS     R0, PSP
    ;branch if cpu_first_task_start
    CMP     R0, #0
    BEQ     _first_task_restore

    ;hardware saved R0~R3,R12,LR,PC,xPSR

	/*R0记录了之前PSP值,也就是堆栈指针,减去0x24,保留出9个寄存器值的存储空间*/
    ;save context
    SUBS    R0, R0, #0x24
    /*将R4~R11,以及LR寄存器,这个9个寄存器的值,放到这个空间中保存起来,
    这样也就保存了当前进程的上下文*/
    STM     R0, {R4-R11, LR}

	/*记录当前进程的g_active_task->task_stack地址,给R1*/
    ;g_active_task->task_stack = context region
    LDR     R1, =g_active_task
    LDR     R1, [R1]
    /*将进程上下午记录的地址R0,写到R1的位置,
    也就是将当前进程的上下文存储保存到了g_active_task->task_stack,
    以供下次切换到当前进程时候能恢复当前进程的上下文*/
    STR     R0, [R1]

#if (RHINO_CONFIG_TASK_STACK_OVF_CHECK > 0)
	//堆栈检查
    BL      krhino_stack_ovf_check
#endif
#if (RHINO_CONFIG_SYS_STATS > 0)
	//任务相关系统状态记录,调试用
    BL      krhino_task_sched_stats_get
#endif
	//这里继续执行下去,下面就是_pendsv_handler_nosave的内容,具体流程上一章已经描述过了,这里不再赘述。
_pendsv_handler_nosave:
    LDR     R0, =g_active_task
    LDR     R1, =g_preferred_ready_task
    LDR     R2, [R1]
    STR     R2, [R0]
    ;R0 = g_active_task->task_stack = context region
    LDR     R0, [R2]

    ;restore context
    LDM     R0, {R4-R11, LR}
    ADDS    R0, R0, #0x24

    ;return stack = PSP
    MSR     PSP, R0

    CPSIE   I
    ;hardware restore R0~R3,R12,LR,PC,xPSR
    BX      LR

回头看我们第一个问题:在SCHED_RR模式下,进程时间片消耗完毕了,怎么切换给同一优先级的另一个待运行的进程呢?
所以,我们可以看到,当每次tick触发中断,只要调度机制是SCHED_RR,在当前CPU运行任务时间片耗完的情况下,都有机会做任务调度,重新安排同优先级的任务执行,整个流程就是我们上面分析的这段代码逻辑,这也就回答了我们第一个问题。

再看第二个问题:当进程主动自己调度,调度器如何切换呢?
我们在alios-things中,可以主动让出CPU,执行的接口是krhino_task_yield(void),我们来分析一下这个接口:

kstat_t krhino_task_yield(void)
{
    CPSR_ALLOC();
	
	/*关中断防止调度*/
    /* make current task to the end of ready list */
    RHINO_CRITICAL_ENTER();
	/*将当前任务放到同一优先级的就绪任务队列最尾部*/
    ready_list_head_to_tail(&g_ready_queue, g_active_task[cpu_cur_get()]);
	/*恢复中断并触发一次调度*/
    RHINO_CRITICAL_EXIT_SCHED();
	
    return RHINO_SUCCESS;
}

void ready_list_head_to_tail(runqueue_t *rq, ktask_t *task)
{
	/*由于rq->cur_list_item[task]是个循环链表,所以往后移动一个位置,
	就相当于将当前执行的任务,放到了队列的尾部, 当然,
	如果同一优先级的队列中只有当前运行程序一个任务,那么这个动作相当于没有效果。*/
    rq->cur_list_item[task->prio] = rq->cur_list_item[task->prio]->next;
}

#define RHINO_CRITICAL_EXIT_SCHED() \
    do {                            \
        RHINO_INTDIS_MEAS_STOP();   \
        RHINO_CPU_INTRPT_ENABLE();  \   //恢复中断
        core_sched();               \	//调度
    } while (0)

void core_sched(void)
{
    CPSR_ALLOC();
    uint8_t  cur_cpu_num;
    ktask_t *preferred_task;
	
	//关中断
    RHINO_CPU_INTRPT_DISABLE();

    cur_cpu_num = cpu_cur_get();
	//判断是否在中断处理过程中,如果是则退出,不允许调度
    if (g_intrpt_nested_level[cur_cpu_num] > 0u) {
        RHINO_CPU_INTRPT_ENABLE();
        return;
    }
	//判断任务是否设置为不可调度,是的话退出
    if (g_sched_lock[cur_cpu_num] > 0u) {
        RHINO_CPU_INTRPT_ENABLE();
        return;
    }
	//这个我们上面分析过,在就绪队列里,找出最应该被调度运行的任务*/
    preferred_task = preferred_cpu_ready_task_get(&g_ready_queue, cur_cpu_num);
	
	//如果就是当前正在运行的任务,则退出
    /* if preferred task is currently task, then no need to do switch and just return */
    if (preferred_task == g_active_task[cur_cpu_num]) {
        RHINO_CPU_INTRPT_ENABLE();
        return;
    }
	//当前CPU的g_preferred_ready_task设置成刚找到的最应该被调度运行的任务
    g_preferred_ready_task[cur_cpu_num] = preferred_task;

    TRACE_TASK_SWITCH(g_active_task[cur_cpu_num], g_preferred_ready_task[cur_cpu_num]);

#if (RHINO_CONFIG_USER_HOOK > 0)
    krhino_task_switch_hook(g_active_task[cur_cpu_num], g_preferred_ready_task[cur_cpu_num]);
#endif
	
	//进行任务切换
    cpu_task_switch();

    RHINO_CPU_INTRPT_ENABLE();
}

所以,分析下来, 任务想要主动出让CPU,需要有相同优先级的其他任务,在就绪任务列表里等待,才可能成功切换,否则执行是没有效果的。

第三个问题:当进程因为sleep/等待同步时间被block住,调度器怎么调度呢?
这个分两个小问题看:

  1. 由于sleep等原因,等待超时:
  2. 由于block阻塞等原因,出让cpu。
    先让我们看第一种:
    我们来看下alios中由于时间等待的典型情况,sleep调用接口:
void aos_msleep(int ms)
{
    krhino_task_sleep(MS2TICK(ms));
}
//根据设定的延时,转换成tick
#define MS2TICK(ms) krhino_ms_to_ticks(ms)

tick_t krhino_ms_to_ticks(sys_time_t ms)
{
    uint16_t padding;
    tick_t   ticks;

    padding = 1000 / RHINO_CONFIG_TICKS_PER_SECOND;
    padding = (padding > 0) ? (padding - 1) : 0;

    ticks = ((ms + padding) * RHINO_CONFIG_TICKS_PER_SECOND) / 1000;

    return ticks;
}

kstat_t krhino_task_sleep(tick_t ticks)
{
    CPSR_ALLOC();

    uint8_t cur_cpu_num;

    kstat_t ret;

    if (ticks == 0u) {
        return RHINO_INV_PARAM;
    }
	//禁止中断
    RHINO_CRITICAL_ENTER();
	//判断是否在中断函数中调用
    INTRPT_NESTED_LEVEL_CHK();
	//取得当前CPU
    cur_cpu_num = cpu_cur_get();
	//判断当前任务是否禁止调度
    /* system is locked so task can not be blocked just return immediately */
    if (g_sched_lock[cur_cpu_num] > 0u) {
        RHINO_CRITICAL_EXIT();
        return RHINO_SCHED_DISABLE;
    }
	//设置当前任务状态为sleep状态
    g_active_task[cur_cpu_num]->task_state = K_SLEEP;
    //将当前任务挂到定时器上
    tick_list_insert(g_active_task[cur_cpu_num], ticks);
    ready_list_rm(&g_ready_queue, g_active_task[cur_cpu_num]);

    TRACE_TASK_SLEEP(g_active_task[cur_cpu_num], ticks);
	//进行任务切换
    RHINO_CRITICAL_EXIT_SCHED();
	//程序运行到这里,表明sleep时间已经到期,任务重新获得运行
	//禁中断
    RHINO_CPU_INTRPT_DISABLE();
	//获得任务退出时的返回值
    /* is task timeout normally after sleep */
    ret = pend_state_end_proc(g_active_task[cpu_cur_get()], 0);
	//使能中断
    RHINO_CPU_INTRPT_ENABLE();

    return ret;
}

//将任务挂到timer上
void tick_list_insert(ktask_t *task, tick_t time)
{
    klist_t *tick_head_ptr;

    if (time > 0u) {
    	//设置tick_match 和 tick_remain
        task->tick_match  = g_tick_count + time;
        task->tick_remain = time;
		
		//挂到g_tick_head上
        tick_head_ptr = &g_tick_head;
        tick_list_pri_insert(tick_head_ptr, task);
        task->tick_head = tick_head_ptr;
    }
}

RHINO_INLINE void tick_list_pri_insert(klist_t *head, ktask_t *task)
{
    tick_t   val;
    klist_t *q;
    klist_t *list_start;
    klist_t *list_end;
    ktask_t *task_iter_temp;

    list_start = head;
    list_end   = head;

	//val记录等待的剩余时间
    val = task->tick_remain;
	//按照val为标准, 将任务插入到从小到大排列的链表中
    for (q = list_start->next; q != list_end; q = q->next) {
        task_iter_temp = krhino_list_entry(q, ktask_t, tick_list);
        if ((task_iter_temp->tick_match - g_tick_count) > val) {
            break;
        }
    }

    klist_insert(q, &task->tick_list);
}

到tick_list_insert,我们已经将当前任务挂到timer上了,那么什么时候触发这个等待时间呢? 当然是我们前面分析到过的的每个tick触发的时钟中断tick_list_update处理过程中,除非禁中断,否则每个tick都会检查一遍当前tick有没有到期任务,到期了就会将该任务放到就绪队列等待任务切换。
那么来看第2 种:由于block阻塞等原因,出让cpu,这个我们就不贴代码分析了,原因是之前mutex lock,buf_queue recv都属于这种情况, 具体代码流程可以看AliOS-Things+STM32 (三) 同步机制(mutex)AliOS-Things+STM32 (四) 同步机制(buf_queue)

到此,我们已经了解了本章开篇的三个问题的程序流程解答。由于进程调度的前两章都是按照单cpu情况来分析code的, 下面一章我们会再分析一下多cpu平台,也就是RHINO_CONFIG_CPU_NUM>1的情况。

——————————————————————————————————————————————
关于线程调度策略:(描述摘录自 https://blog.csdn.net/h490516509/article/details/85252386,图片摘录自https://www.cnblogs.com/cynchanpin/p/6897070.html)
线程的调度有三种策略:SCHED_OTHER、SCHED_RR和SCHED_FIFO。

SCHED_OTHER

它是默认的线程分时调度策略,所有的线程的优先级别都是0,线程的调度是通过分时来完成的。简单地说,如果系统使用这种调度策略,程序将无法设置线程的优先级。请注意,这种调度策略也是抢占式的,当高优先级的线程准备运行的时候,当前线程将被抢占并进入等待队列。这种调度策略仅仅决定线程在可运行线程队列中的具有相同优先级的线程的运行次序。

SCHED_FIFO

它是一种实时的先进先出调用策略,且只能在超级用户下运行。这种调用策略仅仅被使用于优先级大于0的线程。它意味着,使用SCHED_FIFO的可运行线程将一直抢占使用SCHED_OTHER的运行线程J。此外SCHED_FIFO是一个非分时的简单调度策略,当一个线程变成可运行状态,它将被追加到对应优先级队列的尾部。当所有高优先级的线程终止或者阻塞时,它将被运行。对于相同优先级别的线程,按照简单的先进先运行的规则运行。我们考虑一种很坏的情况,如果有若干相同优先级的线程等待执行,然而最早执行的线程无终止或者阻塞动作,那么其他线程是无法执行的,除非当前线程调用如pthread_yield之类的函数,所以在使用SCHED_FIFO的时候要小心处理相同级别线程的动作。
在这里插入图片描述
SCHED_RR

鉴于SCHED_FIFO调度策略的一些缺点,SCHED_RR对SCHED_FIFO做出了一些增强功能。从实质上看,它还是SCHED_FIFO调用策略。它使用最大运行时间来限制当前进程的运行,当运行时间大于等于最大运行时间的时候,当前线程将被切换并放置于相同优先级队列的最后。这样做的好处是其他具有相同级别的线程能在“自私“线程下执行。
在这里插入图片描述
综上:
1,SCHED_OTHER 分时调度策略,
2,SCHED_FIFO 实时调度策略,先到先服务
3,SCHED_RR 实时调度策略,同一优先级时间片轮转

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值