《操作系统真象还原》第9章

感觉本章所涉及的进程与线程的相关知识是我在学习xv6时学的最薄弱、也是最难搞的一部分,因为里面涉及大量出入栈和栈的变换操作。我觉得栈是OS中的核心,弄懂了栈OS也就手到擒来了。

正如书中所说,“有些陌生的内容需要量变才能到质变”,特将部分内容抄录如下以加深自己的印象。

1.实现内核线程

线程和进程比,进程拥有整个地址空间,从而拥有全部资源,线程没有自己的地址空间,因此没有任何属于自己的资源,需要借助进程的资源“生存”,所以线程被称为轻量级进程。

进程中包含此进程中所有线程所使用的资源,因此线程依赖于进程,存在于进程之中,用表达式来表示:进程=线程+资源。

由于各个进程都拥有自己的虚拟地址空间,正常情况下它们彼此无法访问到对方的内部,因为进程之间的安全性是由操作系统的分页机制来保证的,只要操作系统不要把相同的物理页分配给多个进程就行了。

但进程内的线程可都是共享这同一地址空间的,它们彼此能“见面”,这就暴露出一个问题,既然进程内的所有线程共享同一个地址空间,也就意味着任意一个线程都可以去访问同一进程内其它线程的数据,甚至可以改变它们的数据。

进程只是个资源整合体,它将进程中所有线程运行时用到资源收集在一起,供进程中的所有线程使用,真正上处理器上运行的起始都叫线程,进程中的线程才是一个个的执行实体、执行流,因此,经调度器送上处理器执行的程序都是线程。

执行流、调度单位、运行实体等概念都是针对线程而言的,线程才是解决问题的思路、步骤,它是具有能动性的指令,因此只有它才能上处理器运行,即一切执行流起始都是线程,因为任何时候进程中都至少存在一个线程。

进程的状态描述的是进程中有关“动作”的执行流部分,即线程,而不包括静止的资源部分。把上述需要等待外界条件的状态称为“阻塞态”,把外界条件成立时,进程可以随时准备运行的状态称为“就绪态”,把正在处理器上运行的进程的状态称为“运行态”。


 步骤:

1.简单的PCB及线程的实现

2.核心数据结构——双向链表

3.多线程调度


1.简单的PCB及线程的实现

我们将本章的代码放入thread/下。

①thread/thread.h:

复制代码
 1 #ifndef __THREAD_THREAD_H
 2 #define __THREAD_THREAD_H
 3 #include "stdint.h"
 4 
 5 /* 自定义通用函数类型,它将在很多线程程序中作为参数类型 */
 6 typedef void thread_func(void*);
 7 
 8 /* 进程或线程状态 */
 9 enum task_status{
10     TASK_RUNNING,
11     TASK_READY,
12     TASK_BLOCKED,
13     TASK_WAITING,
14     TASK_HANGING,
15     TASK_DIED
16 };
17 
18 /***************** 中断栈intr_stack ******************
19  * 此结构用于中断发生时保护程序(线程或进程)的上下文环境
20  * 进程或线程被外部中断或软中断打断时,会按照此结构压入上下文
21  * 寄存器,intr_exit中的出栈操作是此结构的逆操作
22  * 此栈在线程自己的内核栈中的位置固定,所在页的最顶端
23  *****************************************************/
24 struct intr_stack{
25     uint32_t vec_no;      // kenrel.S宏VECTOR中push %1压入的中断号
26     uint32_t edi;
27     uint32_t esi;
28     uint32_t ebp;
29     uint32_t esp_dummy;   // 虽然pushad会压入esp,但esp是不断变化的,所以会被popad忽略
30     uint32_t ebx;
31     uint32_t edx;
32     uint32_t ecx;
33     uint32_t eax;
34     uint32_t gs;
35     uint32_t fs;
36     uint32_t es;
37     uint32_t ds;
38 
39     /* 以下由cpu从低特权级进入高特权级时压入 */
40     uint32_t err_code;    // error_code会被压入在eip之后
41     void (*eip)(void);
42     uint32_t cs;
43     uint32_t eflags;
44     void* esp;
45     uint32_t ss;
46 };
47 
48 /*****************线程栈thread_stack****************
49  * 线程自己的栈,用于存储线程中待执行的函数
50  * 此结构在线程自己的内核栈中位置不固定,
51  * 仅用在switch_to时保存线程环境。
52  * 实际位置取决于实际运行情况。
53  *************************************************/
54 struct thread_stack{
55     uint32_t ebp;
56     uint32_t ebx;
57     uint32_t edi;
58     uint32_t esi;
59 
60     /* 线程第一次执行时,eip指向待调用的函数kernel_thread
61      * 其它时候,eip是指向switch_to的返回地址 */
62     void (*eip)(thread_func* func,void* func_arg);
63 
64     /****** 以下仅供第一次被调度上cpu时使用 ******/
65 
66     /* 参数unused_ret只为占位置充数为返回地址 */
67     void (*unused_retaddr);
68     thread_func* function;// 由kernel_thread所调用的函数名
69     void* func_arg;       // 由kernel_thread所调用的函数所需的参数
70 };
71 
72 /* 进程或线程的pcb,程序控制块 */
73 struct task_struct{
74     uint32_t* self_kstack;// 各内核线程都有自己的内核栈
75     enum task_status status;
76     uint8_t priority;     // 线程优先级
77     char name[16];
78     uint32_t stack_magic; // 栈的边界标记,用于检测栈的溢出
79 };
80 
81 static void kernel_thread(thread_func* function,void* func_arg);
82 void thread_create(struct task_struct* pthread,thread_func function,void* func_arg);
83 void init_thread(struct task_struct* pthread,char* name,int prio);
84 struct task_struct* thread_start(char* name,int prio,thread_func function,void* func_arg);
85 #endif
复制代码

②thread/thread.c:

复制代码
 1 #include "thread.h"
 2 #include "stdint.h"
 3 #include "string.h"
 4 #include "global.h"
 5 #include "memory.h"
 6 
 7 #define PG_SIZE 4096
 8 
 9 /* 由kernel_thread去执行function(func_arg) */
10 static void kernel_thread(thread_func* function,void* func_arg){
11     function(func_arg);
12 }
13 
14 /* 初始化线程栈thread_stack,将待执行的函数和参数方法到thread_stack中相应的位置 */
15 void thread_create(struct task_struct* pthread,thread_func function,void* func_arg){
16     /* 先预留中断使用栈的空间,可见thread.h中定义的结构 */
17     pthread->self_kstack-=sizeof(struct intr_stack);
18 
19     /* 再留出线程栈空间,可见thread.h中定义 */
20     pthread->self_kstack-=sizeof(struct thread_stack);
21     struct thread_stack* kthread_stack=(struct thread_stack*)pthread->self_kstack;
22     kthread_stack->eip=kernel_thread;
23     kthread_stack->function=function;
24     kthread_stack->func_arg=func_arg;
25     kthread_stack->ebp=kthread_stack->ebx=kthread_stack->esi=kthread_stack->edi=0;
26 }
27 
28 /* 初始化线程基本信息 */
29 void init_thread(struct task_struct* pthread,char* name,int prio){
30     memset(pthread,0,sizeof(*pthread));
31     strcpy(pthread->name,name);
32     pthread->status=TASK_RUNNING;
33     pthread->priority=prio;
34     /* self_kstack是线程自己在内核态下使用的栈顶地址 */
35     pthread->self_kstack=(uint32_t*)((uint32_t)pthread+PG_SIZE);
36     pthread->stack_magic=0x19870916;   // 自定义魔数
37 }
38 
39 /* 创建一个优先级为prio的线程,线程名为name,线程所执行的函数是funciton(func_arg) */
40 struct task_struct* thread_start(char* name,int prio,thread_func function,void* func_arg){
41     /* pcb都位于内核空间,包括用户进程pcb也是在内核空间 */
42     struct task_struct* thread=get_kernel_pages(1);
43 
44     init_thread(thread,name,prio);
45     thread_create(thread,function,func_arg);
46 
47     asm volatile("movl %0,%%esp; pop %%ebp; pop %%ebx; pop %%edi; pop %%esi; ret":: "g"(thread->self_kstack): "memory");
48     return thread;
49 }
复制代码

