《操作系统真象还原》第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
②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 }
③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 }
④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 }
⑤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
⑥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 }
⑦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 }
⑧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
⑩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
⑪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
其实应该是还有一些也要修改的,但是调试太多太久了,有些缝缝补补改动不大的我都不太记得了,就贴出这些吧,大家可以根据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的首地址。
参考博客:
- 《操作系统真象还原》第九章 ---- 终进入线程动斧开刀 豁然开朗拨云见日 还需解决同步机制才能长舒气_Love 6的博客-CSDN博客
- 操作系统真象还原实验记录之实验十五:多线程调度_mxy990811的博客-CSDN博客
结束语:
本来今天还想再多看看本章的代码的,结果一调试就到了晚上,时间也不多了,明天应该会再复习这些代码。看着日益庞大的代码量,深深感觉一旦后期有点失误,可能就会导致整个项目难以维护,找到出错点将十分困难,以后更是要倍加小心。与此同时,看到自己手下诞生了如此大量的代码,成就感也是油然而生,也更有信心和信念要将整个os做出来,加油吧。
再加一句:真的好累,一坐就是7小时,仿佛看到了以后的当码农的生活,不过人生就是要为热爱的事物献出自己的全部,不是吗?
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库