前言 时钟中断前会保存当前任务(gTask) 信息,时钟中断后会恢复当前任务(gTask) 信息。那么可以借助时钟中断函数,时钟中断函数设置当前任务为另一个任务(修改gTask指针指向另一个任务) ,这样就可以实现任务切换了
两个任务并行执行 思路
启动时钟中断
启动 Task A 并打开中断开关
在时钟中断服务程序中使得 gCTaskAddr 指向 Task B
Task B 执行(中断开关已打开)
在时钟中断服务程序中使得 gCTaskAddr 指向 Task A
再再论TSS-共享TSS 目前的设计中 TSS, 使用 TSS 的唯一意义仅是转入高特权级时获取栈的位置。 (潜台词: 仅仅在中断发生时, 将 ss0 和esp0 指定的内存当作初始内核栈。
而注意先前如何表示一个进程的,所有任务可以共享同一个TSS结构体,但是LDT还是每个进程私有的不可共享
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 ; 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 ; 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函数用于切换进程。
修改共享TSS的中断栈为当前任务的regvalue(中断前用于保存当前任务的上下文信息)
重新设置ldt描述符并且加载ldt
发送中断结束标记
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; gTSS.ss0 = GDT_DATA32_FLAT_SELECTOR; gTSS.esp0 = (uint)&gCTaskAddr->rv.gs + sizeof (RegValue); SetDescValue(&gGdtInfo.entry[GDT_TASK_LDT_INDEX], (uint)&gCTaskAddr->ldt, sizeof (gCTaskAddr->ldt)-1 , DA_LDT + DA_DPL0); LoadTask(gCTaskAddr); } 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号任务。 此外取余操作太慢了。
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); }
static TaskNode gTaskBuff[MAX_TASK_NUM] = {0};
第一次时钟中断执行的是0号任务,任务上下文对于此数组是依次初始化的,当初始化4个任务时,4号任务最后被初始化,此时内核栈初始化为4号的栈(4号任务的 RegValue 成员),然而程序从0号任务开始执行。 当中断函数执行时恢复的任务上下文是0号任务的上下保存到了4号任务的栈,因此当执行到4号任务时实际执行的却是0号任务
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); 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;#define List_ForEach(list, pos) for(pos=(list)->next; !IsEqual(list, pos); pos=pos->next) #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];
模块化设计
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个任务同时位于内存中且执行呢?
三个功能宏定义 1 2 3 #define IsEqual(a, b) #define OffsetOf(type, member) #define ContainerOf(ptr, 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))) #define IsEqual(a, b) \ ({ \ unsigned ta = (unsigned)a;\ unsigned tb = (unsigned )b;\ !(ta - tb); \ }) #define OffsetOf(type, member) ((unsigned)&(((type*)0)->member)) #define ContainerOf(ptr, type, member) \ ({ \ const typeof (((type*)0 )->member) * __mptr = (ptr); \ (type*)((char *)__mptr - OffsetOf(type, member)); \ })