lab4 trap
lab4 trap
实验结果

实验
RISC-V assembly实验
问题1。
Which registers contain arguments to functions? For example, which register holds 13 in main's call to printf?
a0-a7,浮点数是f0-f7。如果调用的参数大于7,某些编译器可能不允许,或者是把参数保存在栈中。调用函数约定具体细节看RISCV函数调用约定。c语言函数参数从右到左(入栈)放到寄存器中,13是放在a2里。
问题2。
Where is the call to function f in the assembly code for main? Where is the call to g? (Hint: the compiler may inline functions.)
26: 45b1 li a1,12
编译器优化了函数调用,f(8)+1的值必定是12,所以变成一个立即数传到a1里。
问题3。
At what address is the function printf located?
28: 00000517 auipc a0,0x0
2c: 7b050513 addi a0,a0,1968 # 7d8 <malloc+0xea>
30: 00000097 auipc ra,0x0
34: 600080e7 jalr 1536(ra) # 630 <printf>


如上图两个图片所解释的汇编指令得 printf的在(1536 + 30) = 0x630的地方
问题4。
What value is in the register ra just after the jalr to printf in main?

ra作为返回地址,且根据上图jalr指令得解释,ra的值应该是34。
问题5。
Run the following code.
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);
What is the output? Here's an ASCII table that maps bytes to characters.
The output depends on that fact that the RISC-V is little-endian. If the RISC-V were instead big-endian what would you set i to in order to yield the same output? Would you need to change 57616 to a different value?
Here's a description of little- and big-endian and a more whimsical description.
输出是H0x0000e110 World
如果RISCV是大端,不需要改变57616。假设小端:i存储在50,那么50存储的是0x72,51存储的是0x6c,52存储的是0x64,53存储的是0x00,%s是让指针按char类型去读数,如果i还是存储在50,那么按照大端高字节存储在低位,低字节存储在高位,那么i应该是0x726c6400才能保证一样的输出。
问题6。
In the following code, what is going to be printed after 'y='? (note: the answer is not a specific value.) Why does this happen?
printf("x=%d y=%d", 3);
假设编译器检查通过,那么y后面的值应该等于调用jalr xx时a2的值。
backtrace实验
这个实验不难,主要是简单了解riscv的calling convention,在下面会讲解。
./kernel/printf.c中实现backtrace函数
void backtrace(void)
{
//PGROUNDUP(sz) 最大值
//PGROUNDDOWN(a) 最小值
uint64 PageUperBound = 0;
uint64 PageDownBound = 0;
uint64 Fp = 0;
uint64 ReturnAddr = 0;
Fp = r_fp();
PageUperBound = PGROUNDUP(Fp);
PageDownBound = PGROUNDDOWN(Fp);
printf("backtrace:\n");
while((Fp < PageUperBound) && (Fp > PageDownBound))
{
ReturnAddr = *(((uint64*)Fp) -1);
printf("%p\n", (void*)(ReturnAddr));
Fp = *(((uint64*)Fp) -2);
}
}
.在sys_sleep()中调用backtrace函数
uint64
sys_sleep(void)
{
int n;
uint ticks0;
if(argint(0, &n) < 0)
return -1;
acquire(&tickslock);
ticks0 = ticks;
while(ticks - ticks0 < n){
if(myproc()->killed){
release(&tickslock);
return -1;
}
sleep(&ticks, &tickslock);
}
//lab 4 backtrace使用
backtrace();
release(&tickslock);
return 0;
}
其他
defs.h中声明,risv.h中声明内联函数
alarm实验
解题中的相关疑问
在which_dev==2的时候,要先执行alarm handler函数,还是执行yeild()函数?
阅读部分charpter 7的代码,会发现执行yeild()中的sched()里的swtch后,会跳转到别的进程的内核线程里,所以alarm handler函数在yeild()函数之前。
相关过程讲解
去执行sigalarm()的第二个参数(回调函数指针)时,根据题目的要求,是在user space, kernel时在超级用户模式的,要返回user space,则得通过sret。如下图所示,要执行该函数,则把spec的值设置为该函数的地址。。

