9.1 实现内核线程
计算机要完成的所谓"工作",就是由处理器执行某段程序代码来完成的,这段代码就被称为进程。
一个进程也要尽可能的"同时"做一些子工作,完成这些工作就是线程。
9.1.1 执行流
在处理器不变的情况下,多任务是采用了一种多道程序设计的方式,使处理器在多个任务间切换,实现了"伪并行"。
调度器在内核中维护一任务表,也叫进程表、线程表或调度表,然后按照一定的算法,从任务表中选择一个任务,让后把该任务放到处理器上运行。任务片到期后从任务表中切换任务。
在任务调度器眼里,只有执行流才是调度单元。
9.1.2 线程到底是什么
线程调用的函数和普通的调用函数有什么区别?
调度器维护的线程表或进程表,他有自己独立的一套寄存器映像,有自己的栈,有独立的上下文环境。
我们要做的就是:给任何想单独上处理器的代码块准备好它所依赖的上下文环境,从而使其具备独立性,使之成为执行流,即调度单元。
9.1.3 进程和线程的关系、区别简述
程序是指静态的、存储在文件系统上、尚未运行的指令代码,它是实际运行时程序的映像。
进程是指正在运行的程序,即进行中的程序,程序必须在获得运行所需要的各类资源后才能成为进程,资源包括进程所使用的栈,使用的寄存器等。
对于处理器来说,进程是一种控制流集合,集合中至少包含一条执行流,执行流之间是相互独立的,但它们共享进程的所有资源,它们是处理器的执行单位,或者称为调度单位,它们就是线程。
进程和线程都是执行流,为什么名字不一样。
用表达式表示:进程 = 线程 + 资源
各个进程都有自己的虚拟地址,进程间的安全是由操作系统的分页机制来保证的,只要操作系统不要把相同的物理页分配给多个进程就好。
只要线程具备能动性,它才是处理器的执行单元,调度器眼里的调度单元。进程只是资源整合体
总结一下:
线程是什么?具有能动性、执行力、独立的代码块。
进程是什么?进程=线程+资源。根据进程内线程的数量,进程可分为。
(1)单线程进程:如果厨房中只有一个工作人员,即配菜、炒菜、洗涮厨具等这几样工作都是一个人
做,那么厨房的工作效率必然会很低,因为此时厨房中就一个线程。
(2)多线程进程:厨房人手多了,工作才更高效,因此为配菜、炒菜、洗涮厨具专门配备了 3 个工作人员,也就是说进程内的线程多了。
执行流、调度单位、运行实体等概念都是针对线程而言的,线程才是解决问题的思路、步骤,它是具有能动性的指令,因此只有它才能上处理器运行,即一切执行流其实都是线程,因为任何时候进程中都至少存在一个线程。进程独自拥有整个地址空间,在这个空间中装有线程运行所需的资源,所以地址空间相当于资源容器,就像鱼缸为鱼提供了水。因此,进程与线程的关系是进程是资源容器,线程是资源使用者。进程与线程的区别是线程没有自己独享的资源,因此没有自己的地址空间,它要依附在进程的地址空间中从而借助进程的资源运行。说白了就是线程没有自己的页表,而进程有。
书中提到的进程是单线程进程
9.1.4 进程、线程的状态
操作系统把进程“执行过程”中所经历的不同阶段按状态归为几类,注意,强调的是“执行过程”,意为进程的状态描述的是进程中有关“动作”的执行流部分,即线程,而不包括静止的资源部分。把上述需要等待外界条件的状态称为“阻塞态”,把外界条件成立时,进程可以随时准备运行的状态称为“就绪态”,把正在处理器上运行的进程的状态称为“运行态”。
这里所说的进程状态其实就是指单线程进程中线程的状态,归根结底,状态是描述线程的。
9.1.5 进程的身份证——PCB
(1)要加载一个任务上处理器运行,任务由哪来?也就是说,调度器从哪里才能找到该任务?
答:是要单独维护一个进程表,将所有的 PCB结构加载到此表中,由调度器直接在进程表中找相应进程的 PCB,从而获取到对应进程的信息,将其寄存器映像加载到处理器后,新进程就开始运行了。
(2)即使找到了任务,任务要在系统中运行,其所需要的资源从哪里获得?
答:从 PCB 中把寄存器映像加载到寄存器中就行了
(3)即使任务已经变成进程运行了,此进程应该运行多久呢?总不能让其独占处理器吧。
答:“时间片”解决上面第 3 个问题,当时间片的值为 0 时,表示该进程此次的运行时
间到期了,该下 CPU 啦。
(4)即使知道何时将其换下处理器,那当前进程所使用的这一套资源(寄存器内容)应该存在哪里?
答:“寄存器映像”是用来解决上面第 4 个问题的,即保存进程的“现场”,进程在处理器上运行时,所有寄存器的值都将保存到此处。
(5)进程被换下的原因是什么?下次调度器还能把它换上处理器运行吗?
答:PCB 中包含“进程状态”,,比如进程状态为阻塞态,下次就不能把
它调度到处理器上了
(6)前面都说过了,进程独享地址空间,它的地址空间在哪里
答:“页表”解决了上面第 6 个问题,它代表进程的地址空间。
操作系统为每个进程提供了一个 PCB,Process Control Block,即程序控制块,它就是进程的身份证,用它来记录与此进程相关的信息,比如进程状态、PID、优先级等。
每个进程都有自己的 PCB,所有 PCB 放到一张表格中维护,这就是进程表,调度器可以根据这张表选择上处理器运行的进程,如图 9-7 所示。PCB 就成了进程表中的“项”,因此,PCB 又可称为进程表项。
鉴于“寄存器映像”的位置并不固定,我们在 PCB 中还要维护一个“栈指针”成员,它记录 0 级栈栈顶的位置,借此找到进程或线程的“寄存器映像”。
9.1.6 实现线程的两种方式——内核或用户进程
在用户空间实现线程
优点:
- 调度可以根据实现应用情况为某些线程加权调度。
- 将线程的寄存器装载到 CPU时候,可以在用户空间完成,不用陷入内核态。
缺点:
- 操作系统不知道叫进程中的线程多少,会把整个进程挂起。
- 进程中的线程一次只能调度单个。
在内核空间中实现线程
优点:
线程由内核来实现,进程才真正得到了较大幅度的提速
- 相比在用户空间中实现线程,内核提供的线程相当于让进程多占了处理器资源,比如系统中运行有进程 A 和一传统型进程 B,此时进程 A 中显式创建了 3 个线程,这样一来,进程 A 加上主线程便有了 4 个线程,加上进程 B,内核调度器眼中便有了 5 个独立的执行流,尽管其中 4 个都属于进程 A,但对调度器来说这 4个线程和进程一样被调度,因此调度器调度完一圈后,进程 A 使用了 80%的处理器资源,这才是真正的提速。
- 另一方面的优点是当进程中的某一线程阻塞后,由于线程是由内核空间实现的,操作系统认识线程,所以就只会阻塞这一个线程,此线程所在进程内的其他线程将不受影响,这又相当于提速了。
缺点:
- 用户进程需要通过系统调用陷入内核,这多少增加了一些现场保护的栈操作,这还是会消耗一些处理器时间。
9.2 在内核空间实现线程
9.2.1 简单的 PCB 及线程栈的实现
kernel_thread 函数并不是通过调用 call 指令的形式执行的,而是咱们用汇编指令 ret“返回”执行的,也就是函数 kernel_thread 作为“某个函数”(此函数暂时为 thread_start)的返回地址。
用 struct intr_stack 定义了程序的中断栈,无论是进程,还是线程,此结构用于中断发生时保护程序的上下文环境。也就是说,进入中断后,在 kernel.S 中的中断入口程序“intr%1entry”所执行的上下文保护的一系列压栈操作都是压入了此结构中。因此,进程或线程被外部中断或软中断打断时,中断入口程序会按照此结构压入上下文寄存器,所以,kernel.S 中 intr_exit 中的出栈操作便是此结构的逆操作。
初始情况下此栈在线程自己的内核栈中位置固定,在 PCB 所在页的最顶端,每次进入中断时就不一定了,如果进入中断时不涉及到特权级变化,它的位置就会在当前的 esp 之下,否则处理器会从 TSS 中获得新的esp 的值,然后该栈在新的 esp 之下,这是后话,有关 TSS 这方面的内容以后会介绍。
thread_stack 定义了线程栈,此栈有 2 个作用,主要就是体现在第 5 个成员 eip 上。
(1)大家都知道,线程是使函数单独上处理器运行的机制,因此线程肯定得知道要运行哪个函数,首
次执行某个函数时,这个栈就用来保存待运行的函数,其中 eip 便是该函数的地址。
(2)将来咱们是用 switch_to 函数实现任务切换,当任务切换时,此 eip 用于保存任务切换后的新任务
的返回地址
ABI (Application Binary Interface 即应用程序二进制接口) 。这 5 个寄存器 ebp、ebx、edi、esi、和 esp 归主调函数所用,其余的寄存器归被调
函数所用。换句话说,不管被调函数中是否使用了这 5 个寄存器,在被调函数执行完后,这 5 个寄存器的值不
该被改变。因此被调函数必须为主调函数保护好这 5 个寄存器的值,在被调函数运行完之后,这 5 个寄存器的
值必须和运行前一样,它必须在自己的栈中存储这些寄存器的值。
self_kstack 是各线程的内核栈顶指针,当线程被创建时,self_kstack 被初始化为自己 PCB 所在页的顶端。之后在运行时,在被换下处理器前,我们会把线程的上下文信息(也就是寄存器映像)保存在 0 特权级栈中self_kstack 便用来记录 0 特权级栈在保存线程上下文后的新的栈顶,在下一次此线程又被调度到处理器上时,可以把 self_kstack 的值加载到 esp 寄存器,这样便从 0 特权级栈中获取了线程上下文,从而可以加载到处理器中运行。
9.2.2 线程的实现
thread / thread.h
#ifndef __THREAD_THREAD_H
#define __THREAD_THREAD_H
#include "stdint.h"
/* 自定义通用函数类型,它将在很多线程函数中作为形参类型 */
typedef void thread_func(void*);
/* 进程或线程的状态 */
enum task_status{
TASK_RUNNING,
TASK_READY,
TASK_BLOCKED,
TASK_WAITING,
TASK_HANGING,
TASK_DIED
};
/*中断栈 intr_stack
* 此结构用于中断发生时保护程序(线程或进程)的上下文环境:
* 进程或线程被外部中断或软中断打断时,会按照此结构压入上下文
* 寄存器,intr_exit 中的出栈操作是此结构的逆操作
* 此栈在线程自己的内核栈中位置固定,所在页的最顶端*/
struct intr_stack{
uint32_t vec_no; // kernel.S 宏 VECTOR 中 push %1 压入的中断号
uint32_t edi;
uint32_t esi;
uint32_t ebp;
uint32_t esp_dummy;
// 虽然 pushad 把 esp 也压入,但 esp 是不断变化的,所以会被 popad 忽略
uint32_t ebx;
uint32_t edx;
uint32_t ecx;
uint32_t eax;
uint32_t gs;
uint32_t fs;
uint32_t es;
uint32_t ds;
/* 以下由 cpu 从低特权级进入高特权级时压入 */
uint32_t err_code;// err_code 会被压入在 eip 之后
void (*eip)(void);
uint32_t cs;
uint32_t eflags;
void *esp;
uint32_t ss;
};
/*********** 线程栈 thread_stack ***********
* 线程自己的栈,用于存储线程中待执行的函数
* 此结构在线程自己的内核栈中位置不固定,
* 仅用在 switch_to 时保存线程环境。
* 实际位置取决于实际运行情况。
*/
struct thread_stack{
uint32_t ebp;
uint32_t ebx;
uint32_t edi;
uint32_t esi;
/* 线程第一次执行时,eip 指向待调用的函数 kernel_thread
其他时候,eip 是指向 switch_to 的返回地址*/
void (*eip)(thread_func *func, void *func_arg);
/***** 以下仅供第一次被调度上 cpu 时使用 ****/
/* 参数 unused_ret 只为占位置充数为返回地址 */
void (*unused_retaddr);
thread_func *function;// 由 kernel_thread 所调用的函数名
void* func_arg; // 由 kernel_thread 所调用的函数所需的参数
};
/*进程或线程的pcb,进程控制块*/
struct task_struct{
uint32_t *self_kstack; // 各内核线程都用自己的内核栈
enum task_status status;
uint8_t priority;// 线程优先级
char name[16];
uint32_t stack_magic;//栈的边界标记,用于检测栈的溢出
};
/*函数声明*/
#endif
thread / thread.c
#include "thread.h"
#include "stdint.h"
#include "string.h"
#include "global.h"
#include "memory.h"
#include "debug.h"
#define PG_SIZE 4096
/* 由 kernel_thread 去执行 function(func_arg) */
static void kernel_thread(thread_func *function, void *func_arg){
function(func_arg);
}
/* 初始化线程栈 thread_stack,
将待执行的函数和参数放到 thread_stack 中相应的位置 */
void thread_create(struct task_struct *pthread, thread_func function, void *func_arg){
/*先预留中断使用栈的空间,可见 thread.h 中定义的结构 */
pthread->self_kstack -= sizeof(struct intr_stack);
/*在留出线程栈空间,可见 thread.h 中定义 */
pthread->self_kstack -= sizeof(struct thread_stack);
struct thread_stack *kthread_stack = (struct thread_stack *)pthread->self_kstack;
kthread_stack->eip = kernel_thread;
kthread_stack->function = function;
kthread_stack->func_arg = func_arg;
kthread_stack->ebp = kthread_stack->ebx = \
kthread_stack->esi = kthread_stack->edi = 0;
kthread_stack->eip = kernel_thread;
}
/* 初始化线程基本信息 */
void init_thread(struct task_struct *pthread, char *name, int prio)
{
memset(pthread, 0, sizeof(*pthread));
strcpy(pthread->name, name);
pthread->status = TASK_RUNNING;
pthread->priority = prio;
/* self_kstack 是线程自己在内核态下使用的栈顶地址 */
pthread->self_kstack = (uint32_t *)((uint32_t)pthread + PG_SIZE);
pthread->stack_magic = 0x19870916; //自定义的魔术.
}
/* 创建一优先级为 prio 的线程,线程名为 name,
线程所执行的函数是 function(func_arg) */
struct task_struct *thread_start(char *name, int prio, thread_func function, void *func_arg){
/* pcb 都位于内核空间,包括用户进程的 pcb 也是在内核空间 */
struct task_struct *thread = get_kernel_pages(1);
ASSERT(thread != 0);
init_thread(thread, name, prio);
thread_create(thread, function, func_arg);
asm volatile(" movl %0, %%esp;\
pop %%ebp;\
pop %%ebx;\
pop %%edi;\
pop %%esi;\
ret"\
::"g"(thread->self_kstack):"memory");
return thread;
}
kernel / main.c
#include "print.h"
#include "init.h"
#include "debug.h"
#include "thread.h"
/*在线程中运行的函数*/
void k_thread_a(void *arg);
int main(void){
put_str("I am kernel\n");
init_all();
//ASSERT(1==2);
//asm volatile("sti");
// void *addr = get_kernel_pages(3);
// put_str("\n get_kernel_page start vaddr is ");
// put_int((uint32_t) addr);
// put_str("\n");
thread_start("k_thread_a", 31, k_thread_a, "argA ");
while(1);
return 0;
}
/*在线程中运行的函数*/
void k_thread_a(void *arg)
{
char *para = arg;
int32_t n = 10;
while(n--){
put_str(para);
}
while(1);
}
Makefile
loader.bin:loader.asm inc/boot.inc
nasm -I inc/ -o loader.bin loader.asm
mbr.bin:mbr.asm inc/boot.inc
nasm -I inc/ -o mbr.bin mbr.asm
kernel.bin:kernel/main.c lib/kernel/print.asm kernel/kernel.asm kernel/interrupt.c kernel/init.c device/timer.c
clang -I lib/kernel/ -I lib/ -I kernel/ -I thread/ -m32 -s -w -c -fno-builtin -o build/main.o kernel/main.c
nasm -f elf -o build/print.o lib/kernel/print.asm
nasm -f elf -o build/kernel.o kernel/kernel.asm
clang -I lib/kernel/ -I lib/ -I kernel/ -m32 -s -w -c -fno-builtin -o build/interrupt.o kernel/interrupt.c
clang -I lib/kernel/ -I lib/ -I kernel/ -m32 -s -w -c -fno-builtin -o build/init.o kernel/init.c
clang -I lib/kernel/ -I lib/ -I kernel/ -m32 -s -w -c -fno-builtin -o build/timer.o device/timer.c
clang -I lib/kernel/ -I lib/ -I kernel/ -m32 -s -w -c -fno-builtin -o build/debug.o kernel/debug.c
clang -I lib/kernel/ -I lib/ -I kernel/ -m32 -s -w -c -fno-builtin -o build/memory.o kernel/memory.c
clang -I lib/kernel/ -I lib/ -I kernel/ -m32 -s -w -c -fno-builtin -o build/string.o lib/string.c
clang -I lib/kernel/ -I lib/ -I kernel/ -m32 -s -w -c -fno-builtin -o build/bitmap.o lib/kernel/bitmap.c
clang -I lib/kernel/ -I lib/ -I kernel/ -m32 -s -w -c -fno-builtin -o build/thread.o thread/thread.c
ld -m elf_i386 -Ttext 0xc0001500 -e main -o build/kernel.bin build/main.o \
build/init.o build/interrupt.o build/print.o \
build/debug.o build/kernel.o build/timer.o \
build/string.o build/memory.o build/bitmap.o \
build/thread.o
dd: dd_mbr dd_loader dd_kernel
dd_mbr:
dd if=mbr.bin of=hd60M.img bs=512 count=1 conv=notrunc
dd_loader:
dd if=loader.bin of=hd60M.img bs=512 count=4 seek=2 conv=notrunc
dd_kernel:
dd if=build/kernel.bin of=hd60M.img bs=512 count=200 seek=9 conv=notrunc
运行结果:
9.3 核心数据结构,双向链表
在咱们的内核中也要用到队列,比如进程的就绪队列、锁的等待队列等,为了维护内核中的各种队列,咱们本节要实现自己的链表—双向链表
lib / kernel / list.h
#ifndef __LIB_KERNEL_LIST_H
#define __LIB_KERNEL_LIST_H
#include "global.h"
#define offset(struct_type, member) (int)(&((struct_type *)0)->member)
#define elem2entry(struct_type, struct_member_name, elem_ptr) \
(struct_type *)((int)elem_ptr - offset(struct_type, struct_member_name))
/********** 定义链表结点成员结构 ***********
*结点中不需要数据成元,只要求前驱和后继结点指针*/
struct list_elem
{
struct list_elem* prev; // 前躯结点
struct list_elem* next; // 后继结点
};
/* 链表结构,用来实现队列 */
struct list{
/* head 是队首,是固定不变的,不是第 1 个元素,第 1 个元素为 head.next */
struct list_elem head;
/* tail 是队尾,同样是固定不变的 */
struct list_elem tail;
};
/* 自定义函数类型 function,用于在 list_traversal 中做回调函数 */
typedef bool (function)(struct list_elem*, int arg);
void list_init (struct list*);
void list_insert_before(struct list_elem* before, struct list_elem* elem);
void list_push(struct list* plist, struct list_elem* elem);
void list_iterate(struct list* plist);
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);
struct list_elem* list_traversal(struct list* plist, function func, int arg);
bool elem_find(struct list* plist, struct list_elem* obj_elem);
#endif
lib / kernel / list.c
#include "list.h"
#include "interrupt.h"
/* 初始化双向链表 list */
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 插入在元素 before 之前 */
void list_insert_before(struct list_elem* before, struct list_elem* elem) {
enum intr_status old_status = intr_disable();
/* 将 before 前驱元素的后继元素更新为 elem,暂时使 before 脱离链表*/
before->prev->next = elem;
/* 更新 elem 自己的前驱结点为 before 的前驱,
* 更新 elem 自己的后继结点为 before,于是 before 又回到链表 */
elem->prev = before->prev;
elem->next = before;
/*更新 before 的前驱结点为 elem*/
before->prev = elem;
intr_set_status(old_status);
}
/* 添加元素到列表队首,类似栈 push 操作 */
void list_push(struct list* plist, struct list_elem* elem) {
list_insert_before(plist->head.next, elem);//在队头插入 elem
}
/* 追加元素到链表队尾,类似队列的先进先出操作 */
void list_append(struct list* plist, struct list_elem* elem) {
list_insert_before(&plist->tail, elem); // 在队尾的前面插入
}
/* 使元素 pelem 脱离链表 */
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);
}
/* 将链表第一个元素弹出并返回,类似栈的 pop 操作 */
struct list_elem* list_pop(struct list* plist) {
struct list_elem* elem = plist->head.next;
list_remove(elem);
return elem;
}
/* 从链表中查找元素 obj_elem,成功时返回 true,失败时返回 false */
bool elem_find(struct list* plist, struct list_elem* obj_elem) {
struct list_elem* elem = plist->head.next;
while (elem != &plist->tail) {
if (elem == obj_elem) {
return true;
}
elem = elem->next;
}
return false;
}
/* 把列表 plist 中的每个元素 elem 和 arg 传给回调函数 func,
* arg 给 func 用来判断 elem 是否符合条件.
* 本函数的功能是遍历列表内所有元素,逐个判断是否有符合条件的元素。
* 找到符合条件的元素返回元素指针,否则返回 NULL */
struct list_elem* list_traversal(struct list* plist, function func, int arg) {
struct list_elem* elem = plist->head.next;
/* 如果队列为空,就必然没有符合条件的结点,故直接返回 NULL */
if(list_empty(plist)){
return NULL;
}
while(elem != &plist->tail){
if(func(elem, arg)){
// func 返回 ture,则认为该元素在回调函数中符合条件,命中,故停止继续遍历
return elem;
}
// 若回调函数 func 返回 true,则继续遍历
elem = elem->next;
}
return NULL;
}
/*返回链表长度*/
uint32_t list_len(struct list* plist){
struct list_elem *elem = plist->head.next;
uint32_t length = 0;
while(elem != &plist->tail){
++length;
elem = elem->next;
}
return length;
}
/* 判断链表是否为空,空时返回 true,否则返回 false */
bool list_empty(struct list* plist) { // 判断队列是否为空
return ( (plist->head.next == &plist->tail) ? true : false );
}
9.4 多线程调度
9.4.1 简单优先级调度的基础
补充 thread.h 和 thread.c
thread / threa.h
#ifndef __THREAD_THREAD_H
#define __THREAD_THREAD_H
#include "stdint.h"
#include "list.h"
/* 自定义通用函数类型,它将在很多线程函数中作为形参类型 */
typedef void thread_func(void*);
/* 进程或线程的状态 */
enum task_status{
TASK_RUNNING,
TASK_READY,
TASK_BLOCKED,
TASK_WAITING,
TASK_HANGING,
TASK_DIED
};
/*中断栈 intr_stack
* 此结构用于中断发生时保护程序(线程或进程)的上下文环境:
* 进程或线程被外部中断或软中断打断时,会按照此结构压入上下文
* 寄存器,intr_exit 中的出栈操作是此结构的逆操作
* 此栈在线程自己的内核栈中位置固定,所在页的最顶端*/
struct intr_stack{
uint32_t vec_no; // kernel.S 宏 VECTOR 中 push %1 压入的中断号
uint32_t edi;
uint32_t esi;
uint32_t ebp;
uint32_t esp_dummy;
// 虽然 pushad 把 esp 也压入,但 esp 是不断变化的,所以会被 popad 忽略
uint32_t ebx;
uint32_t edx;
uint32_t ecx;
uint32_t eax;
uint32_t gs;
uint32_t fs;
uint32_t es;
uint32_t ds;
/* 以下由 cpu 从低特权级进入高特权级时压入 */
uint32_t err_code;// err_code 会被压入在 eip 之后
void (*eip)(void);
uint32_t cs;
uint32_t eflags;
void *esp;
uint32_t ss;
};
/*********** 线程栈 thread_stack ***********
* 线程自己的栈,用于存储线程中待执行的函数
* 此结构在线程自己的内核栈中位置不固定,
* 仅用在 switch_to 时保存线程环境。
* 实际位置取决于实际运行情况。
*/
struct thread_stack{
uint32_t ebp;
uint32_t ebx;
uint32_t edi;
uint32_t esi;
/* 线程第一次执行时,eip 指向待调用的函数 kernel_thread
其他时候,eip 是指向 switch_to 的返回地址*/
void (*eip)(thread_func *func, void *func_arg);
/***** 以下仅供第一次被调度上 cpu 时使用 ****/
/* 参数 unused_ret 只为占位置充数为返回地址 */
void (*unused_retaddr);
thread_func *function;// 由 kernel_thread 所调用的函数名
void* func_arg; // 由 kernel_thread 所调用的函数所需的参数
};
/*进程或线程的pcb,进程控制块*/
struct task_struct{
uint32_t *self_kstack; // 各内核线程都用自己的内核栈
enum task_status status;
char name[16];
uint8_t priority;// 线程优先级
uint8_t ticks; // 每次在处理器上执行的时间嘀嗒数
/* 此任务自上 cpu 运行后至今占用了多少 cpu 嘀嗒数,
也就是此任务执行了多久*/
uint32_t elapsed_ticks;
/* general_tag 的作用是用于线程在一般的队列中的结点 */
struct list_elem general_tag;
/* all_list_tag 的作用是用于线程队列 thread_all_list 中的结点 */
struct list_elem all_list_tag;
uint32_t* pgdir; // 进程自己页表的虚拟地址
uint32_t stack_magic;//栈的边界标记,用于检测栈的溢出
};
/*函数声明*/
#endif
thread / thread.c
#include "thread.h"
#include "stdint.h"
#include "string.h"
#include "global.h"
#include "memory.h"
#include "debug.h"
#define PG_SIZE 4096
struct task_struct* main_thread; // 主线程 PCB
struct list thread_ready_list; // 就绪队列
struct list thread_all_list; // 所有任务队列
static struct list_elem* thread_tag;// 用于保存队列中的线程结点
extern void switch_to(struct task_struct* cur, struct task_struct* next);
/* 获取当前线程 pcb 指针 */
struct task_struct *running_thread(){
uint32_t esp;
asm ("mov %%esp, %0" : "=g" (esp));
/* 取 esp 整数部分,即 pcb 起始地址 */
return (struct task_struct*)(esp & 0xfffff000);
}
/* 由 kernel_thread 去执行 function(func_arg) */
static void kernel_thread(thread_func *function, void *func_arg){
/* 执行 function 前要开中断,避免后面的时钟中断被屏蔽,而无法调度其他线程 */
intr_enable();
function(func_arg);
}
/* 初始化线程栈 thread_stack,
将待执行的函数和参数放到 thread_stack 中相应的位置 */
void thread_create(struct task_struct *pthread, thread_func function, void *func_arg){
/*先预留中断使用栈的空间,可见 thread.h 中定义的结构 */
pthread->self_kstack -= sizeof(struct intr_stack);
/*在留出线程栈空间,可见 thread.h 中定义 */
pthread->self_kstack -= sizeof(struct thread_stack);
struct thread_stack *kthread_stack = (struct thread_stack *)pthread->self_kstack;
kthread_stack->eip = kernel_thread;
kthread_stack->function = function;
kthread_stack->func_arg = func_arg;
kthread_stack->ebp = kthread_stack->ebx = \
kthread_stack->esi = kthread_stack->edi = 0;
}
/* 初始化线程基本信息 */
void init_thread(struct task_struct *pthread, char *name, int prio)
{
memset(pthread, 0, sizeof(*pthread));
strcpy(pthread->name, name);
if(pthread == main_thread){
/* 由于把 main 函数也封装成一个线程,
并且它一直是运行的,故将其直接设为 TASK_RUNNING */
pthread->status = TASK_RUNNING;
}else{
pthread->status = TASK_READY;
}
/* self_kstack 是线程自己在内核态下使用的栈顶地址 */
pthread->self_kstack = (uint32_t *)((uint32_t)pthread + PG_SIZE);
pthread->priority = prio;
pthread->ticks = prio;
pthread->elapsed_ticks = 0;
pthread->pgdir = NULL;
pthread->stack_magic = 0x19870916; //自定义的魔数.
}
/* 创建一优先级为 prio 的线程,线程名为 name,
线程所执行的函数是 function(func_arg) */
struct task_struct *thread_start(char *name, int prio, thread_func function, void *func_arg){
/* pcb 都位于内核空间,包括用户进程的 pcb 也是在内核空间 */
struct task_struct *thread = get_kernel_pages(1);
ASSERT(thread != 0);
init_thread(thread, name, prio);
thread_create(thread, function, func_arg);
/* 确保之前不在队列中 */
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);
return thread;
}
/* 将 kernel 中的 main 函数完善为主线程 */
static void make_main_thread(void) {
/* 因为 main 线程早已运行, 咱们在 loader.S 中进入内核时的 mov esp,0xc009f000,
* 就是为其预留 pcb 的,因此 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);
}
9.4.2 任务调度器和任务切换
kernel / interrupt.c
- 添加了一般中断函数
- 注册安装中断处理函数 register_handler
#include "io.h"
#include "interrupt.h"
#include "stdint.h"
#include "global.h"
#include "print.h"
#include "list.h"
#include "thread.h"
#include "debug.h"
#define IDT_DESC_CNT 0x21 //支持的中断数目
#define PIC_M_CTRL 0x20 // 主片的控制端口是 0x20
#define PIC_M_DATA 0x21 // 主片的数据端口是 0x21
#define PIC_S_CTRL 0xa0 // 从片的控制端口是 0xa0
#define PIC_S_DATA 0xa1 // 从片的数据端口是 0xa1
#define EFLAGS_IF 0x00000200 //eflags 寄存器if位为1
#define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0":"=g"(EFLAG_VAR));
// 中断门描述符结构体
struct gate_desc{
uint16_t func_offset_low_word;
uint16_t selector;
uint8_t dcount;
uint8_t attribute;
uint16_t func_offset_high_word;
};
//静态函数声明 非必须
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function);
static struct gate_desc idt[IDT_DESC_CNT]; // idt 是中断描述符表
// 声明引用定义在 kernel.S 中的中断处理函数入口数组
extern intr_handler intr_entry_table[IDT_DESC_CNT];
char *intr_name[IDT_DESC_CNT]; //保存异常的名字
/*定义中断处理程序数组,在kernel.asm中定义的intrXXentry
只是中断处理程序的入口,最终调用的是 ide_table 中的处理程序*/
intr_handler idt_table[IDT_DESC_CNT];
/*初始化可编程中断处理器 8259A*/
static void pic_init(void) {
/*初始化主片 */
outb (PIC_M_CTRL, 0x11); // ICW1: 边沿触发,级联 8259, 需要 ICW4
outb (PIC_M_DATA, 0x20); // ICW2: 起始中断向量号为 0x20
// 也就是 IR[0-7] 为 0x20 ~ 0x27
outb (PIC_M_DATA, 0x04); // ICW3: IR2 接从片
outb (PIC_M_DATA, 0x01); // ICW4: 8086 模式, 正常 EOI
/*初始化从片 */
outb (PIC_S_CTRL, 0x11); // ICW1: 边沿触发,级联 8259, 需要 ICW4
outb (PIC_S_DATA, 0x28); // ICW2: 起始中断向量号为 0x28
// 也就是 IR[8-15]为 0x28 ~ 0x2F
outb (PIC_S_DATA, 0x02); // ICW3: 设置从片连接到主片的 IR2 引脚
outb (PIC_S_DATA, 0x01); // ICW4: 8086 模式, 正常 EOI
/*打开主片上 IR0,也就是目前只接受时钟产生的中断 */
outb (PIC_M_DATA, 0xfe); //1111 1110 fe
outb (PIC_S_DATA, 0xff);
put_str(" pic_init done\n");
}
/*创建中断门描述符*/
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function) {
p_gdesc->func_offset_low_word = (int32_t)function & 0x0000FFFF;
p_gdesc->selector = SELECTOR_K_CODE;
p_gdesc->dcount = 0;
p_gdesc->attribute = attr;
p_gdesc->func_offset_high_word = ((uint32_t)function & 0xFFFF0000) >> 16;
}
/*初始化中断描述符表*/
static void idt_desc_init(void){
int i;
for(i = 0; i < IDT_DESC_CNT; ++i){
make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]);
}
put_str(" idt_desc_init done\n");
}
/*通用的中断处理函数*/
static void general_intr_handler(uint8_t vec_nr){
if(vec_nr == 0x27 || vec_nr == 0x2f){
//IRQ7和IRQ5会产生伪中断,无需处理
//0x2f是从片 8259A 上的最后一个 IRQ 引脚,保留项
return;
}
/* 将光标置为 0,从屏幕左上角清出一片打印异常信息的区域,方便阅读 */
set_cursor(0);
int cursor_pos = 0;
while(cursor_pos < 320){
put_char(' ');
cursor_pos++;
}
set_cursor(0); // 重置光标为屏幕左上角
put_str("!!!!!!! excetion message begin !!!!!!!!\n");
set_cursor(88); // 从第 2 行第 8 个字符开始打印
put_str(intr_name[vec_nr]);
if (vec_nr == 14) { // 若为 Pagefault,将缺失的地址打印出来并悬停
int page_fault_vaddr = 0;
asm ("movl %%cr2, %0" : "=r" (page_fault_vaddr));
// cr2 是存放造成 page_fault 的地址
put_str("\npage fault addr is ");put_int(page_fault_vaddr);
}
put_str("\n!!!!!!! excetion message end !!!!!!!!\n");
// 能进入中断处理程序就表示已经处在关中断情况下
// 不会出现调度进程的情况。故下面的死循环不会再被中断
while(1);
}
/*完成一般中断处理函数注册及异常名称注册*/
static void exception_init(void){
int i;
for(i = 0; i < IDT_DESC_CNT; ++i){
/*idt_table中的函数是在进入中断后根据中断向量好调用的
见kernel/kernel.asm 的 call[idt_table + %1*4]*/
idt_table[i] = general_intr_handler;
//默认为general_intr_handler
//以后会有 register_handler注册具体的处理函数
intr_name[i] = "unknown"; //先统一为 "unknown"
}
intr_name[0] = "#DE Divide Error";
intr_name[1] = "#DB Debug Exception";
intr_name[2] = "NMI Interrupt";
intr_name[3] = "#BP Breakpoint Exception";
intr_name[4] = "#OF Overflow Exception";
intr_name[5] = "#BR BOUND Range Exceeded Exception";
intr_name[6] = "#UD Invalid Opcode Exception";
intr_name[7] = "#NM Device Not Available Exception";
intr_name[8] = "#DF Double Fault Exception";
intr_name[9] = "Coprocessor Segment Overrun";
intr_name[10] = "#TS Invalid TSS Exception";
intr_name[11] = "#NP Segment Not Present";
intr_name[12] = "#SS Stack Fault Exception";
intr_name[13] = "#GP General Protection Exception";
intr_name[14] = "#PF Page-Fault Exception";
// intr_name[15] 第 15 项是 intel 保留项,未使用
intr_name[16] = "#MF x87 FPU Floating-Point Error";
intr_name[17] = "#AC Alignment Check Exception";
intr_name[18] = "#MC Machine-Check Exception";
intr_name[19] = "#XF SIMD Floating-Point Exception";
intr_name[0x20] = "#CLOCK";
}
/*获取当前中断状态*/
enum intr_status intr_get_status(void){
uint32_t eflags = 0;
GET_EFLAGS(eflags);
return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
}
/*设置中断状态*/
enum intr_status intr_set_status(enum intr_status status){
return (status & INTR_ON) ? intr_enable():intr_disable();
}
/*开中断,返回之前的状态*/
enum intr_status intr_enable(){
enum intr_status old_status;
if(INTR_ON == intr_get_status()){
old_status = INTR_ON;
}else{
old_status = INTR_OFF;
asm volatile("sti");//关中断 cli 将IF置1
}
return old_status;
}
/*关闭中断*/
enum intr_status intr_disable(void){
enum intr_status old_status;
if(INTR_ON == intr_get_status()){
old_status = INTR_ON;
asm volatile("cli" : : : "memory");
}else{
old_status = INTR_OFF;
}
return old_status;
}
/* 在中断处理程序数组第 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;
}
/*完成有关中断的所有初始化工作*/
void idt_init(){
put_str("idt_init start\n");
idt_desc_init(); // 初始化中断描述符表
exception_init(); //异常名和中断处理函数初始化
pic_init(); //初始化 8259A
/*加载 idt*/
uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)((uint32_t)idt << 16)));
asm volatile("lidt %0"::"m"(idt_operand));
put_str("idt_init done\n");
}
device / timer.c
- 主要:添加了时钟中断处理函数 intr_timer_handler
#include "timer.h"
#include "io.h"
#include "print.h"
#include "thread.h"
#include "interrupt.h"
#include "thread.h"
#include "debug.h"
#include "list.h"
#define IRQ0_FREQUENCY 100
#define INPUT_FREQUENCY 1193180
#define COUNTER0_VALUE INPUT_FREQUENCY / IRQ0_FREQUENCY
#define CONTRER0_PORT 0x40
#define COUNTER0_NO 0
#define COUNTER_MODE 2
#define READ_WRITE_LATCH 3
#define PIT_CONTROL_PORT 0x43
uint32_t ticks; // ticks 是内核自中断开启以来总共的嘀嗒数
/* 把操作的计数器 counter_no、 读写锁属性 rwl、 计数器模式
counter_mode 写入模式控制寄存器并赋予初始值 counter_value */
static void frequency_set(uint8_t counter_port, \
uint8_t counter_no,\
uint8_t rwl,\
uint8_t counter_mode,\
uint16_t counter_value){
/* 往控制字寄存器端口 0x43 中写入控制字 */
outb(PIT_CONTROL_PORT,(uint8_t)(counter_no << 6 | rwl << 4 | counter_mode << 1));
/* 先写入 counter_value 的低 8 位 */
outb(counter_port, (uint8_t)counter_value);
/* 再写入 counter_value 的高 8 位 */
outb(counter_port, (uint8_t)counter_value >> 8);
}
/* 时钟的中断处理函数 */
static void intr_timer_handler(void){
struct task_struct* cur_thread = running_thread();
ASSERT(cur_thread->stack_magic == 0x19870916); // 检查栈是否溢出
cur_thread->elapsed_ticks++; // 记录此线程占用的 cpu 时间
ticks++; //从内核第一次处理时间中断后开始至今的滴哒数,内核态和用户态总共的嘀哒数
if(cur_thread->ticks == 0){// 若进程时间片用完,就开始调度新的进程上 cpu
schedule();
}else{
// 将当前进程的时间片-1
cur_thread->ticks--;
}
}
/* 初始化 PIT8253 */
void timer_init()
{
put_str("timer_init start\n");
/* 设置 8253 的定时周期,也就是发中断的周期 */
frequency_set( CONTRER0_PORT,\
COUNTER0_NO,\
READ_WRITE_LATCH,\
COUNTER_MODE,\
COUNTER0_VALUE);
register_handler(0x20, intr_timer_handler);
put_str("timer_init done\n");
}
thread / thread.c
- 实现任务调度
- 初始化线程环境
#include "thread.h"
#include "stdint.h"
#include "string.h"
#include "global.h"
#include "memory.h"
#include "debug.h"
#include "interrupt.h"
#include "list.h"
#define PG_SIZE 4096
struct task_struct* main_thread; // 主线程 PCB
struct list thread_ready_list; // 就绪队列
struct list thread_all_list; // 所有任务队列
static struct list_elem* thread_tag;// 用于保存队列中的线程结点
extern void switch_to(struct task_struct* cur, struct task_struct* next);
/* 获取当前线程 pcb 指针 */
struct task_struct *running_thread(){
uint32_t esp;
asm ("mov %%esp, %0" : "=g" (esp));
/* 取 esp 整数部分,即 pcb 起始地址 */
return (struct task_struct*)(esp & 0xfffff000);
}
/* 由 kernel_thread 去执行 function(func_arg) */
static void kernel_thread(thread_func *function, void *func_arg){
/* 执行 function 前要开中断,避免后面的时钟中断被屏蔽,而无法调度其他线程 */
intr_enable();
function(func_arg);
}
/* 初始化线程栈 thread_stack,
将待执行的函数和参数放到 thread_stack 中相应的位置 */
void thread_create(struct task_struct *pthread, thread_func function, void *func_arg){
/*先预留中断使用栈的空间,可见 thread.h 中定义的结构 */
pthread->self_kstack -= sizeof(struct intr_stack);
/*在留出线程栈空间,可见 thread.h 中定义 */
pthread->self_kstack -= sizeof(struct thread_stack);
struct thread_stack *kthread_stack = (struct thread_stack *)pthread->self_kstack;
kthread_stack->eip = kernel_thread;
kthread_stack->function = function;
kthread_stack->func_arg = func_arg;
kthread_stack->ebp = kthread_stack->ebx = \
kthread_stack->esi = kthread_stack->edi = 0;
kthread_stack->eip = kernel_thread;
}
/* 初始化线程基本信息 */
void init_thread(struct task_struct *pthread, char *name, int prio)
{
memset(pthread, 0, sizeof(*pthread));
strcpy(pthread->name, name);
if(pthread == main_thread){
/* 由于把 main 函数也封装成一个线程,
并且它一直是运行的,故将其直接设为 TASK_RUNNING */
pthread->status = TASK_RUNNING;
}else{
pthread->status = TASK_READY;
}
/* self_kstack 是线程自己在内核态下使用的栈顶地址 */
pthread->self_kstack = (uint32_t *)((uint32_t)pthread + PG_SIZE);
pthread->priority = prio;
pthread->ticks = prio;
pthread->elapsed_ticks = 0;
pthread->pgdir = NULL;
pthread->stack_magic = 0x19870916; //自定义的魔数.
}
/* 创建一优先级为 prio 的线程,线程名为 name,
线程所执行的函数是 function(func_arg) */
struct task_struct *thread_start(char *name, int prio, thread_func function, void *func_arg){
/* pcb 都位于内核空间,包括用户进程的 pcb 也是在内核空间 */
struct task_struct *thread = get_kernel_pages(1);
ASSERT(thread != 0);
init_thread(thread, name, prio);
thread_create(thread, function, func_arg);
/* 确保之前不在队列中 */
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);
return thread;
}
/* 将 kernel 中的 main 函数完善为主线程 */
static void make_main_thread(void) {
/* 因为 main 线程早已运行, 咱们在 loader.S 中进入内核时的 mov esp,0xc009f000,
* 就是为其预留 pcb 的,因此 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 schedule()
{
ASSERT(intr_get_status() == INTR_OFF);
struct task_struct* cur = running_thread();
if (cur->status == TASK_RUNNING) {
// 若此线程只是 cpu 时间片到了,将其加入到就绪队列尾
ASSERT(!elem_find(&thread_ready_list, &cur->general_tag));
list_append(&thread_ready_list, &cur->general_tag);
cur->ticks = cur->priority;
// 重新将当前线程的 ticks 再重置为其 priority
cur->status = TASK_READY;
}else{
/* 若此线程需要某事件发生后才能继续上 cpu 运行,
不需要将其加入队列,因为当前线程不在就绪队列中 */
}
ASSERT(!list_empty(&thread_ready_list));
thread_tag = NULL; // thread_tag 清空
/* 将 thread_ready_list 队列中的第一个就绪线程弹出,
准备将其调度上 cpu */
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);
}
/*初始化线程环境*/
void thread_init(void)
{
put_str("thread_init start\n");
list_init(&thread_ready_list);
list_init(&thread_all_list);
/* 将当前 main 函数创建为线程 */
make_main_thread();
put_str("thread_init done\n");
}
list.h
- 添加了 offset 和 elem2entry
#ifndef __LIB_KERNEL_LIST_H
#define __LIB_KERNEL_LIST_H
#include "global.h"
#include "stdint.h"
#define offset(struct_type, member) (int)(&((struct_type *)0)->member)
#define elem2entry(struct_type, struct_member_name, elem_ptr) \
(struct_type *)((int)elem_ptr - offset(struct_type, struct_member_name))
/********** 定义链表结点成员结构 ***********
*结点中不需要数据成元,只要求前驱和后继结点指针*/
struct list_elem
{
struct list_elem* prev; // 前躯结点
struct list_elem* next; // 后继结点
};
/* 链表结构,用来实现队列 */
struct list{
/* head 是队首,是固定不变的,不是第 1 个元素,第 1 个元素为 head.next */
struct list_elem head;
/* tail 是队尾,同样是固定不变的 */
struct list_elem tail;
};
/* 自定义函数类型 function,用于在 list_traversal 中做回调函数 */
typedef bool (function)(struct list_elem*, int arg);
void list_init (struct list*);
void list_insert_before(struct list_elem* before, struct list_elem* elem);
void list_push(struct list* plist, struct list_elem* elem);
void list_iterate(struct list* plist);
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);
struct list_elem* list_traversal(struct list* plist, function func, int arg);
bool elem_find(struct list* plist, struct list_elem* obj_elem);
#endif
thread / switch.asm
[bits 32]
section .text
global switch_to
switch_to:
;栈中此处是返回地址
push esi
push edi
push ebx
push ebp
mov eax, [esp + 20]; 得到栈中的参数 cur,cur = [esp+20]
mov [eax], esp ; 保存栈顶指针 esp. task_struct 的 self_kstack 字段
; self_kstack 在 task_struct 中的偏移为 0
; 所以直接往 thread 开头处存 4 字节便可
;------- 以上是备份当前线程的环境,下面是恢复下一个线程的环境 ---------
mov eax, [esp + 24] ; 得到栈中的参数 next,next = [esp+24]
mov esp, [eax] ; pcb 的第一个成员是 self_kstack 成员
; 它用来记录 0 级栈顶指针,被换上 cpu 时用来恢复 0 级栈
; 0 级栈中保存了进程或线程所有信息,包括 3 级栈指针
pop ebp
pop ebx
pop edi
pop esi
ret ; 返回到上面 switch_to 下面的那句注释的返回地址,
; 未由中断进入,第一次执行时会返回到 kernel_thread
kerne / init.c
#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "../device/timer.h"
#include "memory.h"
/*负责初始化所有模块*/
void init_all()
{
put_str("init_all\n");
idt_init(); //初始化中断
mem_init();
thread_init();
timer_init();
}
kernel / main.c
#include "print.h"
#include "init.h"
#include "debug.h"
#include "thread.h"
/*在线程中运行的函数*/
void k_thread_a(void *arg);
void k_thread_b(void *arg);
int main(void){
put_str("I am kernel\n");
init_all();
thread_start("k_thread_a", 31, k_thread_a, "argA ");
thread_start("k_thread_b", 8, k_thread_b, "argB ");
intr_enable();// 打开中断,使时钟中断起作用
while(1);
return 0;
}
/*在线程中运行的函数*/
void k_thread_a(void *arg)
{
char *para = arg;
while(1){
put_str(para);
}
while(1);
}
void k_thread_b(void *arg)
{
char *para = arg;
while(1){
put_str(para);
}
while(1);
}
缺少很多头文件,这里就不补充了
Makefile
loader.bin:loader.asm inc/boot.inc
nasm -I inc/ -o loader.bin loader.asm
mbr.bin:mbr.asm inc/boot.inc
nasm -I inc/ -o mbr.bin mbr.asm
kernel.bin:kernel/main.c lib/kernel/print.asm kernel/kernel.asm kernel/interrupt.c kernel/init.c device/timer.c
clang -I lib/kernel/ -I lib/ -I kernel/ -I thread/ -I dev/ -m32 -s -w -c -fno-builtin -o build/main.o kernel/main.c
nasm -f elf -o build/print.o lib/kernel/print.asm
nasm -f elf -o build/kernel.o kernel/kernel.asm
nasm -f elf -o build/switch.o thread/switch.asm
clang -I lib/kernel/ -I lib/ -I kernel/ -I thread/ -I dev/ -m32 -s -w -c -fno-builtin -o build/interrupt.o kernel/interrupt.c
clang -I lib/kernel/ -I lib/ -I kernel/ -I thread/ -I dev/ -m32 -s -w -c -fno-builtin -o build/init.o kernel/init.c
clang -I lib/kernel/ -I lib/ -I kernel/ -I thread/ -I dev/ -m32 -s -w -c -fno-builtin -o build/timer.o device/timer.c
clang -I lib/kernel/ -I lib/ -I kernel/ -I thread/ -I dev/ -m32 -s -w -c -fno-builtin -o build/debug.o kernel/debug.c
clang -I lib/kernel/ -I lib/ -I kernel/ -I thread/ -I dev/ -m32 -s -w -c -fno-builtin -o build/memory.o kernel/memory.c
clang -I lib/kernel/ -I lib/ -I kernel/ -I thread/ -I dev/ -m32 -s -w -c -fno-builtin -o build/string.o lib/string.c
clang -I lib/kernel/ -I lib/ -I kernel/ -I thread/ -I dev/ -m32 -s -w -c -fno-builtin -o build/bitmap.o lib/kernel/bitmap.c
clang -I lib/kernel/ -I lib/ -I kernel/ -I thread/ -I dev/ -m32 -s -w -c -fno-builtin -o build/thread.o thread/thread.c
clang -I lib/kernel/ -I lib/ -I kernel/ -I thread/ -I dev/ -m32 -s -w -c -fno-builtin -o build/list.o lib/kernel/list.c
ld -m elf_i386 -Ttext 0xc0001500 -e main -o build/kernel.bin build/main.o \
build/init.o build/interrupt.o build/print.o \
build/debug.o build/kernel.o build/timer.o \
build/string.o build/memory.o build/bitmap.o \
build/thread.o build/list.o build/switch.o
dd: dd_mbr dd_loader dd_kernel
dd_mbr:
dd if=mbr.bin of=hd60M.img bs=512 count=1 conv=notrunc
dd_loader:
dd if=loader.bin of=hd60M.img bs=512 count=4 seek=2 conv=notrunc
dd_kernel:
dd if=build/kernel.bin of=hd60M.img bs=512 count=200 seek=9 conv=notrunc
运行结果: