カーネルスレッドによるマルチタスク処理の実装

カーネルスレッドの構造

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;
};

コンテキストスイッチング

割り込み発生時のコンテキスト保存が設計の要です。具体的な処理手順:

  1. IDTからハンドラを取得し特権レベルを検証
  2. 特権移行時はSS/ESPを新スタックに保存
  3. EFLAGSの状態を保存し、エラーコードを含めて全レジスタをプッシュ
  4. ハンドラ実行後にスタックを復元

アセンブリ実装

[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」スレッドとして初期化します。

スタック構造図

タグ: カーネルスレッド コンテキストスイッチング 組み込みリンクリスト ラウンドロビンスケジューリング オペレーティングシステム開発

5月30日 22:39 投稿