因为发现写笔记能促进自己学习,提出问题,所以记笔记
risk 5如何启动,其实是通过kernerl/kernel.ld跳到汇编文件kernel/entry.S 里面,
OUTPUT_ARCH( "riscv" )
ENTRY( _entry )
SECTIONS
{
/*
* ensure that entry.S / _entry is at 0x80000000,
* where qemu's -kernel jumps.
*/
. = 0x80000000;
.text : {
*(.text .text.*)
. = ALIGN(0x1000);
_trampoline = .;
*(trampsec)
. = ALIGN(0x1000);
ASSERT(. - _trampoline == 0x1000, "error: trampoline larger than one page");
PROVIDE(etext = .);
}
.rodata : {
. = ALIGN(16);
*(.srodata .srodata.*) /* do not need to distinguish this from .rodata */
. = ALIGN(16);
*(.rodata .rodata.*)
}
.data : {
. = ALIGN(16);
*(.sdata .sdata.*) /* do not need to distinguish this from .data */
. = ALIGN(16);
*(.data .data.*)
}
.bss : {
. = ALIGN(16);
*(.sbss .sbss.*) /* do not need to distinguish this from .bss */
. = ALIGN(16);
*(.bss .bss.*)
}
PROVIDE(end = .);
}
~
~
~
如何跳的,其实就是把entry.S文件放在0x8000000中,然后跳到那里执行汇编
# qemu -kernel loads the kernel at 0x80000000
# and causes each CPU to jump there.
# kernel.ld causes the following code to
# be placed at 0x80000000.
.section .text
_entry:
# set up a stack for C.
# stack0 is declared in start.c,
# with a 4096-byte stack per CPU.
# sp = stack0 + (hartid * 4096)
la sp, stack0
li a0, 1024*4
csrr a1, mhartid
addi a1, a1, 1
mul a0, a0, a1
add sp, sp, a0
# jump to start() in start.c
call start
spin:
j spin
执行汇编的时候, la sp, stack0先声明一个栈,然后倒数第三行call start就跳转到start.c里面去执行,start.c又会跳到kernel/main.c里面去执行
#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "riscv.h"
#include "defs.h"
volatile static int started = 0;
// start() jumps here in supervisor mode on all CPUs.
void
main()
{
if(cpuid() == 0){
consoleinit();
printfinit();
printf("\n");
printf("xv6 kernel is booting\n");
printf("\n");
kinit(); // physical page allocator
kvminit(); // create kernel page table
kvminithart(); // turn on paging
procinit(); // process table
trapinit(); // trap vectors
trapinithart(); // install kernel trap vector
plicinit(); // set up interrupt controller
plicinithart(); // ask PLIC for device interrupts
binit(); // buffer cache
iinit(); // inode cache
fileinit(); // file table
virtio_disk_init(); // emulated hard disk
userinit(); // first user process
__sync_synchronize();
started = 1;
} else {
while(started == 0)
;
__sync_synchronize();
printf("hart %d starting\n", cpuid());
kvminithart(); // turn on paging
trapinithart(); // install kernel trap vector
plicinithart(); // ask PLIC for device interrupts
}
scheduler();
}
~
~
-- VISUAL --
在main里面会调用userinit创建第一个进程,这个进程会执行一个汇编小程序initcode.S
系统调用的流程initcode.S,先将SYS_exec放到a7寄存器,然后通过ecall,走进syscall.c 里面的代码,通过trap获得exec对应的系统调用号(调用号映射表在syscall.h里面,SYS_exec映射到数字7),然后在syscall.c里面执行相应函数
System call tracing
1. 在user.h里面添加
int trace(int)
2. 在usys.pl里面添加
entry("trace");
3.在syscall.h里面添加
#define SYS_trace 22
4.在kernel/sysproc.c里面添加
uint64
sys_trace(void)
{
uint64 p;
if(argaddr(0, &p) < 0)
return -1;
myproc()->mask = p; // trace的入参存到mask
return 0;
}
5.在kernel/proc.h的proc结构体添加
int mask;
6.在kernel/proc.c的fork方法添加
np->mask = p->mask;
作用是复制父进程的mask到子进程
7.修改syscall.c
extern uint64 sys_trace(void);
[SYS_trace] sys_trace,
syscall方法里面添加
if(1 << num & p->mask) {
printf("%d: syscall: %s -> %d\n", p->pid, masks[num-1], p->trapframe->a0);
}
其中masks的定义如下:
char* masks[23] = {"fork", "exit", "wait", "pipe", "read", "kill", "exec", "fstat", "chdir", "dup", "getpid", "sbrk", "sleep", "uptime", "open", "write", "mknod", "unlink", "link", "mkdir", "close", "trace"};
总结来说,就是在用户态文件里面声明函数trace,在系统调用头文件syscall.h里面添加对应的系统调用号SYS_trace,并在系统进程里面添加对应的系统方法sys_trace,然后在系统调用程序syscall.c里面添加extern unit64 sys_trace()声明,并修改其中方法syscall()的逻辑。 而实际上trace的执行流程就是:
用户态调用trace,根据entry('trace')找到对应的调用号是SYS_trace, 然后在syscall里面根据调用号,找到系统方法sys_trace(),执行以后把用户设定的mask复制到proc结构体,然后。。。
Sysinfo
0. Makefile里面的UPROGS变量添加
$U/_sysinfotest\
$U/_sysinfo\
1. user.h里面添加:
struct sysinfo;
int sysinfo(struct sysinfo*);//这里我写的是struct sysinfotest(void)不知道为啥我错了额?
//不过看题目要求是这么写的
2. usys.pl里面添加:
entry("sysinfo");
3.在syscall.h里面添加
#define SYS_sysinfo 23
4.在kernel/sysproc.c里面添加sys_sysinfo,参考sys_fstat(void),
uint64
sys_sysinfo(void)
{
struct sysinfo info;
uint64 addr; // user pointer to struct stat
if(argaddr(0, &addr) < 0) // 不能是argaddr(1, &addr) 否则会出错
return -1;
struct proc *p = myproc();
info.freemem = freemem_size();
info.nproc = proc_num();
if(copyout(p->pagetable, addr, (char*)&info, sizeof(info))) {
return -1;
}
return 0;
}
5.在kalloc里面添加freemem_size用来计算剩余内存
int freemem_size() {
struct run *r;
int num = 0;
r = keme.freelist;
while(r) {
num++;
r = r->next;
}
return num * PGSIZE;
}
6.在proc.c里面添加proc_num计算进程数量
int proc_num(void) {
struct proc *p;
int num = 0;
for(p = proc; p < &proc[NPROC]; p++) {
if(p -> state != UNUSED) {
num++;
}
}
return num;
}
7. 在kernel/defs.h里面添加声明
int freemem_size(void);
int proc(void);
8. 在kernel/syscall.c里面添加声明
extern unint64 sysinfo(void);
[SYS_sysinfo] sys_sysinfo,
9. 新建user/sysinfo.c文件,其实这个建不建都行,主要测试方便:
#include "kernel/types.h"
#include "user/user.h"
#include "kernel/sysinfo.h"
int main(int argc, char *argv[])
{
if (argc != 1)
{
fprintf(2, "error input\n");
exit(1);
}
struct sysinfo info;
sysinfo(&info);
printf("%d %d", info.freemem, info.nproc);
exit(0);
}
在debug的过程中,总结一下,还是那句话,要解决问题,得先了解问题,明白问题是什么,这在我找argaddr参数错误问题过程中起了蛮大作用
然后这个sysinfo,总体流程就是,我们首先通过用户态调用sysinfo,触发相应的系统调用sys_sysinfo,在调用里面实现获取内存空闲容量和当前进程数(这两个只有在内核态的时候才能获取),再把这些信息通过copyout,内存拷贝,拷贝到指定的用户态地址
至此lab 2,搞完了,撒花,哈哈哈