カーネルスレッドの構造
HimuOSでは、カーネルスレッドを基本実行単位として使用します。すべてのプロセスは最低1つのスレッドを持ち、初期スレッドは「メインスレッド」と呼ばれます。
タスク構造体
タスク構造体(KR_TASK_STRUCT)は1ページサイズで管理され、カーネルスタックの管理が特徴です。各スレッドのカーネルモードスタックは構造体内に組み込まれています。
struct KR_INTR_STACK {
uint32_t IntrCode;
uint32_t EDI;
uint32_t ESI;
uint32_t EBP;
uint32_t ESPDummy;
uint32_t EBX;
uint32_t EDX;
uint32_t ECX;
uint32_t EAX;
uint32_t GS;
uint32_t FS;
uint32_t ES;
uint32_t DS;
uint32_t ErrorCode;
void (*EIP)(void);
uint32_t CS;
uint32_t EFlags;
void *ESP;
uint32_t SS;
};
struct KR_THREAD_STACK {
uint32_t EBP;
uint32_t EBX;
uint32_t EDI;
uint32_t ESI;
void (*EIP)(KR_THREAD_ROUTINE *routine, void *routineArgs);
void (*Stub)(void);
KR_THREAD_ROUTINE *Entry;
void *Args;
};
#define MAX_TASK_NAME 16
struct KR_TASK_STRUCT {
uint32_t *KernelStack;
enum KR_THREAD_STATE Status;
char Name[MAX_TASK_NAME];
uint8_t PriorityLevel;
uint8_t TimeSlice;
uint32_t TotalTicks;
struct KR_LIST_ELEMENT ReadyQueue;
struct KR_LIST_ELEMENT GlobalList;
uint32_t *PageDirectory;
uint32_t StackGuard;
};
コンテキストスイッチング
割り込み発生時のコンテキスト保存が設計の要です。具体的な処理手順:
- IDTからハンドラを取得し特権レベルを検証
- 特権移行時はSS/ESPを新スタックに保存
- EFLAGSの状態を保存し、エラーコードを含めて全レジスタをプッシュ
- ハンドラ実行後にスタックを復元
アセンブリ実装
[bits 32]
section .text
global SwitchContext
SwitchContext:
push esi
push edi
push ebx
push ebp
mov eax, [esp + 20]
mov [eax], esp
mov eax, [esp + 24]
mov esp, [eax]
pop ebp
pop ebx
pop edi
pop esi
ret
新規スレッド起動
新スレッドは親スレッドによって作成され、スタック領域には以下が事前に配置されます:
- レジスタ保存領域(KR_THREAD_STACK)
- 戻りアドレス
- 割り込みスタックフレーム
初回起動時はSwitchContextの後半部分から実行を開始し、KernelThreadEntry経由でユーザ指定関数を呼び出します。
static void KernelThreadEntry(KR_THREAD_ROUTINE entry, void *args) {
EnableInterrupts();
entry(args);
}
組み込みリンクリスト
C言語における効率的な汎用データ構造として、侵入型双方向リンクリストを使用します。
struct KR_LIST_ELEMENT {
struct KR_LIST_ELEMENT *Prev;
struct KR_LIST_ELEMENT *Next;
};
struct KR_LIST {
struct KR_LIST_ELEMENT Head;
struct KR_LIST_ELEMENT Tail;
};
利用例
struct KR_LIST taskQueue;
KrInitializeList(&taskQueue);
struct TaskData {
int id;
struct KR_LIST_ELEMENT link;
} task1, task2;
KrInsertListTail(&taskQueue, &task1.link);
KrInsertListHeader(&taskQueue, &task2.link);
struct KR_LIST_ELEMENT *current = taskQueue.Head.Next;
while(current != &taskQueue.Tail) {
struct TaskData *task = CONTAINER_OF(current, struct TaskData, link);
printf("Task ID: %d\n", task->id);
current = current->Next;
}
スケジューラ設計
ラウンドロビン方式を採用し、優先度に応じたタイムスライスを実装:
- 時計割り込み(tick)で現在スレッドのタイムスライスを減少
- タイムスライス切れでスケジューラ起動
- 実行中スレッドをレディキュー末尾に再登録
- キュー先頭のスレッドを実行状態に遷移
カーネル起動時に自身を「kernel」スレッドとして初期化します。