③kernel/main.c:

复制代码
 1 #include "print.h"
 2 #include "init.h"
 3 #include "thread.h"
 4 
 5 void k_thread_a(void*);
 6 
 7 int main(void){    
 8     put_str("Welcome,\nI am kernel!\n");
 9     init_all();
10   
11     thread_start("k_thread_a",31,k_thread_a,"argA");
12 
13     while(1);
14     return 0;
15 }
16 
17 /* 在线程中运行的函数 */
18 void k_thread_a(void* arg){
19     /* 用void*来通用表示参数,被调用的函数知道自己需要什么类型的参数,自己转换再用 */
20     char* para=arg;
21     while (1){
22     put_str(para);
23     }
24 }
复制代码

④makefile:

复制代码
 1 BUILD_DIR = ./build
 2 ENTRY_POINT = 0xc0001500
 3 AS = nasm
 4 CC = gcc
 5 LD = ld
 6 LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/ -I thread/
 7 ASFLAGS = -f elf
 8 CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
 9 LDFLAGS =  -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
10 OBJS = $(BUILD_DIR)/main.o  $(BUILD_DIR)/init.o   $(BUILD_DIR)/interrupt.o \
11       $(BUILD_DIR)/timer.o  $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
12       $(BUILD_DIR)/debug.o  $(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o \
13       $(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o
14 ##############     c代码编译     ###############
15 $(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
16         lib/stdint.h kernel/init.h lib/string.h kernel/memory.h \
17     thread/thread.h
18     $(CC) $(CFLAGS) $< -o $@
19 
20 $(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
21         lib/stdint.h kernel/interrupt.h device/timer.h kernel/memory.h
22     $(CC) $(CFLAGS) $< -o $@
23 
24 $(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
25         lib/stdint.h kernel/global.h lib/kernel/io.h lib/kernel/print.h
26     $(CC) $(CFLAGS) $< -o $@
27 
28 $(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/stdint.h\
29         lib/kernel/io.h lib/kernel/print.h
30     $(CC) $(CFLAGS) $< -o $@
31 
32 $(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
33         lib/kernel/print.h lib/stdint.h kernel/interrupt.h
34     $(CC) $(CFLAGS) $< -o $@
35 
36 $(BUILD_DIR)/string.o: lib/string.c lib/string.h \
37     kernel/debug.h kernel/global.h
38     $(CC) $(CFLAGS) $< -o $@
39 
40 $(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \
41     lib/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h
42     $(CC) $(CFLAGS) $< -o $@
43 
44 $(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h \
45     lib/string.h kernel/interrupt.h lib/kernel/print.h kernel/debug.h
46     $(CC) $(CFLAGS) $< -o $@
47 
48 $(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \
49     lib/stdint.h lib/string.h kernel/global.h kernel/memory.h 
50     $(CC) $(CFLAGS) $< -o $@
51 
52 ##############    汇编代码编译    ###############
53 $(BUILD_DIR)/kernel.o: kernel/kernel.S
54     $(AS) $(ASFLAGS) $< -o $@
55 
56 $(BUILD_DIR)/print.o: lib/kernel/print.S
57     $(AS) $(ASFLAGS) $< -o $@
58 
59 ##############    链接所有目标文件    #############
60 $(BUILD_DIR)/kernel.bin: $(OBJS)
61     $(LD) $(LDFLAGS) $^ -o $@
62 
63 .PHONY : mk_dir hd clean all
64 
65 mk_dir:
66     if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi
67 
68 hd:
69     dd if=$(BUILD_DIR)/kernel.bin \
70            of=hd60M.img \
71            bs=512 count=200 seek=9 conv=notrunc
72 
73 clean:
74     cd $(BUILD_DIR) && rm -f  ./*
75 
76 build: $(BUILD_DIR)/kernel.bin
77 
78 all: mk_dir build hd
复制代码

如图,满屏的argA,初战告捷:


2.核心数据结构——双向链表

①lib/kernel/list.h:

复制代码
 1 #ifndef __LIB_KERNEL_LIST_H
 2 #define __LIB_KERNEL_LIST_H
 3 #include "global.h"
 4 
 5 #define offset(struct_type,member)(int)(&((struct_type*)0)->member)
 6 #define elem2entry(struct_type,struct_member_name,elem_ptr)\
 7            (struct_type*)((int)elem_ptr-offset(struct_type,struct_member_name))
 8 
 9 /************ 定义链表结点成员结构 ***********
10  * 结点中不需要数据成员,只要求前驱和后继结点指针*/
11 struct list_elem{
12     struct list_elem* prev;   // 前驱结点
13     struct list_elem* next;   // 后继结点
14 };
15 
16 /* 链表结构,用来实现队列 */
17 struct list{
18     /* head是队首,是固定不变的,不是第1个元素,第一个元素是head.next */
19     struct list_elem head;
20     /* tail是队尾,同样是固定不变的 */
21     struct list_elem tail;
22 };
23 
24 /* 自定义函数类型function,用于在list_traversal中做回调函数 */
25 typedef bool (function)(struct list_elem*,int arg);
26 
27 void list_init(struct list*);
28 void list_insert_before(struct list_elem* before,struct list_elem* elem);
29 void list_push(struct list* plist);
30 void list_iterate(struct list* plist);
31 void list_append(struct list* plist,struct list_elem* elem);
32 void list_remove(struct list_elem* pelem);
33 struct list_elem* list_pop(struct list* plist);
34 bool list_empty(struct list* plist);
35 uint32_t list_len(struct list* plist);
36 struct list_elem* list_traversal(struct list* plist,function func,int arg);
37 bool elem_find(struct list* plist,struct list_elem* obj_elem);
复制代码

②lib/kernel/list.c:

复制代码
  1 #include "list.h"
  2 #include "interrupt.h"
  3 
  4 /* 初始化双向链表list */
  5 void list_init(struct list* list){
  6     list->head.prev=NULL;
  7     list->head.next=&list->tail;
  8     list->tail.prev=&list->head;
  9     list->tail.next=NULL;
 10 }
 11 
 12 /* 把链表元素elem插入在元素before之前 */
 13 void list_insert_before(struct list_elem* before,struct list_elem* elem){
 14     enum intr_status old_status=intr_disable();
 15 
 16     /* 将before前驱元素的后继元素更新为elem,暂时使before脱离链表 */
 17     before->prev->next=elem;
 18 
 19     /* 更新elem自己的前驱结点为before的前驱,
 20      * 更新elem自己的后继结点为before,于是before又回到链表 */
 21     elem->prev=before->prev;
 22     elem->next=before;
 23 
 24     /* 更新before的前驱结点为elem */
 25     before->prev=elem;
 26 
 27     intr_set_status(old_status);
 28 }
 29 
 30 /* 添加元素到列表队首,类似栈push操作 */
 31 void list_push(struct list* plist,struct list_elem* elem){
 32     list_insert_before(plist->head.next,elem);   // 在队头后面插入elem
 33 }
 34 
 35 /* 追加元素到列表队尾,类似于队列的先进先出操作 */
 36 void list_append(struct list* plist,struct list_elem* elem){
 37     list_insert_before(plist->tail,elem);   // 在队尾前面插入elem
 38 }
 39 
 40 /* 使元素pelem脱离链表 */
 41 void list_remove(struct list_elem* pelem){
 42     enum intr_status old_status=intr_disable();
 43 
 44     pelem->prev->next=pelem->next;
 45     pelem->next->prev=pelem->prev;
 46 
 47     intr_set_status(old_status);
 48 }
 49 
 50 /* 将链表第一个元素弹出并返回,类似栈的pop操作 */
 51 struct list_elem* list_pop(struct list* plist){
 52     struct list_elem* elem=plist->head.next;
 53     list_remove(elem);
 54     return elem;
 55 }
 56 
 57 /* 从链表中查找元素obj_elem,成功时返回true,失败时返回false */
 58 bool elem_find(struct list* plist,struct list_elem* obj_elem){
 59     struct list_elem* elem=plist->head.next;
 60     while (elem!=&plist->tail){
 61     if (elem==obj_elem){
 62         return true;
 63     }
 64     elem=elem->next;
 65     }
 66     return false;
 67 }
 68 
 69 /* 把列表plist中的每个元素elem和arg传给回调函数func,
 70  * arg给func用来判断elem是否符合条件,
 71  * 本函数的功能是遍历列表内所有元素,逐个判断是否有符合条件的元素。
 72  * 找到符合条件的元素返回元素指针,否则返回NULL */
 73 struct list_elem* list_traversal(struct list* plist,function func,int arg){
 74     struct list_elem* elem=plist->head.next;
 75     if (list_empty(plist)){
 76     return NULL;
 77     }
 78 
 79     while (elem!=&plist->tail){
 80     if (func(elem,arg)){   // func返回true,则认为该元素在回调函数中符合条件,命中,故停止继续遍历
 81         return elem;
 82     }   // 若回调函数func返回false,则继续遍历
 83     elem=elem->next;
 84     }
 85     return NULL;
 86 }
 87 
 88 /* 返回链表长度 */
 89 uint32_t list_len(struct list* plist){
 90     struct list_elem* elem=plist->head.next;
 91     uint32_t length=0;
 92     while (elem!=&plist->tail){
 93     ++length;
 94     elem=elem->next;
 95     }
 96     return length;
 97 }
 98 
 99 /* 判断链表是否为空,空时返回true,否则返回false */
100 bool list_empty(struct list* plist){
101     return plist->head.next==&plist->tail?true:false;
102 }
复制代码

3.多线程调度

 本节涉及到的代码还是很多的,我还是直接放出来吧:

①thread/thread.h:

复制代码
  1 #ifndef __THREAD_THREAD_H
  2 #define __THREAD_THREAD_H
  3 #include "stdint.h"
  4 #include "list.h"
  5 
  6 /* 自定义通用函数类型,它将在很多线程程序中作为参数类型 */
  7 typedef void thread_func(void*);
  8 
  9 /* 进程或线程状态 */
 10 enum task_status{
 11     TASK_RUNNING,
 12     TASK_READY,
 13     TASK_BLOCKED,
 14     TASK_WAITING,
 15     TASK_HANGING,
 16     TASK_DIED
 17 };
 18 
 19 /***************** 中断栈intr_stack ******************
 20  * 此结构用于中断发生时保护程序(线程或进程)的上下文环境
 21  * 进程或线程被外部中断或软中断打断时,会按照此结构压入上下文
 22  * 寄存器,intr_exit中的出栈操作是此结构的逆操作
 23  * 此栈在线程自己的内核栈中的位置固定,所在页的最顶端
 24  *****************************************************/
 25 struct intr_stack{
 26     uint32_t vec_no;      // kenrel.S宏VECTOR中push %1压入的中断号
 27     uint32_t edi;
 28     uint32_t esi;
 29     uint32_t ebp;
 30     uint32_t esp_dummy;   // 虽然pushad会压入esp,但esp是不断变化的,所以会被popad忽略
 31     uint32_t ebx;
 32     uint32_t edx;
 33     uint32_t ecx;
 34     uint32_t eax;
 35     uint32_t gs;
 36     uint32_t fs;
 37     uint32_t es;
 38     uint32_t ds;
 39 
 40     /* 以下由cpu从低特权级进入高特权级时压入 */
 41     uint32_t err_code;    // error_code会被压入在eip之后
 42     void (*eip)(void);
 43     uint32_t cs;
 44     uint32_t eflags;
 45     void* esp;
 46     uint32_t ss;
 47 };
 48 
 49 /*****************线程栈thread_stack****************
 50  * 线程自己的栈,用于存储线程中待执行的函数
 51  * 此结构在线程自己的内核栈中位置不固定,
 52  * 仅用在switch_to时保存线程环境。
 53  * 实际位置取决于实际运行情况。
 54  *************************************************/
 55 struct thread_stack{
 56     uint32_t ebp;
 57     uint32_t ebx;
 58     uint32_t edi;
 59     uint32_t esi;
 60 
 61     /* 线程第一次执行时,eip指向待调用的函数kernel_thread
 62      * 其它时候,eip是指向switch_to的返回地址 */
 63     void (*eip)(thread_func* func,void* func_arg);
 64 
 65     /****** 以下仅供第一次被调度上cpu时使用 ******/
 66 
 67     /* 参数unused_ret只为占位置充数为返回地址 */
 68     void (*unused_retaddr);
 69     thread_func* function;// 由kernel_thread所调用的函数名
 70     void* func_arg;       // 由kernel_thread所调用的函数所需的参数
 71 };
 72 
 73 /* 进程或线程的pcb,程序控制块 */
 74 struct task_struct{
 75     uint32_t* self_kstack;// 各内核线程都有自己的内核栈
 76     enum task_status status;
 77     uint8_t priority;     // 线程优先级
 78     char name[16];
 79     uint8_t ticks;        // 每次在处理器上执行的时间滴答数
 80 
 81     /* 此任务自上cpu运行后占用了多少cpu嘀嗒数,也就是此任务执行了多久 */
 82     uint32_t elapsed_ticks;
 83 
 84     /* genernal_tag的作用是用于线程在一般的队列中的结点 */
 85     struct list_elem general_tag;
 86 
 87     /* all_list_tag的作用是用于线程队列thread_all_list中的结点 */
 88     struct list_elem all_list_tag;
 89 
 90     uint32_t* pgdir;      // 进程自己页表的虚拟地址
 91     uint32_t stack_magic; // 栈的边界标记,用于检测栈的溢出
 92 };
 93 
 94 void kernel_thread(thread_func* function,void* func_arg);
 95 void thread_create(struct task_struct* pthread,thread_func function,void* func_arg);
 96 void init_thread(struct task_struct* pthread,char* name,int prio);
 97 struct task_struct* thread_start(char* name,int prio,thread_func function,void* func_arg);
 98 struct task_struct* running_thread(void);
 99 void schedule(void);
100 void thread_init(void);
101 #endif
View Code
复制代码

②thread/thread.c:

复制代码
  1 #include "thread.h"
  2 #include "stdint.h"
  3 #include "string.h"
  4 #include "global.h"
  5 #include "memory.h"
  6 #include "interrupt.h"
  7 #include "print.h"
  8 #include "debug.h"
  9 
 10 #define PG_SIZE 4096
 11 
 12 struct task_struct* main_thread;   // 主线程PCB
 13 struct list thread_ready_list;     // 就绪队列
 14 struct list thread_all_list;       // 所有任务队列
 15 static struct list_elem* thread_tag;// 用于保存队列中的线程结点
 16 
 17 extern void switch_to(struct task_struct* cur,struct task_struct* next);
 18 
 19 /* 获取当前pcb指针 */
 20 struct task_struct* running_thread(){
 21     uint32_t esp;
 22     asm("mov %%esp,%0":"=g"(esp));
 23     /* 取asm整数部分,即pcb起始地址 */
 24     return (struct task_struct*)(esp & 0xfffff000);
 25 }
 26 
 27 /* 由kernel_thread去执行function(func_arg) */
 28 void kernel_thread(thread_func* function,void* func_arg){
 29     /* 执行function前要开中断,避免后面的时钟中断被屏蔽,而无法调度其它进程 */
 30     intr_enable();
 31     function(func_arg);
 32 }
 33 
 34 /* 初始化线程栈thread_stack,将待执行的函数和参数方法到thread_stack中相应的位置 */
 35 void thread_create(struct task_struct* pthread,thread_func function,void* func_arg){
 36     /* 先预留中断使用栈的空间,可见thread.h中定义的结构 */
 37     pthread->self_kstack-=sizeof(struct intr_stack);
 38 
 39     /* 再留出线程栈空间,可见thread.h中定义 */
 40     pthread->self_kstack-=sizeof(struct thread_stack);
 41     struct thread_stack* kthread_stack=(struct thread_stack*)pthread->self_kstack;
 42     kthread_stack->eip=kernel_thread;
 43     kthread_stack->function=function;
 44     kthread_stack->func_arg=func_arg;
 45     kthread_stack->ebp=kthread_stack->ebx=kthread_stack->esi=kthread_stack->edi=0;
 46 }
 47 
 48 /* 初始化线程基本信息 */
 49 void init_thread(struct task_struct* pthread,char* name,int prio){
 50     memset(pthread,0,sizeof(*pthread));
 51     strcpy(pthread->name,name);
 52 
 53     if (pthread==main_thread){
 54     /* 由于把main函数也封装成一个线程,并且它一直是运行的,故将其直接设为TASK_RUNNING */
 55     pthread->status=TASK_RUNNING;
 56     }else{
 57     pthread->status=TASK_READY;
 58     }
 59 
 60     /* self_kstack是线程自己在内核态下使用的栈顶地址 */
 61     pthread->self_kstack=(uint32_t*)((uint32_t)pthread+PG_SIZE);
 62     pthread->priority=prio;
 63     pthread->ticks=prio;
 64     pthread->elapsed_ticks=0;
 65     pthread->pgdir=NULL;
 66     pthread->stack_magic=0x19870916;   // 自定义魔数
 67 }
 68 
 69 /* 创建一个优先级为prio的线程,线程名为name,线程所执行的函数是funciton(func_arg) */
 70 struct task_struct* thread_start(char* name,int prio,thread_func function,void* func_arg){
 71     /* pcb都位于内核空间,包括用户进程pcb也是在内核空间 */
 72     struct task_struct* thread=get_kernel_pages(1);
 73 
 74     init_thread(thread,name,prio);
 75     thread_create(thread,function,func_arg);
 76 
 77     /* 确保之前不再队列中 */
 78     ASSERT(!elem_find(&thread_ready_list,&thread->general_tag));
 79     /* 加入就绪队列 */
 80     list_append(&thread_ready_list,&thread->general_tag);
 81 
 82     /* 确保之前不再队列中 */
 83     ASSERT(!elem_find(&thread_all_list,&thread->all_list_tag));
 84     list_append(&thread_all_list,&thread->all_list_tag);
 85 
 86     return thread;
 87 }
 88 
 89 /* 将kernel中的main函数完善为主线程 */
 90 static void make_main_thread(void){
 91     /* 因为main线程早已运行,咱们在loader.S中进入内核时的mov esp,0xc009f000,
 92      * 就是为其预留pcb的,因此pcb地址为0xc009e000,不需要勇敢get_kernel_page另分配一页 */
 93     main_thread=running_thread();
 94     init_thread(main_thread,"main",31);
 95 
 96     /* main函数是当前线程,当前线程不在thread_ready_list中,
 97      * 所以只将其加在thead_all_list中 */
 98     ASSERT(!elem_find(&thread_all_list,&main_thread->all_list_tag));
 99     list_append(&thread_all_list,&main_thread->all_list_tag);
100 }
101 
102 /* 实现任务调度 */
103 void schedule(){
104     ASSERT(intr_get_status()==INTR_OFF);
105     struct task_struct* cur=running_thread();
106     if (cur->status==TASK_RUNNING){
107     // 若此线程只是cpu时间片到了,将其加入就绪队列队尾
108     ASSERT(!elem_find(&thread_ready_list,&cur->general_tag));
109     list_append(&thread_ready_list,&cur->general_tag);
110     cur->ticks=cur->priority;
111     // 重新将当前进程的ticks重置为priority
112     cur->status=TASK_READY;
113     }else{
114     /* 若此线程需要某事件发生后才能继续上cpu运行,
115      * 不需要将其加入队列,因为当前不在就绪队列中 */
116     }
117 
118     ASSERT(!list_empty(&thread_ready_list));
119     thread_tag=NULL;   // thread_tag清空
120     /* 将thread_ready_list队列中的第一个就绪线程弹出,准备将其调度上cpu */
121     thread_tag=list_pop(&thread_ready_list);
122     struct task_struct* next=elem2entry(struct task_struct,general_tag,thread_tag);
123     next->status=TASK_RUNNING;
124     switch_to(cur,next);
125 }
126 
127 /* 初始化线程环境 */
128 void thread_init(void){
129     put_str("thread_init start\n");
130     list_init(&thread_ready_list);
131     list_init(&thread_all_list);
132     /* 将当前main函数创建为线程 */
133     make_main_thread();
134     put_str("thread_init done\n");
135 }
View Code
复制代码

③kernel/interrupt.c:

复制代码
  1 #include "interrupt.h"
  2 #include "stdint.h"
  3 #include "global.h"
  4 #include "io.h"
  5 #include "print.h"
  6 
  7 #define PIC_M_CTRL 0x20           // 这里用的可编程中断控制器是8259A,主片的控制端口是0x20
  8 #define PIC_M_DATA 0x21           // 主片的数据端口是0x21
  9 #define PIC_S_CTRL 0xa0           // 从片的控制端口是0xa0
 10 #define PIC_S_DATA 0xa1           // 从片的数据端口是0xa1
 11 
 12 #define IDT_DESC_CNT 0x21      // 目前总共支持的中断数
 13 
 14 #define EFLAGS_IF    0x00000200//eflags寄存器的if位为1
 15 #define GET_EFLAGS(EFLAGS_VAR) asm volatile("pushfl; popl %0":"=g"(EFLAGS_VAR))
 16 
 17 /* 中断门描述符结构体 */
 18 struct gate_desc{
 19     uint16_t func_offset_low_word;
 20     uint16_t selector;
 21     uint8_t dcount;   //此项为双字计数字段,是门描述符中的第4字节。此项固定值,不用考虑
 22     uint8_t attribute;
 23     uint16_t func_offset_high_word;
 24 };
 25 
 26 // 静态函数声明,非必须
 27 static void make_idt_desc(struct gate_desc* p_gdesc,uint8_t attr,intr_handler function);
 28 static struct gate_desc idt[IDT_DESC_CNT];   // idt是中断描述符表,本质上就是个中断门描述符数组
 29 char* intr_name[IDT_DESC_CNT];             // 用于保存异常的名字
 30 
 31 /************** 定义中断处理程序数组 *************
 32  * 在kernel.S中定义的intrXXentry只是中断处理程序的入口,
 33  * 最终调用的是ide_table中的处理程序*/
 34 intr_handler idt_table[IDT_DESC_CNT];
 35 
 36 /********************************************/
 37 extern intr_handler intr_entry_table[IDT_DESC_CNT];        // 声明引用定义在kernel.S中的中断处理函数入口数组
 38 
 39 /* 初始化可编程中断控制器8259A */
 40 static void pic_init(void){
 41 
 42     /*初始化主片*/
 43     outb(PIC_M_CTRL,0x11);
 44     outb(PIC_M_DATA,0x20);  
 45     outb(PIC_M_DATA,0x04); 
 46     outb(PIC_M_DATA,0x01);
 47  
 48     /*初始化从片*/
 49     outb(PIC_S_CTRL,0x11); 
 50     outb(PIC_S_DATA,0x28); 
 51     outb(PIC_S_DATA,0x02);
 52     outb(PIC_S_DATA,0x01);
 53    
 54     outb(PIC_M_DATA,0xfe);
 55     outb(PIC_S_DATA,0xff);
 56 
 57     put_str("   pic_init done\n");
 58 }
 59 
 60 /* 创建中断门描述符 */
 61 static void make_idt_desc(struct gate_desc* p_gdesc,uint8_t attr,intr_handler function){ 
 62     p_gdesc->func_offset_low_word=(uint32_t)function & 0x0000FFFF;
 63     p_gdesc->selector=SELECTOR_K_CODE;
 64     p_gdesc->dcount=0;
 65     p_gdesc->attribute=attr;
 66     p_gdesc->func_offset_high_word=((uint32_t)function & 0xFFFF0000)>>16;
 67 }
 68 
 69 /* 初始化中断描述符表 */
 70 static void idt_desc_init(void){
 71     int i=IDT_DESC_CNT-1;
 72     for (i=0;i<IDT_DESC_CNT;i++){
 73         make_idt_desc(&idt[i],IDT_DESC_ATTR_DPL0,intr_entry_table[i]); 
 74     }
 75     put_str("   idt_desc_init done\n");
 76 }
 77 
 78 /* 通用的中断处理函数,一般用在异常出现时的处理 */
 79 static void general_intr_handler(uint8_t vec_nr){
 80     if (vec_nr==0x27 || vec_nr==0x2f){    // 0x2f是从片8259A上的最后一个irq引脚,保留
 81         return;        //IRQ7和IRQ15会产生伪中断(spurious interrupt),无须处理。
 82     }
 83 
 84     /* 将光标置为0,从屏幕左上角清出一片打印异常信息的区域,方便阅读 */
 85     set_cursor(0);
 86     int cursor_pos=0;
 87     while (cursor_pos<320){
 88         put_char(' ');
 89     ++cursor_pos;
 90     }
 91 
 92     set_cursor(0);       // 重置光标为屏幕左上角
 93     put_str("!!!!!     excetion message begin     !!!!!\n");
 94     set_cursor(88);
 95     put_str(intr_name[vec_nr]);
 96     if (vec_nr==14){     // 若为page_fault,将缺失的地址打印出来并悬停
 97         int page_fault_vaddr=0;
 98     asm("movl %%cr2,%0":"=r"(page_fault_vaddr));   // cr2是存放page_fault的地址
 99     put_str("\npage fault addr is ");put_int(page_fault_vaddr);
100     }
101     put_str("\n!!!!!     excetion message end     !!!!!");
102     // 能进入中断处理程序就表示已经处在关中断情况下
103     // 不会出现调度进程的情况。故下面的死循环不会再被中断
104     while(1);
105 }
106 
107 /* 在中断处理程序数组第vector_no个元素中注册安装中断处理程序function */
108 void register_handler(uint8_t vector_no,intr_handler function){
109     /* idt_table数组中的函数是在进入中断后根据中断向量号调用的
110      * 见kernel/kernel.S的call [idt_table+%1*4] */
111     idt_table[vector_no]=function;
112 }
113 
114 /* 完成一般中断处理函数注册及异常名称注册 */
115 static void exception_init(void){                // 完成一般中断处理函数注册及异常名称注册
116     int i;
117     for (i=0;i<IDT_DESC_CNT;i++){
118 /* idt_table数组中的函数是在进入中断后根据中断向量号调用的,
119  * 见kernel/kernel.S的call [idt_table + %1*4] */
120        idt_table[i]=general_intr_handler;            // 默认为general_intr_handler。
121                                 // 以后会由register_handler来注册具体处理函数。
122        intr_name[i]="unknown";                    // 先统一赋值为unknown 
123     }
124     intr_name[0] = "#DE Divide Error";
125     intr_name[1] = "#DB Debug Exception";
126     intr_name[2] = "NMI Interrupt";
127     intr_name[3] = "#BP Breakpoint Exception";
128     intr_name[4] = "#OF Overflow Exception";
129     intr_name[5] = "#BR BOUND Range Exceeded Exception";
130     intr_name[6] = "#UD Invalid Opcode Exception";
131     intr_name[7] = "#NM Device Not Available Exception";
132     intr_name[8] = "#DF Double Fault Exception";
133     intr_name[9] = "Coprocessor Segment Overrun";
134     intr_name[10] = "#TS Invalid TSS Exception";
135     intr_name[11] = "#NP Segment Not Present";
136     intr_name[12] = "#SS Stack Fault Exception";
137     intr_name[13] = "#GP General Protection Exception";
138     intr_name[14] = "#PF Page-Fault Exception";
139     // intr_name[15] 第15项是intel保留项,未使用
140     intr_name[16] = "#MF x87 FPU Floating-Point Error";
141     intr_name[17] = "#AC Alignment Check Exception";
142     intr_name[18] = "#MC Machine-Check Exception";
143     intr_name[19] = "#XF SIMD Floating-Point Exception";
144 }
145 
146 /* 完成有关中断的所有初始化工作 */
147 void idt_init(){
148     put_str("idt_init start\n");
149     idt_desc_init();               // 初始化中断描述符表
150     exception_init();              // 异常名初始化并注册通常的中断处理函数
151     pic_init();                    // 初始化PIC(8259A)
152     /*加载idt*/
153     uint64_t idt_operand=((sizeof(idt)-1)|((uint64_t)((uint32_t)idt<<16)));
154     asm volatile("lidt %0"::"m"(idt_operand));
155     put_str("idt_init done\n");
156 }
157 
158 /* 开中断并返回开中断前的状态 */
159 enum intr_status intr_enable(){
160     enum intr_status old_status;
161     if (INTR_ON==intr_get_status()){
162         old_status=INTR_ON;
163         return old_status;
164     }
165     else{
166         old_status=INTR_OFF;
167         asm volatile("sti");       // 开中断,sti指令将IF位置1
168         return old_status;
169     }
170 }
171 
172 /* 关中断并返回关中断前的状态 */
173 enum intr_status intr_disable(){
174     enum intr_status old_status;
175     if (INTR_ON==intr_get_status()){
176         old_status=INTR_ON;
177         asm volatile("cli":::"memory");
178         return old_status;
179     }
180     else{
181         old_status=INTR_OFF;
182         return old_status;
183     }
184 }
185 
186 /* 将中断状态设置为status */
187 enum intr_status intr_set_status(enum intr_status status){
188     return status&INTR_ON?intr_enable():intr_disable();
189 }
190 
191 /* 获取当前中断状态 */
192 enum intr_status intr_get_status(){
193     uint32_t eflags=0;
194     GET_EFLAGS(eflags);
195     return (EFLAGS_IF&eflags)?INTR_ON:INTR_OFF;
196 }
View Code
复制代码

④device/timer.c:

复制代码
 1 #include "timer.h"
 2 #include "io.h"
 3 #include "print.h"
 4 #include "../thread/thread.h"
 5 #include "../kernel/interrupt.h"
 6 #include "debug.h"
 7 
 8 #define IRQ0_FREQUENCY             100
 9 #define INPUT_FREQUENCY         1193180
10 #define COUNTER0_VALUE        INPUT_FREQUENCY / IRQ0_FREQUENCY
11 #define COUNTER0_PORT        0X40
12 #define COUNTER0_NO         0
13 #define COUNTER_MODE        2
14 #define READ_WRITE_LATCH    3
15 #define PIT_COUNTROL_PORT    0x43
16 
17 uint32_t ticks;     // ticks是内核自中断开启以来总共的嘀嗒数
18 
19 static void frequency_set(uint8_t counter_port,\
20               uint8_t counter_no,\
21               uint8_t rwl,\
22               uint8_t counter_mode,\
23               uint16_t counter_value)
24 {
25     outb(PIT_COUNTROL_PORT,(uint8_t) (counter_no<<6|rwl<<4|counter_mode<<1));
26     outb(counter_port,(uint8_t)counter_value);
27     outb(counter_port,(uint8_t)counter_value>>8);
28 }
29 
30 static void intr_timer_handler(void){
31     struct task_struct* cur_thread=running_thread();
32 
33     ASSERT(cur_thread->stack_magic==0x19870916);   // 检查栈是否溢出
34 
35     cur_thread->elapsed_ticks++;   // 记录此线程占用的cpu时间
36     ++ticks;                       // 从内核第一次处理时间中断后开始至今的嘀嗒数,内核态和用户态总共的嘀嗒数
37 
38     if (cur_thread->ticks==0){
39     schedule();
40     }else{
41         --cur_thread->ticks;
42     }
43 }
44 
45 /* 初始化PIT8253 */
46 void timer_init(void)
47 {
48     put_str("timer_init start!\n");
49     frequency_set(COUNTER0_PORT,\
50           COUNTER0_NO,\
51           READ_WRITE_LATCH,\
52           COUNTER_MODE,\
53           COUNTER0_VALUE);
54     register_handler(0x20,intr_timer_handler);
55     put_str("timer_init done!\n");
56 }
View Code
复制代码

⑤thread/switch.S:

复制代码
 1 [bits 32]
 2 section .text
 3 global switch_to
 4 switch_to:
 5    ; 栈中此处是返回地址
 6    push esi
 7    push edi
 8    push ebx
 9    push ebp
10 
11    mov eax,[esp+20]   ; 得到栈中参数cur,cur=[esp+20]
12    mov [eax],esp      ; 保存栈顶指针esp.task_struct的self_kstack字段
13                       ; self_kstack在task_struct中的偏移为0
14                       ; 所以直接往thread的开头处存4字节即可
15    ;------------ 以上是备份当前线程的环境,下面是恢复下一个线程的环境 ------------
16    mov eax,[esp+24]   ; 得到栈中的参数next,next=[esp+24] 
17    mov esp,[eax]      ; pcb的第一个成员是self_kstack成员,它用来记录0级栈顶指针,被换上cpu是用来恢复0级栈
18    ; 0级栈中保存了进程或线程的所有信息,包括3级栈指针
19    pop ebp
20    pop ebx
21    pop edi
22    pop esi
23    ret   ; 返回到上面switch_to下面的那句注释的返回地址,
24          ; 未由中断进入,第一次执行时会返回到kernel_thread
View Code
复制代码

⑥kernel/main.c:

复制代码
 1 #include "print.h"
 2 #include "init.h"
 3 #include "thread.h"
 4 #include "interrupt.h"
 5 
 6 void k_thread_a(void*);
 7 void k_thread_b(void*);
 8 
 9 int main(void) {
10    put_str("Welcome,\nI am kernel!\n");
11    init_all();
12 
13    thread_start("k_thread_a",31,k_thread_a,"argA ");
14    thread_start("k_thread_b",31,k_thread_b,"argB ");
15 
16    intr_enable();
17    
18    while(1){
19        //intr_disable();
20        put_str("Main ");
21        //intr_enable();
22    }
23    return 0;
24 }
25 
26 /* 在线程中运行的函数 */
27 void k_thread_a(void* arg){
28     /* 用void*来通用表示参数,被调用的函数知道自己需要什么类型的参数,自己转换再用 */
29     char* para=arg;
30     while (1){
31         //intr_disable();
32         put_str(para);
33         //intr_enable();
34     }
35 }
36 
37 void k_thread_b(void* arg){
38     char* para=arg;
39     while (1){
40         //intr_disable();
41         put_str(para);
42         //intr_enable();
43     }
44 }
View Code
复制代码

⑦kernel/init.c:

复制代码
 1 #include "init.h"
 2 #include "print.h"
 3 #include "interrupt.h"
 4 #include "../device/timer.h"   //相对路径
 5 #include "memory.h"
 6 #include "thread.h"
 7 
 8 /*负责初始化所有模块*/
 9 void init_all(){
10     put_str("init_all\n");
11     idt_init();   // 初始化中断
12     timer_init(); // 初始化PIT
13     mem_init();
14     thread_init();
15 }
View Code
复制代码

⑧lib/kernel/list.c:

添加头文件"stdint.h"

⑨lib/stdint.h:

复制代码
 1 #ifndef __LIB_STDINT_H
 2 #define __LIB_STDINT_H
 3 
 4 #define bool int
 5 #define NULL 0
 6 #define false 0
 7 #define true 1
 8 
 9 typedef signed char int8_t;
10 typedef signed short int int16_t;
11 typedef signed int int32_t;
12 typedef signed long long int int64_t;
13 typedef unsigned char uint8_t;
14 typedef unsigned short int uint16_t;
15 typedef unsigned int uint32_t;
16 typedef unsigned long long int uint64_t;
17 #endif
View Code
复制代码

⑩lib/kernel/print.S:

复制代码
  1 TI_GDT equ 0
  2 RPL0   equ 0
  3 SELECTOR_VIDEO equ (0x0003<<3)+TI_GDT+RPL0
  4 
  5 section .data
  6 put_int_buffer dq 0        ;定义8字节缓冲区用于数字到字符的转换
  7 
  8 [bits 32]
  9 section .text
 10    ;-------------------- put_char ---------------------
 11    ;功能描述:把栈中的1个字符写入光标所在处
 12    ;---------------------------------------------------
 13 global put_char:
 14 put_char:
 15    pushad                  ;备份32位寄存器环境,需要保证gs为正确的视频段选择子,保险起见,每次打印时都为gs赋值
 16    mov ax,SELECTOR_VIDEO
 17    mov gs,ax
 18    
 19    ;获取当前光标位置
 20    ;先获取高8位
 21    mov dx,0x03d4
 22    mov al,0x0e
 23    out dx,al
 24    mov dx,0x03d5
 25    in al,dx
 26    mov ah,al
 27    ;再获取低8位
 28    mov dx,0x03d4
 29    mov al,0x0f
 30    out dx,al
 31    mov dx,0x03d5
 32    in al,dx
 33    
 34    ;将光标存入bx
 35    mov bx,ax
 36    mov ecx,[esp+36]        ;pushad压入4*8=32B,加上主调函数4B的返回地址,故esp+36B
 37    cmp cl,0xd              ;CR是0x0d,LF是0x0a
 38    jz .is_carriage_return
 39    cmp cl,0xa
 40    jz .is_line_feed
 41   
 42    cmp cl,0x8              ;BS(backspace)的ASCII码是8
 43    jz .is_backspace
 44    jmp .put_other
 45    
 46 .is_backspace:
 47    dec bx
 48    shl bx,1                ;光标左移1位等于乘2,表示光标对应显存中的偏移字节
 49    mov byte [gs:bx],0x20   ;将待删除的字节补为0或空格皆可
 50    inc bx
 51    mov byte [gs:bx],0x07
 52    shr bx,1
 53    jmp set_cursor
 54    
 55 .put_other:
 56    shl bx,1                ;光标位置用2字节表示,将光标值*2表示对应显存中的偏移字节
 57    mov [gs:bx],cl          ;ASCII字符本身
 58    inc bx
 59    mov byte [gs:bx],0x07   ;字符属性
 60    shr bx,1                ;恢复老光标值
 61    inc bx                  ;下一个光标值
 62    cmp bx,2000             
 63    jl set_cursor          ;若光标值小于2000,表示未写到显存的最后,则去设置新的光标值,若超出屏幕字符大小(2000),则换行处理
 64 
 65 .is_line_feed:             ;换行符LF(\n)
 66 .is_carriage_return:       ;回车符CR(\r)
 67    ;如果是CR(\r),只要把光标移到行首就行了
 68    xor dx,dx               ;dx是被除数的高16位,清0
 69    mov ax,bx               ;ax是被除数的低16位
 70    mov si,80
 71    div si
 72    sub bx,dx               ;光标值取整
 73 
 74 .is_carriage_return_end:
 75    add bx,80
 76    cmp bx,2000
 77 .is_line_feed_end:         ;若是LF(\n),将光标移+80即可
 78    jl set_cursor
 79          
 80    ;屏幕行范围是0~24,滚屏的原理是将屏幕第1~24行搬运到第0~23行,再将第23行用空格填充
 81 .roll_screen:              ;若超出屏幕大小,滚屏
 82    cld
 83    mov ecx,960             ;2000-80=1920个字符,共1920*2=3840B,一次搬4B,共3840/4=960次
 84    mov esi,0xc00b80a0      ;第1行行首
 85    mov edi,0xc00b8000      ;第0行行首
 86    rep movsd
 87 
 88    ;将最后一行填充为空白
 89    mov ebx,3840
 90    mov ecx,80
 91 
 92 .cls:
 93    mov word [gs:ebx],0x0720;0x0720是黑底白字的空格键
 94    add ebx,2
 95    loop .cls
 96    mov bx,1920             ;将光标值重置为1920,最后一行首字符
 97 
 98 global set_cursor:
 99 set_cursor:
100    ;将光标设为bx值
101    ;1.先设置高8位
102    mov dx,0x03d4           ;索引寄存器
103    mov al,0x0e             ;光标位置高8位
104    out dx,al
105    mov dx,0x03d5           ;通过读写数据端口0x3d5来获得或设置光标位置
106    mov al,bh
107    out dx,al
108    
109    ;2.再设置低8位
110    mov dx,0x03d4
111    mov al,0x0f
112    out dx,al
113    mov dx,0x03d5
114    mov al,bl
115    out dx,al
116 .put_char_done:
117    popad
118    ret
119    
120    ;--------------------- put_str -----------------------
121    ;-- 功能描述:通过put_char来打印以0字符结尾的字符串 --
122    ;-----------------------------------------------------
123 global put_str:
124 put_str:
125    push ebx
126    push ecx
127    xor ecx,ecx
128    mov ebx,[esp+12]
129 .put_char_loop:
130    mov cl,[ebx]
131    cmp cl,0
132    jz .str_over
133    push ecx
134    call put_char
135    add esp,4
136    inc ebx
137    jmp .put_char_loop
138 .str_over:
139    pop ecx
140    pop ebx
141    ret
142  
143    ;--------------------- put_int ---------------------
144    ;--功能描述:将小端字节序的数字变成对应的ASCII后,倒置
145    ;----------------------------------------------------
146 global put_int
147 put_int:
148    pushad
149    mov ebp,esp
150    mov eax,[esp+36]        ;32B+4B返回地址
151    mov edx,eax
152    mov edi,7               ;put_int_buffer偏移量
153    mov ecx,8               ;循环八次
154    mov ebx,put_int_buffer
155     
156 .16based_4bits:
157    and edx,0x0000000F
158    cmp edx,9             
159    jg  .is_A2F             ;A~F的ASCII码
160    add edx,'0'             ;0~9的ASCII码
161    jmp .store    
162     
163 .is_A2F:
164    sub edx,10
165    add edx,'A'             ;减去10等于A~F的字符序+'A'得ASCII码
166     
167 .store:
168    mov [ebx+edi],dl        ;此时dl中是数字对应字符的ASCII码
169    dec edi                
170    shr eax,4
171    mov edx,eax
172    loop .16based_4bits    
173     
174 .ready_to_print:
175    inc edi                 ;edi减退为-1
176 .skip_prefix_0:            ;跳过前导0
177    cmp edi,8               ;edi偏移量为8的时候表示到了第9个字符
178    je .full0               ;前导0有8个,说明全是0
179     
180 .go_on_skip:   
181    mov cl,[put_int_buffer+edi]
182    inc edi                 ;下一个字符
183    cmp cl,'0'
184    je .skip_prefix_0       ;判断下一个字符是否为'\0'
185    dec edi                 ;不是'\0',edi减1恢复当前字符
186    jmp .put_each_num
187     
188 .full0:
189    mov cl,'0'              ;全为0,输出一个0即可
190 .put_each_num:
191    push ecx
192    call put_char
193    add esp,4
194    inc edi                 ;使edi指向下一个字符
195    mov cl,[put_int_buffer+edi]
196    cmp edi,8
197    jl .put_each_num
198    popad 
199    ret 
View Code
复制代码

⑪makefile:

复制代码
 1 BUILD_DIR = ./build
 2 ENTRY_POINT = 0xc0001500
 3 AS = nasm
 4 CC = gcc
 5 LD = ld
 6 LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/ -I thread/
 7 ASFLAGS = -f elf
 8 CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
 9 LDFLAGS =  -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
10 OBJS = $(BUILD_DIR)/main.o  $(BUILD_DIR)/init.o   $(BUILD_DIR)/interrupt.o \
11       $(BUILD_DIR)/timer.o  $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
12       $(BUILD_DIR)/debug.o  $(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o \
13       $(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o $(BUILD_DIR)/list.o \
14       $(BUILD_DIR)/switch.o
15 ##############     c代码编译     ###############
16 $(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
17         lib/stdint.h kernel/init.h lib/string.h kernel/memory.h \
18     thread/thread.h kernel/interrupt.h
19     $(CC) $(CFLAGS) $< -o $@
20 
21 $(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
22         lib/stdint.h kernel/interrupt.h device/timer.h kernel/memory.h \
23     thread/thread.h
24     $(CC) $(CFLAGS) $< -o $@
25 
26 $(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
27         lib/stdint.h kernel/global.h lib/kernel/io.h lib/kernel/print.h
28     $(CC) $(CFLAGS) $< -o $@
29 
30 $(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/stdint.h\
31         lib/kernel/io.h lib/kernel/print.h thread/thread.h kernel/interrupt.h \
32     kernel/debug.h
33     $(CC) $(CFLAGS) $< -o $@
34 
35 $(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
36         lib/kernel/print.h lib/stdint.h kernel/interrupt.h
37     $(CC) $(CFLAGS) $< -o $@
38 
39 $(BUILD_DIR)/string.o: lib/string.c lib/string.h \
40     kernel/debug.h kernel/global.h
41     $(CC) $(CFLAGS) $< -o $@
42 
43 $(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \
44     lib/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h
45     $(CC) $(CFLAGS) $< -o $@
46 
47 $(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h \
48     lib/string.h kernel/interrupt.h lib/kernel/print.h kernel/debug.h
49     $(CC) $(CFLAGS) $< -o $@
50 
51 $(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \
52     lib/stdint.h lib/string.h kernel/global.h kernel/memory.h \
53     kernel/interrupt.h kernel/debug.h lib/kernel/print.h
54     $(CC) $(CFLAGS) $< -o $@
55 
56 $(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h \
57     kernel/interrupt.h lib/stdint.h kernel/debug.h
58     $(CC) $(CFLAGS) $< -o $@
59 
60 ##############    汇编代码编译    ###############
61 $(BUILD_DIR)/kernel.o: kernel/kernel.S
62     $(AS) $(ASFLAGS) $< -o $@
63 
64 $(BUILD_DIR)/print.o: lib/kernel/print.S
65     $(AS) $(ASFLAGS) $< -o $@
66 
67 $(BUILD_DIR)/switch.o: thread/switch.S
68     $(AS) $(ASFLAGS) $< -o $@
69 
70 ##############    链接所有目标文件    #############
71 $(BUILD_DIR)/kernel.bin: $(OBJS)
72     $(LD) $(LDFLAGS) $^ -o $@
73 
74 .PHONY : mk_dir hd clean all
75 
76 mk_dir:
77     if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi
78 
79 hd:
80     dd if=$(BUILD_DIR)/kernel.bin \
81            of=hd60M.img \
82            bs=512 count=200 seek=9 conv=notrunc
83 
84 clean:
85     cd $(BUILD_DIR) && rm -f  ./*
86 
87 build: $(BUILD_DIR)/kernel.bin
88 
89 all: mk_dir build hd
View Code
复制代码

其实应该是还有一些也要修改的,但是调试太多太久了,有些缝缝补补改动不大的我都不太记得了,就贴出这些吧,大家可以根据make all后的报错信息来更新。

试着运行一下:

是的,直接就卡死了。。

one afternoon later...

最后终于发现原来是main()函数里面的thread_start()中要传“argA ”,字符串的后面是有个空格的!可恶啊,差点就要回溯上一章的版本了,这也提醒我和各位要及时备份当前的代码QAQ

当我把空格补上:

这样应该就是了,如果大家还不放心,可以尝试把我的main.c中注释的代码放开,再运行,如下:

如果持续输出argA、argB、Main那应该就真的可以放心了。


同样是在完成项目后的本章总结:本章我们实现了内核线程的运行和调度,从单线程的多线程,就直接来说说多线程的整个过程吧(考虑到线程+进程较复杂,就不把后续的进程部分加进来了,先只说线程):

1.初始化线程环境——thread_init()

(1)就绪队列thread_ready_list的初始化和全局队列thread_all_list的初始化。

thread_ready_list放置所有处于ready状态的线程,thread_all_list则放置所有的线程。

(2)将当前的main()创建为线程——make_main_thread()

①将PCB指针main_thread指向正在运行的线程的PCB(因为在loader.S中就已经mov esp,0xc009f000,因此PCB地址为0xc009e000)。为什么要这样做?因为把握了PCB其实也就把握了整个thread

②初始化该线程——init_thread()——将PCB结构体中的内容清0,再为其中的name、status、self_kstack、priority、ticks、elapsed_ticks、pgdir、stack_magic等属性一一赋值。特别地,self_stack指向线程的内核栈栈顶=0xc009f000;(elapsed_)ticks为多线程调度所用,ticks减到0就调度线程;pgdir为进程的页目录的虚拟地址,本章我们实现的线程,所以置NULL即可。

③由于main()线程为当前正在运行的线程,因此将其加入thread_all_list,而不必加入thread_ready_list。

2.在main()中创建其它内核线程——thread_start()

(1)使该线程的PCB指向一页新开辟的内核空间(开辟方式:get_kernel_pages(1))。

(2)同样地,初始化该线程——init_thread()

(3)初始化线程栈kthread_stack——thread_create()——线程栈的主要作用就是记录我们将要执行的代码

①预留中断栈空间,pthread->self_kstack-=sizeof(struct intr_stack);。

②留出线程内核栈空间,pthread->self_kstack-=sizeof(struct thread_stack);。

③为线程内核栈赋值:eip=kernel_thread()的函数指针(当然,这是函数第一次执行的时候,此后,eip就指向的是该函数上次运行到的位置),在kernel_thread()中执行开中断和main()中具体的函数;function=main()中具体的函数的指针;func_arg=函数参数;ebp=ebx=esi=edi=0。

(4)将当前线程加入thread_ready_list(因为正在执行的是main()的线程)和thread_all_list中。

此时,我们就已经准备好了所有的线程,就等着调度执行了。

3.在timer.c中,我们获得了当前进程的PCB,将其--ticks,一旦ticks==0,就执行调度——schedule()

(1)判断调度原因:若此线程只是CPU时间片到了,就将其加入thread_ready_list,重置线程优先级和状态;其它情况暂不考虑。

(2)弹出thread_ready_list的队首并调度上CPU——switch_to()(switch.S中的汇编函数):

这里我也没有看得很很懂,简单来说就是:切换线程栈!

最后再说说所谓的将线程放入队列是怎么一回事儿:可不是把PCB放入队列,但也差不多,是把PCB中的成员——tag(list的node的实例化)放入队列,代表一个线程。PCB转tag就直接取成员变量即可;若要把tag还原成PCB,则需要elem2entry()宏定义,将tag的地址减去tag在PCB中的offset,即可获得该PCB的首地址。


参考博客:


结束语:

本来今天还想再多看看本章的代码的,结果一调试就到了晚上,时间也不多了,明天应该会再复习这些代码。看着日益庞大的代码量,深深感觉一旦后期有点失误,可能就会导致整个项目难以维护,找到出错点将十分困难,以后更是要倍加小心。与此同时,看到自己手下诞生了如此大量的代码,成就感也是油然而生,也更有信心和信念要将整个os做出来,加油吧。

再加一句:真的好累,一坐就是7小时,仿佛看到了以后的当码农的生活,不过人生就是要为热爱的事物献出自己的全部,不是吗?

posted @   Hell0er  阅读(210)  评论(0编辑  收藏  举报
编辑推荐:
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示