还有疑问,执行那个函数时,要用怎么样的寄存器组,内存上会有啥变化,通常执行函数用的是jalr,jal指令,不止pc上的值有变化,rd上的值也有变化。根据函数调用约定,函数传入的参数(非浮点数)的数目在小于8个的时候,会把参数分别放在a0-a7中,返回值是放在ra中,即调用这个函数汇编时,a0-a7和栈要放参数,ra要放返回值。但是观察发现,调用的函数都有sigreturn,即执行回调函数后,永远不返回,且函数没参数,没有使用堆,所以把spec的值变一下,其他还是用该进程trapfame里的值。
执行sigreturn()函数后,要返回到原本被定时器中断打断的代码,所以要把spec的值保存,trapframe中不关于kernel的寄存器全部save,在调用sigreturn()时restore。
注意事项
xv6中,NULL并不一定是0,只是部分系统中NULL = (void*)0,但在xv6中,没有定义NULL,空指针也不是0。即有个函数指针的值为0,也是正确的。
NULL 不一定= (void*)0。NULL 不一定= '\0’。 '\0'一定等于0。在xv6的process 进程中,0地址有.text段,看下图,原本以为函数输入第二个参数不能为0,导致test0不通过,后面debug挺久的。

部分重要代码
./kernel/proc.h中proc结构体的定义
struct __Alarm
{
int IsExcuteAlarm;//0 can't excute, !0 can
int AlarmOrginTime;
int AlarmRemainTime;
int AlarmIsReturn; // 0代表return了,或者处于初始化状态,非0 no return
uint64 AlarmHnadler;
};
struct __AlarmTrapFrame
{
uint64 Scause;
uint64 Stval;
/* 40 */ uint64 ra;
/* 48 */ uint64 sp;
/* */ uint64 gp;
/* */ uint64 tp;
/* 72 */ uint64 t0;
/* 80 */ uint64 t1;
/* 88 */ uint64 t2;
/* 96 */ uint64 s0;
/* 104 */ uint64 s1;
/* 112 */ uint64 a0;
/* 120 */ uint64 a1;
/* 128 */ uint64 a2;
/* 136 */ uint64 a3;
/* 144 */ uint64 a4;
/* 152 */ uint64 a5;
/* 160 */ uint64 a6;
/* 168 */ uint64 a7;
/* 176 */ uint64 s2;
/* 184 */ uint64 s3;
/* 192 */ uint64 s4;
/* 200 */ uint64 s5;
/* 208 */ uint64 s6;
/* 216 */ uint64 s7;
/* 224 */ uint64 s8;
/* 232 */ uint64 s9;
/* 240 */ uint64 s10;
/* 248 */ uint64 s11;
/* 256 */ uint64 t3;
/* 264 */ uint64 t4;
/* 272 */ uint64 t5;
/* 288 */ uint64 t6;
/*296 */ uint64 OrginSpec;
};
// Per-process state
struct proc {
struct spinlock lock;
// p->lock must be held when using these:
enum procstate state; // Process state
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
int xstate; // Exit status to be returned to parent's wait
int pid; // Process ID
// wait_lock must be held when using this:
struct proc *parent; // Parent process
// these are private to the process, so p->lock need not be held.
struct __Alarm AlarmS;
struct __AlarmTrapFrame* AlarmTrapFrameP;
uint64 kstack; // Virtual address of kernel stack
uint64 sz; // Size of process memory (bytes)
pagetable_t pagetable; // User page table
struct trapframe *trapframe; // data page for trampoline.S
struct context context; // swtch() here to run process
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
};
在./kernel/proc.c中的代码。在tranframe下的page中存放alarm所需save的寄存器。
// Look in the process table for an UNUSED proc.
// If found, initialize state required to run in the kernel,
// and return with p->lock held.
// If there are no free procs, or a memory allocation fails, return 0.
static struct proc*
allocproc(void)
{
struct proc *p;
for(p = proc; p < &proc[NPROC]; p++) {
acquire(&p->lock);
if(p->state == UNUSED) {
goto found;
} else {
release(&p->lock);
}
}
return 0;
found:
p->pid = allocpid();
p->state = USED;
// Allocate a trapframe page.
if((p->trapframe = (struct trapframe *)kalloc()) == 0){
freeproc(p);
release(&p->lock);
return 0;
}
// An empty user page table.
p->pagetable = proc_pagetable(p);
if(p->pagetable == 0){
freeproc(p);
release(&p->lock);
return 0;
}
//lab4 trap lab
p->AlarmTrapFrameP = (struct __AlarmTrapFrame*)kalloc();
if (p->AlarmTrapFrameP == 0)
{
freeproc(p);
release(&p->lock);
}
// Set up new context to start executing at forkret,
// which returns to user space.
memset(&p->context, 0, sizeof(p->context));
p->context.ra = (uint64)forkret;
p->context.sp = p->kstack + PGSIZE;
//lab4 trap lab third question
p->AlarmS.IsExcuteAlarm = 0;//can't excute
p->AlarmS.AlarmRemainTime = 0;
p->AlarmS.AlarmHnadler = 0;
p->AlarmS.AlarmOrginTime = 0;
p->AlarmS.AlarmIsReturn = 0;
return p;
}
// free a proc structure and the data hanging from it,
// including user pages.
// p->lock must be held.
static void
freeproc(struct proc *p)
{
if(p->trapframe)
kfree((void*)p->trapframe);
p->trapframe = 0;
if(p->pagetable)
proc_freepagetable(p->pagetable, p->sz);
p->pagetable = 0;
if (p->AlarmTrapFrameP)
kfree((void*)p->AlarmTrapFrameP);
p->AlarmTrapFrameP = 0;
p->sz = 0;
p->pid = 0;
p->parent = 0;
p->name[0] = 0;
p->chan = 0;
p->killed = 0;
p->xstate = 0;
p->state = UNUSED;
}
// Create a user page table for a given process,
// with no user memory, but with trampoline pages.
pagetable_t
proc_pagetable(struct proc *p)
{
pagetable_t pagetable;
// An empty page table.
pagetable = uvmcreate();
if(pagetable == 0)
return 0;
// map the trampoline code (for system call return)
// at the highest user virtual address.
// only the supervisor uses it, on the way
// to/from user space, so not PTE_U.
if(mappages(pagetable, TRAMPOLINE, PGSIZE,
(uint64)trampoline, PTE_R | PTE_X) < 0){
uvmfree(pagetable, 0);
return 0;
}
// map the trapframe just below TRAMPOLINE, for trampoline.S.
if(mappages(pagetable, TRAPFRAME, PGSIZE,
(uint64)(p->trapframe), PTE_R | PTE_W) < 0){
uvmunmap(pagetable, TRAMPOLINE, 1, 0);
uvmfree(pagetable, 0);
return 0;
}
//alarm trap fram放在TRAPFRAME下面 用于alarm保存上下文
if ((mappages(pagetable, ALARMTRAPFRAME, PGSIZE, \
(uint64)(p->AlarmTrapFrameP), PTE_R | PTE_W) < 0))
{
uvmunmap(pagetable, TRAMPOLINE, 1, 0);
uvmunmap(pagetable, TRAPFRAME, 1, 0);
uvmfree(pagetable, 0);
return 0;
}
return pagetable;
}
// Free a process's page table, and free the
// physical memory it refers to.
void
proc_freepagetable(pagetable_t pagetable, uint64 sz)
{
uvmunmap(pagetable, TRAMPOLINE, 1, 0);
uvmunmap(pagetable, TRAPFRAME, 1, 0);
uvmunmap(pagetable, ALARMTRAPFRAME, 1, 0);
uvmfree(pagetable, sz);
}
在./kernel/sysproc.c中的代码。两个systemcall的实现
uint64 sys_sigalarm(void)
{
int n;
uint64 add = 1;
struct proc *p = myproc();
if (argint(0, &n) < 0)
{
return -1;
}
if (argaddr(1, &add) < 0)
{
return -1;
}
if ((n == 0) && (add == 0))
{
p->AlarmS.IsExcuteAlarm = 0;
p->AlarmS.AlarmRemainTime = 0;
p->AlarmS.AlarmOrginTime = 0;
p->AlarmS.AlarmHnadler = 0;
p->AlarmS.AlarmIsReturn = 0;
}
else
{
p->AlarmS.IsExcuteAlarm = 1;
p->AlarmS.AlarmRemainTime = n;
p->AlarmS.AlarmOrginTime = n;
p->AlarmS.AlarmHnadler = add;
p->AlarmS.AlarmIsReturn = 0; // on important
}
return 0;
}
uint64 sys_sigreturn(void)
{
AlarmHandlerToOrUser();//never return
return 0;
}
在.kernel/usertrap.c中的代码。
/
// handle an interrupt, exception, or system call from user space.
// called from trampoline.S
//
void
usertrap(void)
{
int which_dev = 0;
if((r_sstatus() & SSTATUS_SPP) != 0)
panic("usertrap: not from user mode");
// send interrupts and exceptions to kerneltrap(),
// since we're now in the kernel.
w_stvec((uint64)kernelvec);
struct proc *p = myproc();
// save user program counter.
p->trapframe->epc = r_sepc();
if(r_scause() == 8){
// system call
if(p->killed)
exit(-1);
// sepc points to the ecall instruction,
// but we want to return to the next instruction.
p->trapframe->epc += 4;
// an interrupt will change sstatus &c registers,
// so don't enable until done with those registers.
intr_on();
syscall();
} else if((which_dev = devintr()) != 0){
// ok
} else {
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());
p->killed = 1;
}
if(p->killed)
exit(-1);
// give up the CPU if this is a timer interrupt.
if(which_dev == 2)
{
if (p->AlarmS.IsExcuteAlarm != 0)
{
if (p->AlarmS.AlarmRemainTime == 0)
{
p->AlarmS.AlarmRemainTime = p->AlarmS.AlarmOrginTime;
if (p->AlarmS.AlarmIsReturn == 0)
{
AlarmHandlerToFun(); // 虚拟地址处0是对的,不是空指针,,,,,,
}
}
else
{
p->AlarmS.AlarmRemainTime = p->AlarmS.AlarmRemainTime - 1;
}
}
yield();
}
usertrapret();
}
//
// return to user space
//
void
usertrapret(void)
{
struct proc *p = myproc();
// we're about to switch the destination of traps from
// kerneltrap() to usertrap(), so turn off interrupts until
// we're back in user space, where usertrap() is correct.
intr_off();
// send syscalls, interrupts, and exceptions to trampoline.S
w_stvec(TRAMPOLINE + (uservec - trampoline));
// set up trapframe values that uservec will need when
// the process next re-enters the kernel.
p->trapframe->kernel_satp = r_satp(); // kernel page table
p->trapframe->kernel_sp = p->kstack + PGSIZE; // process's kernel stack
p->trapframe->kernel_trap = (uint64)usertrap;
p->trapframe->kernel_hartid = r_tp(); // hartid for cpuid()
// set up the registers that trampoline.S's sret will use
// to get to user space.
// set S Previous Privilege mode to User.
unsigned long x = r_sstatus();
x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode
x |= SSTATUS_SPIE; // enable interrupts in user mode
w_sstatus(x);
// set S Exception Program Counter to the saved user pc.
w_sepc(p->trapframe->epc);
// tell trampoline.S the user page table to switch to.
uint64 satp = MAKE_SATP(p->pagetable);
// jump to trampoline.S at the top of memory, which
// switches to the user page table, restores user registers,
// and switches to user mode with sret.
uint64 fn = TRAMPOLINE + (userret - trampoline);
((void (*)(uint64,uint64))fn)(TRAPFRAME, satp);
}
// interrupts and exceptions from kernel code go here via kernelvec,
// on whatever the current kernel stack is.
void
kerneltrap()
{
int which_dev = 0;
uint64 sepc = r_sepc();
uint64 sstatus = r_sstatus();
uint64 scause = r_scause();
if((sstatus & SSTATUS_SPP) == 0)
panic("kerneltrap: not from supervisor mode");
if(intr_get() != 0)
panic("kerneltrap: interrupts enabled");
if((which_dev = devintr()) == 0){
printf("scause %p\n", scause);
printf("sepc=%p stval=%p\n", r_sepc(), r_stval());
panic("kerneltrap");
}
// give up the CPU if this is a timer interrupt.
if(which_dev == 2 && myproc() != 0 && myproc()->state == RUNNING)
yield();
// the yield() may have caused some traps to occur,
// so restore trap registers for use by kernelvec.S's sepc instruction.
w_sepc(sepc);
w_sstatus(sstatus);
}
void
clockintr()
{
acquire(&tickslock);
ticks++;
wakeup(&ticks);
release(&tickslock);
}
// check if it's an external interrupt or software interrupt,
// and handle it.
// returns 2 if timer interrupt,
// 1 if other device,
// 0 if not recognized.
int
devintr()
{
uint64 scause = r_scause();
if((scause & 0x8000000000000000L) &&
(scause & 0xff) == 9){
// this is a supervisor external interrupt, via PLIC.
// irq indicates which device interrupted.
int irq = plic_claim();
if(irq == UART0_IRQ){
uartintr();
} else if(irq == VIRTIO0_IRQ){
virtio_disk_intr();
} else if(irq){
printf("unexpected interrupt irq=%d\n", irq);
}
// the PLIC allows each device to raise at most one
// interrupt at a time; tell the PLIC the device is
// now allowed to interrupt again.
if(irq)
plic_complete(irq);
return 1;
} else if(scause == 0x8000000000000001L){
// software interrupt from a machine-mode timer interrupt,
// forwarded by timervec in kernelvec.S.
if(cpuid() == 0){
clockintr();
}
// acknowledge the software interrupt by clearing
// the SSIP bit in sip.
w_sip(r_sip() & ~2);
return 2;
} else {
return 0;
}
}
static void AlarmHandlerToFun(void)
{
struct proc *p = myproc();
p->AlarmS.AlarmIsReturn = 1;
intr_off();
w_stvec(TRAMPOLINE + (uservec - trampoline));
p->trapframe->kernel_satp = r_satp(); // kernel page table
p->trapframe->kernel_sp = p->kstack + PGSIZE; // process's kernel stack
p->trapframe->kernel_trap = (uint64)usertrap;
p->trapframe->kernel_hartid = r_tp(); // hartid for cpuid()
unsigned long x = r_sstatus();
x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode
x |= SSTATUS_SPIE; // enable interrupts in user mode
w_sstatus(x);
//sret退出中断后,pc=spec,去执行函数的命令,函数不返回,rd没有用
p->AlarmTrapFrameP->OrginSpec = p->trapframe->epc;
p->trapframe->epc = p->AlarmS.AlarmHnadler;
w_sepc(p->trapframe->epc);
//save register
p->AlarmTrapFrameP->Scause = r_scause();
p->AlarmTrapFrameP->Stval = r_stval();
p->AlarmTrapFrameP->a0 = p->trapframe->a0;
p->AlarmTrapFrameP->a1 = p->trapframe->a1;
p->AlarmTrapFrameP->a2 = p->trapframe->a2;
p->AlarmTrapFrameP->a3 = p->trapframe->a3;
p->AlarmTrapFrameP->a4 = p->trapframe->a4;
p->AlarmTrapFrameP->a5 = p->trapframe->a5;
p->AlarmTrapFrameP->a6 = p->trapframe->a6;
p->AlarmTrapFrameP->a7 = p->trapframe->a7;
p->AlarmTrapFrameP->gp = p->trapframe->gp;
p->AlarmTrapFrameP->ra = p->trapframe->ra;
p->AlarmTrapFrameP->s0 = p->trapframe->s0;
p->AlarmTrapFrameP->s1 = p->trapframe->s1;
p->AlarmTrapFrameP->s2 = p->trapframe->s2;
p->AlarmTrapFrameP->s3 = p->trapframe->s3;
p->AlarmTrapFrameP->s4 = p->trapframe->s4;
p->AlarmTrapFrameP->s5 = p->trapframe->s5;
p->AlarmTrapFrameP->s6 = p->trapframe->s6;
p->AlarmTrapFrameP->s7 = p->trapframe->s7;
p->AlarmTrapFrameP->s8 = p->trapframe->s8;
p->AlarmTrapFrameP->s9 = p->trapframe->s9;
p->AlarmTrapFrameP->s10 = p->trapframe->s10;
p->AlarmTrapFrameP->s11 = p->trapframe->s11;
p->AlarmTrapFrameP->sp = p->trapframe->sp;
p->AlarmTrapFrameP->t0 = p->trapframe->t0;
p->AlarmTrapFrameP->t1 = p->trapframe->t1;
p->AlarmTrapFrameP->t2 = p->trapframe->t2;
p->AlarmTrapFrameP->t3 = p->trapframe->t3;
p->AlarmTrapFrameP->t4 = p->trapframe->t4;
p->AlarmTrapFrameP->t5 = p->trapframe->t5;
p->AlarmTrapFrameP->t6 = p->trapframe->t6;
p->AlarmTrapFrameP->tp = p->trapframe->tp;
uint64 satp = MAKE_SATP(p->pagetable);
uint64 fn = TRAMPOLINE + (userret - trampoline);
((void (*)(uint64,uint64))fn)(TRAPFRAME, satp);
}
void AlarmHandlerToOrUser(void)
{
struct proc *p = myproc();
p->AlarmS.AlarmIsReturn = 0;
intr_off();
w_stvec(TRAMPOLINE + (uservec - trampoline));
p->trapframe->kernel_satp = r_satp(); // kernel page table
p->trapframe->kernel_sp = p->kstack + PGSIZE; // process's kernel stack
p->trapframe->kernel_trap = (uint64)usertrap;
p->trapframe->kernel_hartid = r_tp(); // hartid for cpuid()
unsigned long x = r_sstatus();
x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode
x |= SSTATUS_SPIE; // enable interrupts in user mode
w_sstatus(x);
//换成原来epc
p->trapframe->epc = p->AlarmTrapFrameP->OrginSpec;
w_sepc(p->trapframe->epc);
p->trapframe->a0 = p->AlarmTrapFrameP->a0;
p->trapframe->a1 = p->AlarmTrapFrameP->a1;
p->trapframe->a2 = p->AlarmTrapFrameP->a2;
p->trapframe->a3 = p->AlarmTrapFrameP->a3;
p->trapframe->a4 = p->AlarmTrapFrameP->a4;
p->trapframe->a5 = p->AlarmTrapFrameP->a5;
p->trapframe->a6 = p->AlarmTrapFrameP->a6;
p->trapframe->a7 = p->AlarmTrapFrameP->a7;
p->trapframe->gp = p->AlarmTrapFrameP->gp;
p->trapframe->ra = p->AlarmTrapFrameP->ra;
p->trapframe->s0 = p->AlarmTrapFrameP->s0;
p->trapframe->s1 = p->AlarmTrapFrameP->s1;
p->trapframe->s2 = p->AlarmTrapFrameP->s2;
p->trapframe->s3 = p->AlarmTrapFrameP->s3;
p->trapframe->s4 = p->AlarmTrapFrameP->s4;
p->trapframe->s5 = p->AlarmTrapFrameP->s5;
p->trapframe->s6 = p->AlarmTrapFrameP->s6;
p->trapframe->s7 = p->AlarmTrapFrameP->s7;
p->trapframe->s8 = p->AlarmTrapFrameP->s8;
p->trapframe->s9 = p->AlarmTrapFrameP->s9;
p->trapframe->s10 = p->AlarmTrapFrameP->s10;
p->trapframe->s11= p->AlarmTrapFrameP->s11;
p->trapframe->sp = p->AlarmTrapFrameP->sp;
p->trapframe->t0 = p->AlarmTrapFrameP->t0;
p->trapframe->t1 = p->AlarmTrapFrameP->t1;
p->trapframe->t2 = p->AlarmTrapFrameP->t2;
p->trapframe->t3 = p->AlarmTrapFrameP->t3;
p->trapframe->t4 = p->AlarmTrapFrameP->t4;
p->trapframe->t5 = p->AlarmTrapFrameP->t5;
p->trapframe->t6= p->AlarmTrapFrameP->t6;
p->trapframe->tp = p->AlarmTrapFrameP->tp;
uint64 satp = MAKE_SATP(p->pagetable);
uint64 fn = TRAMPOLINE + (userret - trampoline);
((void (*)(uint64,uint64))fn)(TRAPFRAME, satp);
}
相关知识
RISCV calling convention
函数调用规范要包含什么
1:函数参数和返回值要怎么实现
函数的参数个数,类型。返回值的类型。函数的参数放在哪个寄存器里,寄存器不够放怎么办?返回值放在哪个寄存器。
2:calle寄存器和caller寄存器的区分
哪些寄存器属于calle寄存器,哪些寄存器属于caller寄存器。
3:函数帧栈布局
如fp寄存器和sp寄存器
riscv 函数具体调用规范
关于riscv函数调用规范,可以参考https://riscv.org/wp-content/uploads/2024/12/riscv-calling.pdf
下图揭示了在riscv c项目中,c的基础类型的大小。至于如果该基础类型长度小于寄存器长度,在寄存器中是怎么扩展的,详细见链接中的文章。

