5.15. tvinit関数
大域変数idtに256個のゲートディスクリプタを作成する。
IDT(Interrupt Descriptor Table)を作成する。
IDTについては「Intel 64 and IA-32 architectures software developer's manual combined volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4」(リンク8)のVol.3 Chapter6.10「Interrupt Descriptor Table (IDT)」で説明されている。
ここでは、IDTはgatedesc構造体の配列になっており、大域変数idtで宣言されている。
Intel SDMによると256個の割り込みベクタを持てるため、idtの要素数も256。
IDTに格納される各ゲートディスクリプタの構造はIntel SDMの図6-2「IDT Gate Descriptors」に記載されており、ここで使用するのはInterrupt GateとTrap Gate。 割り込みにはInterrupt Gateを使用し、システムコールにはTrap Gateを使用する。
gatedesc構造体はmmu.hで定義されている。
- off_15_0: 割り込みベクタのアドレスの下半分を設定する。
- cs: GDT上のエントリのインデックスを設定する。 セグメントディスクリプタは8バイトなので8の倍数。
- args: 予約済みなので0埋め。
- rsv1: 全て0。
- type: Interrupt Gateの場合は0b1110(0xE)、Trap Gateの場合は0b1111(0xF)を設定する。 Intel SDMの図で確認すると3bitがDになり、ゲートディスクリプタのサイズが32bitの場合1、16bitの場合0になる。 なのでここでは1で固定。
- s: 12bitに当たるので0固定。
- dpl: ディスクリプタ特権レベル(Descriptor Privilege Level)なので、Interrupt Gateの場合0、Trap Gateの場合3を設定する。 特権レベルについてはVol.3 Chapter5.5「PRIVILEGE LEVELS」で説明されており、カーネルが0、ユーザが3。
- p: セグメント存在フラグ。今回はセグメントがメモリ上に存在するので1を設定する。
- off_31_16: 割り込みベクタのアドレスの上半分を設定する。
SETGATEマクロで各ゲートディスクリプタを作成する。
trap.c
struct gatedesc idt[256];
mmu.h
struct gatedesc {
uint off_15_0 : 16; // low 16 bits of offset in segment
uint cs : 16; // code segment selector
uint args : 5; // # args, 0 for interrupt/trap gates
uint rsv1 : 3; // reserved(should be zero I guess)
uint type : 4; // type(STS_{IG32,TG32})
uint s : 1; // must be 0 (system)
uint dpl : 2; // descriptor(meaning new) privilege level
uint p : 1; // Present
uint off_31_16 : 16; // high bits of offset in segment
};
/* 略 */
#define SETGATE(gate, istrap, sel, off, d) \
{ \
(gate).off_15_0 = (uint)(off) & 0xffff; \
(gate).cs = (sel); \
(gate).args = 0; \
(gate).rsv1 = 0; \
(gate).type = (istrap) ? STS_TG32 : STS_IG32; \
(gate).s = 0; \
(gate).dpl = (d); \
(gate).p = 1; \
(gate).off_31_16 = (uint)(off) >> 16; \
}
割り込みベクタテーブルはvectors.Sに定義されている256個の関数のアドレスが、同ファイル内のvectorsラベル以下に列挙されている。
関数はvector0からvector255まであり、スタックに0とIRQ番号をプッシュし、alltraps関数に跳んでいる。
alltraps関数はtrapasm.Sで定義されている。
vectors.Sはmake時にvectors.plを実行することで作られる。
trap.cで、extern宣言として符号なし整数型の配列vectorsで割り込みベクタテーブルを参照している。
vectors.S
.globl alltraps
.globl vector0
vector0:
pushl $0
pushl $0
jmp alltraps
.globl vector1
# 略
.globl vector255
vector255:
pushl $0
pushl $255
jmp alltraps
# vector table
.data
.globl vectors
vectors:
.long vector0
# 略
.long vector255
trap.c
extern uint vectors[]; // in vectors.S: array of 256 entry pointers
tvinit関数では、まずforループで配列idtに割り込みベクタテーブルの全エントリ分(256個)の割り込みゲートディスクリプタを作成する。
DPLは0。
次に、システムコールにはIRQ64番を使うので、idtの64番目を上書きする形でトラップゲートディスクリプタをDPL3で作成する。
最後にtickslockのロックを初期化する。
trap.c
void
tvinit(void)
{
int i;
for(i = 0; i < 256; i++)
SETGATE(idt[i], 0, SEG_KCODE<<3, vectors[i], 0);
SETGATE(idt[T_SYSCALL], 1, SEG_KCODE<<3, vectors[T_SYSCALL], DPL_USER);
initlock(&tickslock, "time");
}
tick
tickは一般的に一定周期で時間を刻むことを言い、ここでも同様に使われる。
符号なしint型の大域変数ticksと、そのロックに使用するtickslockが定義されている。
使用箇所をgrepすると、タイマー割り込みに関する部分とシステムコールsys_sleep内でのみ使用されており、インクリメントだけで代入は行われない。
sysproc.cに定義されているsys_sleep関数でその使われ方を見ることができる。
この関数はticksが引数n分だけインクリメントされるまで、現在のプロセスをスリープ状態にする。
はじめに、argint関数を使用して自身に与えられたint型の引数を変数nに取り出す。
そして現在のticksの値をローカル変数ticks0に保存しておく。
whileループで ticks - ticks0 < n
が偽になるまでプロセスをスリープする。
起床するためのチャネルにはticksのアドレスを用いる。
ticksはcpu0からのタイマー割り込みによりインクリメントされるので、その度に起床されることになる。
trap.c
struct spinlock tickslock;
uint ticks;
sysproc.c
int
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);
}
release(&tickslock);
return 0;
}
ticksがインクリメントされる部分だけ合わせて見ることにする。
割り込みやトラップが生じるとIDTや割り込みベクタテーブル等を経由してtrapasm.Sのalltraps関数からtrap.cのtrap関数が呼び出されれる。
trap関数はIRQ番号によって分岐する。
ticksはタイマー割り込みかつcpu0からの割り込みである場合にのみインクリメントされる。
その後ticksのアドレスをチャネルとしてスリープ状態のプロセスを起床させる。
LAPICタイマの設定はlapicinitで見た。
trap.c
switch(tf->trapno){
case T_IRQ0 + IRQ_TIMER:
if(cpuid() == 0){
acquire(&tickslock);
ticks++;
wakeup(&ticks);
release(&tickslock);
}
lapiceoi();
break;