首页 > 其他分享 >NJU PA4.1记录

NJU PA4.1记录

时间:2024-02-15 12:45:19浏览次数:28  
标签:rt PA4.1 记录 void char NJU Context cp stack

PA4-虚实交错的魔法:分时多任务

多道程序

上下文切换

内核线程

实现上下文切换(1)

首先是kcontext(),理解讲义之后我们会发现其实很简单,就是让我们创建一个Context *cp指向所给的栈底位置,然后把entry填入Contextmepc中,为了后续在__am_asm_trapmret时会返回到f函数中,这里的减4是为了后面方便统一加4。

Context *kcontext(Area kstack, void (*entry)(void *), void *arg) {
  Context *cp = (Context *)(kstack.end - sizeof(Context));
  cp->mepc = (uintptr_t)entry - 4;
  return cp;
}

然后我们修改CTE中__am_asm_trap()的实现,这个也很简单,修改一下sp寄存器即可。

__am_asm_trap:
  ...
  mv a0, sp
  jal __am_irq_handle

  mv sp, a0

 ...

然后修改一下yield-os

static Context *schedule(Event ev, Context *prev) {
  current->cp = prev;
  current = (current == &pcb[0] ? &pcb[1] : &pcb[0]);
  current->cp->mepc += 4;
  return current->cp;
}
实现上下文切换(2)

用a0传参即可。

Context *kcontext(Area kstack, void (*entry)(void *), void *arg) {
  Context *cp = (Context *)(kstack.end - sizeof(Context));
  cp->mepc = (uintptr_t)entry - 4;
  cp->gpr[10] = (uintptr_t)(arg);
  return cp;
}

OS中的上下文切换

RT-Thread(选做)

这部分主要让我们实现context.c中的几个函数。

rt_hw_stack_init需要我们创建一个上下文结构,使得在进行内核线程切换时可以利用上下文结构恢复现场,我们需要创建的结构如下图所示。

|               |
+---------------+ <---- stack.end(stack_addr)
|               |
|    context    |
|               |
+---------------+ <---- pcb->sp
|   tentry      | 
|---------------|
|   texit       |
|---------------|
|   parameter   |
|---------------|
|               |    
+---------------+ <---- stack,start
|               |
void wrap(void *arg) {
  // parse paramater
  rt_ubase_t *stack_bottom = (rt_ubase_t *)arg;
  rt_ubase_t tentry = *stack_bottom; 
  stack_bottom--;
  rt_ubase_t texit = *stack_bottom; 
  stack_bottom--;
  rt_ubase_t parameter = *stack_bottom; 
  // function call
  ((void(*)())tentry) (parameter);
  ((void(*)())texit) ();
}

rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter, rt_uint8_t *stack_addr, void *texit) {
  // align
  stack_addr = (rt_uint8_t*)((uintptr_t)stack_addr & ~(sizeof(uintptr_t) - 1));
  // create context
  Area stack;
  stack.start = stack_addr - STACK_SIZE;
  stack.end = stack_addr;
  Context *cp = kcontext(stack, wrap, (void *)(stack.end - sizeof(Context) - 1));
  // set parameter
  rt_ubase_t *stack_bottom = (rt_ubase_t *)(stack.end - sizeof(Context) - 1);
  *stack_bottom = (rt_ubase_t)tentry;
  stack_bottom--;
  *stack_bottom = (rt_ubase_t)texit;
  stack_bottom--;
  *stack_bottom = (rt_ubase_t)parameter;

  return (rt_uint8_t *)cp;
}

然后就是实现上下文的切换,需要注意的是tofrom是二级指针,需要强转后再解引用。

static Context* ev_handler(Event e, Context *c) {
  switch (e.event) {
    case EVENT_YIELD:  {
      rt_thread_t cp = rt_thread_self(); 
      rt_ubase_t to = cp->user_data;
      c = *(Context**)to;
      c->mepc += 4;
      break;
    }
    default: printf("Unhandled event ID = %d\n", e.event); assert(0);
  }
  return c;
}

void rt_hw_context_switch_to(rt_ubase_t to) {
  rt_thread_t pcb = rt_thread_self();
  rt_ubase_t user_data_replicator = pcb->user_data;
  pcb->user_data = to;
  yield();
  pcb->user_data = user_data_replicator;
}

void rt_hw_context_switch(rt_ubase_t from, rt_ubase_t to) {
  rt_thread_t pcb = rt_thread_self();
  rt_ubase_t user_data_replicator = pcb->user_data;
  pcb->user_data = to;
  *(Context**)from = pcb->sp;
  yield();
  pcb->user_data = user_data_replicator;
}

Nanos-lite

在Nanos-lite中实现上下文切换

在实现了yield-os之后,实现这个就不难了。

void context_kload(PCB *p, void (*entry)(void *), void *arg) {
  p->cp = kcontext((Area) { p->stack, p + 1 }, hello_fun, arg);
}

