当前位置: 首页 > news >正文

13 Day:实现内核线程

前言:我们昨天完成了内核的内存池以及内存管理程序,今天我们要揭开操作系统多任务执行的神秘面纱,来了解并实现一个多任务的操作系统。


一,实现内核线程 

在聊线程之间我们先聊聊处理器吧,众所周之现在我们的CPU动不动就是几核的,所以大家理所应当的认为他的多任务是处理器之间并行完成的。但是实际上以前的计算机只有单核,那任务不可能串行执行吧,如果任务A执行一天,任务B执行2分钟,那我要为这个2分钟的B等A一天也太不值得了。于是便有了任务调度器这一玩意儿来实现任务之间来回切换,实现伪并行。


 1,任务调度器

任务调度器就是把任务轮流调度上处理器运行的一个软件模块,他是操作系统的一部分,调度器中维护一个任务表,按照一定算法从中选定任务,然后放在处理器上面运行,当时间片到达的时候就重新找一个任务放上去,周而复始。

2,执行流

  • 执行流就是一段逻辑上独立的指令区域,是人为给处理器安排的处理单元
  • 任何代码块,无论大小都可以独立成为执行流,我们只需提前准备好他的上下文环境即可
  • 执行流就是进程线程

3,线程和进程 

① 线程

线程就是运行函数的另一种方式。


线程与函数的区别?

线程是作为调度单元(执行流)在处理器上运行的,处理器能“看到”执行流。函数是伴随着调度单元顺带在处理器上执行的,处理器并不能看到函数

 打个比方:

在餐馆中,一道菜需要食品+盘子组合起来,就相当于一个调度单位。用户点的宫保鸡丁这道菜就相当于是执行一个线程,而做菜需要需多材料,鸡,花生米这些都是顺带的这些就是函数。如果你想要吃花生米吃的过瘾,可以单点一道花生米,就可以让该函数变成线程。

② 进程 = 线程 + 资源

进程是运行的程序,即进行中的程序,程序必须获取运行所需的各类资源才能成为进程


进程是一种控制流集合,集合至少包括一条执行流,也就是说虽说有单线程进程和多线程进程,但是单线程进程也是拥有一个执行流的,你可以认为线程是进程的并行


线程资源容器

每个进程都有自己独立的资源空间,也就是说进程与进程之间的资源空间很难共享,但是线程是没有自己独立的资源空间的,他是“寄生"在进程之中的,也就是说使用的是进程中的资源,所以线程之间的资源是共享的也就容易发生线程安全问题


打个比方:

在餐馆中,厨子,配菜员,清洁工这些就是线程,而餐馆就是进程,这些员工各司其职,使用餐馆中的资源。

 4,任务

任务就是指大的执行流单线程进程,或者是小的执行流线程

而只有线程才具备能动性,他才是处理器的执行单元,是调度器眼中调度资源

5,PCB

PCB是进程的身份证,方便操作系统识别,这里简单说一下寄存器映像,其就是保存进程的”现场“,所有寄存器的值都将保存于此,而栈是进程所使用的0特权级的内核栈,寄存器映像的位置随着栈指针变动而变动。 

6,内核态线程与用户态线程

线程实现有两种方式一个是内核态线程一个是用户态线程

  • 用户态线程:用户态线程是指在用户特权级空间下实现,也就是由用户实现线程调度器,线程机制等等,一般是由某个权威机构实现封装代码库由用户自己去调用

优点:每次开辟线程,上下文切换无需陷入内核

缺点:

  • 在操作系统内核层面他并不认识线程,也就是说当进程中有线程阻塞,一般是进行系统调用等等,整个进程都会挂起
  • 对于任务调度器来说,他并不认识线程,只认识进程,也就是说会出现一种情况,如果用户的线程调度器没有实现好,导致一个线程独占CPU,就会导致这个进程中只会有这个线程能进行处理直到时间片耗尽。
  • 内核态线程:内核态线程是指在0特权级的特权级空间下实现,线程机制由内核提供

