xv6学習

kernel.ldを元にカーネルに必要なデータを0x80000000からアラインメントしながら配置していき、entry.Sの_entryを実行する。stack0にアクセスできるのはすでに配置されているから。
cpu用のスタックを作って、start.cを実行する。
例外や割り込みをスーパーバイザモードに移譲する、mepcにmainを指定する、タイマー割り込みを設定し、各種例外割り込みを許可する。そしてmretでmepcに保存していたmainに処理を移行する。

kernel/main.cでさまざまな初期化処理が行われる。
その後userinit(proc.c)が実行される。
proc構造体
enum procstate { UNUSED, USED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };
// 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.
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)
};

userinitのuvmfirstで、最初のプロセスのページテーブルにinitcodeに関する設定をしている模様
pagetableの0にマッピングすることで、ユーザープロセスは仮想アドレス空間の最初のアドレスから実行を開始することができるらしい

プロセスはprocinitによって、64個のプロセスを保管しているのプロセステーブルの中身を全部ステータスUNUSEDで初期化する
allocprocはそのテーブルの中から、ステータスがUNUSEDのものをアロケートする

最初のプロセスは、initcode.Sを実行する
ecallから、例外ベクタを経由してきっとexecが実行されるのだろうけど、それをコードで確認できていない
スケジューラは無限ループしてプロセステーブルにrunnableなものがないか常に見ている

initcode.Sから、最初の処理init.cが実行される。
init.cで、
char *argv[] = { "sh", 0 };
があり、その下にforkによって生まれた子プロセスについての処理が
if(pid == 0){
exec("sh", argv);
printf("init: exec sh failed\n");
exit(1);
}
なので、ここでシェルが実行されている!
ただ具体的にexecとargvによって、sh.cのどこから実行されているのかみたいなのはよくわからない
mainでwhileで回ってシェルは動いている。whileの条件のgetcmdの中のgetsの中のreadがブロッキング動作だから入力されるまで処理が止まっているっていうけど、具体的にシステムコールの内容がどこかわからい。readだったらsys_read(sysfile.c)?

システムコールの名前とsys_がプレフィックスについてるやつに関しては、どこかでシステムコールを呼び出す際はsyscall関数を共通のエントリポイントとしていて、パラメータごとに実行するシステムコールを振り分けているっぽい。

シェルにコマンドを打ち込んでプロセスを作って走らせるところの処理を見ていく
if(fork1() == 0)
runcmd(parsecmd(buf));
bufは入力したコマンドの文字が一つずつ入っている
int
fork1(void)
{
int pid;
pid = fork();
if(pid == -1)
panic("fork");
return pid;
}
// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
fork(void)
{
int i, pid;
struct proc *np;
struct proc *p = myproc();
// Allocate process.
if((np = allocproc()) == 0){
return -1;
}
// Copy user memory from parent to child.
if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
freeproc(np);
release(&np->lock);
return -1;
}
np->sz = p->sz;
// copy saved user registers.
*(np->trapframe) = *(p->trapframe);
// Cause fork to return 0 in the child.
np->trapframe->a0 = 0;
// increment reference counts on open file descriptors.
for(i = 0; i < NOFILE; i++)
if(p->ofile[i])
np->ofile[i] = filedup(p->ofile[i]);
np->cwd = idup(p->cwd);
safestrcpy(np->name, p->name, sizeof(p->name));
pid = np->pid;
release(&np->lock);
acquire(&wait_lock);
np->parent = p;
release(&wait_lock);
acquire(&np->lock);
np->state = RUNNABLE;
release(&np->lock);
return pid;
}

forkを完全に理解するところから始めて、具体的にどういうふうにコードが実行されるか理解する
// Given a parent process's page table, copy
// its memory into a child's page table.
// Copies both the page table and the
// physical memory.
// returns 0 on success, -1 on failure.
// frees any allocated pages on failure.
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
pte_t *pte;
uint64 pa, i;
uint flags;
char *mem;
for(i = 0; i < sz; i += PGSIZE){
if((pte = walk(old, i, 0)) == 0)
panic("uvmcopy: pte should exist");
if((*pte & PTE_V) == 0)
panic("uvmcopy: page not present");
pa = PTE2PA(*pte);
flags = PTE_FLAGS(*pte);
if((mem = kalloc()) == 0)
goto err;
memmove(mem, (char*)pa, PGSIZE);
if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
kfree(mem);
goto err;
}
}
return 0;
err:
uvmunmap(new, 0, i / PGSIZE, 1);
return -1;
}
// Create PTEs for virtual addresses starting at va that refer to
// physical addresses starting at pa. va and size might not
// be page-aligned. Returns 0 on success, -1 if walk() couldn't
// allocate a needed page-table page.
int
mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm)
{
uint64 a, last;
pte_t *pte;
if(size == 0)
panic("mappages: size");
a = PGROUNDDOWN(va);
last = PGROUNDDOWN(va + size - 1);
for(;;){
if((pte = walk(pagetable, a, 1)) == 0)
return -1;
if(*pte & PTE_V)
panic("mappages: remap");
*pte = PA2PTE(pa) | perm | PTE_V;
if(a == last)
break;
a += PGSIZE;
pa += PGSIZE;
}
return 0;
}