返回值实现
在有浮点硬件的cpu中。只有返回值是基本小浮点类型时或者有一两个浮点类型的结构体时,才在fa0和fa1返回,其他都在a0和a1中返回。如果返回值的大小两个寄存器的长度,则由调用者分配空间,并把空间的指针作为实际的第一个参数传递个被调用函数。如果没有浮点硬件,传递规则与同样大小的整形类型一样。
参数传递规则
浮点数:如果浮点数是在联合体,结构体,可变参数中,那么还是把参数放在整数寄存器中。除非单独传递浮点数,那么会在fax中传递。如果cpu没有浮点硬件单元,那么传递规则与同样大小的整形一样。
如果传递的整数小于寄存器长度,参数上面c规则的拓展,且riscv是小端的。如果大小大于寄存器长度且小于等于两倍,那么它们从对齐的偶数位寄存器开始放,比如In RV32, for example, the function void foo(int, long long) is passed its first argument in a0 and its second in a2 and a3. Nothing is passed in a1。如果长度大于寄存器的两倍,会放在堆栈里,通过指针进行引用。
8个寄存器放不下,那么就把参数放在堆栈中。
函数帧栈布局
栈是向下的增长的,且一定是16字节对齐。sp是栈底指针,fp的值是上一个栈存储ra的地址,ra是栈最上面的值,即sp是某个栈帧的最低地址,ra是某个栈帧的最高地址。参考xv6中rsicv的栈帧的布局。