优点:

  • 线程表与进程表都由内核管理,线程和进程都作为执行流来轮流使用CPU,这样使得进程的占用率大大提高,比如进程A有4个线程,进程B有1个线程,一共五个线程轮流执行,这样进程A就使用了80%的资源大大提速了
  • 线程阻塞不会导致进程挂起,很简单操作系统是认识线程的,他会把线程和进程都当作执行流,这样一个线程阻塞就可以执行进程的另一个线程。


二,编写内核线程

首先我们已经知道了线程切换和调度实际上就是切换执行流,那既然是切换执行流改变CS或者EIP的值,我们可以用哪些指令呢,call?jmp?ret?。不卖关子了,这次我们使用ret指令,也就是返回指令,再讲函数调用之前 我们首先来聊聊ABI

ABI:ABI即应用程序二进制接口,比我们所熟知的API还要底层,他规定了参数如何传递,返回值如何存储,系统调用的实现方式。

在这里我们切换执行流时,需要调用其他线程的函数,这个时候便形成了主调函数被调函数的关系,此时我们主调函数要维护五个寄存器 ebp,ebx,edi,esi,esp。被调函数维护其余的寄存器。

所以将 线程的函数地址压入栈中,然后函数地址位于栈顶时,利用ret返回栈最上层的函数地址,就形成了执行流的变化

 


1,线程实现 

忘记介绍了我们的线程是拥有状态的,状态大概有这么几种

其中最主要的是RUNNING和READY

RUNNING:正在运行的线程

READY:准备就绪的线程,刚刚创建出来的线程或者时间片到了的线程都可以称位就绪线程

enum task_status {TASK_RUNNING,TASK_READY,TASK_BLOCKED,TASK_WAITING,TASK_TIMEWAITING,TASK_HANGINH,TASK_DIED
};

 thread/thread.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_TIMEWAITING,TASK_HANGINH,TASK_DIED
};
/*中断栈*/
struct intr_stack {uint32_t vec_no;		//中断号uint32_t edi;uint32_t esi;uint32_t ebp;uint32_t esp_dummy;uint32_t ebx;uint32_t edx;uint32_t ecx;uint32_t eax;uint32_t gs;uint32_t fs;uint32_t es;uint32_t ds;uint32_t err_code;void(*eip) (void);uint32_t cs;uint32_t eflags;void* esp;uint32_t ss;
};/*线程栈 thread_stack*/
struct thread_stack {uint32_t ebp;uint32_t ebx;uint32_t edi;uint32_t esi;//线程第一次执行时,eip指向待调用的函数kernel_thread其他的时候指向switch_to的返回地址void(*eip) (thread_func* func, void* func_arg);//以下仅第一次被调度上CPU使用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;			//每次在处理器上执行的时间//uint32_t elapsed_ticks;	//此任务已经执行的时间//struct list_elem general_tag;	//线程在一般队列中的节点//struct list_elem all_list_tag;	//线程队列thread_all_list的节点//uint32_t* pgdir;uint32_t stack_magic; //标记栈溢出
};struct task_struct* thread_start(char* name, int prio, thread_func function, void* func_arg);
#endif // !_THREAD_THREAD_H

thread/thread.c 

#include "thread.h"
#include "stdint.h"
#include "string.h"
#include "global.h"
#include "memory.h"
//#include "list.h"
#define PG_SIZE 4096/*
struct tasl_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);static void kernel_thread(thread_func* function, void* func_arg) {//intr_enable(); //打开时钟,防止时钟中断被屏蔽function(func_arg);
}void thread_create(struct task_struct* pthread, thread_func function, void* func_arg) {//预留中断栈空间和线程栈空间pthread->self_kstack -= sizeof(struct intr_stack);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);pthread->status = TASK_RUNNING;pthread->priority = prio;//指向PCB顶端pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE);pthread->stack_magic = 0x19870916; //自定义魔数 作为边缘数检测是否出现栈溢出
}struct task_struct* thread_start(char* name, int prio, thread_func function, void* func_arg) {struct task_struct* thread = get_kernel_pages(1);init_thread(thread, name, prio);thread_create(thread, function, func_arg);//使相应的值弹入相应的寄存器,然后用ret调用eip中的方法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"//注意这里不能将g_thread放在main上面,否则会改变main函数入口地址出现错误
void g_thread(void* arg);void main(void) {put_str("Hello GeniusOS\n");put_int(2023);put_str("\n");init_all();thread_start("genius", 5, g_thread, "genius");intr_enable();while (1) {put_str("Main ");}
}void g_thread(void* arg) {char* para = arg;while (1) {put_str(para);}
}

(makefile省略相信你们应该会添加,最后我再放出全部的)

编译运行,运行结果,出现以下结果就说明成功了,但是现在还不是时候庆祝我们接着往下走。


三,双向链表--管理任务的关键数据结构

双向链表我就不多说了,如果你们学过链表的话,双向链表就是多了一个指向前节点的指针,就是这么简单,该部分的链表功能可以自己实现,也可以直接copy我的代码,这里不是重点,所以我贴代码直接跳过了。如果你不知道链表的话,建议点击链接了解一下:链表

lib/kernel/list.h

#ifndef __LIB_KERNEL_LIST_H
#define __LIB_KERNEL_LIST_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
{struct list_elem head; // 亘古不变的头部struct list_elem tail; // 亘古不变的尾部
};typedef bool (function)(struct list_elem*, int arg);void list_init(struct list*);
void insert(struct list_elem* before, struct list_elem* elem);
void push(struct list* plist, struct list_elem* elem);
void append(struct list* plist, struct list_elem* elem);
void remove(struct list_elem* pelem);
struct list_elem* pop(struct list* plist);
bool empty(struct list* plist);
uint32_t len(struct list* plist);
struct list_elem* traversal(struct list* plist, function func, int arg);
bool find(struct list* plist, struct list_elem* obj_elem);#endif

 lib/kernel/list.c

#include "list.h"
#include "interrupt.h"
#include "stdint.h"
#include "debug.h"#define NULL 0//初始化双向链表
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 insert(struct list_elem* before, struct list_elem* elem)
{enum intr_status old_status = intr_disable();elem->next = before;elem->prev = before->prev;before->prev->next = elem;before->prev = elem;intr_set_status(old_status);}//添加元素到链表队首
void push(struct list* plist, struct list_elem* elem)
{insert(plist->head.next, elem);
}//添加元素到链表队尾
void append(struct list* plist, struct list_elem* elem)
{insert(&plist->tail, elem);
}//让pelem脱离链表
void remove(struct list_elem* pelem)
{enum intr_status old_status = intr_disable();pelem->prev->next = pelem->next;pelem->next->prev = pelem->prev;intr_set_status(old_status);
}//让链表的第一个元素脱离链表
struct list_elem* pop(struct list* plist)
{ASSERT(plist->head.next != &plist->tail);struct list_elem* ret = plist->head.next;remove(plist->head.next);return ret;
}bool empty(struct list* plist)
{return (plist->head.next == &plist->tail ? true : false);
}uint32_t len(struct list* plist)
{uint32_t ret = 0;struct list_elem* next = plist->head.next;while (next != &plist->tail){next = next->next;++ret;}return ret;
}struct list_elem* traversal(struct list* plist, function func, int arg)
{struct list_elem* elem = plist->head.next;if (empty(plist))	return NULL;while (elem != &plist->tail){if (func(elem, arg))	return elem;elem = elem->next;}return NULL;
}bool find(struct list* plist, struct list_elem* obj_elem)
{struct list_elem* ptr = plist->head.next;while (ptr != &plist->tail){if (ptr == obj_elem)	return true;ptr = ptr->next;}return false;
}

四,实现多线程调度 

🆗,我们正式来进行多线程调度的编写,这里我们来理一下整个多线程调度的结构

策略: RR算法(时间片轮转算法),根据线程的时间片来轮流调度线程,当线程执行完便放入就绪队列。

队列:就绪队列和运行队列(main函数一开始就在运行队列中)

执行流程:在进行多线程调度的时候需要打开中断,由时钟中断不断计算时间片,然后触发中断事件,进行线程调度切换。


1,改造线程

话不多说我们先来改造我们的thread代码

thread/thread.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_TIMEWAITING,TASK_HANGINH,TASK_DIED
};
/*中断栈*/
struct intr_stack {uint32_t vec_no;		//中断号uint32_t edi;uint32_t esi;uint32_t ebp;uint32_t esp_dummy;uint32_t ebx;uint32_t edx;uint32_t ecx;uint32_t eax;uint32_t gs;uint32_t fs;uint32_t es;uint32_t ds;uint32_t err_code;void(*eip) (void);uint32_t cs;uint32_t eflags;void* esp;uint32_t ss;
};/*线程栈 thread_stack*/
struct thread_stack {uint32_t ebp;uint32_t ebx;uint32_t edi;uint32_t esi;//线程第一次执行时,eip指向待调用的函数kernel_thread其他的时候指向switch_to的返回地址void(*eip) (thread_func* func, void* func_arg);//以下仅第一次被调度上CPU使用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;			//每次在处理器上执行的时间uint32_t elapsed_ticks;	//此任务已经执行的时间struct list_elem general_tag;	//线程在一般队列中的节点struct list_elem all_list_tag;	//线程队列thread_all_list的节点uint32_t* pgdir;        //页表的虚拟地址uint32_t stack_magic; //标记栈溢出
};struct task_struct* thread_start(char* name, int prio, thread_func function, void* func_arg);
void schedule();
void thread_init(void);
#endif // !_THREAD_THREAD_H​