我们把mepc+4的操作放到do_event中实现,让代码格式统一一些。

Context* schedule(Context *prev) {
  current->cp = prev;
  current = (current == &pcb[0] ? &pcb[1] : &pcb[0]);
  return current->cp;
}
static Context* do_event(Event e, Context* c) {
  switch (e.event) {
    case EVENT_YIELD : c = schedule(c); c->mepc += 4; break;
    case EVENT_SYSCALL : do_syscall(c); c->mepc += 4; break;
    default: panic("irq:Unhandled event ID = %d", e.event);
  }

  return c;
}

mian函数中已经有了一个yield(),所以我们没必要在init_proc中添加了。

用户进程

创建用户进程上下文

实现多道程序系统

首先是ucontext(),和kcontext()基本一致。

Context *ucontext(AddrSpace *as, Area kstack, void *entry) {
  Context *cp = (Context *)(kstack.end - sizeof(Context));
  cp->mepc = (uintptr_t)entry - 4;
  return cp;
}

接下来是context_uload()函数,这里我本来使用的是loader函数,也定义了,但是编译还是报未定义错,搞不清楚什么原因,就修改了下naive_uload()函数,把函数入口直接返回。

void context_uload(PCB *p, const char *filename) {
  uintptr_t entry = naive_uload(p, filename);
  p->cp = ucontext(&p->as, (Area) { p->stack, p + 1 }, (void *)entry);
  p->cp->GPRx = (uintptr_t)heap.end;
}

然后是在Navy的_start中设置正确的栈指针。

用户进程的参数

给用户进程传递参数

此处的指针操作比较麻烦,需要耐心根据图示写。

void context_uload(PCB *p, const char *filename, char *const argv[], char *const envp[]) {
  uintptr_t entry = naive_uload(p, filename);

  int argc = 0, envc = 0;
  while (argv[argc] != NULL) argc++;
  while (envp[envc] != NULL) envc++;

  char* us1 = (char*)heap.end;
  // clone argv
  for (int i = 0; i < argc; i++) {
    size_t len = strlen(argv[i]) + 1; // include null character
    us1 -= len;
    strncpy(us1, argv[i], len);
  }
  // clone envp
  for (int i = 0; i < envc; i++) {
    size_t len = strlen(envp[i]) + 1; // include null character
    us1 -= len;
    strncpy(us1, envp[i], len);
  }

  uintptr_t* us2 = (uintptr_t *)us1;
  us2 -= (argc + envc + 3);

  us2[0] = argc;

  char* us_tmp = (char*)heap.end;
  for (int i = 0; i < argc; i++) {
    size_t len = strlen(argv[i]) + 1; 
    us_tmp -= len;
    us2[i + 1] = (uintptr_t)us_tmp;
  }
  us2[argc + 1] = 0;
  for (int i = 0; i < envc; i++) {
    size_t len = strlen(envp[i]) + 1; // include null character
    us_tmp -= len;
    us2[argc + i + 2] = (uintptr_t)us_tmp;
  }
  us2[argc + 2 + envc] = 0;

  p->cp = ucontext(&p->as, (Area) { p->stack, p + 1 }, (void *)entry);
  p->cp->GPRx = (uintptr_t)us2;
}
void call_main(uintptr_t *args) {
  int argc = (int)args[0];
  char **argv = (char **)(args + 1);
  char **envp = (char **)(args + argc + 2);
  environ = envp;
  exit(main(argc, argv, envp));
  assert(0);
}

在前面已经把Navy中_start的代码修改过了,就是把a0放到sp上。

仙剑的商标打印就在mian函数中,非常简单就可以修改成功。

实现带参数的execve()

接下来这里我尝试只跑一个用户进程总是会出错,查了半天,发现原来是之前为了模拟设备的等待来实现切换进程,写了很多yield在device中,去掉就可以了。

好多天没看代码了,记忆不清楚导致这个问题卡了很久,每次隔段时间写pa就会需要整体回顾一遍,这样太浪费时间了,不过对代码的理解似乎更深刻了,也算是螺旋上升的过程了。

// context_uload()
char* us1 = (char*)new_page(8);
char* us_tmp = us1;

...

// char* us_tmp = (char*)heap.end;
int sys_execve(const char *pathname, char *const argv[], char *const envp[]) {
  if (pathname == NULL)
    return -1;
  //naive_uload(NULL, pathname);
  context_uload(current, pathname, argv, envp);
  switch_boot_pcb();
  yield();
  return 0;
}
void* new_page(size_t nr_page) {
  pf += nr_page * PGSIZE;
  return pf;
}

为了理解深刻进程的用户栈和进程pcb中的内核栈的不同,我打印了一个进程的用户栈和内核栈,可以看到kernel stack:0x82260000, user stack:0x82299000

