xv6はx86用に実装されているのだが、それを他のアーキテクチャに移植する場合に、何が必要かについて調査している。 最近は仕事も忙しくて、まったく調査できていなかったのだが、少し時間ができたので、続きをやっていこう。
cpuステータス変数について、コンパイル時にハマったこと
proc.hには、以下のように定義されている。これに気が付かずに、結構長い時間ハマってしまった。
- proc.h
// Per-CPU state struct cpu { uchar id; // Local APIC ID; index into cpus[] below struct context *scheduler; // swtch() here to enter scheduler struct taskstate ts; // Used by x86 to find stack for interrupt struct segdesc gdt[NSEGS]; // x86 global descriptor table volatile uint started; // Has the CPU started? int ncli; // Depth of pushcli nesting. int intena; // Were interrupts enabled before pushcli? // Cpu-local storage variables; see below struct cpu *cpu; struct proc *proc; // The currently-running process. }; // Per-CPU variables, holding pointers to the // current cpu and to the current process. // The asm suffix tells gcc to use "%gs:0" to refer to cpu // and "%gs:4" to refer to proc. seginit sets up the // %gs segment register so that %gs refers to the memory // holding those two variables in the local cpu's struct cpu. // This is similar to how thread-local variables are implemented // in thread libraries such as Linux pthreads. extern struct cpu *cpu asm("%gs:0"); // &cpus[cpunum()] extern struct proc *proc asm("%gs:4"); // cpus[cpunum()].proc
GSというのは、Gセグメントの略称らしい。GはFの次、ということ?
X86アセンブラ/x86アーキテクチャ - Wikibooks
FSやGSはこの決まりの例外となっており、スレッド独自のデータを指し示すのに使われる。
このgsレジスタが各コア用のcpu変数を持っている。さらにオフセットを4つずらすことで、procを参照している?そんな馬鹿な。
%gs:foo [gs]:[foo] セッションは"%gs"で示され、オフセットは変数"foo"の内容で示される。
とりあえず、移植するときはgsは省略するようにした。
extern struct cpu *cpu; // &cpus[cpunum()] extern struct proc *proc; // cpus[cpunum()].proc
trapframeの移植について
trapframeはx86用に定義されている。これをRISC-V用に移植するとどうなるだろう。
//PAGEBREAK: 36 // Layout of the trap frame built on the stack by the // hardware and by trapasm.S, and passed to trap(). struct trapframe { // registers as pushed by pusha uint edi; uint esi; uint ebp; uint oesp; // useless & ignored uint ebx; uint edx; uint ecx; uint eax; // rest of trap frame ushort gs; ushort padding1; ushort fs; ushort padding2; ushort es; ushort padding3; ushort ds; ushort padding4; uint trapno; // below here defined by x86 hardware uint err; uint eip; ushort cs; ushort padding5; uint eflags; // below here only when crossing rings, such as from user to kernel uint esp; ushort ss; ushort padding6; };
すべてのレジスタを定義して、プロセスが切り替わるときに退避するときに利用するとしたらこうなるだろうか。
//PAGEBREAK: 36 // Layout of the trap frame built on the stack by the // hardware and by trapasm.S, and passed to trap(). struct trapframe { // registers as pushed by pusha uint ra; uint s0; uint s1; uint s2; uint s3; uint s4; uint s5; uint s6; uint s7; uint s8; uint s9; uint s10; uint s11; uint sp; uint tp; uint v0; uint v1; uint a0; uint a1; uint a2; uint a3; uint a4; uint a5; uint a6; uint a7; uint t0; uint t1; uint t2; uint t3; uint t4; uint gp; uint mepc; uint mcause; uint mstatus; };
このときに、mepc, mcause, mstatusを対比しているが、ここは勉強不足で疑問が残る。mxxxレジスタはマシンモード時のステートを保存するためのシステムレジスタだが、実際にプロセスが実行されるのはユーザモードの時ではないのか?そうすると、ユーザモードの状態を保持しているシステムレジスタを保存するのが適切ではないだろうか。 あるいは、すべてのレジスタを保存しておくべきかなあ。
xchgの代替にはamoswapを使う?
xchgについても、まだ勉強不足で分かっていないのだが、同様の機能を持っているものとしたらRISC-Vだとamoswapが使えるかもしれない。
- x86.h
static inline uint xchg(volatile uint *addr, uint newval) { uint result; // The + in "+m" denotes a read-modify-write operand. asm volatile("lock; xchgl %0, %1" : "+m" (*addr), "=a" (result) : "1" (newval) : "cc"); return result; }
- riscv.h
static inline uint xchg(volatile uint *addr, uint newval) { uint result; asm volatile ("amoswap.w %0, %0, (%1)": "=r" (result), "+r" (*addr) : "r" (newval)); return result; }