这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » MCU » Nuttx Task Schedule

共3条 1/1 1 跳转至

Nuttx Task Schedule

高工
2019-01-17 17:57:44     打赏
调度概念进程调度

按照某种调度算法从就绪队列中选取进程分配CPU,主要是协调对CPU等的资源使用。进程调度目标是最大限度地利用CPU时间,只要有可以执行的进程,那么总会有进程正在执行,只要进程数目比处理器个数多,就注定某一个给定时刻会有一些进程不能执行。

进程切换

CPU资源的当前占有者进行切换,将Context(CPU状态,主要是寄存器的状态)保存至当前进程的TCB中,并恢复下一个进程的上下文。

进程状态

进程在运行过程中一般存在三种状态:

  • 就绪: 进程已分配到除CPU之外的所有必要资源,只要获取CPU便可执行;

  • 执行: 当前进程正在CPU上运行;

  • 阻塞: 正在执行的进程,由于等待某些资源无法执行,放弃CPU处于阻塞状态

进程状态数据结构

与调度相关的数据结构包括:

  • 任务描述符,在nuttx中是以struct tcb_s来定义任务

  • 任务状态,对应进程三状态,及其他相关状态,nuttx中以enum tstate_e来定义

  • 任务队列,存放不同状态的task


任务描述符

FAR struct wdog_s;                       /* Forward reference                   */struct tcb_s{
  /* Fields used to support list management *************************************/
  /* 双向链表,用于将相同状态的task连成任务队列 */
  FAR struct tcb_s *flink;               /* Doubly linked list                  */
  FAR struct tcb_s *blink;

  /* Task Group *****************************************************************/#ifdef HAVE_TASK_GROUP
  FAR struct task_group_s *group;        /* Pointer to shared task group data   */#endif

  /* Task Management Fields *****************************************************/
  /* 任务管理的相关字段 */
  pid_t    pid;                          /* This is the ID of the thread        */
  start_t  start;                        /* Thread start function               */
  entry_t  entry;                        /* Entry Point into the thread         */
  uint8_t  sched_priority;               /* Current priority of the thread      */
  uint8_t  init_priority;                /* Initial priority of the thread      */#ifdef CONFIG_PRIORITY_INHERITANCE#if CONFIG_SEM_NNESTPRIO > 0
  uint8_t  npend_reprio;                 /* Number of nested reprioritizations  */
  uint8_t  pend_reprios[CONFIG_SEM_NNESTPRIO];#endif
  uint8_t  base_priority;                /* "Normal" priority of the thread     */#endif

 /* 进程的状态,对应后边会描述的不同进程状态 */
  uint8_t  task_state;                   /* Current state of the thread         */#ifdef CONFIG_SMP
  uint8_t  cpu;                          /* CPU index if running or assigned    */
  cpu_set_t affinity;                    /* Bit set of permitted CPUs           */#endif
  uint16_t flags;                        /* Misc. general status flags          */
  int16_t  lockcount;                    /* 0=preemptable (not-locked)          */#ifdef CONFIG_SMP
  int16_t  irqcount;                     /* 0=interrupts enabled                */#endif#ifdef CONFIG_CANCELLATION_POINTS
  int16_t  cpcount;                      /* Nested cancellation point count     */#endif#if CONFIG_RR_INTERVAL > 0 || defined(CONFIG_SCHED_SPORADIC)
  int32_t  timeslice;                    /* RR timeslice OR Sporadic budget     */
                                         /* interval remaining                  */#endif#ifdef CONFIG_SCHED_SPORADIC
  FAR struct sporadic_s *sporadic;       /* Sporadic scheduling parameters      */#endif

  FAR struct wdog_s *waitdog;            /* All timed waits use this timer      */

  /* 每个task都有自己的栈区域 */
  /* Stack-Related Fields *******************************************************/

  size_t    adj_stack_size;              /* Stack size after adjustment         */
                                         /* for hardware, processor, etc.       */
                                         /* (for debug purposes only)           */
  FAR void *stack_alloc_ptr;             /* Pointer to allocated stack          */
                                         /* Need to deallocate stack            */
  FAR void *adj_stack_ptr;               /* Adjusted stack_alloc_ptr for HW     */
                                         /* The initial stack pointer value     */

  /* External Module Support ****************************************************/#ifdef CONFIG_PIC
  FAR struct dspace_s *dspace;           /* Allocated area for .bss and .data   */#endif

  /* POSIX Semaphore Control Fields *********************************************/

  sem_t *waitsem;                        /* Semaphore ID waiting on             */

  /* POSIX Signal Control Fields ************************************************/#ifndef CONFIG_DISABLE_SIGNALS
  sigset_t   sigprocmask;                /* Signals that are blocked            */
  sigset_t   sigwaitmask;                /* Waiting for pending signals         */
  sq_queue_t sigpendactionq;             /* List of pending signal actions      */
  sq_queue_t sigpostedq;                 /* List of posted signals              */
  siginfo_t  sigunbinfo;                 /* Signal info when task unblocked     */#endif

  /* POSIX Named Message Queue Fields *******************************************/#ifndef CONFIG_DISABLE_MQUEUE
  FAR struct mqueue_inode_s *msgwaitq;   /* Waiting for this message queue      */#endif

  /* Library related fields *****************************************************/

  int pterrno;                           /* Current per-thread errno            */

  /* State save areas ***********************************************************/
  /* The form and content of these fields are platform-specific.                */

  struct xcptcontext xcp;                /* Interrupt register save area        */#if CONFIG_TASK_NAME_SIZE > 0
  char name[CONFIG_TASK_NAME_SIZE+1];    /* Task name (with NUL terminator)     */#endif};

在struct tcb_s中,有一个数据结构需要了解一下,那就是struct task_group_s,用于描述任务组的信息,其中里边包括了子任务状态、环境变量、文件描述符、Sockets等信息,这就能对应到Linux系统中的任务描述符包含的信息了。

/* struct task_group_s ***********************************************************//* All threads created by pthread_create belong in the same task group (along with
 * the thread of the original task).  struct task_group_s is a shared structure
 * referenced by the TCB of each thread that is a member of the task group.
 *
 * This structure should contain *all* resources shared by tasks and threads that
 * belong to the same task group:
 *
 *   Child exit status
 *   Environment variables
 *   PIC data space and address environments
 *   File descriptors
 *   FILE streams
 *   Sockets
 *   Address environments.
 *
 * Each instance of struct task_group_s is reference counted. Each instance is
 * created with a reference count of one.  The reference incremented when each
 * thread joins the group and decremented when each thread exits, leaving the
 * group.  When the reference count decrements to zero, the struct task_group_s
 * is free.
 */#ifdef HAVE_TASK_GROUP#ifndef CONFIG_DISABLE_PTHREADstruct join_s;                      /* Forward reference                        */
                                    /* Defined in sched/pthread/pthread.h       */#endifstruct task_group_s{#if defined(HAVE_GROUP_MEMBERS) || defined(CONFIG_ARCH_ADDRENV)
  struct task_group_s *flink;       /* Supports a singly linked list            */
  gid_t tg_gid;                     /* The ID of this task group                */#endif#ifdef HAVE_GROUP_MEMBERS
  gid_t tg_pgid;                    /* The ID of the parent task group          */#endif#if !defined(CONFIG_DISABLE_PTHREAD) && defined(CONFIG_SCHED_HAVE_PARENT)
  pid_t tg_task;                    /* The ID of the task within the group      */#endif
  uint8_t tg_flags;                 /* See GROUP_FLAG_* definitions             */

  /* Group membership ***********************************************************/

  uint8_t    tg_nmembers;           /* Number of members in the group           */#ifdef HAVE_GROUP_MEMBERS
  uint8_t    tg_mxmembers;          /* Number of members in allocation          */
  FAR pid_t *tg_members;            /* Members of the group                     */#endif#if defined(CONFIG_SCHED_ATEXIT) && !defined(CONFIG_SCHED_ONEXIT)
  /* atexit support ************************************************************/# if defined(CONFIG_SCHED_ATEXIT_MAX) && CONFIG_SCHED_ATEXIT_MAX > 1
  atexitfunc_t tg_atexitfunc[CONFIG_SCHED_ATEXIT_MAX];# else
  atexitfunc_t tg_atexitfunc;       /* Called when exit is called.             */# endif#endif#ifdef CONFIG_SCHED_ONEXIT
  /* on_exit support ***********************************************************/# if defined(CONFIG_SCHED_ONEXIT_MAX) && CONFIG_SCHED_ONEXIT_MAX > 1
  onexitfunc_t tg_onexitfunc[CONFIG_SCHED_ONEXIT_MAX];
  FAR void *tg_onexitarg[CONFIG_SCHED_ONEXIT_MAX];# else
  onexitfunc_t tg_onexitfunc;       /* Called when exit is called.             */
  FAR void *tg_onexitarg;           /* The argument passed to the function     */# endif#endif#ifdef CONFIG_SCHED_HAVE_PARENT
  /* Child exit status **********************************************************/#ifdef CONFIG_SCHED_CHILD_STATUS
  FAR struct child_status_s *tg_children; /* Head of a list of child status     */#endif#ifndef HAVE_GROUP_MEMBERS
  /* REVISIT: What if parent thread exits?  Should use tg_pgid. */

  pid_t    tg_ppid;                 /* This is the ID of the parent thread      */#ifndef CONFIG_SCHED_CHILD_STATUS
  uint16_t tg_nchildren;            /* This is the number active children       */#endif#endif /* HAVE_GROUP_MEMBERS */#endif /* CONFIG_SCHED_HAVE_PARENT */#if defined(CONFIG_SCHED_WAITPID) && !defined(CONFIG_SCHED_HAVE_PARENT)
  /* waitpid support ************************************************************/
  /* Simple mechanism used only when there is no support for SIGCHLD            */

  uint8_t tg_nwaiters;              /* Number of waiters                        */
  sem_t tg_exitsem;                 /* Support for waitpid                      */
  int *tg_statloc;                  /* Location to return exit status           */#endif#ifndef CONFIG_DISABLE_PTHREAD
  /* Pthreads *******************************************************************/
                                    /* Pthread join Info:                       */
  sem_t tg_joinsem;                 /*   Mutually exclusive access to join data */
  FAR struct join_s *tg_joinhead;   /*   Head of a list of join data            */
  FAR struct join_s *tg_jointail;   /*   Tail of a list of join data            */
  uint8_t tg_nkeys;                 /* Number pthread keys allocated            */#endif#ifndef CONFIG_DISABLE_SIGNALS
  /* POSIX Signal Control Fields ************************************************/

  sq_queue_t tg_sigactionq;         /* List of actions for signals              */
  sq_queue_t tg_sigpendingq;        /* List of pending signals                  */#endif#ifndef CONFIG_DISABLE_ENVIRON
  /* Environment variables ******************************************************/

  size_t     tg_envsize;            /* Size of environment string allocation    */
  FAR char  *tg_envp;               /* Allocated environment strings            */#endif

  /* PIC data space and address environments ************************************/
  /* Logically the PIC data space belongs here (see struct dspace_s).  The
   * current logic needs review:  There are differences in the away that the
   * life of the PIC data is managed.
   */#if CONFIG_NFILE_DESCRIPTORS > 0
  /* File descriptors ***********************************************************/

  struct filelist tg_filelist;      /* Maps file descriptor to file             */#endif#if CONFIG_NFILE_STREAMS > 0
  /* FILE streams ***************************************************************/
  /* In a flat, single-heap build.  The stream list is allocated with this
   * structure.  But kernel mode with a kernel allocator, it must be separately
   * allocated using a user-space allocator.
   */#if (defined(CONFIG_BUILD_PROTECTED) || defined(CONFIG_BUILD_KERNEL)) && \
     defined(CONFIG_MM_KERNEL_HEAP)
  FAR struct streamlist *tg_streamlist;#else
  struct streamlist tg_streamlist;  /* Holds C buffered I/O info                */#endif#endif#if CONFIG_NSOCKET_DESCRIPTORS > 0
  /* Sockets ********************************************************************/

  struct socketlist tg_socketlist;  /* Maps socket descriptor to socket         */#endif#ifndef CONFIG_DISABLE_MQUEUE
  /* POSIX Named Message Queue Fields *******************************************/

  sq_queue_t tg_msgdesq;            /* List of opened message queues           */#endif#ifdef CONFIG_ARCH_ADDRENV
  /* Address Environment ********************************************************/

  group_addrenv_t tg_addrenv;       /* Task group address environment           */#endif#ifdef CONFIG_MM_SHM
  /* Shared Memory **************************************************************/

  struct group_shm_s tg_shm;        /* Task shared memory logic                 */#endif};#endif

基于struct tcb_s又扩展了两个数据结构,分别用于描述task和线程:

/* struct task_tcb_s *************************************************************//* This is the particular form of the task control block (TCB) structure used by
 * tasks (and kernel threads).  There are two TCB forms:  one for pthreads and
 * one for tasks.  Both share the common TCB fields (which must appear at the
 * top of the structure) plus additional fields unique to tasks and threads.
 * Having separate structures for tasks and pthreads adds some complexity, but
 * saves memory in that it prevents pthreads from being burdened with the
 * overhead required for tasks (and vice versa).
 */struct task_tcb_s{
  /* Common TCB fields **********************************************************/

  struct tcb_s cmn;                      /* Common TCB fields                   */

  /* Task Management Fields *****************************************************/#ifdef CONFIG_SCHED_STARTHOOK
  starthook_t starthook;                 /* Task startup function               */
  FAR void *starthookarg;                /* The argument passed to the function */#endif

  /* [Re-]start name + start-up parameters **************************************/

  FAR char **argv;                       /* Name+start-up parameters            */};
/* struct pthread_tcb_s **********************************************************//* This is the particular form of the task control block (TCB) structure used by
 * pthreads.  There are two TCB forms:  one for pthreads and one for tasks.  Both
 * share the common TCB fields (which must appear at the top of the structure)
 * plus additional fields unique to tasks and threads.  Having separate structures
 * for tasks and pthreads adds some complexity,  but saves memory in that it
 * prevents pthreads from being burdened with the overhead required for tasks
 * (and vice versa).
 */#ifndef CONFIG_DISABLE_PTHREADstruct pthread_tcb_s{
  /* Common TCB fields **********************************************************/

  struct tcb_s cmn;                      /* Common TCB fields                   */

  /* Task Management Fields *****************************************************/

  pthread_addr_t arg;                    /* Startup argument                    */
  FAR void *joininfo;                    /* Detach-able info to support join    */

  /* Clean-up stack *************************************************************/#ifdef CONFIG_PTHREAD_CLEANUP
  /* tos   - The index to the next avaiable entry at the top of the stack.
   * stack - The pre-allocated clean-up stack memory.
   */

  uint8_t tos;  struct pthread_cleanup_s stack[CONFIG_PTHREAD_CLEANUP_STACKSIZE];#endif

  /* POSIX Thread Specific Data *************************************************/#if CONFIG_NPTHREAD_KEYS > 0
  FAR void *pthread_data[CONFIG_NPTHREAD_KEYS];#endif};#endif /* !CONFIG_DISABLE_PTHREAD */

任务状态

/* General Task Management Types ************************************************//* This is the type of the task_state field of the TCB. NOTE: the order and
 * content of this enumeration is critical since there are some OS tables indexed
 * by these values.  The range of values is assumed to fit into a uint8_t in
 * struct tcb_s.
 */enum tstate_e
{
  TSTATE_TASK_INVALID    = 0, /* INVALID      - The TCB is uninitialized */
  TSTATE_TASK_PENDING,        /* READY_TO_RUN - Pending preemption unlock */
  TSTATE_TASK_READYTORUN,     /* READY-TO-RUN - But not running */#ifdef CONFIG_SMP
  TSTATE_TASK_ASSIGNED,       /* READY-TO-RUN - Not running, but assigned to a CPU */#endif
  TSTATE_TASK_RUNNING,        /* READY_TO_RUN - And running */

  TSTATE_TASK_INACTIVE,       /* BLOCKED      - Initialized but not yet activated */
  TSTATE_WAIT_SEM,            /* BLOCKED      - Waiting for a semaphore */#ifndef CONFIG_DISABLE_SIGNALS
  TSTATE_WAIT_SIG,            /* BLOCKED      - Waiting for a signal */#endif#ifndef CONFIG_DISABLE_MQUEUE
  TSTATE_WAIT_MQNOTEMPTY,     /* BLOCKED      - Waiting for a MQ to become not empty. */
  TSTATE_WAIT_MQNOTFULL,      /* BLOCKED      - Waiting for a MQ to become not full. */#endif#ifdef CONFIG_PAGING
  TSTATE_WAIT_PAGEFILL,       /* BLOCKED      - Waiting for page fill */#endif
  NUM_TASK_STATES             /* Must be last */};typedef enum tstate_e tstate_t;

任务队列

/* Task Lists ***************************************************************//* The state of a task is indicated both by the task_state field of the TCB
 * and by a series of task lists.  All of these tasks lists are declared
 * below. Although it is not always necessary, most of these lists are
 * prioritized so that common list handling logic can be used (only the
 * g_readytorun, the g_pendingtasks, and the g_waitingforsemaphore lists
 * need to be prioritized).
 *//* This is the list of all tasks that are ready to run.  This is a
 * prioritized list with head of the list holding the highest priority
 * (unassigned) task.  In the non-SMP cae, the head of this list is the
 * currently active task and the tail of this list, the lowest priority
 * task, is always the IDLE task.
 */volatile dq_queue_t g_readytorun;#ifdef CONFIG_SMP/* In order to support SMP, the function of the g_readytorun list changes,
 * The g_readytorun is still used but in the SMP cae it will contain only:
 *
 *  - Only tasks/threads that are eligible to run, but not currently running,
 *    and
 *  - Tasks/threads that have not been assigned to a CPU.
 *
 * Otherwise, the TCB will be reatined in an assigned task list,
 * g_assignedtasks.  As its name suggests, on 'g_assignedtasks queue for CPU
 * 'n' would contain only tasks/threads that are assigned to CPU 'n'.  Tasks/
 * threads would be assigned a particular CPU by one of two mechanisms:
 *
 *  - (Semi-)permanently through an RTOS interfaces such as
 *    pthread_attr_setaffinity(), or
 *  - Temporarily through scheduling logic when a previously unassigned task
 *    is made to run.
 *
 * Tasks/threads that are assigned to a CPU via an interface like
 * pthread_attr_setaffinity() would never go into the g_readytorun list, but
 * would only go into the g_assignedtasks[n] list for the CPU 'n' to which
 * the thread has been assigned.  Hence, the g_readytorun list would hold
 * only unassigned tasks/threads.
 *
 * Like the g_readytorun list in in non-SMP case, each g_assignedtask[] list
 * is prioritized:  The head of the list is the currently active task on this
 * CPU.  Tasks after the active task are ready-to-run and assigned to this
 * CPU. The tail of this assigned task list, the lowest priority task, is
 * always the CPU's IDLE task.
 */volatile dq_queue_t g_assignedtasks[CONFIG_SMP_NCPUS];#endif/* This is the list of all tasks that are ready-to-run, but cannot be placed
 * in the g_readytorun list because:  (1) They are higher priority than the
 * currently active task at the head of the g_readytorun list, and (2) the
 * currently active task has disabled pre-emption.
 */volatile dq_queue_t g_pendingtasks;/* This is the list of all tasks that are blocked waiting for a semaphore */volatile dq_queue_t g_waitingforsemaphore;/* This is the list of all tasks that are blocked waiting for a signal */#ifndef CONFIG_DISABLE_SIGNALSvolatile dq_queue_t g_waitingforsignal;#endif/* This is the list of all tasks that are blocked waiting for a message
 * queue to become non-empty.
 */#ifndef CONFIG_DISABLE_MQUEUEvolatile dq_queue_t g_waitingformqnotempty;#endif/* This is the list of all tasks that are blocked waiting for a message
 * queue to become non-full.
 */#ifndef CONFIG_DISABLE_MQUEUEvolatile dq_queue_t g_waitingformqnotfull;#endif/* This is the list of all tasks that are blocking waiting for a page fill */#ifdef CONFIG_PAGINGvolatile dq_queue_t g_waitingforfill;#endif/* This the list of all tasks that have been initialized, but not yet
 * activated. NOTE:  This is the only list that is not prioritized.
 */volatile dq_queue_t g_inactivetasks;
调度策略

调度策略又称调度算法,根据系统的资源分配策略所规定的资源分配算法。在代码实现中,看到的就是将task在不同的任务队列中进行移动。在Nuttx中支持的调度算法有:

  • FIFO,先来先服务,在优先级相同时的一种调度策略,FIFO会导致后面的任务延时较大

  • Round Robin,时间片轮转,在优先级相同时的一种调度策略,比如一个task分配200ms的时间片,在同一优先级时,当前task执行完200ms后,让出CPU,切换至队列中的下一个task。

  • Sporadic,偶发调度,sporadic的引入主要是为了去除周期性和非周期性事件对实时性的影响,相比RR策略,它可以在一个设定的时间段里限制线程执行时间的长短。当一个系统同时处理周期性和非周期性事件,对其进行速率单调性分析(Rate Monotonic Analysis)时,这个偶发调度算法是必须的。

/* POSIX-like scheduling policies */#define SCHED_FIFO                1  /* FIFO priority scheduling policy */#define SCHED_RR                  2  /* Round robin scheduling policy */#define SCHED_SPORADIC            3  /* Sporadic scheduling policy */#define SCHED_OTHER               4  /* Not supported */

nuttx支持根据优先级进行抢占,以便支持实时性,使用下边的接口来设置调度策略和优先级:

/****************************************************************************
 * Name:sched_setscheduler
 *
 * Description:
 *   sched_setscheduler() sets both the scheduling policy and the priority
 *   for the task identified by pid. If pid equals zero, the scheduler of
 *   the calling task will be set.  The parameter 'param' holds the priority
 *   of the thread under the new policy.
 *
 * Inputs:
 *   pid - the task ID of the task to modify.  If pid is zero, the calling
 *      task is modified.
 *   policy - Scheduling policy requested (either SCHED_FIFO or SCHED_RR)
 *   param - A structure whose member sched_priority is the new priority.
 *      The range of valid priority numbers is from SCHED_PRIORITY_MIN
 *      through SCHED_PRIORITY_MAX.
 *
 * Return Value:
 *   On success, sched_setscheduler() returns OK (zero).  On error, ERROR
 *   (-1) is returned, and errno is set appropriately:
 *
 *   EINVAL The scheduling policy is not one of the recognized policies.
 *   ESRCH  The task whose ID is pid could not be found.
 *
 * Assumptions:
 *
 ****************************************************************************/int sched_setscheduler(pid_t pid, int policy, FAR const struct sched_param *param)
调度点

进程的调度并不是任意时刻都能进行,必须在某些时间点上完成。调度器这个词容易带来理解误区:有一个scheduler在运行,类似于一个内核线程,由它去完成任务的调度。实际上,调度器只是一个接口函数,当一个task在某些条件下,要让出CPU时,此时就会调用到schedule的接口函数,从而完成进程的切换。


task schedule

常见的调度点有:

  • 时间片轮转调度时机,主要是在system tick时,在timer的中断中调用sched_process_timer()函数,周期性的处理tick,当优先级发生转换时,会调用up_reprioritize_ptr()函数,并会出发context的切换。

  • 抢占式调度时机,包括在等待信号量、信号、消息队列、环境变量、调度器设置、任务创建与恢复、yield等。

从代码来入手分析就能清晰看到调度点了,以arm926为例,在路径arch/arm/src/arm目录下,有两个函数up_saveusercontext(), up_fullcontextrestore(),分别用于context的保存和恢复。在任务调度的时候,最终都会调用到这两个函数来完成切换。
在arch/arm/src/arm下,分别有up_block_task(), up_unblock_task(), up_reprioritizertr(), up_releasepending(),四个函数调用了context切换的函数接口, 其中up_releasepending()函数在sched_unlock()函数中调用,因此,便得到了上下文切换的四个上层函数:

  • up_block_task()

  • up_unblock_task()

  • up_reprioritize_rtr()

  • sched_unlock()
    凡是调用到上述四个函数的其中的一个,都可能带来任务的切换,也就是对应的调度点。

  • 调用up_block_task()的接口有:

  1. mq_receive() //message接收

  2. mq_timedsend()

  3. mq_send() //message发送

  4. mq_timedreceive()

  5. sem_wait() //信号量加锁

  6. sigsuspend() //信号suspend

  7. sigtimedwait() //信号等待

  • 调用up_unblock_task()的接口有:

  1. mq_receive() //message接收

  2. mq_timedsend()

  3. mq_send() //message发送

  4. mq_timedreceive()

  5. sem_post() //信号量解锁

  6. sig_tcbdispatch() //信号dispatch

  7. sem_waitirq()

  8. mq_waitirq()

  9. sig_timeout()

  10. task_activate() //激活task,这个在task_create/task_vforkstart/时都会调用到

  • 调用sched_unlock()的接口有:

  1. mq_receive() //message接收

  2. mq_timedsend()

  3. mq_send() //message发送

  4. mq_timedreceive()

  5. mq_notify()

  6. sem_reset()

  7. sig_deliver()

  8. sig_queueaction()

  9. sig_findaction()

  10. kill()

  11. sig_mqnotempty()

  12. sigprocmask()

  13. sigqueue()

  14. sigsuspend()

  15. sigtimedwait()

  16. sig_unmaskpendingsignal()

  17. env_dup() //环境变量相关

  18. getenv()

  19. setenv()

  20. unsetenv()

  21. group_assigngid() //组相关

  22. sched_getaffinity()

  23. sched_getparam()

  24. setaffinity()

  25. sched_setparam()

  26. sched_setscheduler()

  27. waitid()

  28. atexit()

  29. task_signalparent()

  30. on_exit()

  31. posix_spawn()

  32. task_assignpid()

  33. thread_schedsetup()

  34. task_restart()

  35. task_spawn()

  36. task_terminate()

  37. lpwork_boostpriority()

  38. lpwork_restorerepriority()

  39. work_lpstart()

  • 调用up_reprioritizertr()的接口有:

  1. sched_roundrobin_process() //在进行RR调度时会调用

  2. sched_running_setpriority() //在运行时的优先级设置

  3. sched_readytorun_setpriority() //在ready-to-run时的优先级设置

Context切换

以arm926为例,底层实现context切换的代码位于arch/arm/src/arm/目录下,分别为up_saveusercontext(), up_fullcontextrestore()两个函数。

  • up_saveusercontext(), 完成的任务是将所有的寄存器保存至tcb->xcptcontext中,也就是将现场都保存好

    .text
    .globl  up_saveusercontext
    .type   up_saveusercontext, function
up_saveusercontext:    /* On entry, a1 (r0) holds address of struct xcptcontext.
     * Offset to the user region.
     */

    /* Make sure that the return value will be non-zero (the
     * value of the other volatile registers don't matter --
     * r1-r3, ip).  This function is called throught the
     * noraml C calling conventions and the values of these
     * registers cannot be assumed at the point of setjmp
     * return.
     */

        mov ip, #1
    str ip, [r0, #(4*REG_R0)]

    /* Save the volatile registers (plus r12 which really
     * doesn't need to be saved)
     */

    add r1, r0, #(4*REG_R4)
    stmia   r1, {r4-r14}    /* Save the current cpsr */

    mrs r2, cpsr        /* R3 = CPSR value */
    add r1, r0, #(4*REG_CPSR)
    str r2, [r1]    /* Finally save the return address as the PC so that we
     * return to the exit from this function.
     */

        add r1, r0, #(4*REG_PC)
    str lr, [r1]    /* Return 0 */

    mov r0, #0      /* Return value == 0 */
    mov pc, lr      /* Return */
    .size   up_saveusercontext, . - up_saveusercontext
  • up_fullcontextrestore(), 完成将tcb->xtcpcontext中保存的寄存器值恢复到CPU的寄存器中,将现场恢复。

    .globl  up_fullcontextrestore
    .type   up_fullcontextrestore, functionup_fullcontextrestore:

    /* On entry, a1 (r0) holds address of the register save area */

    /* Recover all registers except for r0, r1, R15, and CPSR */    add r1, r0, #(4*REG_R2) /* Offset to REG_R2 storage */    ldmia   r1, {r2-r14}        /* Recover registers */

    /* Create a stack frame to hold the PC */
    sub sp, sp, #4              /* Frame for one register */
    ldr r1, [r0, #(4*REG_PC)]   /* Fetch the stored pc value */
    str r1, [sp]                /* Save it in the stack */

    /* Now we can restore the CPSR.  We wait until we are completely
     * finished with the context save data to do this. Restore the CPSR
     * may re-enable and interrupts and we could be in a context
     * where the save structure is only protected by interrupts being
     * disabled.
     */

    ldr r1, [r0, #(4*REG_CPSR)] /* Fetch the stored CPSR value */
    msr cpsr, r1        /* Set the CPSR */

    /* Now recover r0 and r1
     * Then return to the address at the stop of the stack,
     * destroying the stack frame
     */

    ldr r1, [r0, #(4*REG_R1)]       /* Restore r1 register firstly */
    ldr r0, [r0, #(4*REG_R0)]
    ldmia sp!, {r15}                /* Return pc value */

    .size up_fullcontextrestore, . - up_fullcontextrestore

上文中提到过调用context切换的四个上层函数:up_block_task(), up_unblock_task(), up_reprioritize_rtr(), up_release_pending(),其中四个函数的实现机制都类似,以up_reprioritize_rtr()为例:

/****************************************************************************
 * Name: up_reprioritize_rtr
 *
 * Description:
 *   Called when the priority of a running or
 *   ready-to-run task changes and the reprioritization will
 *   cause a context switch.  Two cases:
 *
 *   1) The priority of the currently running task drops and the next
 *      task in the ready to run list has priority.
 *   2) An idle, ready to run task's priority has been raised above the
 *      the priority of the current, running task and it now has the
 *      priority.
 *
 * Inputs:
 *   tcb: The TCB of the task that has been reprioritized
 *   priority: The new task priority
 *
 ****************************************************************************/void up_reprioritize_rtr(struct tcb_s *tcb, uint8_t priority)
{  /* Verify that the caller is sane */

  if (tcb->task_state < FIRST_READY_TO_RUN_STATE ||
      tcb->task_state > LAST_READY_TO_RUN_STATE#if SCHED_PRIORITY_MIN > 0
      || priority < SCHED_PRIORITY_MIN#endif#if SCHED_PRIORITY_MAX < UINT8_MAX
      || priority > SCHED_PRIORITY_MAX#endif
    )
    {
       PANIC();
    }  else
    {
      struct tcb_s *rtcb = this_task();  /* 获取g_readytorun队列的头结点 */
      bool switch_needed;

      sinfo("TCB=%p PRI=%d\n", tcb, priority);      
      /* Remove the tcb task from the ready-to-run list.
       * sched_removereadytorun will return true if we just
       * remove the head of the ready to run list.
       */
      
      switch_needed = sched_removereadytorun(tcb);    /* 将tcb从g_readytorun队列中移走 */

      /* Setup up the new task priority */
     
      tcb->sched_priority = (uint8_t)priority;     /* 设置新的优先级 */

      /* Return the task to the specified blocked task list.
       * sched_addreadytorun will return true if the task was
       * added to the new list.  We will need to perform a context
       * switch only if the EXCLUSIVE or of the two calls is non-zero
       * (i.e., one and only one the calls changes the head of the
       * ready-to-run list).
       */

      switch_needed ^= sched_addreadytorun(tcb);    /* 添加回g_readytorun队列中 */

      /* Now, perform the context switch if one is needed */

      if (switch_needed)
        {          /* If we are going to do a context switch, then now is the right
           * time to add any pending tasks back into the ready-to-run list.
           * task list now
           */

          if (g_pendingtasks.head)
            {
              sched_mergepending();    /* 在切换前,将pending队列和readytorun队列merge */
            }          /* Update scheduler parameters */

          sched_suspend_scheduler(rtcb);    

          /* Are we in an interrupt handler? */

          if (CURRENT_REGS)    /* CURRENT_REGS宏用于判断是否在中断中,在中断处理完后CURRENT_REGS会被赋值为NULL */
            {              /* Yes, then we have to do things differently.
               * Just copy the CURRENT_REGS into the OLD rtcb.
               */

               up_savestate(rtcb->xcp.regs);    /* 保存寄存器到老的rtcb中 */

              /* Restore the exception context of the rtcb at the (new) head
               * of the ready-to-run task list.
               */

              rtcb = this_task();  /* 找到将要切换过去的新tcb */

              /* Update scheduler parameters */

              sched_resume_scheduler(rtcb);    /* 更新参数 */

              /* Then switch contexts.  Any necessary address environment
               * changes will be made when the interrupt returns.
               */

              up_restorestate(rtcb->xcp.regs);    /* 将新tcb的寄存器值恢复到CPU中 */
            }          /* Copy the exception context into the TCB at the (old) head of the
           * ready-to-run Task list. if up_saveusercontext returns a non-zero
           * value, then this is really the previously running task restarting!
           */

          else if (!up_saveusercontext(rtcb->xcp.regs))    /* 不在中断中,进行context切换 */
            {              /* Restore the exception context of the rtcb at the (new) head
               * of the ready-to-run task list.
               */

              rtcb = this_task();#ifdef CONFIG_ARCH_ADDRENV
              /* Make sure that the address environment for the previously
               * running task is closed down gracefully (data caches dump,
               * MMU flushed) and set up the address environment for the new
               * thread at the head of the ready-to-run list.
               */

              (void)group_addrenv(rtcb);#endif
              /* Update scheduler parameters */

              sched_resume_scheduler(rtcb);              /* Then switch contexts */

              up_fullcontextrestore(rtcb->xcp.regs);   /* 恢复之后,变跳转到新的任务上执行了 */
            }
        }
    }

最后,把nuttx的调度器相关代码图片贴一下,当你把流程跟一下后,发现其实很多文件都已经用到了

Task Schedule source code


补充

之前一直在类似中断中是否能进行任务切换,在关了中断之后怎么还能进行任务切换等问题上纠缠不清,可以重新捋了一下。

  • 在timer中断中完成任务切换,如下图:


    中断中完成任务切换


    中断触发后的步骤:

  1. IRQ disable & Mode Switch:进入中断时处理器是在中断模式下,会先切换至SVC模式,并关掉中断

  2. IRQ Context Save:现场保存,将所有的寄存器保存至中断栈上,并会将保存的地址R0传递给up_decodeirq,在up_decodeirq中,会用一个全局的宏CURRENT_REGS指向传递的地址R0

  3. up_savestate():在IRQ dispatch的过程中,如果遇到了需要task切换的点,比如调到up_reprioritize_rtr()函数,此时该函数会判断CURRENT_REGS宏是否为NULL,用于确定任务的切换是否发生在中断Handler中。发生在中断Handler中时,调用up_savestate()函数,将CURRENT_REGS中的内容保存至task A中。

  4. up_restorestate():将需要切换的task B中的现场恢复到CURRENT_REGS中,此时已经完成了内容的覆盖,但是并没有将值写入寄存器中,也就是没有完成真正的任务切换。

  5. IRQ Context Restore:当中断执行完毕后,恢复现场,此时会将之前地址上保存的值恢复到寄存器里,关键点来了,因为在up_restorestate()的时候,已经将新任务的内容覆盖了原有的,当完成现场恢复后,此时就跳转到了task B中去了,也就完成了任务的切换。

  • 在关中断后进行任务切换

    关中断后任务切换


    我在阅读源码的时候,发现enter_critical_section()后进行了任务切换。当场就有点迷糊了,这个一关中断,切换到新任务后,新任务难道就不能响应中断了?后来发现了其实忽略了一个重要关键点,在TASK_B Context Restore的过程中,会将TASK_B保存的context中的CPSR恢复到寄存器中,这时候运行的就是TASK_B的现场了,跟之前TASK_A没有关系了。
    上图中,TASK_B Context Restore执行完后,TASK_A就中断在这个点上了,当下一次再调度到TASK_A时,会接着从这个点往下执行,所以就算在之前enter_critical_section()关了中断,运行后leave_critical_section()会打开中断,接着执行TASK_A后续的流程。


作者:Loyen




管理员
2019-01-18 09:30:22     打赏
2楼

涨姿势


院士
2019-01-18 10:46:04     打赏
3楼

这个是新发的一款RTOS吗?



共3条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]