本章节所有代码托管在miniOS_32
章节任务介绍
任务简介
上一节,我们成功模拟pthread_create创建了新的线程并成功执行
本节我们将介绍如何进行多线程轮转调度
本节的主要任务有:
- 创建多线程轮转调度的数据结构——双向链表
- 借时钟中断完成多线程的时间片轮转调度
任务目标
/kernel/main.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
|
#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"
void thread_work_a(void *arg);
void thread_work_b(void *arg);
int main(void)
{
put_str("I am kernel\n");
init_all();
thread_start("thread_work_a", 31, thread_work_a, "pthread_create_A\n");
thread_start("thread_work_b", 8, thread_work_b, "pthread_create_B\n");
/*打开中断,主要是打开时钟中断,以让时间片轮转调度生效*/
intr_enable();
while (1)
{
put_str("Main");
}
return 0;
}
/* 线程执行函数 */
void thread_work_a(void *arg)
{
char *para = (char *)arg;
while (1)
put_str(para);
}
/* 线程执行函数 */
void thread_work_b(void *arg)
{
char *para = (char *)arg;
while (1)
put_str(para);
}
|
如上所示,我们将实现多线程轮转——当用户创建thread_work_a
和thread_work_b
两个线程时,操作系统能够为每个线程分配时间片,并根据时间片进行轮转调度两个线程
线程组织队列——双向链表
如图所示,为了实现多线程的轮转调度,我们需要使用队列将存储线程信息的PCB组织起来,如就绪队列、阻塞队列等,为此需要首先需要定义这种数据结构——双向链表
/lib/kernel/list.h
首先定义链表的节点类型,将来其将作为PCB中的数据成员,负责将线程的PCB串联起来
1
2
3
4
5
6
|
/*链表节点类型*/
struct list_elem
{
struct list_elem *prev;
struct list_elem *next;
};
|
接下来定义双向链表的结构
1
2
3
4
5
6
7
8
|
/*双链表类型*/
struct list
{
/*头结点*/
struct list_elem head;
/*尾节点*/
struct list_elem tail;
};
|
然后是对链表的操作函数原型的定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/*链表初始化*/
void list_init(struct list *);
/*头插法*/
void list_insert_before(struct list_elem *cur, struct list_elem *elem);
/*在链表头部插入元素*/
void list_push(struct list *plist, struct list_elem *elem);
/*在链表尾部插入元素*/
void list_append(struct list *plist, struct list_elem *elem);
/*移除元素*/
void list_remove(struct list_elem *pelem);
/*弹出链表头部元素*/
struct list_elem *list_pop(struct list *plist);
/*判断链表是否为空*/
bool list_empty(struct list *plist);
/*获取链表长度*/
uint32_t list_len(struct list *plist);
/*在链表中查找节点*/
bool elem_find(struct list *plist, struct list_elem *obj_elem);
// 定义个叫function的函数类型,返回值是int,参数是链表结点指针与一个整形值
typedef bool(function)(struct list_elem *, int arg);
/*判断链表中是否有节点elem
使得func(elem,arg)成立*/
struct list_elem *list_traversal(struct list *plist, function func, int arg);
void list_iterate(struct list *plist);
|
以下是对链表操作的实现
/lib/kernel/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
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
#include "list.h"
#include "interrupt.h"
/*初始化双向链表*/
void list_init(struct list *list)
{
list->head.prev = NULL;
list->head.next = &list->tail;
list->tail.prev = &list->head;
list->tail.next = NULL;
}
/*把链表元素elem插入在元素cur之前*/
void list_insert_before(struct list_elem *cur, struct list_elem *elem)
{
// 插入元素之前先关中断,确保元素插入过程中不会被中断影响
enum intr_status old_status = intr_disable();
elem->prev = cur->prev;
elem->next = cur;
cur->prev->next = elem;
cur->prev = elem;
// 恢复关中断之前的状态
intr_set_status(old_status);
}
/*在链表头部插入元素*/
void list_push(struct list *plist, struct list_elem *elem)
{
list_insert_before(plist->head.next, elem);
}
/*在链表尾部添加元素*/
void list_append(struct list *plist, struct list_elem *elem)
{
list_insert_before(&plist->tail, elem);
}
/*删除链表元素节点*/
void list_remove(struct list_elem *pelem)
{
enum intr_status old_status = intr_disable();
pelem->prev->next = pelem->next;
pelem->next->prev = pelem->prev;
intr_set_status(old_status);
}
/*弹出链表头部元素*/
struct list_elem *list_pop(struct list *plist)
{
struct list_elem *elem = plist->head.next;
list_remove(elem);
return elem;
}
/*在链表中查找元素obj_elem*/
bool elem_find(struct list *plist, struct list_elem *obj_elem)
{
struct list_elem *ptr = plist->head.next;
while (ptr != &plist->tail)
{
if (ptr == obj_elem)
return true;
ptr = ptr->next;
}
return false;
}
/*判断链表是否为空*/
bool list_empty(struct list *plist)
{
return (plist->head.next == &plist->tail);
}
/*返回链表长度*/
uint32_t list_len(struct list *plist)
{
struct list_elem *ptr = plist->head.next;
uint32_t len = 0;
while (ptr != &plist->tail)
{
++len;
ptr = ptr->next;
}
return len;
}
/* 把链表plist中的每个元素elem和arg传给回调函数func,
* arg给func用来判断elem是否符合条件.
* 本函数的功能是遍历列表内所有节点elem
* 逐个判断链表中是否有节点elem使得func(elem,arg)成立。
* 找到符合条件的元素返回元素指针,否则返回NULL. */
struct list_elem *list_traversal(struct list *plist, function func, int arg)
{
struct list_elem *ptr = plist->head.next;
if (list_empty(plist))
return NULL;
while (ptr != &plist->tail)
{
if (func(ptr, arg))
return ptr;
ptr = ptr->next;
}
return NULL;
}
|
这部分内容相对简单,读者看代码即可明白
唯一需要主要的是**,由于队列是公共资源,对于它的修改一定要保证为原子操作**,所以在对队列的修改操作都需要通过intr disable将中断关闭,旧中断状态用变量 old status 保存,以此保证修改操作的原子性(不可拆分、连续性),操作结束后再通过“intr set status(old status)”将中断恢复。
多线程轮转调度
轮转调度前的数据准备
我们主要通过时钟中断进行线程调度,其主要过程如下:
- 每个线程在运行之前都被分配一个时间片,这个时间片其实就是PCB中的优先级
priority
- 假如
thread_work_a
这个线程被分配的时间片为31个时钟周期,那么每当线程thread_work_a
运行一个时钟周期(也就是没发生一次时钟中断)之后,时间片就减一
- 因此我们需要有一个变量来记录线程可运行的剩余时间,也就是PCB当中的
ticks
- 当ticks的值减为0的时候,就表示该线程的时间片用完了
- 此时调度函数便将该线程的状态从运行态修改为就绪态,然后将其插入到就绪态的末尾,并从就绪队列的头部弹出一个新的线程上cpu运行,然后继续上述过程,每运行一个时钟就减去一个时钟,直到可用的时钟用完继续切换
经过以上描述,我们需要为线程的PCB新增一些数据成员:
- 用以表示该线程可运行的剩余时间(时钟数):
ticks
- 用以表示线程从运行开始到当前时间一共运行的时间:
elapsed_ticks
- 用以将所有处于就绪状态的PCB连接起来,使其成为一个就绪队列的节点成员:
general_tag
- 用以将所有线程PCB连接起来(只要是线程就连接,不管其处于什么状态),使其成为一个记录所有线程的队列的节点成员:
all_list_tag
如下所示,是我们增添新成员后的PCB结构体
/thread/thread.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
|
/*PCB结构体*/
struct task_struct
{
// 线程栈的栈顶指针
uint32_t *self_kstack;
// 线程状态
enum task_status status;
// 线程的优先级
uint8_t priority;
// 线程函数名
char name[16];
// 线程在一个时间片上可运行的剩余时间(时钟数)
uint8_t ticks;
//记录线程从运行开始,已经运行的时间(时钟数)
uint32_t elapsed_ticks;
//用于连接就绪队列或者阻塞队列
struct list_elem general_tag;
//用于连接存储所有线程的队列
struct list_elem all_list_tag;
/*
进程自己页表的虚拟地址
由于线程共享进程的虚拟地址空间
因此线程PCB中此值为NULL
*/
uint32_t pgdir;
// 用于PCB结构体的边界标记
uint32_t stack_magic;
};
|
同时我们需要定义就绪队列和存储所有线程的队列,以及其他必要的数据结构
/thread/thread.c
1
2
3
4
5
6
7
8
|
// 主线程PCB
struct task_struct *main_thread;
// 线程就绪队列
struct list thread_ready_list;
// 存储所有任务的队列
struct list thread_all_list;
// 存储临时队列节点
static struct list_elem *thread_tag;
|
由于PCB结构体已经修改,因此需要修改PCB的初始化逻辑,线程栈运行信息的初始化不必修改
轮转调度前的初始化
修改线程PCB初始化
首先是PCB的初始化
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
|
/*初始化PCB*/
void init_thread(struct task_struct *pthread, char *name, int prio)
{
memset(pthread, 0, sizeof(*pthread));
strcpy(pthread->name, name);
// pthread->status = TASK_RUNNGING;
if (pthread == main_thread)
pthread->status = TASK_RUNNING;
else
pthread->status = TASK_READY;
pthread->priority = prio;
// 初始化线程在一个时间片上可运行的剩余时间
pthread->ticks = prio;
// 记录线程从运行开始,已经运行的时间
pthread->elapsed_ticks = 0;
/*
线程没有自己的地址空间
进程的pcb这一项才有用,指向自己的页表虚拟地址
*/
pthread->pgdir = (uint32_t)NULL;
/*
一个线程的栈空间分配一页空间,将PCB放置在栈底
pthread是申请的一页空间的起始地址,因此加上一页的大小,就是栈顶指针
*/
pthread->self_kstack = (uint32_t *)((uint32_t)pthread + PG_SIZE);
/*PCB的边界标记,防止栈顶指针覆盖掉PCB的内容*/
pthread->stack_magic = 0x20241221;
}
|
与之前PCB的初始化新增的部分是
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
if (pthread == main_thread)
pthread->status = TASK_RUNNING;
else
pthread->status = TASK_READY;
pthread->priority = prio;
// 初始化线程在一个时间片上可运行的剩余时间
pthread->ticks = prio;
// 记录线程从运行开始,已经运行的时间
pthread->elapsed_ticks = 0;
/*
线程没有自己的地址空间
进程的pcb这一项才有用,指向自己的页表虚拟地址
*/
pthread->pgdir = (uint32_t)NULL;
|
如前所述,priority
是记录线程一个运行时间片的信息变量,ticks
是记录线程剩余可运行的时钟数的信息变量,因此他们都被初始化为prio
,其中prio
是用户指定的,即线程的一个运行时间片时用户控制的;另外elapsed_ticks
是记录线程从运行开始到当前时刻运行的所有时间,因此应该初始化为0
需要另外说明的有两点
其一,main_thread
表示主线程,也就是内核main函数所代表的线程,由于之前我们在loader.S
中加载内核并运行的时候,内核中并没有PCB
,因此在这里我们需要也为其添加相关PCB信息,同时也因为main线程从一开始就是运行着的,因此应该对其PCB中的线程状态变量初始化为TASK_RUNNING
其二,PCB
中新增的pgdir变量,表示进程自己的页表虚拟地址,该信息是给将来的进程使用的,线程共享进程的虚拟地址空间,因此该值在这里应该为NULL
线程运行函数的逻辑修改——就绪队列的初始化
当PCB和线程栈的运行信息初始化后,就需要开始着手启动线程,但这是之前的逻辑
但在这里,我们需要统一调度逻辑,因此,我们需要将准备好的PCB插入就绪队列和全队列中,将来由调度器从就绪队列中选择线程然后上CPU运行
如下所示
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
|
/*根据线程栈的运行信息开始运行线程函数*/
struct task_struct *thread_start(char *name, int prio, thread_func function, void *func_args)
{
/*
1.分配一页的空间给线程作为线程执行的栈空间
*/
struct task_struct *thread = get_kernel_pages(1);
/*
2.初始化PCB,PCB里存放了线程的基本信息以及线程栈的栈顶指针
*/
init_thread(thread, name, prio);
/*
3.根据线程栈的栈顶指针,初始化线程栈,也就是初始化线程的运行信息
比如线程要执行的函数,以及函数参数
*/
thread_create(thread, function, func_args);
/*
4.将准备好PCB和运行信息的线程插入到就绪队列和存储所有任务的队列
*/
ASSERT(!elem_find(&thread_ready_list, &thread->general_tag));
list_append(&thread_ready_list, &thread->general_tag);
ASSERT(!elem_find(&thread_all_list, &thread->all_list_tag));
list_append(&thread_all_list, &thread->all_list_tag);
// /*5.上述准备好线程运行时的栈信息后,即可运行执行函数了*/
// asm volatile("movl %0,%%esp; \
// pop %%ebp; \
// pop %%ebx; \
// pop %%edi; \
// pop %%esi; \
// ret"
// :
// : "g"(thread->self_kstack)
// : "memory");
return thread;
}
|
以上我们对初始化逻辑进行一个总结
- 初始化PCB
- 初始化线程栈的运行信息
- 将初始化所有信息的线程PCB插入就绪队列和全队列,将来由调度统一选择调度
在正式实现轮转调度之前,因为main
线程的特殊性,我们还需要对main
线程进行初始化,如下所示
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
|
/*获取当前正在运行的线程的PCB*/
struct task_struct *running_thread(void)
{
uint32_t esp;
// 获取当前栈顶指针地址
asm("mov %%esp,%0" : "=g"(esp));
/*
由于栈顶指针是在一页内存空间内,而PCB存储在这一页空间的起始地址处
因此栈顶指针地址取整得到的这一页空间的起始地址就是PCB的起始地址
*/
return (struct task_struct *)(esp & 0xfffff000);
}
static void make_main_thread(void)
{
/* 因为main线程早已运行,咱们在loader.S中进入内核时的mov esp,0xc009f000,
就是为其预留了pcb,地址为0xc009e000,因此不需要通过get_kernel_page另分配一页*/
main_thread = running_thread();
init_thread(main_thread, "main", 31);
/* main函数是当前线程,当前线程不在thread_ready_list中,
* 所以只将其加在thread_all_list中. */
ASSERT(!elem_find(&thread_all_list, &main_thread->all_list_tag));
list_append(&thread_all_list, &main_thread->all_list_tag);
}
// 初始化主线程
void thread_init(void)
{
put_str("thread_init start\n");
list_init(&thread_ready_list);
list_init(&thread_all_list);
make_main_thread();
put_str("thread_init done\n");
}
|
其初始化逻辑与之前是相同的,也就是对PCB
和线程栈运行信息的初始化,但是由于main
线程已经在运行,因此不需要对线程栈的运行信息进行初始化,也不需要将其添加到就绪队列,只需添加到全队列即可
轮转调度
接下来我们开始实现轮转调度
首先是轮转调度的基本逻辑,如前所述
- 每个线程在运行之前都被分配一个时间片,然后线程每执行一个时钟周期(每发生一次时钟中断),时间片就减一,直到减为0的时候就开始进行轮转调度
- 而我们知道,时钟中断无时无刻都在发生,每发生一次时钟中断都要触发一次时钟中断的中断处理程序,因此我们只需要修改时钟中断的中断处理程序,让其每次被调用的时候就将当前正在运行的线程可用时间(ticks)减去一,直到减为0的时候就调用调度函数
如下所示
/device/timer.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
uint32_t ticks; // ticks是内核自中断开启以来总共的嘀嗒数
/* 时钟的中断处理函数 */
static void intr_timer_handler(void)
{
struct task_struct *cur_thread = running_thread();
// 检查栈是否溢出
ASSERT(cur_thread->stack_magic == 0x20241221);
// 记录此线程占用的cpu时间嘀
cur_thread->elapsed_ticks++;
// 从内核第一次处理时间中断后开始至今的滴哒数,内核态和用户态总共的嘀哒数
ticks++;
if (cur_thread->ticks == 0)
{ // 若进程时间片用完就开始调度新的进程上cpu
schedule();
}
else
{ // 将当前进程的时间片-1
cur_thread->ticks--;
}
}
|
当然,我们要把上述中断处理程序注册到中断描述符表IDT
中,如下所示,其中0x20
是时钟中断的中断向量号
1
2
3
4
5
6
7
|
void timer_init()
{
put_str("timer_init start\n");
/* 设置8253的定时周期,也就是发中断的周期 */
register_handler(0x20, intr_timer_handler);
put_str("timer_init done\n");
}
|
具体的注册实现register_handler
如下所示
/kernel/interrupt.c
1
2
3
4
5
6
|
/* 在中断处理程序数组第vector_no个元素中注册安装中断处理程序function */
void register_handler(uint8_t vector_no, intr_handler function) {
/* idt_table数组中的函数是在进入中断后根据中断向量号调用的,
* 见kernel/kernel.S的call [idt_table + %1*4] */
idt_table[vector_no] = function;
}
|
上述由时钟中断触发的中断处理程序,最终当发生轮转调度时的处理如下所示
- 把当前时间片已经用完的线程换到就绪队列尾
- 然后从就绪队列头取出一个新的线程换上执行新的时间片
/thread/thread.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
|
/*时间片轮转调度函数*/
void schedule(void)
{
ASSERT(intr_get_status() == INTR_OFF);
/*在关中断的情况下
把当前时间片已经用完的线程换到就绪队列尾
然后从就绪队列头取出一个新的线程换上执行新的时间片
*/
struct task_struct *cur = running_thread();
if (cur->status == TASK_RUNNING)
{
ASSERT(!elem_find(&thread_ready_list, &cur->general_tag));
list_append(&thread_ready_list, &cur->general_tag);
cur->ticks = cur->priority;
cur->status = TASK_READY;
}
else
{
/* 若此线程需要某事件发生后才能继续上cpu运行,
不需要将其加入队列,因为当前线程不在就绪队列中。*/
}
/* 将thread_ready_list队列中的第一个就绪线程弹出,准备将其调度上cpu. */
ASSERT(!list_empty(&thread_ready_list));
thread_tag = NULL; // thread_tag清空
thread_tag = list_pop(&thread_ready_list);
struct task_struct *next = elem2entry(struct task_struct, general_tag, thread_tag);
next->status = TASK_RUNNING;
switch_to(cur, next);
}
|
终于到了最后一步,将从就绪队列中拿到的新线程上cpu运行
- 将当前正在运行的栈顶指针(
esp
)和寄存器映像保存到旧PCB中
- 将新PCB中的栈顶指针取出赋值给
esp
准备执行
- 借用
ret
指令弹出新线程的可执行函数地址,赋值给指令寄存器eip
,正式运行新线程的执行函数
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
|
[bits 32]
section .text
global switch_to
switch_to:
; 栈中此处是返回地址
push esi
push edi
push ebx
push ebp
; 取出栈中的参数cur,cur = [esp+20]
; 即当前已经时间片运行结束的线程PCB
mov eax,[esp+20]
; 保存栈顶指针esp到当前时间片运行结束的线程PCB的self_kstack字段,
; self_kstack在task_struct中的偏移为0,
; 所以直接往thread开头处存4字节便可。
mov [eax],esp
; 取出栈中的参数next, next = [esp+24]
; 即要被换上CPU的线程PCB
mov eax,[esp+24]
;让esp指向新线程PCB中的运行栈的栈顶指针
mov esp,[eax]
pop ebp
pop ebx
pop edi
pop esi
; 返回到上面switch_to下面的那句注释的返回地址,
; 未由中断进入,第一次执行时会返回到kernel_thread
ret
|
编译运行
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
|
mkdir -p bin
#编译mbr
nasm -o $(pwd)/bin/mbr -I $(pwd)/boot/include/ $(pwd)/boot/mbr.S
dd if=$(pwd)/bin/mbr of=~/bochs/hd60M.img bs=512 count=1 conv=notrunc
#编译loader
nasm -o $(pwd)/bin/loader -I $(pwd)/boot/include/ $(pwd)/boot/loader.S
dd if=$(pwd)/bin/loader of=~/bochs/hd60M.img bs=512 count=4 seek=2 conv=notrunc
#编译print函数
nasm -f elf32 -o $(pwd)/bin/print.o $(pwd)/lib/kernel/print.S
# 编译kernel
nasm -f elf32 -o $(pwd)/bin/kernel.o $(pwd)/kernel/kernel.S
# 编译switch
nasm -f elf32 -o $(pwd)/bin/switch.o $(pwd)/thread/switch.S
#编译main文件
gcc-4.4 -o $(pwd)/bin/main.o -c -fno-builtin -m32 -I $(pwd)/lib/kernel/ -I $(pwd)/lib/ -I $(pwd)/kernel/ -I $(pwd)/device/ -I $(pwd)/thread/ $(pwd)/kernel/main.c
#编译interrupt文件
gcc-4.4 -o $(pwd)/bin/interrupt.o -c -fno-builtin -m32 -I $(pwd)/lib/kernel/ -I $(pwd)/lib/ -I $(pwd)/kernel/ $(pwd)/kernel/interrupt.c
#编译init文件
gcc-4.4 -o $(pwd)/bin/init.o -c -fno-builtin -m32 -I $(pwd)/lib/kernel/ -I $(pwd)/lib/ -I $(pwd)/kernel/ -I $(pwd)/thread/ -I $(pwd)/device/ $(pwd)/kernel/init.c
# 编译debug文件
gcc-4.4 -o $(pwd)/bin/debug.o -c -fno-builtin -m32 -I $(pwd)/lib/kernel/ -I $(pwd)/lib/ -I $(pwd)/kernel/ $(pwd)/kernel/debug.c
# 编译string文件
gcc-4.4 -o $(pwd)/bin/string.o -c -fno-builtin -m32 -I $(pwd)/lib/kernel/ -I $(pwd)/lib/ -I $(pwd)/kernel/ $(pwd)/lib/string.c
# 编译bitmap文件
gcc-4.4 -o $(pwd)/bin/bitmap.o -c -fno-builtin -m32 -I $(pwd)/lib/kernel/ -I $(pwd)/lib/ -I $(pwd)/kernel/ $(pwd)/lib/kernel/bitmap.c
# 编译memory文件
gcc-4.4 -o $(pwd)/bin/memory.o -c -fno-builtin -m32 -I $(pwd)/lib/kernel/ -I $(pwd)/lib/ -I $(pwd)/kernel/ $(pwd)/kernel/memory.c
# 编译thread文件
gcc-4.4 -o $(pwd)/bin/thread.o -c -fno-builtin -m32 -I $(pwd)/lib/kernel/ -I $(pwd)/lib/ -I $(pwd)/kernel/ -I $(pwd)/thread/ $(pwd)/thread/thread.c
# 编译list文件
gcc-4.4 -o $(pwd)/bin/list.o -c -fno-builtin -m32 -I $(pwd)/lib/kernel/ -I $(pwd)/lib/ -I $(pwd)/kernel/ $(pwd)/lib/kernel/list.c
# 编译timer文件
gcc-4.4 -o $(pwd)/bin/timer.o -c -fno-builtin -m32 -I $(pwd)/lib/kernel/ -I $(pwd)/lib/ -I $(pwd)/kernel/ -I $(pwd)/thread/ $(pwd)/device/timer.c
#将main函数与print函数进行链接
ld -m elf_i386 -Ttext 0xc0001500 -e main -o $(pwd)/bin/kernel.bin $(pwd)/bin/main.o $(pwd)/bin/thread.o $(pwd)/bin/switch.o $(pwd)/bin/list.o $(pwd)/bin/print.o $(pwd)/bin/init.o $(pwd)/bin/timer.o $(pwd)/bin/interrupt.o $(pwd)/bin/kernel.o $(pwd)/bin/memory.o $(pwd)/bin/bitmap.o $(pwd)/bin/string.o $(pwd)/bin/debug.o
#将内核文件写入磁盘,loader程序会将其加载到内存运行
dd if=$(pwd)/bin/kernel.bin of=~/bochs/hd60M.img bs=512 count=200 conv=notrunc seek=9
#rm -rf bin/*
|
运行结果