找回密码
 立即注册
首页 业界区 业界 ysyx学习:移植rt-thread

ysyx学习:移植rt-thread

枢覆引 前天 23:05
OS中的上下文切换
上一期我们搞懂了yield-os.c的原理如何,我们理解   玩上下文切换的核心细节之后,我们可以把这些原理迁移到RT-Thread这个更大的操作系统中。
RT-Thread中有两个抽象层, 一个是BSP(Board Support Package), 另一个是libcpu。
负责把 RT-Thread 的线程结构(PCB)和抽象机器(AM)的上下文切换接口连接起来:调用处把目标/来源上下文地址临时放到线程的 user_data,触发 yield(),AM 回调读出这些信息并完成实际的上下文切换;另外还准备线程首次运行时的栈/参数(通过 kcontext)。
对于上下文的创建,需要先实现rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter, rt_uint8_t *stack_addr, void *texit)函数。
  1. rt_uint8_t* rt_hw_stack_init(void* tentry, void* parameter, rt_uint8_t* stack_addr, void* texit) {
  2.     stack_addr = (rt_uint8_t*)(((uintptr_t)stack_addr + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1));
  3.     stack_addr -= sizeof(temp_args);
  4.     temp_args* args = (temp_args*)stack_addr; // 在栈上保存 入口 参数 出口 等信息
  5.     // 设置参数
  6.     args->tentry = (void (*)(void*))tentry;   // tentry是“接受一个 void* 参数、返回 void 的函数指针”
  7.     args->parameter = parameter;
  8.     args->texit = (void (*)(void))texit;      // texit是“不接受参数、返回 void 的函数指针”
  9.     rt_uint8_t* c = (rt_uint8_t*)kcontext((Area){ stack_addr - sizeof(Area) , stack_addr }, (void*)wrap, args);
  10.     return c;
  11. }
复制代码
一开始先对齐,然后拉出栈的位置,在栈上保存 入口 参数 出口 等信息,然后使用kcontext函数(详情见之前的随笔),将控制状态寄存器mepc设置为函数的地址,当你执行mret指令的时候就会跳转到该函数中去,因此会跳转到wrap中,附带参数args。
这个wrap()函数如下,这个是包裹函数,讲义中提到让包裹函数来调用tentry, 并在tentry返回后调用texit,T-Thread会保证代码不会从texit中返回,因此我这个代码这么写。
执行入口函数,并附带上参数,执行完之后执行退出函数。
  1. void wrap(temp_args* args) {
  2.     args->tentry(args->parameter);
  3.     args->texit();
  4.     // 不应该执行于此,RT-Thread会保证代码不会从texit中返回
  5.     while (1);
  6. }
复制代码
接下来来看main函数,main函数代码如下:
  1. int main() {
  2.   ioe_init();
  3. #ifdef __ISA_NATIVE__
  4.   // trigger the real initialization of IOE to
  5.   // perform SDL initialization int this main thread with large stack
  6.   io_read(AM_TIMER_CONFIG);
  7. #endif
  8.   extern void __am_cte_init();
  9.   __am_cte_init();
  10.   extern int entry(void);
  11.   entry();
  12.   return 0;
  13. }
复制代码
先初始化一下ioe外设,在调用__am_cte_init()函数初始化cte。
  1. void __am_cte_init() {
  2.   cte_init(ev_handler);
  3. }
复制代码
意思就是把__am_asm_trap赋值给mtvec,方便后面ecall的时候直接跳进去__am_asm_trap函数。
然后把ev_handler函数注册进回调函数中。
ev_handler函数如下:定义一个当前的线程,可以通过rt_thread_self()这个函数获取当前线程,再使用这个线程中的user_data,可以用current->user_data来使用他,为什么用这个呢,因为这个user_data它用于存放线程的私有数据, 这意味着RT-Thread中调度相关的代码必定不会使用这个成员, 因此它很适合我们用来传递from和to的信息,这里的from是当前线程,这个to是你要跳往的线程,这里的ev_handler是回调函数,当系统陷入yield的时候,执行完一些列操作(看以前的随笔)后会执行这个回调函数了。这个函数主要作用就是把当前上下文保存到from中然后利用to作为下一个上下文进行跳转。
  1. static Context* ev_handler(Event e, Context* c) {
  2.     rt_thread_t current;
  3.     rt_ubase_t* para;
  4.     switch (e.event) {
  5.         case EVENT_YIELD:
  6.             current = rt_thread_self();
  7.             para = (rt_ubase_t*)current->user_data;
  8.             rt_ubase_t from = para[0];
  9.             rt_ubase_t to = para[1];
  10.             if (from) {
  11.                 *((Context**)from) = c; //保存上下文到from中
  12.             }
  13.             c = *(Context**)to;  // 返回to作为下一个上下文
  14.             break;
  15.         case EVENT_IRQ_TIMER:
  16.             return c;
  17.         default:
  18.             printf("Unhandled event ID = %d\n", e.event);
  19.             assert(0);
  20.     }
  21.     return c;
  22. }
复制代码
具体怎么上下文的切换呢,看这两个函数void rt_hw_context_switch_to(rt_ubase_t to)//切换到to的上下文
void rt_hw_context_switch(rt_ubase_t from, rt_ubase_t to)//切换到to的上下文,又要保存当前上下文到from上。
  1. void rt_hw_context_switch_to(rt_ubase_t to) {
  2.     rt_ubase_t data[2];
  3.     rt_thread_t current = rt_thread_self();
  4.     rt_ubase_t temp_ud = current->user_data;   //保存原来的 from 和 to
  5.     data[1] = to;
  6.     current->user_data = (rt_ubase_t)data;
  7.    
  8.     yield();
  9.     current->user_data = temp_ud;
  10. }
复制代码
  1. void rt_hw_context_switch(rt_ubase_t from, rt_ubase_t to) {
  2.     rt_ubase_t data[2];
  3.     rt_thread_t current = rt_thread_self();
  4.     rt_ubase_t temp_ud = current->user_data;   //保存原来的 from 和 to
  5.     data[0] = from;
  6.     data[1] = to;
  7.     current->user_data = (rt_ubase_t)data;
  8.     yield();
  9.     current->user_data = temp_ud;
  10. }
复制代码
这两个函数实现起来基本差不多,使用刚刚的user_data保存完from和to之后将,切换线程,然后切换完之后再将原先的user_data数据还回去。
然后启动rt-thread,成功!!!
但是很奇怪,我每次输入完一行命令,下次那一行命令会多一个/>,之前貌似不会,不知道改了什么之后就会了。
1.png


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册