xv6源码阅读——进程切换
发布时间:2022-10-17 12:49:24 所属栏目:Unix 来源:
导读: 说明 概述
从一个用户进程(旧进程)切换到另一个用户进程(新进程)所涉及的步骤:
上面过程就简单阐述,是怎样进行进程切换的
当然,里面会有很多细节,后面会详细介绍
从用户
从一个用户进程(旧进程)切换到另一个用户进程(新进程)所涉及的步骤:
上面过程就简单阐述,是怎样进行进程切换的
当然,里面会有很多细节,后面会详细介绍
从用户
说明 概述 从一个用户进程(旧进程)切换到另一个用户进程(新进程)所涉及的步骤: 上面过程就简单阐述,是怎样进行进程切换的 当然,里面会有很多细节,后面会详细介绍 从用户线程到内核线程 这一部分在上一章是有过介绍的,通过中断机制,通过uservec保存寄存器,然后进入usertrap() usertrap void usertrap(void) { int which_dev = 0; if((r_sstatus() & SSTATUS_SPP) != 0) panic("usertrap: not from user mode"); //省略 ·········· // give up the CPU if this is a timer interrupt. if(which_dev == 2) yield(); usertrapret(); } 其他部分就不过多赘述了(上一篇博客讲的很详细),通过判断中断类型,这是一个定时器中断,就调用yield()函数 从旧进程进入调度进程 yield // Give up the CPU for one scheduling round. void yield(void) { struct proc *p = myproc(); acquire(&p->lock); p->state = RUNNABLE; sched(); release(&p->lock); } 该函数干的事情很简单,先获取该进程的锁,将进程状态设置为RUNNABLE 然后调用sched()函数进行后续操作 题:为什么再设置进程状态前unix线程切换,要先获取该进程的锁呢 因为一旦将进程状态设置为RUNNABLE,调度器就会认为该进程处于等待状态的可能会让他接下来运行,但实际上,该进程还是处于运行状态的 如果这是一个多核CPU的,就可能会发生一个进程被两个CPU同时运行,程序可能会立马崩溃。 所以我们要保证改变进程状态,保存寄存器,载入另一个进程的寄存器这三步是原子的。 sched void sched(void) { int intena; struct proc *p = myproc(); if(!holding(&p->lock)) panic("sched p->lock"); if(mycpu()->noff != 1) panic("sched locks"); if(p->state == RUNNING) panic("sched running"); if(intr_get()) panic("sched interruptible"); intena = mycpu()->intena; swtch(&p->context, &mycpu()->context); mycpu()->intena = intena; } swtch 这是一段汇编代码,主要干的工作是将该进程的寄存器保存下来,然后载入调度进程的寄存器 .globl swtch swtch: # 保存寄存器 sd ra, 0(a0) # 省略``````````````````` sd s11, 104(a0) # 恢复所要切换进程的寄存器 ld ra, 0(a1) # 省略``````````````````` ld s11, 104(a1) ret 问题:为什么RISC-V中有32个寄存器,但是swtch函数中只保存并恢复了14个寄存器? 因为swtch函数是从C代码调用的,所以我们知道Caller Saved Register会被C编译器保存在当前的栈上。 该函数完成后,就进入了调度进程 调度 scheduler void scheduler(void) { struct proc *p; struct cpu *c = mycpu(); c->proc = 0; for(;;){ //通过确保设备可以中断来避免死锁。 intr_on(); for(p = proc; p < &proc[NPROC]; p++) { acquire(&p->lock); if(p->state == RUNNABLE) { // 切换到所选的进(释放它的锁,然后在跳回我们之前重新获取它) p->state = RUNNING; c->proc = p; swtch(&c->context, &p->context); // 进程目前已完成运行。 c->proc = 0; } release(&p->lock); } } } 调用swtch后就会进入另一个进程了 从调度进程进入新进程 该过程也是在完成swtch后完成切换的 他会从sched函数返回 void sched(void) { int intena; struct proc *p = myproc(); //、、、、、、、、、、、、、、 swtch(&p->context, &mycpu()->context); mycpu()->intena = intena; } 他会返回到swtch处,然后恢复 mycpu()->intena 从内核线程返回用户线程 这后面就是一步步返回,通过trap机制返回用户层,在上一篇博客讲的很详细,不过多赘述 XV6线程第一次调用swtch函数 allocproc static struct proc* allocproc(void) { struct proc *p; //、、、、、、、、、、省略 memset(&p->context, 0, sizeof(p->context)); p->context.ra = (uint64)forkret; p->context.sp = p->kstack + PGSIZE; return p; } 在创建进程时,会设置ra和sp ra很重要,因为这是进程的第一个switch调用会返回的位置。同时因为进程需要有自己的栈,所以ra和sp都被设置了。这里设置的forkret函数就是进程的第一次调用swtch函数会切换到的“另一个”线程位置。 当调度线程将CPU交给该进程时,该进程会从forkret()函数处返回,执行该函数, // A fork child's very first scheduling by scheduler() // will swtch to forkret. void forkret(void) { static int first = 1; // Still holding p->lock from scheduler. release(&myproc()->lock); if (first) { // File system initialization must be run in the context of a // regular process (e.g., because it calls sleep), and thus cannot // be run from main(). first = 0; fsinit(ROOTDEV); } usertrapret(); } 从代码中看,它的工作其实就是释放调度器之前获取的锁。函数最后的usertrapret函数其实也是一个假的函数,它会使得程序表现的看起来像是从trap中返回,但是对应的trapframe其实也是假的,这样才能跳到用户的第一个指令中。 (编辑:开发网_新乡站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
推荐文章
站长推荐