我们在PCB中添加了以下属性:

ticks:该任务的时间片

elapsed_ticks:已经执行的时间

general_tag与all_list_tag在之后会介绍

stack_magic:PCB最后的元素,他是一个魔数,每次切换线程时都会检测魔数完整性,如果完整性错误说明栈溢出了


/kernel/thread.c

#include "thread.h"
#include "stdint.h"
#include "string.h"
#include "global.h"
#include "memory.h"
#include "list.h"
#include "interrupt.h"
#include "debug.h"
#define PG_SIZE 4096struct 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(void)
{uint32_t esp;asm("mov %%esp,%0" : "=g"(esp));return (struct task_struct*)(esp & 0xfffff000);
}void schedule() {//判断是否关闭中断ASSERT(intr_get_status() == INTR_OFF);//如果线程在Task-RUNNING状态就进入就绪队列struct task_struct* runing_task = running_thread();if (runing_task->status == TASK_RUNNING) {ASSERT(!find(&thread_ready_list, &runing_task->general_tag));runing_task->status = TASK_READY;runing_task->ticks = runing_task->priority;append(&thread_ready_list, &runing_task->general_tag);}else {/*还没设计捏*/}ASSERT(!empty(&thread_ready_list));thread_tag = NULL;thread_tag = pop(&thread_ready_list);struct task_struct* next = elem2entry(struct task_struct, general_tag, thread_tag);next->status = TASK_RUNNING;switch_to(runing_task, next);}static void kernel_thread(thread_func* function, void* func_arg) {intr_enable(); //打开时钟,防止时钟中断被屏蔽function(func_arg);
}void thread_create(struct task_struct* pthread, thread_func function, void* func_arg) {//预留中断栈空间和线程栈空间pthread->self_kstack -= sizeof(struct intr_stack);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) {pthread->status = TASK_RUNNING;}else {pthread->status = TASK_READY;}pthread->priority = prio;pthread->ticks = prio;pthread->elapsed_ticks = 0;pthread->pgdir = NULL;//指向PCB顶端pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE);pthread->stack_magic = 0x66666666; //自定义魔数 作为边缘数检测是否出现栈溢出
}struct task_struct* thread_start(char* name, int prio, thread_func function, void* func_arg) {struct task_struct* thread = get_kernel_pages(1);init_thread(thread, name, prio);thread_create(thread, function, func_arg);//队列检查ASSERT(!find(&thread_ready_list, &thread->general_tag));append(&thread_ready_list, &thread->general_tag);ASSERT(!find(&thread_all_list, &thread->all_list_tag));append(&thread_all_list, &thread->all_list_tag);//使相应的值弹入相应的寄存器,然后用ret调用eip中的方法/*asm volatile("movl %0, %%esp; \pop %%ebp; pop %%ebx; pop %%edi; pop %%esi; \ret": : "g" (thread->self_kstack) : "memory");*/return thread;
}/*将kernel的main函数完善为主线程
*/
static void make_main_thread(void) {/*主线程的PCB早在内存管理时就已经为其预留地址为0xc009e000,所以无需分配页*/main_thread = running_thread();init_thread(main_thread, "main", 31);/*main函数时当前线程不能再ready_list中*/ASSERT(!find(&thread_all_list, &main_thread->all_list_tag));append(&thread_all_list, &main_thread->all_list_tag);
}void thread_init(void) {put_str("thread_init start\n");list_init(&thread_ready_list);list_init(&thread_all_list);make_main_thread();put_str("thread_init done");
}

  • running_thread

 我们之前有说过,线程的PCB内存地址严格按照 0xXXXXX000~0xXXXXXfff,所以获取当前栈指针的位置再和0xfffff000相与便可以得到当前线程PCB的地址

  • thread_start

 在这里我们便可以说说general_tagall_list_tag的作用了,大家有没有想过为什么我们链表不存放PCB,而是一个个tag,首先各各线程在内存中是离散的我们需要用链表将其联系起来。其次大家想想一个PCB有多大?4KB,我们链表节点全部都是一个个4KB的PCB未免有点小材大用了,于是乎我们便直接使用其中的general_tagall_list_tag作为各个链表联系,那么有人会问了,你不连PCB你怎么获取线程中PCB的内容啊?!,别急我们接着往下看

 

