今日は午前、富士に行くつもりだったのだけど、体調が悪くパス。これからは
マシンのメンテだけじゃなく自分のメンテもしないと。自分が走れないんじゃ
どうしようもない。
ということで今日から節酒することにした。焼酎は二杯までにしよう。
しかし明日は徹カラだ。二年振りの会合だから仕方ない。
チンゲンサイを半分ほど(食べれる分くらい)収穫しました。菜園活動では初め ての普通に食材として使える収穫だったのだけど、食べてみると普通だった。 買ってきたのとそう変わらないかな。

x86続き。ブートローダまで終わったので、カーネルに。例外処理について。例 外処理のうち、一部はタスクゲートを使った方がよいのがある。例外処理は CS:IP、例外によってはエラーコードをスタックにプッシュして呼び出されるの で、スタックが不定である可能性がある場合には、タスクゲートを登録してお くことで、そのタスクの確実なスタックにプッシュして例外処理をできる。
ということで今日から節酒することにした。焼酎は二杯までにしよう。
しかし明日は徹カラだ。二年振りの会合だから仕方ない。
チンゲンサイを半分ほど(食べれる分くらい)収穫しました。菜園活動では初め ての普通に食材として使える収穫だったのだけど、食べてみると普通だった。 買ってきたのとそう変わらないかな。

x86続き。ブートローダまで終わったので、カーネルに。例外処理について。例 外処理のうち、一部はタスクゲートを使った方がよいのがある。例外処理は CS:IP、例外によってはエラーコードをスタックにプッシュして呼び出されるの で、スタックが不定である可能性がある場合には、タスクゲートを登録してお くことで、そのタスクの確実なスタックにプッシュして例外処理をできる。
- ダブルフォルト:これが起きる時にはスタックも信用できない可能性がある。 トリプルフォルトでリセットなので、その前にEIPくらいは表示したい。
- 無効TSS: TSSの切り替えで失敗した時の例外。状況によっては切り替えた 先のTSSのSSを参照するので、それが信用できない場合もある。
- スタックオーバーラン:スタックで例外が起きた時。実際にはSSは4Gフラットでとっているのでこれは起こることはなさそうだけれど。
CPUのファームウェアが退避/復旧するのはこの形。
struct tss
{
uint16_t backlink; uint16_t pad0;
uint32_t esp0;
uint16_t ss0; uint16_t pad1;
uint32_t esp1;
uint16_t ss1; uint16_t pad2;
uint32_t esp2;
uint16_t ss2; uint16_t pad3;
uint32_t cr3;
uint32_t eip;
uint32_t eflags;
uint32_t eax;
uint32_t ecx;
uint32_t edx;
uint32_t ebx;
uint32_t esp;
uint32_t ebp;
uint32_t esi;
uint32_t edi;
uint16_t es; uint16_t pad4;
uint16_t cs; uint16_t pad5;
uint16_t ss; uint16_t pad6;
uint16_t ds; uint16_t pad7;
uint16_t fs; uint16_t pad8;
uint16_t gs; uint16_t pad9;
uint16_t ldt; uint16_t pada;
uint16_t t; uint16_t iomap;
//#define IO_MASK_MAX 0x800
#define IO_MASK_MAX 1 最低でもこれはいる。そして最後のバイトを0xffに。
4byteアライン。それはこのTSSのセグメントの大きさをCPUがチェックするから。
uint32_t io_mask[IO_MASK_MAX];
} __attribute ((packed));
この構造体全体をデスクリプタとして登録する。アクセスバイトでこのデスク
リプタがTSSであることを宣言する。このTSSのLDTの設定はGDTの配列をこれも
そのままセグメントとしてアクセスバイトでLDTと設定して登録しておき、その
セレクタをTSSのldtに設定。(これはこのOSの設定)
タスクゲートは、このタスクデスクリプタへのポインタ。GDTへの参照でljmp,
lcallする分にはデスクリプタをオペランドにしても、ゲートをオペランドにし
てもどっちでもいい。しかしIDTに登録できるのはゲートだけ。IDTのゲートは
GDTのTSSセグメントを指すようにする。ゲートに登録するのはセグメントだけ。
ゲートにゲートを登録すると一般保護例外。
void
tss_install_gdt (struct tss *tss, uint16_t desc_idx, uint16_t gate_idx)
{
union descriptor *d;
ここでstruct tssをセグメントとしてGDTに登録する。
// TSS itself.
d = descriptor (desc_idx);
descriptor_set_base (&d->desc, (uint32_t)tss);
descriptor_set_size (&d->desc, sizeof (struct tss));
そのセグメントへのゲートをGDTに登録
// Corresponding gate.
d = descriptor (gate_idx);
gate_task (&d->gate, desc_idx);
}
IDTは別にまた登録。
{ 0x08, SYS_TASKGATE, 0, GDT_TSS_DOUBLE_FAULT, 0 }, // double fault
{ 0x09, SYS_INTRGATE, 0, GDT_CODE32, abort09_cop_segment_overrun },
{ 0x0a, SYS_TASKGATE, 0, GDT_TSS_INVALID_TSS, 0 }, //invalid_tss
{ 0x0b, SYS_INTRGATE, 0, GDT_CODE32, fault0b_no_segment },
{ 0x0c, SYS_TASKGATE, 0, GDT_TSS_STACK_OVERRUN, 0 },//stack overrun
{ 0x0d, SYS_INTRGATE, 0, GDT_CODE32, fault0d_general_protection },
タスクゲートの参照先はGDTのTSSセグメント。ゲートではない。
void
exception_init ()
{
struct tss_config *config;
int i;
最初のタスクスイッチで今のコンテキストを退避する場所を登録する。
// Setup my backup area.
tss_install_gdt (&tss_kernel, GDT_TSS_KERNEL, GDT_GATE_KERNEL);
ltr命令は退避する場所を設定して、BUSYビットを立てるだけで、タスクスイッ
チはしない。なのでtssの中身の設定は不用。最初のスイッチでCPUがこの場所
に退避してくれる。
// Load myself. (no task change)
tss_install_tr (GDT_TSS_KERNEL);
タスクを登録。これは全てTSSの内容を設定しておく必要がある。
for (i = 0, config = tss_config; i < sizeof tss_config / sizeof tss_config[0];
i++, config++)
{
tss_setup (config->tss, config->func, config->stack_top);
tss_install_gdt (config->tss, config->segment, config->gate);
}
// Load IDT.
idt_init ();
// Now OK to use gate.
#define tss_call(selector) __asm volatile ("lcall %0, $0":: "i"(selector));
tss_call (GDT_GATE_USER);ゲートでも、
tss_call (GDT_TSS_USER);セグメントでもよい。(特権保護がなければ)
}
void
tss_install_gdt (struct tss *tss, uint16_t desc_idx, uint16_t gate_idx)
{
union descriptor *d;
これはTSS構造体の領域をセグメントとして登録。
// TSS itself.
d = descriptor (desc_idx);
descriptor_set_base (&d->desc, (uint32_t)tss);
descriptor_set_size (&d->desc, sizeof (struct tss));
そのTSS構造体にジャンプするゲートを登録。GDT経由なら、なくてもいい。
// Corresponding gate.
d = descriptor (gate_idx);
gate_task (&d->gate, desc_idx);
}
void
tss_install_tr (uint16_t selector)
{
__asm volatile ("ltr %0":: "a"(selector));
}
void
tss_setup (struct tss *tss, void (*start_func)(void), uint32_t stack_start)
{
struct lgdt_arg gdt;
union descriptor *d;
// LDT
GDTをそのままLDTとして登録(ダミー)。登録すべきものが欲しいので。
__asm volatile ("sgdt %0" : "=m"(gdt):);
d = descriptor (GDT_LDT);
descriptor_set_base (&d->desc, gdt.base);
descriptor_set_size (&d->desc, gdt.limit + 1);
memset (tss, 0, sizeof (struct tss));
tss->esp0 = tss->esp1= tss->esp2 = stack_start;
tss->ss0 = tss->ss1 = tss->ss2 = GDT_DATA32;
tss->eip = (uint32_t)start_func;
tss->eflags = eflags_get (); //XXX ここはおいおい。
tss->esp = stack_start;
tss->ldt = GDT_LDT;
tss->cs = GDT_CODE32;
tss->ss = tss->ds = tss->es = tss->fs = tss->gs = GDT_DATA32;
tss->iomap = offsetof (struct tss, io_mask);
tss->io_mask[IO_MASK_MAX - 1] |= 0xff000000; // Marker.
tss->eax = 0xac1dcafe;テスト用の引数。
}
呼び出される側は
void
user_task ()
{
int i = 0;
int arg = 0;
__asm volatile ("movl %%eax, %0": "=g"(arg):);
while (/*CONSTCOND*/1)
{
printf ("hello tss %x %d\n", arg, i++);
__asm volatile ("iret"); ここでリターン。
printf ("world\n"); 次の呼び出された時はここから。
}
}
やっとこのあたりわかった。どれを読んでもこのあたり何だかよくわからない
記述が多いと思っていたのだけど、実際自分で書いてもうまく書けない。忘れ
た頃の自分にわかればいいけど。
