《真象还原》读书笔记——第九章 线程

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

运行结果:
在这里插入图片描述

  • 28
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值