diff --git a/docs/doc/kernel/imagesrc/arm_test_code.png b/docs/doc/kernel/imagesrc/arm_test_code.png deleted file mode 100644 index 527f07c..0000000 Binary files a/docs/doc/kernel/imagesrc/arm_test_code.png and /dev/null differ diff --git a/docs/doc/kernel/imagesrc/arm_test_switch.png b/docs/doc/kernel/imagesrc/arm_test_switch.png deleted file mode 100644 index 52f8b7b..0000000 Binary files a/docs/doc/kernel/imagesrc/arm_test_switch.png and /dev/null differ diff --git a/docs/doc/kernel/imagesrc/arm_test_switch1.png b/docs/doc/kernel/imagesrc/arm_test_switch1.png deleted file mode 100644 index e33a7f2..0000000 Binary files a/docs/doc/kernel/imagesrc/arm_test_switch1.png and /dev/null differ diff --git a/docs/doc/kernel/imagesrc/riscv_test_switch.png b/docs/doc/kernel/imagesrc/riscv_test_switch.png deleted file mode 100644 index 246025e..0000000 Binary files a/docs/doc/kernel/imagesrc/riscv_test_switch.png and /dev/null differ diff --git a/docs/doc/kernel/task.md b/docs/doc/kernel/task.md index 8c6384a..e85007c 100644 --- a/docs/doc/kernel/task.md +++ b/docs/doc/kernel/task.md @@ -27,85 +27,76 @@ ## 任务机制 -任务(task)是XiUOS中处理器使用权分配的最小单位。每个任务有自己的程序栈与寄存器上下文,在多处理器平台上可以互不干扰地同时运行,但单个处理器上任意时刻只能有一个任务在运行。用户可以使用XiUOS提供的接口创建任意数量的任务。内核会对系统中的所有任务按照一定策略(抢占式优先级或先来先服务)进行调度,以最大限度地利用处理器资源。用户可以使用XiUOS提供的接口创建任意数量的任务。 +任务(task)是 XiUOS 中处理器使用权分配的最小单位。每个任务有自己的程序栈与寄存器上下文,在多处理器平台上可以互不干扰地同时运行,但单个处理器上任意时刻只能有一个任务在运行。用户可以使用 XiUOS 提供的接口创建任意数量的任务。内核会对系统中的所有任务按照一定策略(抢占式优先级或先来先服务)进行调度,最大限度地利用处理器资源。 ### 任务状态 -系统中的任务在任意时刻都处于就绪(ready)、运行(running)、阻塞/挂起(suspend)、退出(quit)四种状态之一。状态之间的变化关系如下图所示。 +XiUOS 中的任务在任意时刻都处于就绪(ready)、运行(running)、阻塞/挂起(suspend)、退出(quit)四种状态之一。状态之间的变化关系如下图所示。 -任务在创建完成后会进入就绪状态并被加入就绪队列等待内核调度。当任务被调度开始运行时,任务会进入运行状态。若任务在运行过程中被更高优先级的任务抢占,则被抢占的任务会回到就绪队列并再次进入就绪状态。当任务在运行过程中申请资源失败时,任务会被挂起并进入挂起状态,并在所申请资源能够被满足时回到就绪状态。当任务执行完成,即从入口函数返回时,会进入终止状态,并由内核回收其相关资源。 +* 任务在创建完成后会进入就绪状态并被加入就绪队列等待内核 CPU 调度 +* 当任务被调度开始运行时,任务会进入运行状态 +* 若任务在运行过程中被更高优先级的任务抢占,则被抢占的任务会回到就绪队列并再次进入就绪状态 +* 当任务在运行过程中申请资源失败时,任务会被挂起并进入挂起状态,并在所申请资源能够被满足时回到就绪状态 +* 当任务执行完成,即从入口函数返回时,会进入终止状态,并由内核回收其相关资源 ### 任务调度 -任务调度即从系统就绪队列中按一定策略选择任务,使其进入运行状态的过程。XiUOS支持以下调度方式: +任务调度即从系统就绪队列中按一定策略选择某一任务,使其进入运行状态的过程。XiUOS 支持以下调度方式: -* 抢占式优先级调度:在创建任务时可以指定任务的优先级,内核总是选取就绪队列中优先级最高的任务。当新创建的任务优先级高于正在运行的任务的优先级时,当前运行的任务会被抢占。若就绪队列中最高优先级任务有多个,则这些任务会按时间片轮转交替运行。 -* 先来先服务(FIFO)调度:任务按照被创建的顺序依次被执行。当一个任务运行完成后,系统才会让下一个任务开始运行。 -* 时间片轮转(RR)调度:任务按照分配的时间片执行,时间片结束,调度一个新的就绪任务执行,当前任务重新就绪,等待下一次的调度。 - +* 抢占式优先级调度,即在创建任务时,用户可以指定任务的优先级,内核总是选取就绪队列中优先级最高的任务。当新创建的任务优先级高于正在运行的任务的优先级时,当前运行任务的CPU使用权会被抢占。若就绪队列中最高优先级任务有多个,则这些任务会按时间片轮转方式交替运行。 +* 先来先服务(FIFO)调度,即任务按照被创建的顺序依次被执行。当一个任务运行完成后,系统才会让下一个任务开始运行。 +* 时间片轮转(RR)调度,即任务按照分配的时间片执行,时间片结束,调度一个新的就绪任务执行,当前任务重新就绪,等待下一次的调度。 + ### 任务结构定义 -每个任务在内核中由一个task_descriptor结构表示,二者一一对应。 - +每一个任务在内核中都有一个task_descriptor描述符,其结构体定义如下: ```c struct task_descriptor { - void *stack_pointer; - + void *stack_pointer; struct dync_sched_member dync_sched_member_x; - - struct t_baseinfo t_baseinfo_x; + struct task_baseinfo t_baseinfo_x; #ifdef XS_USING_SMP - struct smp_info smp_info_x; + struct smp_info smp_info_x; #endif #if defined(XS_USING_EVENT) - xs_uint32 event_ctrl1:3; - xs_uint32 event_ctrl2:29; + xs_uint32 event_ctrl1:3; + xs_uint32 event_ctrl2:29; #endif - xs_err_t error; - XS_DOUBLE_LINKLIST link; + xs_err_t error; + XS_DOUBLE_LINKLIST link; }; ``` -其中stack_pointer成员表示该任务的栈的起始地址,dync_sched_member_x成员包含与任务调度相关的信息,t_baseinfo_x包含任务的基本信息,smp_info_x包含与多处理器相关的信息,event_ctrl1/event_ctrl2用于实现事件集机制(见任务间通信部分文档),error为任务调用内核接口时最近的错误码,link用于将系统中的所有任务组织成一个双链表。各复合成员的详细定义如下: - -```c -struct task_baseinfo { - char name[XS_NAME_MAX]; - void *func_entry; - void *func_param; - xs_uint32 stack_size; - xs_uint8 origin_prio; -}; -``` - -struct task_baseinfo结构记录了任务的基本属性,包括任务的名称(name)、入口函数和参数(func_entry、func_param)、栈大小(stack_size)、优先级(origin_prio)。 - +其中,stack_pointer 指向任务堆栈的起始地址,dync_sched_member_x 包含与任务调度相关的信息,t_baseinfo_x 记录任务的基本信息,smp_info_x 统计与多处理器相关的信息,event_ctrl1 / event_ctrl2 用于实现事件集机制(详见[任务通信](#communication)),error为任务调用内核接口时最近的错误码,即用户线程在使用内核接口时可能会执行失败,此时内核接口返回-1,具体的错误码被保存在error成员中,且在下一次调用内核接口失败时被覆盖,link 用于组织内核中所有的任务。各复合成员的详细定义如下。 +* struct dync_sched_member ```c struct dync_sched_member { - xs_uint8 stat; - xs_uint8 advance_cnt; - xs_uint8 cur_prio; - xs_ubase origin_timeslice; - xs_ubase rest_timeslice; + xs_uint8 stat; + xs_uint8 advance_cnt; + xs_uint8 cur_prio; + xs_ubase origin_timeslice; + xs_ubase rest_timeslice; + #ifdef XS_USING_TASK_ISOLATION - xs_uint8 isolation_flag; - void *isolation; + xs_uint8 isolation_flag; + void *isolation; #endif + union { XS_DOUBLE_LINKLIST sched_link; - XS_AVL sched_avl; - } + XS_AVL sched_avl; + }; XS_DOUBLE_LINKLIST pend_link; - xs_Timer_t task_timer; + xs_Timer_t task_timer; }; #define XS_SUSPEND ((1) << (0)) @@ -114,15 +105,26 @@ struct dync_sched_member { #define XS_QUIT ((1) << (3)) ``` -struct dync_sched_member结构的成员用于记录与调度相关的信息。stat表示任务的当前状态,可以为挂起(XS_SUSPEND)、就绪(XS_READY)、运行(XS_RUNNING)或退出(XS_QUIT)。advance_cnt表示在配置成短作业预先调度时优先处理的时间片周期个数。cur_prio表示任务当前的优先级:为防止出现优先级反转,该优先级可以高于任务创建时配置的优先级。origin_timeslice表示在时间片轮转调度中该任务每次运行的时间片。地址空间隔离信息(isolation_flag变量和指针isolation)。sched_link、sched_avl构成的联合体为就绪队列节点,XiUOS中就绪队列可以配置为双链表(sched_link)或AVL树(sched_avl)。pend_link为任务挂起时使用的等待队列节点。task_timer用于任务睡眠的计时。 +struct dync_sched_member结构用于记录与调度相关的信息。stat记录任务的当前状态,可以为挂起(XS_SUSPEND)、就绪(XS_READY)、运行(XS_RUNNING)或退出(XS_QUIT)。advance_cnt表示在配置成短作业预先调度时优先处理的时间片周期个数。cur_prio表示任务当前的优先级,用于优先级反转,该优先级可以高于任务创建时配置的优先级。origin_timeslice表示在时间片轮转调度时,任务每次运行的时间片。isolation_flag变量和指针isolation支持地址空间隔离。sched_link和sched_avl构成的联合体为就绪队列节点,XiUOS中就绪队列可以组织为双链表(sched_link)或平衡二叉树(sched_avl)。pend_link为任务挂起时使用的等待队列节点。task_timer为任务睡眠的计数器。 +* struct task_baseinfo +```c +struct task_baseinfo { + char name[XS_NAME_MAX]; + void *func_entry; + void *func_param; + xs_uint32 stack_size; + xs_uint8 origin_prio; +}; +``` +struct task_baseinfo结构记录了任务的基本属性,包括任务的名称(name)、入口函数(func_entry)和参数(func_param)、栈大小(stack_size)、初始优先级(origin_prio)。 +* struct smp_info ```c struct smp_info { xs_uint8 combined_coreid; xs_uint8 running_coreid; }; ``` - struct smp_info结构包含多处理器相关的信息,其成员分别表示该任务绑定的CPU ID与正在运行的CPU ID。 @@ -142,7 +144,7 @@ typedef struct xs_utask xs_utask_x; struct task_descriptor* xs_UserTaskCreate(xs_utask_x task); ``` -该函数用于用户态创建一个任务。任务的各个属性由一个struct xs_utask结构表示,包括任务的名称、入口函数及参数、栈大小和优先级,在调用该函数时需要传入该结构的实例用于配置任务。任务创建成功后,内核会为其分配指定大小的栈及其他结构(如struct task_descriptor)结构,并返回任务描述符指针。 +该函数用于用户态的任务创建。任务的各个属性由struct xs_utask结构表示,包括任务的名称、入口函数及参数、栈大小和优先级,在调用该函数时需要传入该结构的实例用于配置任务属性。任务创建成功后,内核会为其分配指定大小的栈及其他结构(如struct task_descriptor),并返回任务描述符指针。 | 参数 | 描述 | | --- | --- | @@ -152,28 +154,28 @@ struct task_descriptor* xs_UserTaskCreate(xs_utask_x task); xs_int32 xs_UserTaskDelete(struct task_descriptor task); ``` -该函数用于删除一个任务,强制使其进入退出状态。若删除成功则返回XS_EOK,若失败则返回-XS_ERROR。 +该函数用于删除一个任务,强制其进入退出状态。若删除成功则返回XS_EOK,否则返回-XS_ERROR。 | 参数 | 描述 | | --- | --- | | task| 待删除的任务描述符 | ```c -xs_int32 xs_UserTaskCoreCombine(struct task_descriptor task, xs_uint8 coreid); +xs_int32 xs_UserTaskCoreCombine(struct task_descriptor task_id, xs_uint8 core_id); ``` -该函数用于将任务绑定至指定的处理器上。若绑定成功则返回XS_EOK,若失败则返回-XS_ERROR。 +该函数用于将任务绑定至指定的处理器上。若绑定成功则返回XS_EOK,否则返回-XS_ERROR。 | 参数 | 描述 | | --- | --- | | task_id | 待绑定的任务描述符 | -| coreid | 待绑定的处理器ID | +| core_id | 待绑定的处理器ID | ```c xs_int32 xs_UserTaskCoreUncombine(struct task_descriptor task); ``` -该函数用于解除任务与处理器的绑定。若解除成功则返回XS_EOK,若失败则返回-XS_ERROR。 +该函数用于解除任务与处理器的绑定。若解除成功则返回XS_EOK,否则返回-XS_ERROR。 | 参数 | 描述 | | --- | --- | @@ -183,7 +185,7 @@ xs_int32 xs_UserTaskCoreUncombine(struct task_descriptor task); xs_int32 xs_UserTaskDelay(xs_ticks_x ticks); ``` -该函数用于将当前任务挂起一定时间,单位为tick。挂起时间结束后,任务会进入就绪状态,可以继续被调度。 +该函数用于将当前任务挂起一定时间,单位为tick。挂起时间结束后,任务会进入就绪状态,等待系统调用。 | 参数 | 描述 | | --- | --- | @@ -193,18 +195,16 @@ xs_int32 xs_UserTaskDelay(xs_ticks_x ticks); struct task_descriptor* xs_UserTaskSearch(char *name) ``` -该函数用于从任务名称获取任务描述符。 +该函数根据任务名称获取任务描述符。 | 参数 | 描述 | | --- | --- | | name | 任务名称 | - - ## 任务通信 -XiUOS提供多种任务间通信机制,包括消息队列、信号量、互斥量与事件集。 +XiUOS 提供多种任务间通信机制,包括消息队列、信号量、互斥量与事件集。 ### 消息队列 @@ -299,12 +299,12 @@ xs_int32 xs_UserMsgQueueReinit(xs_uint16 id); | 参数 | 描述 | | --- | --- | -| id | 来源消息队列ID | +| id | 消息队列ID | ### 信号量 -信号量(semaphore)具有一个给定的初值。任务可以获取或释放一个信号量。当任务获取信号量时信号量的值递减,释放信号量时值递增。当信号量值递减至0时,后续尝试获取信号量的任务会被挂起。每当一个已获取信号量的任务释放信号量时,内核会从信号量挂起队列上唤醒一个任务。信号量可以用于实现任务间的同步与互斥。 +信号量(semaphore)具有一个给定的初值。任务可以获取或释放一个信号量。当任务获取信号量时,信号量的值递减,释放信号量时,信号量的值递增。当信号量的值递减至0时,后续尝试获取信号量的任务会被挂起。当任务释放信号量时,内核会从该信号量的挂起队列上唤醒一个任务。信号量可以实现任务间的同步与互斥。 #### 信号量结构定义 @@ -346,7 +346,7 @@ void xs_UserSemaphoreDelete(xs_uint16 id); | 参数 | 描述 | | --- | --- | -| id | 待删除的信号量的ID | +| id | 待删除的信号量ID | ```c xs_int32 xs_UserSemaphoreObtain(xs_uint16 id, xs_ticks_x wait_time); @@ -356,7 +356,7 @@ xs_int32 xs_UserSemaphoreObtain(xs_uint16 id, xs_ticks_x wait_time); | 参数 | 描述 | | --- | --- | -| id | 欲获取的信号量的ID | +| id | 欲获取的信号量ID | | wait_time | 等待时间上限,单位ticks;若为0,则不等待 | ```c @@ -367,7 +367,7 @@ xs_int32 xs_UserSemaphoreAbandon(xs_uint16 id); | 参数 | 描述 | | --- | --- | -| id | 待释放的信号量的ID | +| id | 待释放的信号量ID | ```c xs_int32 xs_UserSemaphoreSetValue(xs_uint16 id, xs_uint16 val); @@ -452,7 +452,7 @@ xs_int32 xs_UserMutexAbandon(xs_uint16 id); ### 事件集 -事件集(event set)允许用户定义一个事件集合,集合中的每个事件都可以被任务触发或等待。任务可以同时等待多个事件,此时等待触发的条件可以配置为AND或者OR:当等待触发条件配置为AND时,只有所有被等待的事件均被触发才视作等待结束;当等待触发条件配置为OR时,任意一个被等待的事件触发即视作等待结束。使用事件集可以实现多对多的任务间同步与互斥。 +事件集(event set)允许用户定义一个事件集合,集合中的每个事件都可以被任务触发或等待。任务可以同时等待多个事件,此时等待触发的条件可以配置为 AND 或者 OR :当等待触发条件配置为 AND 时,只有所有被等待的事件均被触发才视作等待结束;当等待触发条件配置为 OR 时,任意一个被等待的事件触发即视作等待结束。使用事件集可以实现多对多的任务间同步与互斥。 #### 事件集结构定义 @@ -465,7 +465,7 @@ xs_int32 xs_UserMutexAbandon(xs_uint16 id); struct xs_event { xs_uint16 id; - xs_uint32 trigge_way; + xs_uint32 trigger_way; XS_DOUBLE_LINKLIST pend_list; XS_DOUBLE_LINKLIST link; @@ -475,7 +475,7 @@ struct xs_event | 成员 | 描述 | | --- | --- | | id | 事件集ID,用于唯一标识一个事件集 | -| trigge_way | 高3位用于记录事件集属性(等待触发条件、是否自动清空),低29位用于表示至多29个事件 | +| trigger_way | 高3位用于记录事件集属性(等待触发条件、是否自动清空),低29位用于表示至多29个事件 | | pend_list | 等待任务链表 | | link | 系统中所有事件集构成的链表 | @@ -485,11 +485,11 @@ struct xs_event xs_int32 xs_UserEventCreate(xs_uint32 options); ``` -该函数用于创建一个事件集。options参数用于配置事件集的属性。可配置的属性有等待触发方式(XS_EVENT_AND或XS_EVENT_OR)及等待触发后是否自动清空其他已触发的事件(XS_EVENT_AUTOCLEAN)。创建成功后,新的事件集会被加入内核的事件集管理链表,并返回该事件集的ID,ID默认范围0-255,可配置。 +该函数用于创建一个事件集。options参数用于配置事件集的属性。可配置的属性有等待触发方式(XS_EVENT_AND 或 XS_EVENT_OR)及等待触发后是否自动清空其他已触发的事件(XS_EVENT_AUTOCLEAN)。创建成功后,新的事件集会被加入内核的事件集管理链表,并返回该事件集的ID,ID默认范围0-255,可配置。 | 参数 | 描述 | | --- | --- | -| options | 事件集配置选项,须在XS_EVENT_AND及XS_EVENT_OR中指定其一,并可以按位或上XS_EVENT_AUTOCLEAN | +| options | 事件集配置选项,须在 XS_EVENT_AND 及 XS_EVENT_OR 中指定其一,并可以按位或上 XS_EVENT_AUTOCLEAN | ```c void xs_UserEventDele(xs_uint16 id); @@ -509,13 +509,13 @@ xs_int32 xs_UserEventTrigger(xs_uint16 id, xs_uint32 events); | 参数 | 描述 | | --- | --- | | id | 事件集ID | -| events | 欲触发的事件,其中被置1的位标识被触发的事件 ,可以使用XS_EVENTS宏按位或发送事件类型| +| events | 欲触发的事件,其中被置1的位标识被触发的事件 ,可以使用 XS_EVENTS 宏按位或发送事件类型| ```c xs_int32 xs_UserEventProcess(xs_uint16 id, xs_uint32 events, xs_ticks_x wait_time); ``` -该函数用于等待事件集中的一组事件。若等待成功则返回XS_EOK,此时若XS_EVENT_AUTOCLEAN被打开则事件集中所有已触发事件会被清空;若等待失败(超时)则返回-XS_ETIMEOUT。 +该函数用于等待事件集中的一组事件。若等待成功则返回 XS_EOK,此时若 XS_EVENT_AUTOCLEAN 被打开则事件集中所有已触发事件会被清空;若等待失败(超时)则返回 -XS_ETIMEOUT。 | 参数 | 描述 | | --- | --- | @@ -527,12 +527,12 @@ xs_int32 xs_UserEventProcess(xs_uint16 id, xs_uint32 events, xs_ticks_x wait_tim xs_int32 xs_UserEventConfig(xs_uint16 id, xs_uint32 options); ``` -该函数用于配置事件集中的一种事件类型。若等配置成功则返回XS_EOK,options参数用于配置事件集的属性。可配置的属性有等待触发方式(XS_EVENT_AND或XS_EVENT_OR)及等待触发后是否自动清空其他已触发的事件(XS_EVENT_AUTOCLEAN)。 +该函数用于配置事件集中的一种事件类型。若配置成功则返回 XS_EOK,options 参数用于配置事件集的属性。可配置的属性有等待触发方式(XS_EVENT_AND 或 XS_EVENT_OR)及等待触发后是否自动清空其他已触发的事件(XS_EVENT_AUTOCLEAN)。 | 参数 | 描述 | | --- | --- | | id | 事件集ID | -| options | 事件集配置选项,须在XS_EVENT_AND及XS_EVENT_OR中指定其一,并可以按位或上XS_EVENT_AUTOCLEAN | +| options | 事件集配置选项,须在 XS_EVENT_AND 及 XS_EVENT_OR 中指定其一,并可以按位或上 XS_EVENT_AUTOCLEAN | ```c xs_int32 xs_UserEventReinit(xs_uint16 id); @@ -613,7 +613,7 @@ XiUOS 中的任务在编译链接后形成Linux通用的ELF文件结构,其中 XiUOS 的任务通过 malloc/free 等内核服务接口来动态申请和释放内存空间,并能根据用户任务申请和释放的内存地址更新任务隔离表。当用户程序通过 malloc 等内核服务接口向内核申请指定大小的内存空间时,系统会在用户程序对应的任务中增加对内存空间的访问权限,同时将更新后的任务隔离表加载到内存保护单元配置寄存器中,使其生效。当用户程序通过 free 等内核服务接口向内核释放指定大小的内存空间时,系统会在用户程序对应的任务隔离表中清除对这段内存空间的访问权限,同时加载更新后的任务隔离表到内存保护单元寄存器中,使其生效。 此外,XiUOS 还支持共享内存的任务隔离,其基本思路同动态内存申请/释放时相同。进一步地,当用户程序因为某种原因试图访问没有权限的内存空间时,CPU 会产生一个访问错误的异常,并进入内核服务接口的异常处理流程,在这个流程中会将该任务直接杀死并回收任务资源。 4. RISC-V 64架构任务隔离 -大部分 RISC-V 架构的 CPU 拥有特权模式和非特权模式。这两种模式都为 CPU 核提供了物理内存保护(PMP,Physical Memory Protection)功能。通过编程 PMP,可以为指定大小的内存区域设置读、写、执行等访问权限。PMP 包括 8~16 组配置寄存器和地址寄存器,一组配置寄存器和地址寄存器称为一个 PMP entry,PMP entry 对应之前定义的 isolation_region,用于标识一段内存地址空间的访问权限,其结构定义如下: +大部分 RISC-V 架构的 CPU 拥有特权模式和非特权模式。这两种模式都为 CPU 核提供了物理内存保护(PMP,Physical Memory Protection)功能。通过编程 PMP,可以为指定大小的内存区域设置读、写、执行等访问权限。PMP 包括 8~16 组配置寄存器和地址寄存器,一组配置寄存器和地址寄存器称为一个 PMP entry,PMP entry 对应之前定义的 isolation_region,用于标识一段内存地址空间的访问权限。 对于一个正在运行用户任务,其isolation_table的内容大致如下: ```c isolation_table[16] = @@ -639,22 +639,22 @@ isolation_table[16] = ``` 相关PMP操作的接口如下: ```c -// isolation_table增加一个region: -PMP_add_region(isolation_region *isolation_table, void *region_address,xs_size_t size, int flag ); -// isolation_table增加清除一个region: +// isolation_table增加一个region +PMP_add_region(isolation_region *isolation_table, void *region_address, xs_size_t size, int flag); +// isolation_table增加清除一个region PMP_clear_region(isolation_region *isolation_table, void *region_address,xs_size_t size); -// 将isolation_table加载到PMP中: -PMP_load(isolation_region *isolation_table , xs_uint8 coreid); +// 将isolation_table加载到PMP中 +PMP_load(isolation_region *isolation_table, xs_uint8 coreid); ``` 5. ARM-32 架构任务隔离 ARM32 的 handler mode 和 thread mode 分别对应特权模式和非特权模式。ARM32 架构的 MPU 单元可以对内存地址空间的访问权限进行设置,从而实现任务地址空间的隔离访问。MPU 通过将内存空间划分为多个 “region” 进行权限设置,一个 region 就是一段连续的地址空间,对应之前定义的 isolation_region,一般 MPU 支持设置8~16个regions。在启用MPU后,程序就无法访问定义之外的地址区间,也不得访问未经授权的region,否则,将会触发内存访问错误。对于正在运的行用户任务,ARM32 的 isolation_table 的内容同 RISC-V 架构基本相同。 相关MPU的操作接口如下: ```c -// isolation_table增加一个region: -MPU_add_region(isolation_region *isolation_table, void *region_address, xs_size_t size, int flag ); -// isolation_table增加清除一个region: +// isolation_table增加一个region +MPU_add_region(isolation_region *isolation_table, void *region_address, xs_size_t size, int flag); +// isolation_table增加清除一个region MPU_clear_region(isolation_region *isolation_table, void *region_address, xs_size_t size); -// 将isolation_table加载到MPU中: +// 将isolation_table加载到MPU中 MPU_load(isolation_region *isolation_table, xs_uint8 coreid); ``` @@ -666,7 +666,7 @@ MPU_load(isolation_region *isolation_table, xs_uint8 coreid); ### 概述 -下面分别测试XiUOS系统运行在基于ARM和RISC-V不同处理器的开发板时,任务的切换时间。 +下面分别测试XiUOS系统运行在基于 ARM 和 RISC-V 不同处理器的开发板时,任务的切换时间。 @@ -690,25 +690,95 @@ XiUOS的任务切换函数为xs_SwitchKthreadContext,在SwitchKthreadContext
-![RISCV TEST CONNECT](./imagesrc/arm_test_code.png) +```c +void realtime_taskswitch_test() +{ + xs_PinMode(GPIO_C13, PIN_MODE_OUTPUT); + xs_PinWrite(GPIO_C13, PIN_LOW); + + while(1){ + xs_DelayKThread(1); + } +} +```
因为测试单板为cortex-m4单板,该系列单板的线程切换是基于pendSV CPU异常进行线程切换,因此下面基于该特点区分测试场景: -* 测量pendSV异常切换: 在SwitchKthreadContext函数入口处将C13管脚置为高电平,在pendSV异常处理过程,保存现场之后,切换到目标任务之前将C13管脚置为低电平。得出的管脚电平时间即为带pendSV异常的的任务切换时间。 -* 只测了SwitchKthreadContext:在SwitchKthreadContext函数入口处将C13管脚置为高电平,在出口位置,将C13管脚置为低电平。得出的管脚电平时间即为不计算pendSV异常的的任务切换时间。 +* 测量pendSV异常切换,在SwitchKthreadContext函数入口处将C13管脚置为高电平,在pendSV异常处理过程,保存现场之后,切换到目标任务之前将C13管脚置为低电平。得出的管脚电平时间即为带pendSV异常的的任务切换时间。 +* 只测了SwitchKthreadContext,在SwitchKthreadContext函数入口处将C13管脚置为高电平,在出口位置,将C13管脚置为低电平。得出的管脚电平时间即为不计算pendSV异常的的任务切换时间。 -
+```c +xs_SwitchKthreadContext: + /* 将GPIO C13置为高电平 */ + LDR r2, =0x40020818 // 测试代码 + MOV r3, #0x2000 // 测试代码 + STR r3, [r2] // 测试代码 -![RISCV TEST CONNECT](./imagesrc/arm_test_switch.png) + LDR r2, =xs_KthreadSwitchInterruptFlag + LDR r3, [r2] + CMP r3, #1 + BEQ _reswitch + MOV r3, #1 + STR r3, [r2] -
+ LDR r2, =xs_InterruptFromKthread + STR r0, [r2] -
+_reswitch: + LDR r2, =xs_InterruptToKthread + STR r1, [r2] -![RISCV TEST CONNECT](./imagesrc/arm_test_switch1.png) + LDR r0, =NVIC_INT_CTRL + LDR r2, =NVIC_PENDSVSET + STR r1, [r0] -
+ /* 将GPIO C13置为低电平 */ + LDR r2, =0x4002081a // 测试代码 + MOV r3, #0x2000 // 测试代码 + STR r3, [r2] // 测试代码 + + BX LR + +.global PendSV_Handler +.type PendSV_Handler, %function +PendSV_Handler: + MRS r2, PRIMASK + CPSID I + + LDR r0, =xs_KthreadSwitchInterruptFlag + LDR r1, [r0] + CBZ r1, pendsv_exit + + MOV r1, #0x00 + STR r1, [r0] + + LDR r0, =xs_InterruptFromKthread + LDR r1, [r0] + CBZ r1, switch_to_thread + + MRS r1, psp + + STMFD r1!, {r4 - r11} + +#if defined (__VFP_FP__) && !defined(__SOFTFP__) + MOV r4, #0x00 + + TST lr, #0x10 + MOVEQ r4, #0x01 + + STMFD r1!, {r4} +#endif + + LDR r0, [r0] + STR r1, [r0] + +switch_to_thread: + /* 将GPIO C13置为低电平 */ + LDR r2, =0x4002081a // 测试代码 + MOV r3, #0x2000 // 测试代码 + STR r3, [r2] // 测试代码 +``` #### 示波器测试选项设置 @@ -720,7 +790,7 @@ XiUOS的任务切换函数为xs_SwitchKthreadContext,在SwitchKthreadContext * 反相:关闭 * 触发设置 * 类型:边沿 - * 信源: CH1 + * 信源: CH1 * 斜率:上升 * 模式:自动 * 触发电压:略低于最高电平即可 @@ -738,14 +808,14 @@ XiUOS的任务切换函数为xs_SwitchKthreadContext,在SwitchKthreadContext -从示波器测试结果上来看,单独测试SwitchKthreadContext的执行时间是1.26us。 +从示波器测试结果上来看,单独测试 SwitchKthreadContext 的执行时间是1.26us。
![RISCV TEST CONNECT](./imagesrc/arm_test_result1.png)
-从示波器测试结果上来看,测试SwitchKthreadContext加上pendSV异常的的执行时间是17us。 +从示波器测试结果上来看,测试 SwitchKthreadContext 加上 pendSV 异常的的执行时间是17us。 @@ -763,23 +833,65 @@ XiUOS的任务切换函数为xs_SwitchKthreadContext,在SwitchKthreadContext -XiUOS的任务切换函数为xs_SwitchKthreadContext,在SwitchKthreadContext函数入口位置将GPIO18管脚置为高电平,出口位置置为低电平;则GPIO18管脚保持高电平的时间即切换时间。 +XiUOS的任务切换函数为 xs_SwitchKthreadContext,在 SwitchKthreadContext 函数入口位置将 GPIO18 管脚置为高电平,出口位置置为低电平;则GPIO18管脚保持高电平的时间即切换时间。 #### 编程代码清单 -
+```c +void realtime_taskswitch_test() +{ + xs_PinMode(GPIO_18, PIN_MODE_OUTPUT); + xs_PinWrite(GPIO_18, PIN_LOW); + while(1){ + xs_DelayKThread(1); + } +} +``` -![RISCV TEST CONNECT](./imagesrc/riscv_test_code.png) - -
初始化GPIO18为输出模式,并初始化为低电平;在while(1)当中调用delay函数,每隔1个时间片发生一次调度。在下面的switch函数入口和出口位置操作GPIO。 -
+```c +.global xs_SwitchKthreadContext +xs_SwitchKthreadContext: + /* 将 GPIO18 置为高电平 */ + lui a5, 0x38001 // 测试代码 + addi a5, a5, 12 // 测试代码 + lw a5, 0(a5) // 测试代码 + sext.w a4, a5 // 测试代码 + lui a5, 0x38001 // 测试代码 + addi a5, a5, 12 // 测试代码 + ori a4, a4, 5 // 测试代码 + sext.w a4, a4 // 测试代码 + sw a4, 0(a5) // 测试代码 -![RISCV TEST CONNECT](./imagesrc/riscv_test_switch.png) + addi sp, sp, -32 * REGBYTES + STORE sp, (a0) -
+ STORE x1, 0 * REGBYTES(sp) + STORE x1, 1 * REGBYTES(sp) + + csrr a0, mstatus + andi a0, a0, 8 + beqz a0, save_mpie + li a0, 0x80 +> save_mpie: ... + +> #ifdef XS_USING_SMP ... + #endif + /* 将GPIO18 置为低电平 */ + lui a5, 0x38001 // 测试代码 + addi a5, a5, 12 // 测试代码 + lw a5, 0(a5) // 测试代码 + sext.w a4, a5 // 测试代码 + lui a5, 0x38001 // 测试代码 + addi a5, a5, 12 // 测试代码 + addi a4, a4, -6 // 测试代码 + sext.w a4, a4 // 测试代码 + sw a4, 0(a5) // 测试代码 + + j xs_switch_kthread_context_exit +``` ### 示波器测试选项设置 @@ -791,7 +903,7 @@ XiUOS的任务切换函数为xs_SwitchKthreadContext,在SwitchKthreadContext * 反相:关闭 * 触发设置 * 类型:边沿 - * 信源: CH1 + * 信源: CH1 * 斜率:上升 * 模式:自动 * 触发电压:略低于最高电平即可 @@ -810,7 +922,7 @@ XiUOS的任务切换函数为xs_SwitchKthreadContext,在SwitchKthreadContext -从示波器测试结果上来看,测试SwitchKthreadContext的执行时间是160ns. +从示波器测试结果上来看,测试 SwitchKthreadContext 的执行时间是160ns。 @@ -822,11 +934,11 @@ XiUOS的任务切换函数为xs_SwitchKthreadContext,在SwitchKthreadContext | XiUOS | KD233开发板 | RISC-V K210 主频 400MHz | 160 | | XiUOS | STM32F407G-DISC1开发板 | STM32f407 主频 168MHz | 1260 | -结果分析: -* XiUOS在RISC-V K210 400MHz CPU主频上任务切换时间为 160 ns低于sylixos的 577.1 ns -* 若进行同等1GHz主频换算,K210上的任务切换时间应为 62.5 ns,XiUOS的任务切换的效率比sylixos提高 89.16% +结果分析: +* XiUOS 在RISC-V K210 400MHz CPU主频上任务切换时间为 160 ns 低于 sylixos 的 577.1 ns +* 若进行同等1 GHz 主频换算,K210 上的任务切换时间应为 62.5 ns,XiUOS 的任务切换的效率比 sylixos 提高 89.16% * 在ARM stm32f407 168MHz CPU主频任务切换时间 1260 ns高于1GHz主频测试的sylixos -* 若进行同等1GHz主频换算,STM32F407上的任务切换时间应为 206.718 ns,XiUOS的任务切换的效率比sylixos提高 64.18% +* 若进行同等1 GHz 主频换算,STM32F407 上的任务切换时间应为 206.718 ns,XiUOS 的任务切换的效率比 sylixos 提高 64.18% 由于XiUOS优化了任务切换的流程,减少了执行指令数,因此,同等主频条件下,任务切换时间更短。