第6課:多任務(wù) 下載源代碼
聲明:轉(zhuǎn)載請(qǐng)保留:
譯者:http://www.shnenglu.com/jinglexy
原作者:xiaoming.mo at skelix dot org
MSN & Email: jinglexy at yahoo dot com dot cn
目標(biāo)
在本課中,我們將在skelix內(nèi)核中同時(shí)運(yùn)行多個(gè)任務(wù)(本文中可以暫時(shí)理解成進(jìn)程)。每個(gè)任務(wù)都有自己的LDT表,并且是基于優(yōu)先級(jí)的任務(wù)調(diào)度。
現(xiàn)在我們開(kāi)始skelix內(nèi)核的多任務(wù)支持工作了。首先要澄清的是:在單處理器上面,進(jìn)程只能并發(fā),而不是并行。就是說(shuō)同一時(shí)刻只能有一個(gè)任務(wù)在運(yùn)行,cpu會(huì)劃分很多很小的時(shí)間片來(lái)運(yùn)行各個(gè)任務(wù),這樣看起來(lái)就像多個(gè)任務(wù)在同時(shí)運(yùn)行一樣。i386從硬件上就支持多任務(wù)了,但是也可以通過(guò)編程來(lái)實(shí)現(xiàn),本文就是一個(gè)例子,每個(gè)任務(wù)輪流運(yùn)行一個(gè)小的時(shí)間片。我們知道,單個(gè)cpu只有1組寄存器,這些可以用于所有任務(wù)。當(dāng)由一個(gè)任務(wù)切換到另一個(gè)任務(wù)時(shí),必須先保存原來(lái)任務(wù)的運(yùn)行環(huán)境(就是一堆寄存器和其他信息),通常稱這個(gè)運(yùn)行環(huán)境叫‘上下文’。i386使用一個(gè)叫TSS的段來(lái)存儲(chǔ)這些信息,這個(gè)TSS段至少104字節(jié)(不包括IOPL位圖的話),對(duì)應(yīng)它的是一個(gè)TSS描述符。TSS描述符只能在GDT表中,這意味著任務(wù)不能自己通過(guò)LDT來(lái)切換到其他任務(wù)。當(dāng)時(shí)鐘中斷到來(lái)切換任務(wù)時(shí),CPU會(huì)自動(dòng)保存相關(guān)信息到TSS中。
TSS定義了任務(wù)的上下文環(huán)境,結(jié)構(gòu)如下:
(圖-1)
31
16,15 0
+------------------------+
|
| NO use | Back
Link
|
| 高地址
+------------------------+
|
| ESP0 |
|
+------------------------+
|
| NO use |
SS0 |
|
+------------------------+
|
|
ESP1 |
|
+------------------------+
|
| NO use |
SS1 |
| 低地址
+------------------------+ \|/
|
ESP2 |
+------------------------+
| NO use |
SS2 |
+------------------------+
| CR3 |
+------------------------+
| EIP |
+------------------------+
| EFLAGS |
+------------------------+
| EAX |
+------------------------+
| ECX |
+------------------------+
| EDX |
+------------------------+
| EBX |
+------------------------+
| ESP |
+------------------------+
| EBP |
+------------------------+
| ESI |
+------------------------+
| EDI |
+------------------------+
| NO use | ES
|
+------------------------+
| NO use | CS
|
+------------------------+
| NO use | SS
|
+------------------------+
| NO use | DS
|
+------------------------+
| NO use | FS
|
+------------------------+
| NO use | GS
|
+------------------------+
| NO use
| LDT |
+------------------------+
| I/O 位圖 | NO use | T |
+------------------------+
i386處理器可以使用嵌套任務(wù),就是說(shuō)當(dāng)任務(wù)1切換到任務(wù)2時(shí),任務(wù)2 的 Back Link 域被設(shè)置成任務(wù)1的選擇子,且任務(wù)2的EFLAGS中的NT(嵌套任務(wù)標(biāo)識(shí))置位,這樣任務(wù)2返回時(shí),cpu就知道切換回任務(wù)1。我們知道,i386有4中特權(quán)級(jí),所以可以在TSS中設(shè)置這四種特權(quán)級(jí)下的堆棧。比如一個(gè)任務(wù)運(yùn)行在ring3特權(quán)級(jí)下,它使用用戶態(tài)堆棧,使用系統(tǒng)調(diào)用可以讓這個(gè)任務(wù)切換到ring0的內(nèi)核態(tài),這是堆棧也必須切換到內(nèi)核態(tài)堆棧。這些堆棧指針就是存放在TSS中的。現(xiàn)在我們來(lái)看看TSS描述符(它只能放在GDT中):
圖-2:TSS描述符定義
63_______________56__55__54__53__52__51_____________48_
| 基地址(31到24位) | G | 0 | 0 |AVL| 長(zhǎng)度(19到16位) |
|_______________________________________________________|
_47__46__45__44________41__40____39_________________32_
| P | DPL | 0 | 1 | 0 |
B | 1 | 基地址(23到16位) |
|_______________________________________________________|
31____________________________________________________16
|
基地址(15到0位)
|
|_______________________________________________________|
16_____________________________________________________
|
長(zhǎng)度(15到0位)
|
|_______________________________________________________|
*********************************************************
圖-3:通用描述符定義
63_______________56__55__54__53__52__51_____________48_
| 基地址(31到24位) | G |D/B| X | U | 長(zhǎng)度(19到16位) |
|_______________________________________________________|
_47__46__45__44____41______40____39_________________32_
| P | DPL | 類型
| A | 基地址(23到16位) |
|_______________________________________________________|
31____________________________________________________16
|
基地址(15到0位)
|
|_______________________________________________________|
16_____________________________________________________
|
長(zhǎng)度(15到0位)
|
|_______________________________________________________|
我們現(xiàn)在來(lái)比較一下通用描述符和TSS描述符,在TSS描述符中,我們看到D位(數(shù)據(jù)還是代碼段)和X位(未使用)都被置為0,而且AVL位有效。類型type是010B,第41位,及B位是TSS描述符的忙標(biāo)識(shí),因?yàn)橐粋€(gè)進(jìn)程只能有一個(gè)TSS,所以當(dāng)進(jìn)程執(zhí)行時(shí),B位將被設(shè)置,表示這個(gè)TSS正在被使用,除了進(jìn)程調(diào)度外,不能再使用它了。A位被置為1,即便A位被置為可寫,TSS也不能被進(jìn)程讀或?qū)憽?span lang="EN-US">TSS的其他域和通用描述符一樣,只是要注意一點(diǎn):就是TSS的長(zhǎng)度界限至少要有104字節(jié)。另外需要注意的是,skelix暫時(shí)不支持fpu,mmx和sse,如果讀者想擴(kuò)展的話需要注意每個(gè)進(jìn)程都有自己的fpu,sse環(huán)境。
在本課中,TSS描述符的DPL將被置為0,所以只有內(nèi)核可以進(jìn)程任務(wù)切換。和GDTR和LDTR一樣,TSS描述符也有自己的用于切換任務(wù)的寄存器,就是TR。可以用LTR指令來(lái)加載這個(gè)寄存器。GDT中的TSS描述符不能被加載到任何其他寄存器,否則會(huì)發(fā)生一個(gè)異常。
切換任務(wù)有多種方法,我們的做法是使用jmp指令跳轉(zhuǎn)到TSS描述符。(還有其他方法,如從中斷返回,使用任務(wù)門(起始也相當(dāng)于jmp到TSS上))。趙博的linux內(nèi)核完全剖析上講到了切換任務(wù)的幾種方法,并詳細(xì)討論了,不清楚的同學(xué)可以購(gòu)買該書。
在切換任務(wù)時(shí),CPU首先保存當(dāng)前進(jìn)程的上下文環(huán)境到當(dāng)前TSS中,然后加載新任務(wù)的TSS到TR寄存器中,然后從新任務(wù)的TSS中加載上下文到寄存器中,最后跳轉(zhuǎn)到新任務(wù)的第一條指令并開(kāi)始執(zhí)行。不知道這樣有沒(méi)有說(shuō)清楚,這個(gè)是比較關(guān)鍵的地方,歡迎和我討論:jinglexy at yahoo dot com dot cn(MSN)。
OK,讓程序來(lái)說(shuō)明一起吧。先看看TSS數(shù)據(jù)結(jié)構(gòu)的定義:
06/include/task.h
struct TSS_STRUCT {
int back_link;
int esp0, ss0;
int esp1, ss1;
int esp2, ss2;
int cr3;
int eip;
int eflags;
int eax,ecx,edx,ebx;
int esp, ebp;
int esi, edi;
int es, cs, ss, ds, fs, gs;
int ldt;
int trace_bitmap;
};
沒(méi)有pad的數(shù)據(jù)結(jié)構(gòu)是104字節(jié),如果你使用或模擬 IA64 平臺(tái)的話,就需要查找相關(guān)的資料了。
對(duì)于一個(gè)正在運(yùn)行的OS,上面的信息顯然是不夠的,所有我定義了另外一個(gè)包裝TSS的數(shù)據(jù)結(jié)構(gòu),
即進(jìn)程信息(process control block),以后簡(jiǎn)稱為pcb:
#define TS_RUNNING
0 //
任務(wù)的三種狀態(tài)
#define TS_RUNABLE 1
#define TS_STOPPED 2
struct TASK_STRUCT
{
// pcb定義
struct TSS_STRUCT tss;
unsigned long long tss_entry;
// tss描述符在gdt中的8字節(jié)入口項(xiàng)
unsigned long long ldt[2];
unsigned long long ldt_entry;
int state;
int priority;
struct TASK_STRUCT *next;
};
#define DEFAULT_LDT_CODE 0x00cffa000000ffffULL
#define DEFAULT_LDT_DATA 0x00cff2000000ffffULL
#define INITIAL_PRIO 200
priority 表示任務(wù)優(yōu)先級(jí),新建任務(wù)默認(rèn)的優(yōu)先級(jí)是下面的 INITIAL_PRIO。skelix內(nèi)核中所有的任務(wù)都用一個(gè)單向鏈表來(lái)表示,這樣做起來(lái)比較簡(jiǎn)單。我們現(xiàn)在來(lái)看一個(gè)任務(wù)的例子,就是任務(wù)0:內(nèi)核初始化完成后,就執(zhí)行任務(wù)0。
06/task.c
static unsigned long TASK0_STACK[256] = {0xf};
上面數(shù)據(jù)是任務(wù)0在特權(quán)級(jí)0下使用的堆棧,0xf值如果不設(shè)置的話,這塊內(nèi)存就會(huì)發(fā)生一些錯(cuò)誤,我也不清楚是為什么,所以第一個(gè)元素隨便給了一個(gè)非0值。
struct TASK_STRUCT TASK0 = {
/* tss */
{
0,
/*
esp0
ss0 */
(unsigned)&TASK0_STACK+sizeof
TASK0_STACK, DATA_SEL,
上面定義會(huì)使任務(wù)的esp0執(zhí)行棧底,使用內(nèi)核數(shù)據(jù)段選擇子就可以了
/* esp1 ss1 esp2 ss2 */
0, 0, 0, 0,
/* cr3 */
0,
/* eip eflags */
0, 0,
/* eax ecx edx ebx */
0, 0, 0, 0,
/* esp ebp */
0, 0,
/* esi edi */
0, 0,
/*
es
cs ds
*/
USER_DATA_SEL, USER_CODE_SEL,
USER_DATA_SEL,
/*
ss
fs gs
*/
USER_DATA_SEL, USER_DATA_SEL,
USER_DATA_SEL,
/* ldt:見(jiàn)后面說(shuō)明 */
0x20,
/* trace_bitmap */
0x00000000},
/* tss_entry */
0,
/* ldt[2] */
{DEFAULT_LDT_CODE, DEFAULT_LDT_DATA},
/* ldt_entry */
0,
/* state */
TS_RUNNING,
/* priority */
INITIAL_PRIO,
/* next */
0,
};
現(xiàn)在,我們定義了一個(gè)TSS,再在GDT中加入描述符索引:
06/bootsect.s
gdt:
.quad 0x0000000000000000
# null descriptor
.quad 0x00cf9a000000ffff # cs
.quad
0x00cf92000000ffff # ds
.quad
0x0000000000000000 # reserved for further use
.quad
0x0000000000000000 # reserved for further use
第四項(xiàng)(0x3)用于存放該任務(wù)的TSS,所以定義了一個(gè)宏: CURR_TASK_TSS = 3來(lái)索引GDT中的這個(gè)TSS。當(dāng)任務(wù)釋放控制權(quán)后,會(huì)加載自己的TSS描述符索引到pcb的tss_entry域。不管多少任務(wù),都可以只使用兩個(gè)描述符,切換任務(wù)前更新GDT中的描述符即可。由于GDT有長(zhǎng)度限制,只能存放8096個(gè)描述符,linux操作系統(tǒng)限制了任務(wù)的數(shù)量,我不知道為什么要這樣做,因?yàn)橥黄七M(jìn)程數(shù)限制看起來(lái)如此的簡(jiǎn)單。
06/task.c
unsigned long long set_tss(unsigned long long tss)
{ // 參數(shù)為tss在內(nèi)存中的地址
unsigned long long __tss_entry = 0x0080890000000067ULL;
__tss_entry |= ((tss)<<16) &
0xffffff0000ULL; //
基地址低24位
__tss_entry |= ((tss)<<32) & 0xff00
0000 0000 0000ULL; // 基地址高8位
return gdt[CURR_TASK_TSS] = __tss_entry;
}
這個(gè)函數(shù)產(chǎn)生TSS描述符,并存放它到GDT中,可以看到,描述符的DPL設(shè)置為0,所以只有內(nèi)核可以用這個(gè)描述符。
屬性0080,8900,0000,0067分析:0x67是長(zhǎng)度103(從0開(kāi)始計(jì)算,即長(zhǎng)104),89即p位為1,dpl位為0,b位為1,80是粒度G位為1
unsigned long long get_tss(void) {
return gdt[CURR_TASK_TSS];
}
LDT
我們看了這么多關(guān)于GDT和LDT的代碼后,LDT非常容易理解了。它和GDT差不多,區(qū)別是具有局部特性,每個(gè)任務(wù)都可以有自己的LDT,我們?cè)?span lang="EN-US">skelix內(nèi)核中為每個(gè)任務(wù)在LDT中設(shè)置兩個(gè)描述符項(xiàng),第一項(xiàng)是代碼段,第二項(xiàng)是數(shù)據(jù)段和堆棧段,描述符選擇子格式如下:
圖-4
15______________________________3___2____1___0__
|
Index |
TI | RPL
|
|_______________________________________________|
我們?cè)O(shè)置所有任務(wù)的特權(quán)級(jí)為3,即RPL = 11b,且TI = 1(表示使用LDT而不是GDT),和GDT不同的是,LDT的第一項(xiàng)可以使用而不是保留,所以代碼段的選擇子為0x7,而數(shù)據(jù)段和堆棧段選擇子為0xf。
TSS數(shù)據(jù)結(jié)構(gòu)中,有一個(gè)LDT域,保存的是GDT表中的描述符。聽(tīng)起來(lái)可能昏菜,我們現(xiàn)在來(lái)搞明白她。首先,每個(gè)進(jìn)程都有自己的LDT表,而且這個(gè)表可以在內(nèi)存的任何地方(暫時(shí)不考慮虛擬內(nèi)存),所以需要一個(gè)描述符來(lái)索引這個(gè)內(nèi)存地址(即LDT表),這個(gè)描述符就放在GDT中,并且在TSS中存放一份該描述符的選擇子。
畫個(gè)圖來(lái)說(shuō)明好了:
圖-5
________________
________________
|
TASK
| | GDT
|
|________________|
|
|
| TSS __________|/ 選擇子存于此 |________________|
| | LDT
field|--------------------------| 描述符 |
|
----------|\
/|________________|
|________________| /
| |
|
| /
|
|
|
| 描述符索引該
LDT /
|
|
|
| -------------
|
|
|
|
/
|________________|
|
| /
|
| /
|
| /
|
| /
|
| /
|
| /
|________________| /
|
LDT |/
|________________|
|________________|
GDT中的第三項(xiàng)用于任務(wù)TSS,第四項(xiàng)用于任務(wù)LDT。通過(guò)選擇子0x18和0x20分包可以索引到它們。和設(shè)置TSS一樣,對(duì)應(yīng)也寫有兩個(gè)LDT操作的函數(shù)。
06/task.c
unsigned long long
set_ldt(unsigned long long ldt) {
unsigned long long ldt_entry =
0x008082000000000fULL; // DPL 是3而不是0
ldt_entry |= ((ldt)<<16) &
0xffffff0000ULL;
ldt_entry |= ((ldt)<<32) & 0xff00000000000000ULL;
return gdt[CURR_TASK_LDT] = ldt_entry;
}
unsigned long long
get_ldt(void) {
return gdt[CURR_TASK_LDT];
}
現(xiàn)在,我們將設(shè)置所有任務(wù)使用相同的LDT,這些任務(wù)共享相同的內(nèi)存空間。如果你要設(shè)計(jì)字節(jié)的OS,這不會(huì)是個(gè)好主意。但是后面會(huì)為這些任務(wù)通過(guò)虛擬內(nèi)存機(jī)制來(lái)設(shè)置不同的內(nèi)存空間,后續(xù)課程會(huì)講到。
06/include/task.h
#define DEFAULT_LDT_CODE
0x00cffa000000ffffULL
#define DEFAULT_LDT_DATA 0x00cff2000000ffffULL
上面就是任務(wù)的LDT描述符定義,注意DPL(descriptor priority level)值為3。
上面已經(jīng)提到,所有任務(wù)使用一個(gè)單向鏈表連接起來(lái),其中有兩個(gè)重要指針:一個(gè)是任務(wù)0(TASK0)的next指針,它是所有任務(wù)鏈表的頭指針;另一個(gè)是current指針,指向當(dāng)前正在運(yùn)行的任務(wù)。
創(chuàng)建任務(wù)并調(diào)度
首先,我們定義有:
06/task.c
struct TASK_STRUCT *current = &TASK0;
現(xiàn)在我們來(lái)看看新任務(wù)是如何創(chuàng)建的:
06/init.c
static void
new_task(struct TASK_STRUCT *task, unsigned int eip,
unsigned int stack0, unsigned int stack3) {
// 這個(gè)函數(shù)有4個(gè)參數(shù):第一個(gè)參數(shù)是任務(wù)數(shù)據(jù)結(jié)構(gòu)的內(nèi)存地址
// 第二個(gè)參數(shù)是任務(wù)入口地址
// 第三個(gè)和第四個(gè)參數(shù)是0環(huán)和3環(huán)特權(quán)級(jí)下堆棧地址
// 由于堆棧地址的描述符選擇子是固定的,所以就不用傳進(jìn)來(lái)了
memcpy(task, &TASK0, sizeof(struct
TASK_STRUCT)); // TASK0 作為任務(wù)模板
task->tss.esp0 = stack0;
task->tss.eip = eip;
task->tss.eflags =
0x3202; // 合適的狀態(tài)標(biāo)識(shí)
task->tss.esp = stack3;
task->priority = INITIAL_PRIO; // 新任務(wù)默認(rèn)優(yōu)先級(jí)
task->state = TS_STOPPED;
task->next = current->next;
current->next = task;
task->state = TS_RUNABLE;
}
extern void task1_run(void);
// 任務(wù)入口函數(shù)
extern void task2_run(void);
static long task1_stack0[1024] = {0xf, };
static long task1_stack3[1024] = {0xf, };
static long task2_stack0[1024] = {0xf, };
static long task2_stack3[1024] = {0xf, };
// 因?yàn)闆](méi)有內(nèi)核中沒(méi)有實(shí)現(xiàn)內(nèi)存管理,所以固定設(shè)置一些任務(wù)和她們使用的堆棧。
// 任務(wù)0運(yùn)行在內(nèi)核態(tài),任務(wù)1和任務(wù)2運(yùn)行在ring3
void
init(void) {
char wheel[] = {'\\', '|', '/', '-'};
int i = 0;
struct TASK_STRUCT
task1;
// 移到全局定義較合適
struct TASK_STRUCT task2;
idt_install();
pic_install();
kb_install();
timer_install(100);
set_tss((unsigned long
long)&TASK0.tss);
// 讓任務(wù)0先跑起來(lái)
set_ldt((unsigned long long)&TASK0.ldt);
__asm__ ("ltrw
%%ax\n\t"::"a"(TSS_SEL)); // 加載任務(wù)0使用的TR和LDTR寄存器
__asm__ ("lldt %%ax\n\t"::"a"(LDT_SEL)); //
使用ltrw和lldt指令
sti();
// 從現(xiàn)在開(kāi)始捕獲中斷或異常
new_task(&task1, //
注意:創(chuàng)建任務(wù)時(shí),中斷是使能的
(unsigned
int)task1_run,
(unsigned
int)task1_stack0+sizeof task1_stack0,
(unsigned
int)task1_stack3+sizeof task1_stack3);
new_task(&task2,
(unsigned
int)task2_run,
(unsigned
int)task2_stack0+sizeof task2_stack0,
(unsigned
int)task1_stack3+sizeof task2_stack3);
__asm__ ("movl
%%esp,%%eax\n\t" \
"pushl
%%ecx\n\t" \ // 任務(wù)0內(nèi)核棧ss
"pushl %%eax\n\t" \
// 任務(wù)0內(nèi)核棧esp0
"pushfl\n\t"
\
// eflags
"pushl
%%ebx\n\t" \ // 任務(wù)0代碼段選擇子cs
"pushl
$1f\n\t"
\ // 任務(wù)0函數(shù)地址eip:就是下面第2行匯編
"iret\n"
\
"1:\tmovw
%%cx,%%ds\n\t" \
"movw %%cx,%%es\n\t" \
"movw %%cx,%%fs\n\t" \
"movw %%cx,%%gs" \
::"b"(USER_CODE_SEL),"c"(USER_DATA_SEL));
// 注意:現(xiàn)在開(kāi)始內(nèi)核運(yùn)行在任務(wù)0上了,通過(guò)iret指令長(zhǎng)跳轉(zhuǎn)到任務(wù)0上
// (先加載正確的EIP,CS,SS,ESP,圖如下:)
+--------------------+
| LDT stack selector |
+--------------------+
|
ESP |
+--------------------+
|
EFLAGS |
+--------------------+
| LDT code selector |
+--------------------+
|
EIP |
+--------------------+
for (;;) {
__asm__ ("movb
%%al, 0xb8000+160*24"::"a"(wheel[i]));
if (i == sizeof wheel)
i = 0;
else
++i;
}
}
現(xiàn)在,任務(wù)已經(jīng)有了,萬(wàn)事具備只欠東風(fēng)了,下面加入時(shí)鐘中斷代碼進(jìn)行任務(wù)調(diào)度。
06/timer.c
void do_timer(void)
{ // 時(shí)鐘中斷處理函數(shù)
struct TASK_STRUCT *v = &TASK0;
int x, y;
++timer_ticks;
get_cursor(&x, &y);
set_cursor(71, 24);
kprintf(KPL_DUMP, "%x", timer_ticks);
set_cursor(x, y);
outb(0x20, 0x20);
cli();
for (; v; v=v->next) { // 遍歷鏈表,調(diào)整任務(wù)優(yōu)先級(jí)
if (v->state == TS_RUNNING) {
if
((v->priority+=30) <= 0)
v->priority = 0xffffffff;
} else
v->priority -=
10; // 值越低,優(yōu)先級(jí)越高(等待的任務(wù)優(yōu)先級(jí)會(huì)變高)
// *nix內(nèi)核通常這樣做:較小的值優(yōu)先級(jí)較高
}
if (! (timer_ticks%1))
scheduler();
sti();
}
調(diào)度函數(shù)實(shí)現(xiàn):
06/task.c
void scheduler(void) {
struct TASK_STRUCT *v = &TASK0, *tmp = 0;
int cp = current->priority;
for (; v; v = v->next) {
if ((v->state==TS_RUNABLE) &&
(cp>v->priority)) {
tmp = v;
cp = v->priority;
}
} // 遍歷鏈表,找尋優(yōu)先級(jí)最高的任務(wù)(即值最小的任務(wù))
if (tmp && (tmp!=current))
{ // tmp是遍歷的結(jié)果
current->tss_entry =
get_tss(); // 置TSS 和 LDT描述符
current->ldt_entry = get_ldt();
tmp->tss_entry =
set_tss((unsigned long long)((unsigned int)&tmp->tss));
tmp->ldt_entry = set_ldt((unsigned
long long)((unsigned int)&tmp->ldt));
current->state = TS_RUNABLE;
tmp->state = TS_RUNNING;
current = tmp;
__asm__
__volatile__("ljmp $" TSS_SEL_STR
", $0\n\t");
// skelix通過(guò)長(zhǎng)跳轉(zhuǎn)到TSS描述符上(偏移應(yīng)為0)切換任務(wù)
}
}
下面是任務(wù)
A & B
06/isr.s
task1_run:
call do_task1
jmp task1_run
task2_run:
call do_task2
jmp task2_run
這里使用匯編而不是c語(yǔ)言來(lái)寫這兩個(gè)任務(wù)的入口函數(shù)的原因,
是不想在跳轉(zhuǎn)前后改變內(nèi)核棧。
我們現(xiàn)在看看能不能在任務(wù)1和任務(wù)2中使用kprintf:
06/init.c
void
do_task1(void) {
unsigned int cs;
__asm__ ("movl
%%cs, %%eax":"=a"(cs));
kprintf(KPL_DUMP, "%x", cs);
for (;;)
;
}
void
do_task2(void) {
unsigned int cs;
__asm__ ("movl
%%cs, %%eax":"=a"(cs));
kprintf(KPL_PANIC, "%x", cs);
for (;;)
;
}
修改Makefile 中的 KERNEL_OBJS:
06/MakefileKERNEL_OBJS= load.o init.o isr.o timer.o libcc.o scr.o kb.o task.o
kprintf.o exceptions.o
編譯,運(yùn)行一把。截圖就不貼出來(lái)了(結(jié)果是看不到任務(wù)1和任務(wù)2的打印:ring3任務(wù)當(dāng)然不能使用ring0函數(shù))
ok,現(xiàn)在改一下這兩個(gè)任務(wù)函數(shù):
06/init.cvoid
do_task1(void) {
print_c('A', BLUE, WHITE);
}
void
do_task2(void) {
print_c('B', GRAY, BROWN);
}
編譯,運(yùn)行,正是我們想要的結(jié)果。