在list.h中我们定义了offset和elem2entry函数, 其作用是获取一个属性在结构体位置中的偏移,大家想想  当前这个属性的位置-在该属性结构体偏移 是不是就等于 结构体的地址 ,所以根据这个函数我们可以通过tag来获取PCB的位置。

        

 

 


 2,实现任务调度和任务切换

线程每次在处理器的执行时间有ticks决定,每产生一次时钟中断就将ticks-1,当ticks为0时则返回就绪队列。

(1) 时钟中断处理函数

先修改interrupt.c常规函数添加中断注册函数

static void general_intr_handler(uint8_t vec_nr) {if (vec_nr == 0x27 || vec_nr == 0x2f) {return;}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);if (vec_nr == 14) {int page_fault_vaddr = 0;asm("movl %%cr2,%0": "=r" (page_fault_vaddr));		//缺页问题会将导致PageFault的虚拟地址存访到CR2中put_str("\npage fault addr is ");put_int(page_fault_vaddr);}put_str("!!!! excetion message begin !!!!\n");while (1);
}//注册中断
void register_intr(uint32_t vectr, intr_handler func,char* name) {idt_table[vectr] = func;intr_name[vectr] = name;
}

 device/time.c

#include "timer.h"
#include "io.h"
#include "print.h"
#include "interrupt.h"
#include "thread.h"
#include "debug.h"
#define IRQ0_FREQUENCY 100
#define INPUT_FREQUENCY 1193180
#define COUNTER0_VALUE INPUT_FREQUENCY/IRQ0_FREQUENCY
#define COUNTER0_PORT 0x40
#define COUNTER0_NO 0
#define COUNTER_MODE 2
#define READ_WRITE_LATCH 3
#define PIT_CONTROL_PORT 0x43uint32_t ticks;	//ticks是内核自中断开启以来总共的滴答声static void intr_timer_handler(void) {struct task_struct* cur_thread = running_thread();ASSERT(cur_thread->stack_magic == 0x66666666);cur_thread->elapsed_ticks++;ticks++;if (cur_thread->ticks == 0) {schedule();}else {cur_thread->ticks--;}
}/** 初始化频率 **/
static void frequency_set(uint8_t counter_port,uint8_t counter_no,uint8_t rw,uint8_t mode,uint16_t counter_value) {outb(PIT_CONTROL_PORT, (uint8_t)(counter_no<<6 | rw<<4 |mode<<1)); //规定PIT的工作模式outb(counter_port, (uint8_t)counter_value);outb(counter_port, (uint8_t)counter_value >> 8);
}/** 初始化timer **/
void timer_init() {put_str("timer_init start\n");frequency_set(COUNTER0_PORT,COUNTER0_NO,READ_WRITE_LATCH,COUNTER_MODE,COUNTER0_VALUE);register_intr(0x20, intr_timer_handler,"time");put_str("timer_init done\n");
}

(2) 调度器 schedule

就是前面thread的schedule()函数


