前言

时钟中断前会保存当前任务(gTask)信息,时钟中断后会恢复当前任务(gTask)信息。那么可以借助时钟中断函数,时钟中断函数设置当前任务为另一个任务(修改gTask指针指向另一个任务),这样就可以实现任务切换了

两个任务并行执行

思路

  1. 启动时钟中断
  2. 启动 Task A 并打开中断开关
  3. 在时钟中断服务程序中使得 gCTaskAddr 指向 Task B
  4. Task B 执行(中断开关已打开)
  5. 在时钟中断服务程序中使得 gCTaskAddr 指向 Task A

image-20220713194957395

再再论TSS-共享TSS

目前的设计中 TSS, 使用 TSS 的唯一意义仅是转入高特权级时获取栈的位置。 (潜台词: 仅仅在中断发生时, 将 ss0 和esp0 指定的内存当作初始内核栈。

而注意先前如何表示一个进程的,所有任务可以共享同一个TSS结构体,但是LDT还是每个进程私有的不可共享

image-20220709114532799

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
volatile Task* gCTaskAddr = NULL; //注意设置volatile避免编译器优化
Task p = {0};
Task t = {0};
TSS gTSS = {0};

void InitTask(Task* pt, void(*entry)())
{
pt->rv.cs = LDT_CODE32_SELECTOR;
pt->rv.gs = LDT_VIDEO_SELECTOR;
pt->rv.ds = LDT_DATA32_SELECTOR;
pt->rv.es = LDT_DATA32_SELECTOR;
pt->rv.fs = LDT_DATA32_SELECTOR;
pt->rv.ss = LDT_DATA32_SELECTOR;

pt->rv.esp = (uint)pt->stack + sizeof(pt->stack);
pt->rv.eip = (uint)entry; //任务的入口地址由参数指定
pt->rv.eflags = 0x3202;

//指定gss的值,pt->TSS修改为gTss,共享同一个Tss
gTSS.ss0 = GDT_DATA32_FLAT_SELECTOR;
gTSS.esp0 = (uint)&pt->rv + sizeof(pt->rv);
gTSS.iomb = sizeof(TSS);

SetDescValue(pt->ldt + LDT_VIDEO_INDEX, 0xB8000, 0x07FFF, DA_DRWA + DA_32 + DA_DPL3);
SetDescValue(pt->ldt + LDT_CODE32_INDEX, 0x00, 0xFFFFF, DA_C + DA_32 + DA_DPL3);
SetDescValue(pt->ldt + LDT_DATA32_INDEX, 0x00, 0xFFFFF, DA_DRW + DA_32 + DA_DPL3);

pt->ldtSelector = GDT_TASK_LDT_SELECTOR;
pt->tssSelector = GDT_TASK_TSS_SELECTOR;

SetDescValue(&gGdtInfo.entry[GDT_TASK_LDT_INDEX], (uint)&pt->ldt, sizeof(pt->ldt)-1, DA_LDT + DA_DPL0);
//
SetDescValue(&gGdtInfo.entry[GDT_TASK_TSS_INDEX], (uint)&gTSS, sizeof(gTSS)-1, DA_386TSS + DA_DPL0);
}

main函数中只需调用InitTask就可以初始化好Task进程相关信息。

changeTask

接下来实现changeTask函数用于切换进程。

  1. 修改共享TSS的中断栈为当前任务的regvalue(中断前用于保存当前任务的上下文信息)
  2. 重新设置ldt描述符并且加载ldt
  3. 发送中断结束标记
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void ChangeTask()
{
gCTaskAddr = (gCTaskAddr == &p) ? &t : &p; //

//由于共享TSS,切换任务的时候需要改变栈
gTSS.ss0 = GDT_DATA32_FLAT_SELECTOR;
//将esp0指向目标task的末尾,当中断发生时此成员用于保存上下文,详见上节上下文恢复
gTSS.esp0 = (uint)&gCTaskAddr->rv.gs + sizeof(RegValue);

//重新设置ldt描述符
SetDescValue(&gGdtInfo.entry[GDT_TASK_LDT_INDEX], (uint)&gCTaskAddr->ldt, sizeof(gCTaskAddr->ldt)-1, DA_LDT + DA_DPL0);

LoadTask(gCTaskAddr);
}

//每5个时钟中断执行一次任务
void TimerHandler()
{
static uint i = 0;

i = (i + 1) % 5;

if( i == 0 )
{
ChangeTask();
}

SendEOI(MASTER_EOI_PORT);
}

loader.asm需要新定义一个LoadTask用于加载任务,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//loader.asm

//参数是Task* pt 任务结构的数据地址
RunTask:
push ebp ; 函数调用约定需要遵守
mov ebp, esp

; 取参数的值,指向任务数据结构
mov esp, [ebp + 8] ; mov esp, &(pt->rv.gs) esp先指向regvalue起始位置

lldt word [esp + 200] ; lldt pt->ldtSelector 加载局部段描述符表 Task结构的偏移可以计算,ldt位于200
ltr word [esp + 202] ; ltr pt->tssSelector 加载任务状态段
pop gs ; 开始修改寄存器的值
pop fs
pop es
pop ds

popad ; pop edi,esi,ebp,esp,ebx,edx,ecx,eax

add esp + 4 ; 等价于mov esp, &(pt->rv.eip)

iret

; void LoadTask(Task* pt);
;
LoadTask:
push ebp
mov ebp, esp

mov eax, [ebp + 8] ;eax里面就是Task* pt的地址

lldt word [eax + 96] ;lldt加载pt偏移200的地方即局部段描述符的位置
; 由于所有任务共用同一个TSS,不需要重新加载TSS
leave

ret

多任务执行方案

初套方案 数组

  • n个任务(n>=3)同时位于内存中
  • 循环执行每个任务,每个任务执行一个时间片,时间片的关键在于时钟中断的中断处理函数
  • 任务切换通过时钟中断处理函数(时钟中断调用Schedule函数)完成
1
2
3
4
5
6
7
void Schedule()
{
gCTaskAddr = &gTaskBuff[ index % 4 ];
index++; //全局变量
PrepareForRun(gCTaskAddr);
LoadTask(gCTaskAddr);
}

上面代码太过简单粗暴,如果1号任务结束了,由于1号任务单纯放在一个数组里面不方便删除,后续index++又会导致执行1号任务。
此外取余操作太慢了。

image-20220720233007207

1
2
3
4
volatile Task* gCTaskAddr = NULL;
static TaskNode gTaskBuff[MAX_TASK_NUM] = {0};
static TSS gTSS = {0};
static uint index = 0;

然后针对于全局变量的TaskNode数组的各个成员依次初始化,详情可见任务初始化

时钟中断晚于TaskNode初始化

注意启用时钟中断必须晚于TaskNode初始化完毕的时间,否则时钟中断调用任务时任务未初始化好报错。

因此修改代码,将启用时钟中断的代码移动到RunTask中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
;
; parameter ===> Task* pt
RunTask:
push ebp
mov ebp, esp

mov esp, [ebp + 8]

lldt word [esp + 96]
ltr word [esp + 98]

pop gs
pop fs
pop es
pop ds

popad

add esp, 4

; 增加I/O操作打开时钟中断
mov dx, MASTER_IMR_PORT

in ax, dx

%rep 5
nop
%endrep

and ax, 0xFE

out dx, al

iret

任务栈需重新指定

初始化任务InitTask时添加打印任务的结构体地址和esp的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static void InitTask(Task* pt, void(*entry)())
{
//打印语句
PrintIntHex(pt);
PrintString(" ");
PrintIntHex((uint)pt + 48);
PrintChar('\n');

pt->rv.cs = LDT_CODE32_SELECTOR;
pt->rv.gs = LDT_VIDEO_SELECTOR;
pt->rv.ds = LDT_DATA32_SELECTOR;
pt->rv.es = LDT_DATA32_SELECTOR;
pt->rv.fs = LDT_DATA32_SELECTOR;
pt->rv.ss = LDT_DATA32_SELECTOR;

pt->rv.esp = (uint)pt->stack + sizeof(pt->stack);
pt->rv.eip = (uint)entry;
pt->rv.eflags = 0x3202;

//问题出现在这里每次初始化任务重新指定内核栈
gTSS.ss0 = GDT_DATA32_FLAT_SELECTOR;
gTSS.esp0 = (uint)&pt->rv + sizeof(pt->rv);
gTSS.iomb = sizeof(TSS);

SetDescValue(AddrOff(pt->ldt, LDT_VIDEO_INDEX), 0xB8000, 0x07FFF, DA_DRWA + DA_32 + DA_DPL3);
SetDescValue(AddrOff(pt->ldt, LDT_CODE32_INDEX), 0x00, 0xFFFFF, DA_C + DA_32 + DA_DPL3);
SetDescValue(AddrOff(pt->ldt, LDT_DATA32_INDEX), 0x00, 0xFFFFF, DA_DRW + DA_32 + DA_DPL3);

pt->ldtSelector = GDT_TASK_LDT_SELECTOR;
pt->tssSelector = GDT_TASK_TSS_SELECTOR;

SetDescValue(AddrOff(gGdtInfo.entry, GDT_TASK_LDT_INDEX), (uint)&pt->ldt, sizeof(pt->ldt)-1, DA_LDT + DA_DPL0);
SetDescValue(AddrOff(gGdtInfo.entry, GDT_TASK_TSS_INDEX), (uint)&gTSS, sizeof(gTSS)-1, DA_386TSS + DA_DPL0);
}

image-20220723160608961

static TaskNode gTaskBuff[MAX_TASK_NUM] = {0};第一次时钟中断执行的是0号任务,任务上下文对于此数组是依次初始化的,当初始化4个任务时,4号任务最后被初始化,此时内核栈初始化为4号的栈(4号任务的 RegValue 成员),然而程序从0号任务开始执行。
当中断函数执行时恢复的任务上下文是0号任务的上下保存到了4号任务的栈,因此当执行到4号任务时实际执行的却是0号任务

image-20220723161143616

static TaskNode gTaskBuff[MAX_TASK_NUM] = {0};当初始化4个任务时,4号任务最后被初始化,此时内核栈初始化为4号的栈,然而程序从0号任务开始执行

因此每个任务运行前的需要重新设置内核栈,如下:

1
2
3
4
5
6
7
8
9
static void PrepareForRun(volatile Task* pt)
{
//初始化好内核栈
gTSS.ss0 = GDT_DATA32_FLAT_SELECTOR;
gTSS.esp0 = (uint)&pt->rv + sizeof(pt->rv);
gTSS.iomb = sizeof(TSS);
//GDT中设置好ldt段描述符的属性,基址,节限,属性
SetDescValue(AddrOff(gGdtInfo.entry, GDT_TASK_LDT_INDEX), (uint)&pt->ldt, sizeof(pt->ldt)-1, DA_LDT + DA_DPL0);
}

在每次RunTask之前执行此函数即可

更新方案 队列扩展实现多任务

  • 可采用队列对任务进行管理
  • 每次取队列头部任务调度执行
  • 时间片结束,将当前任务从队列头部换入队列尾部(队列循环)

list.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#ifndef LIST_H
#define LIST_H

#include "const.h"
#include "utility.h"

typedef struct _ListNode {
struct _ListNode* next;
struct _ListNode* prev;
} ListNode;

typedef ListNode List;

//接口参数都是指针,遍历整个list,for循环简写
#define List_ForEach(list, pos) for(pos=(list)->next; !IsEqual(list, pos); pos=pos->next)
//获取listNode结构体的地址
#define List_Node(ptr, type, member) ContainerOf(ptr, type, member)

void List_Init(List* list);
void List_Add(List* list, ListNode* node);
void List_AddTail(List* list, ListNode* node);
void List_AddBefore(ListNode* before, ListNode* node);
void List_AddAfter(ListNode* after, ListNode* node);
void List_DelNode(ListNode* node);
void List_Replace(ListNode* old, ListNode* node);
int List_IsLast(List* list, ListNode* node);
int List_IsEmpty(List* list);

#endif

list.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include "list.h"

void List_Init(List* list)
{
list->next = list;
list->prev = list;
}

static void _List_Add(ListNode* node, ListNode* prev, ListNode* next)
{
next->prev = node;
node->next = next;
prev->next = node;
node->prev = prev;
}

void List_Add(List* list, ListNode* node)
{
_List_Add(node, list, list->next);
}

void List_AddTail(List* list, ListNode* node)
{
_List_Add(node, list->prev, list);
}

void List_AddBefore(ListNode* before, ListNode* node)
{
_List_Add(node, before->prev, before);
}

void List_AddAfter(ListNode* after, ListNode* node)
{
_List_Add(node, after, after->next);
}

static void _List_Del(ListNode* prev, ListNode* next)
{
prev->next = next;
next->prev = prev;
}

void List_DelNode(ListNode* node)
{
_List_Del(node->prev, node->next);

node->prev = NULL;
node->next = NULL;
}

void List_Replace(ListNode* old, ListNode* node)
{
node->next = old->next;
node->next->prev = node;
node->prev = old->prev;
node->prev->next = node;

old->prev = NULL;
old->next = NULL;
}

int List_IsLast(List* list, ListNode* node)
{
return IsEqual(list, node->next);
}

int List_IsEmpty(List* list)
{
ListNode* next = list->next;

return IsEqual(next, list) && IsEqual(next, list->prev);
}

queue.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef QUEUE_H
#define QUEUE_H

#include "list.h"

typedef ListNode QueueNode;

typedef struct {
ListNode head;
int length;
} Queue;

void Queue_Init(Queue* queue);
int Queue_IsEmpty(Queue* queue);
int Queue_IsContained(Queue* queue, QueueNode* node);
void Queue_Add(Queue* queue, QueueNode* node);
QueueNode* Queue_Front(Queue* queue);
QueueNode* Queue_Remove(Queue* queue);
int Queue_Length(Queue* queue);
void Queue_Rotate(Queue* queue);

#endif

queue.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include "queue.h"

void Queue_Init(Queue* queue)
{
List_Init((List*)queue);

queue->length = 0;
}

int Queue_IsEmpty(Queue* queue)
{
return List_IsEmpty((List*)queue);
}

int Queue_IsContained(Queue* queue, QueueNode* node)
{
ListNode* pos = NULL;
int ret = 0;

List_ForEach((List*)queue, pos)
{
if( ret = IsEqual(pos, node) )
{
break;
}
}

return ret;
}

void Queue_Add(Queue* queue, QueueNode* node)
{
List_AddTail((List*)queue, node);

queue->length++;
}

QueueNode* Queue_Front(Queue* queue)
{
return queue->head.next;
}

QueueNode* Queue_Remove(Queue* queue)
{
QueueNode* ret = NULL;

if( queue->length > 0 )
{
ret = queue->head.next;

List_DelNode(ret);

queue->length--;
}

return ret;
}

int Queue_Length(Queue* queue)
{
return queue->length;
}

void Queue_Rotate(Queue* queue)
{
if( queue->length > 0 )
{
QueueNode* node = Queue_Remove(queue);

Queue_Add(queue, node);
}
}
1
2
3
4
5
6
struct Test{
ListNode head;
int value;
};
Queue g_queue;
struct Test g_add[5];

image-20220807154056518

模块化设计

image-20220713210206645

  • utility: 内核通用功能函数(宏) 模块
  • task: 任务模块, 涉及任务定义与实现, 任务调度, 等
  • interrupt: 中断模块, 涉及中断控制, 中断服务程序实现, 等

上层模块单向依赖于下层模块

utility 模块接口

  • #define AddrOff(a,i) ((void*)((uint)a+i*sizeof(*a))) ,主要规范地取地址,不使用C语言地方式了
  • void Delay(int n);

task 模块接口

  • typedef struct {…} RegValue;
  • typedef struct {…} TSS;
  • typedef struct {…} Task;
  • extern void (* const RunTask)(volatile Task* pt);
  • extern void (* const LoadTask)(volatile Task* pt);
  • void TaskModlnit();
  • void LaunchTask();
  • void Schedule();

interrupt模块接口

  • extern void (* const EnableTimer)();
  • extern void (* const SendEOl)(uint port);
  • void IntModInit();
  • int SetIntHandler(Gate*
  • pGate, uint ifunc);
  • int GetIntHandler(Gate* pGate, uint* plFunc);

先前实现了两个任务通过时钟中断来回切换,那么如何实现n个任务同时位于内存中且执行呢?

image-20220720224636924

三个功能宏定义

1
2
3
#define IsEqual(a, b) 					//判断a和b是否相等
#define OffsetOf(type, member) //计算member在struct type中的偏移
#define ContainerOf(ptr, type, member) //通过struct type中member的地址计算结构体变量的地址

这种功能宏应当放在unity.h当中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define AddrOff(a, i)    ((void*)((uint)a + i * sizeof(*a)))//内核开发规范,尽量少用a[i]这样取数据取地址

#define IsEqual(a, b) \
({ \
unsigned ta = (unsigned)a;\ //强制类型转化
unsigned tb = (unsigned)b;\ //强制类型转化
!(ta - tb); \ //都是同样的类型了做个减法即可
})

#define OffsetOf(type, member) ((unsigned)&(((type*)0)->member))
//(unsigned)&(((type*)0)->member)
// ((type*)0)这里假想0地址处有个type结构体变量
// & ((type*)0)->member对0做一个强制类型转化成type*指针,通过指针拿到member的偏移地址

#define ContainerOf(ptr, type, member) \
({ \ //typeof可以获取变量的类型
const typeof(((type*)0)->member)* __mptr = (ptr); \ //member成员类型指针保存 具体实际ptr的地址
(type*)((char*)__mptr - OffsetOf(type, member)); \ //再做 减法
})