callee和caller寄存器

函数的例子
jalr rd a0(122) # 指向aaa函数
aaa函数汇编实现
# 开场
# 为当前函数分配 64 字节的栈帧
addi sp, sp, -64
# 将 ra 和 fp 压栈保存
sd ra, 56(sp)
sd s0, 48(sp)
# 更新 fp 为当前函数栈帧顶端地址
addi s0, sp, 64
# 函数执行
# 中间如果再调用了其他函数会修改 ra
# 结尾
# 恢复 ra 和 fp
ld ra, 56(sp)
ld s0, 48(sp)
# 退栈
addi sp, sp, 64
# 返回,使用 ret 指令或其他等价的实现方式
ret
riscv进入异常的和退出异常的流程
进入异常
下面以玄铁c906 riscv在user space 的定时器异常为例。
RISCV产生异常时并不会切换页表,切换栈,保存除pc外的寄存器,这些工作都要交个软件
硬件方面
1:在执行 addi a0 a0 2时(前)产生了异常,该指令执行完后,pc跳到下一个地址,然后异常开始响应。
2:pc上的值存储到spec寄存器中。
3:把异常的类型设置为SCAUSE,并在STVAL中保存异常的原因。
4:把SIE的值放到SPIE中,然后把SIE的值清零,禁止中断。
5:在SSP中保存要返回的模式,并切换到用户模式。
6:根据STVEC中值,确定要跳转的地址。
软件方面
1:利用SSRATCH寄存器保存通用寄存器组的所有寄存器。
2:切换页表,切换内核线程栈,得到cpu id。
3:把STVECT重定向到kernel trap中。
退出异常
1:禁用中断。
2:把user trap重定向到svetc中。
3:保存页表,内核栈,cpu id,内核异常响应函数
4:设置SSP保证前一个模式是u mode,设置spie保证返回 user mode后,可以响应中断。
5:restore 寄存器组,切换页表,sret。
rsicv 切换环境
user mode到s(m) mode再到u mode
ecall指令产生中断,根据medeleg 和 mideleg确定该异常是在s模式还是m模式下处理。
然后根据spp(mpp)和s(m)ret返回u(s,m ) mode
s(m) mode到u mode
ecall指令产生中断,再根据spp, mpp和s(m)ret返回到某个模式。
中断和异常的区别
异常产生的原因在内核里面,中断产生的原因在内核以外。

浙公网安备 33010602011771号