(3) 任务切换函数switch_to

接下来便是我们的重头戏,switch_to函数

首先我们要明白一点为什么要保护任务的上下文,每个任务都有一个执行流,按道理来说应该是从头执行到尾部,结果临时改道,是不是要保护原有的资源和路径才能恢复执行。那么问题又来了,这种“改道”可能是深度多层的,也就是有点类似于递归不断向下执行,那我们的任务切换设计了几层呢 ?

① 首先我们来逐步分析,一开始我们任务时间片到了后,进入时钟中断程序,此时是第一层,该层我们要保护整个任务的上下文环境所有寄存器都需要保存,这个在kernel.S的中断汇编文件中已经实现,此时任务进入内核态

② 由中断进入switch函数要在进行一次执行流变道,此时我们遵循ABI原则,来保护主调函数需要保存的那4个寄存器即可

 

 thread/switch.S

[bits 32]
section .text
global switch_to
switch_to:;保存内核栈push esipush edipush ebxpush ebp;得到栈中参数mov eax,[esp+20]	;获取cur,将cur的esp指针保存在self_kstack中mov [eax],esp;上半部分是保护cur线程的栈数据,下半部分是恢复next的栈数据mov eax,[esp+24]	;得到next参数mov esp,[eax]		;pcb的第一个成员self_kstackpop ebppop ebxpop edipop esiret		;  此时的ret执行的是栈顶的kernel_thread

inti.c

#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "../device/timer.h"
#include "memory.h"
#include "thread.h"
void init_all(void) {put_str("init all\n");idt_init();timer_init();mem_init();thread_init();
}

main.c

#include "print.h"
#include "init.h"
#include "debug.h"
#include "thread.h"void g_thread(void* arg);
void g_thread2(void* arg);
void main(void) {put_str("Hello GeniusOS\n");put_int(2023);put_str("\n");init_all();thread_start("genius", 5, g_thread, "genius");thread_start("genius2", 31, g_thread2, "genius2");intr_enable();while (1) {put_str("Main ");}
}void g_thread(void* arg) {char* para = arg;while (1) {put_str(para);}
}void g_thread2(void* arg) {char* para = arg;while (1) {put_str(para);}
}

 运行后的结果就是这样啦,好的今天的任务就到此结束了!

http://www.lryc.cn/news/3041.html

相关文章:

  • GPU服务器安装显卡驱动、CUDA和cuDNN
  • 结构体变量
  • Java 多态
  • 九龙证券|一夜暴跌36%,美股走势分化,标普指数创近2月最差周度表现
  • 【数据库】 mysql用户授权详解
  • 【性能】性能测试理论篇_学习笔记_2023/2/11
  • C语言(输入printf()函数)
  • Zabbix 构建监控告警平台(四)
  • 2004-2019年285个地级市实际GDP与名义GDP
  • Node.js笔记-Express(基于Node.js的web开发框架)
  • 力扣sql简单篇练习(十五)
  • 浅谈动态代理
  • Idea超好用的管理工具ToolBox(附带idea工具)
  • Spring 中 ApplicationContext 和 BeanFactory 的区别
  • 情人节有哪些数码好物值得送礼?情人节实用性强的数码好物推荐
  • java中flatMap用法
  • 【MySQL Shell】8.9.2 InnoDB ClusterSet 集群中的不一致事务集(GTID集)
  • logstash毫秒时间戳转日期以及使用业务日志时间戳替换原始@timestamp
  • 【C语言】qsort——回调函数
  • 8年软件测试工程师经验感悟
  • 腾讯云安全组配置参考版
  • 代码覆盖率工具OpenCppCoverage在Windows上的使用
  • 代码随想录算法训练营第24天25天|● 77. 组合● 216.组合总和III ● 17.电话号码的字母组合
  • Python_pytorch
  • 【Java|golang】2335. 装满杯子需要的最短总时长
  • shell编程之sed
  • 安全寒假作业nginx反向代理+负载均衡上传webshell重难点+apache漏洞
  • day35|01背包问题、416. 分割等和子集
  • Linux内核启动(3,0.11版本)内核启动完成与进入内核main函数
  • 【2023】Prometheus-Alertmanager高可用集群