int
exec(char *path, char **argv)
{
char *s, *last;
int i, off;
uint64 argc, sz = 0, sp, ustack[MAXARG], stackbase;
struct elfhdr elf;
struct inode *ip;
struct proghdr ph;
pagetable_t pagetable = 0, oldpagetable;
struct proc *p = myproc(); // 今のプロセスを取得
begin_op();
if((ip = namei(path)) == 0){
end_op();
return -1;
}
ilock(ip);
// Check ELF header
if(readi(ip, 0, (uint64)&elf, 0, sizeof(elf)) != sizeof(elf))
goto bad;
if(elf.magic != ELF_MAGIC)
goto bad;
if((pagetable = proc_pagetable(p)) == 0)
goto bad;
// Load program into memory.
for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){
if(readi(ip, 0, (uint64)&ph, off, sizeof(ph)) != sizeof(ph))
goto bad;
if(ph.type != ELF_PROG_LOAD)
continue;
if(ph.memsz < ph.filesz)
goto bad;
if(ph.vaddr + ph.memsz < ph.vaddr)
goto bad;
if(ph.vaddr % PGSIZE != 0)
goto bad;
uint64 sz1;
if((sz1 = uvmalloc(pagetable, sz, ph.vaddr + ph.memsz, flags2perm(ph.flags))) == 0)
goto bad;
sz = sz1;
if(loadseg(pagetable, ph.vaddr, ip, ph.off, ph.filesz) < 0)
goto bad;
}
iunlockput(ip);
end_op();
ip = 0;
p = myproc();
uint64 oldsz = p->sz;
// Allocate two pages at the next page boundary.
// Make the first inaccessible as a stack guard.
// Use the second as the user stack.
sz = PGROUNDUP(sz);
uint64 sz1;
if((sz1 = uvmalloc(pagetable, sz, sz + 2*PGSIZE, PTE_W)) == 0)
goto bad;
sz = sz1;
uvmclear(pagetable, sz-2*PGSIZE);
sp = sz;
stackbase = sp - PGSIZE;
// Push argument strings, prepare rest of stack in ustack.
for(argc = 0; argv[argc]; argc++) {
if(argc >= MAXARG)
goto bad;
sp -= strlen(argv[argc]) + 1;
sp -= sp % 16; // riscv sp must be 16-byte aligned
if(sp < stackbase)
goto bad;
if(copyout(pagetable, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
goto bad;
ustack[argc] = sp;
}
ustack[argc] = 0;
// push the array of argv[] pointers.
sp -= (argc+1) * sizeof(uint64);
sp -= sp % 16;
if(sp < stackbase)
goto bad;
if(copyout(pagetable, sp, (char *)ustack, (argc+1)*sizeof(uint64)) < 0)
goto bad;
// arguments to user main(argc, argv)
// argc is returned via the system call return
// value, which goes in a0.
p->trapframe->a1 = sp;
// Save program name for debugging.
for(last=s=path; *s; s++)
if(*s == '/')
last = s+1;
safestrcpy(p->name, last, sizeof(p->name));
// Commit to the user image.
oldpagetable = p->pagetable;
p->pagetable = pagetable;
p->sz = sz;
p->trapframe->epc = elf.entry; // initial program counter = main
p->trapframe->sp = sp; // initial stack pointer
proc_freepagetable(oldpagetable, oldsz);
return argc; // this ends up in a0, the first argument to main(argc, argv)
bad:
if(pagetable)
proc_freepagetable(pagetable, sz);
if(ip){
iunlockput(ip);
end_op();
}
return -1;
}

User
execはシステムコールで、それがユーザに変える瞬間にusertrapretが実行されてpcが更新されるってことっぽい。
システムコール発行時に割り込みが起こるのは、user/usys.Sにおいてアセンブリで書かれている部分を実行しているからに見える
kernel/exec.cにもuser/usys.Sにもexec
が定義されているけど、Makefileでディレクトリ別でリンクしているから衝突していないっぽい?

exec実行までの流れ
shでexecを実行 -> user/usys.Sのexecを実行後、ecall -> trampoline.Sのuservecを実行 -> usertrapを実行 -> syscallを実行 -> sys_execを実行 -> exec(exec.c)を実行 -> syscall.cに戻ってきて返り値をp->trapframe->a0に保存 -> usertrapに戻ってきてusertrapretを実行 -> userretを実行し、sretで権限をユーザに戻してsepcにジャンプ
で完了と思われる

exec後に実行するアドレス、おそらくp->trapframe->epcが、どこで設定され実行されるかをなんとなく把握しているがコードを見て確認できていないので確認する

trap処理している最中にmyprocで確認するのはなんのプロセスなんだろ、割り込みをされているプロセス?

スレッドを実装するにあたって理解しておきたい点は以下だと考える
- プロセス実行中のスタックポインタの動き
- スレッドスタック確保とそれに伴うスタックポインタの管理
コードが実行中にプロセス内のメモリ空間がどうなっているのかを時系列順に把握してみたい

スレッドスタック確保の手法を考えるため、sbrkについてみていく