新加坡网站建设鲜花团购网站建设

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

新加坡网站建设,鲜花团购网站建设,品牌商品怎么做防伪网站,什么叫网站外链前言#xff1a;我们昨天完成了内核的内存池以及内存管理程序#xff0c;今天我们要揭开操作系统多任务执行的神秘面纱#xff0c;来了解并实现一个多任务的操作系统。 一#xff0c;实现内核线程
在聊线程之间我们先聊聊处理器吧#xff0c;众所周之现在我们的CPU动不动… 前言我们昨天完成了内核的内存池以及内存管理程序今天我们要揭开操作系统多任务执行的神秘面纱来了解并实现一个多任务的操作系统。 一实现内核线程  在聊线程之间我们先聊聊处理器吧众所周之现在我们的CPU动不动就是几核的所以大家理所应当的认为他的多任务是处理器之间并行完成的。但是实际上以前的计算机只有单核那任务不可能串行执行吧如果任务A执行一天任务B执行2分钟那我要为这个2分钟的B等A一天也太不值得了。于是便有了任务调度器这一玩意儿来实现任务之间来回切换实现伪并行。 1任务调度器 任务调度器就是把任务轮流调度上处理器运行的一个软件模块他是操作系统的一部分调度器中维护一个任务表按照一定算法从中选定任务然后放在处理器上面运行当时间片到达的时候就重新找一个任务放上去周而复始。 2执行流 执行流就是一段逻辑上独立的指令区域是人为给处理器安排的处理单元任何代码块无论大小都可以独立成为执行流我们只需提前准备好他的上下文环境即可执行流就是进程和线程3线程和进程  ① 线程 线程就是运行函数的另一种方式。 线程与函数的区别 线程是作为调度单元执行流在处理器上运行的处理器能“看到”执行流。函数是伴随着调度单元顺带在处理器上执行的处理器并不能看到函数  打个比方 在餐馆中一道菜需要食品盘子组合起来就相当于一个调度单位。用户点的宫保鸡丁这道菜就相当于是执行一个线程而做菜需要需多材料鸡花生米这些都是顺带的这些就是函数。如果你想要吃花生米吃的过瘾可以单点一道花生米就可以让该函数变成线程。 ② 进程 线程 资源 进程是运行的程序即进行中的程序程序必须获取运行所需的各类资源才能成为进程 进程是一种控制流集合集合至少包括一条执行流也就是说虽说有单线程进程和多线程进程但是单线程进程也是拥有一个执行流的你可以认为线程是进程的并行 线程资源容器 每个进程都有自己独立的资源空间也就是说进程与进程之间的资源空间很难共享但是线程是没有自己独立的资源空间的他是“寄生在进程之中的也就是说使用的是进程中的资源所以线程之间的资源是共享的也就容易发生线程安全问题 打个比方 在餐馆中厨子配菜员清洁工这些就是线程而餐馆就是进程这些员工各司其职使用餐馆中的资源。 4任务 任务就是指大的执行流单线程进程或者是小的执行流线程 而只有线程才具备能动性他才是处理器的执行单元是调度器眼中调度资源 5PCB PCB是进程的身份证方便操作系统识别这里简单说一下寄存器映像其就是保存进程的”现场“所有寄存器的值都将保存于此而栈是进程所使用的0特权级的内核栈寄存器映像的位置随着栈指针变动而变动。  6内核态线程与用户态线程 线程实现有两种方式一个是内核态线程一个是用户态线程 用户态线程用户态线程是指在用户特权级空间下实现也就是由用户实现线程调度器线程机制等等一般是由某个权威机构实现封装代码库由用户自己去调用优点每次开辟线程上下文切换无需陷入内核 缺点 在操作系统内核层面他并不认识线程也就是说当进程中有线程阻塞一般是进行系统调用等等整个进程都会挂起对于任务调度器来说他并不认识线程只认识进程也就是说会出现一种情况如果用户的线程调度器没有实现好导致一个线程独占CPU就会导致这个进程中只会有这个线程能进行处理直到时间片耗尽。内核态线程内核态线程是指在0特权级的特权级空间下实现线程机制由内核提供优点 线程表与进程表都由内核管理线程和进程都作为执行流来轮流使用CPU这样使得进程的占用率大大提高比如进程A有4个线程进程B有1个线程一共五个线程轮流执行这样进程A就使用了80%的资源大大提速了线程阻塞不会导致进程挂起很简单操作系统是认识线程的他会把线程和进程都当作执行流这样一个线程阻塞就可以执行进程的另一个线程。二编写内核线程 首先我们已经知道了线程切换和调度实际上就是切换执行流那既然是切换执行流改变CS或者EIP的值我们可以用哪些指令呢call?jmp?ret?。不卖关子了这次我们使用ret指令也就是返回指令再讲函数调用之前 我们首先来聊聊ABI ABIABI即应用程序二进制接口比我们所熟知的API还要底层他规定了参数如何传递返回值如何存储系统调用的实现方式。 在这里我们切换执行流时需要调用其他线程的函数这个时候便形成了主调函数和被调函数的关系此时我们主调函数要维护五个寄存器 ebpebxediesiesp。被调函数维护其余的寄存器。 所以将 线程的函数地址压入栈中然后函数地址位于栈顶时利用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_Hthread/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);#endiflib/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.htypedef 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_magicPCB最后的元素他是一个魔数每次切换线程时都会检测魔数完整性如果完整性错误说明栈溢出了 /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_tag与all_list_tag的作用了大家有没有想过为什么我们链表不存放PCB而是一个个tag首先各各线程在内存中是离散的我们需要用链表将其联系起来。其次大家想想一个PCB有多大4KB我们链表节点全部都是一个个4KB的PCB未免有点小材大用了于是乎我们便直接使用其中的general_tag和all_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_no6 | rw4 |mode1)); //规定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,[esp20] ;获取cur将cur的esp指针保存在self_kstack中mov [eax],esp;上半部分是保护cur线程的栈数据下半部分是恢复next的栈数据mov eax,[esp24] ;得到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.hvoid 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);} } 运行后的结果就是这样啦好的今天的任务就到此结束了