[/home/silly/ysyx-workbench/nanos-lite/src/main.c,13,main] 'Hello World!' from Nanos-lite
[/home/silly/ysyx-workbench/nanos-lite/src/main.c,14,main] Build time: 23:03:44, Feb  8 2024
[/home/silly/ysyx-workbench/nanos-lite/src/mm.c,27,init_mm] free physical pages starting from 0x82291000
[/home/silly/ysyx-workbench/nanos-lite/src/device.c,74,init_device] Initializing devices...
[/home/silly/ysyx-workbench/nanos-lite/src/ramdisk.c,27,init_ramdisk] ramdisk info: start = 0x8000315d, end = 0x8225e55d, size = 36025344 bytes
[/home/silly/ysyx-workbench/nanos-lite/src/irq.c,17,init_irq] Initializing interrupt/exception handler...
kernel stack:0x82260000, user stack:0x82299000

标签:rt,PA4.1,记录,void,char,NJU,Context,cp,stack
From: https://www.cnblogs.com/xulizs666/p/18016154

相关文章

  • 使用lanczos算法进行的预处理共轭梯度算法(Preconditioned Conjugate Gradients Metho
    构造预处理矩阵M(对称正定)下图来自:预处理共轭梯度法(1)......
  • NOI真题记录
    NOI真题记录一些做过的NOI真题。NOI2013向量内积题意:有\(n\)个\(d\)为向量,求是否有两对向量的点积是2或3的倍数。思路:先random_shuffle一下,然后一次判断和前面的和的乘积,如果发现出现了不满足全部模起来都不为0就说明出现了答案,与前面的每一个判断一下就可以了。......
  • openJudge | 统计学生信息(使用动态链表完成)C语言
    总时间限制:1000ms内存限制:65536kB描述利用动态链表记录从标准输入输入的学生信息(学号、姓名、性别、年龄、得分、地址)其中,学号长度不超过20,姓名长度不超过40,性别长度为1,地址长度不超过40输入包括若干行,每一行都是一个学生的信息,如:00630018zhouyanm2010.028......
  • 数位 DP 做题记录
    数位DP数位DP的常见套路就是记录当前到哪一位,是否抵着上界,转移时枚举当前可以填哪些数,做一遍记忆化搜索。P3413SAC#1-萌数题意:求\([l,r]\)中有多少个数中含有回文子串。思路:如果存在回文子串,那么必然有相邻两位相同或者间隔一位相同,在数位DP时额外记录前2位就可以......
  • 预处理共轭梯度算法(Preconditioned Conjugate Gradients Method)
    预处理共轭梯度算法(PreconditionedConjugateGradientsMethod)给出百度百科上的解释:预处理共轭梯度法预处理共轭梯度法是。不必预先估计参数等特点。共轭梯度法近年来在求解大型稀疏方程组中取得了较好的成效。理论上普通的共扼梯度法对于对称超正定方程,只要迭代步数达到......
  • 势能相关做题记录
    势能相关P5905【模板】全源最短路(Johnson)题意:有负权情况下的全源最短路。思路:Johnson全源最短路可以在\(O(nm\logm)\)的复杂度内解决带有负权的全源最短路。这个算法的巧妙之处在于为每个点赋予势能\(h_i\)。从一个点到另一个点,无论走什么路径,势能的变化量都是一定的。......
  • 数学题做题记录
    数学主要是计数和数论函数相关。[AGC031F]WalkonGraph题意:有一张\(n\)个点\(m\)条边的无向连通图\(G\),每条边有长度\(L_i\),有一个人在上面游走。有\(q\)组询问,每组询问给出\(s_i,t_i,r_i\),询问是否存在一条从\(s_i\)出发到\(t_i\)结束且长度为\(r_i\)的路径......
  • manjaro 开机黑屏问题记录
    环境信息系统:manjaro-kde6.6.12-1-MANJARO显卡:RadeonRX5802048SP问题描述偶现开机黑屏,无法进入登录界面,无法进入tty检查/var/log/Xorg.0.log日志,可以发现以下异常信息:AMDGPU(0):getvblankcounterfailed:Invalidargument很有可能是AMD图形驱动模块AMDGPU......
  • Vue3杂碎知识记录
    vue引入bootstrap当卸载App.vue里不行的时候就还可以写在main.js里导入bootstrap的时候写在main.js里,(还要添加依赖@popperjs/core)import'bootstrap/dist/css/bootstrap.css';import'bootstrap/dist/js/bootstrap';//注意js文件也要引入进来写在vue的script里面不行,要......
  • sublimetext 使用中遇到的问题记录
    sublimetext使用关键词:应该是编码过程中出现了系统问题,所以导致无法正常运行,才会显示“unregistered”(未登记、未注册)。sublimetext本身是不支持中文编码的,所以要解决“unregistered”的问题,需要通过安装插件来解决。具体步骤是:打开这个文件,并确认它的编码是UTF-8。然后选择......