1.多线程原理:

(1)概述:

多线程是指CPU可以在一段时间中并行执行多个程序,比如我们可以一边听音乐、一边写代码(这两个程序可以“同时进行”,我们称之为多进程,而多进程实现的本质就是内核多线程)。实际上,针对多核CPU,可以将这两个程序放在不同的核上边执行,但是单核CPU没法这样,所以单核CPU的多线程只能在一个核上边短时间切换执行程序任务,由于切换的速度很快,这样使用者会感受到多个任务似乎是同时执行的。(当然,多核CPU也会快速切换任务,毕竟可以同时执行的程序是比CPU核数量多的)

(2)线程上下文:

线程的执行需要用到多个通用寄存器,并且为了安全性考虑,每个线程的物理空间应该是隔离的,所以每个线程的执行也需要各自的物理空间,由于线程执行的栈空间是必要的,所以每个线程的esp、ss等寄存器的值也是不同的。线程的上下文实际上就是指的这一系列寄存器的数据,当我们切换线程执行时,我们也需要同时将现在线程的上下文保存起来,再将目标线程的上下文加载到寄存器中,这个过程我们称之为线程上下文切换

同样,线程的切换也是伴随着执行流的改变,而执行流的改变是通过修改cs以及eip寄存器实现的,这也就是线程上下文切换的内容之一。

(3)TCB

每个线程需要对应一个线程控制结构块(TCB),TCB存放了线程的相关信息,比如线程ID等等,并且TCB可以存放线程切换上下文。所有线程的TCB会串起来存放(单向链表),而线程控制模块的功能就是维护这个链表。

2.动手实现吧

内核线程的内容比较少,实现起来也非常简单

/include/thread.h

#ifndef THREADS_H
#define THREADS_H
#include "types.h"
typedef             //定义线程或进程状态
enum task_status_t{     TASK_RUNNING,TASK_READY,TASK_BLOCKED,TASK_WAITING,TASK_HANGING,TASK_DIED
} task_status_t;typedef
struct context_t{       //存放在内核栈中的任务上下文uint32_t ebp;uint32_t ebx;uint32_t ecx;uint32_t edx;uint32_t esi;uint32_t edi;uint32_t eflags;uint32_t esp;     //esp是保存在kern_stack_top中的
} __attribute__((packed)) context_t;       //由于要在汇编中使用 要编译成连续的分布typedef
struct TCB_t{uint32_t * kern_stack_top;    //对应的内核栈顶地址task_status_t task_status;  uint32_t time_counter;      //记录运行总的时钟中断数uint32_t time_left;         //剩余时间片struct TCB_t * next;        //下一个TCB(用于线程调度)//uint32_t idt_addr;        在用户进程中使用的页表uint32_t tid;               //线程iduint32_t page_counte;       //分配的页空间大小uint32_t page_addr;            //page_counte与page_addr用于释放内存context_t context;
} TCB_t;typedef void * thread_function(void * args);       //定义线程的实际执行函数类型extern void switch_to(void * cur_coontext_ptr,void * next_context_ptr);    //使用汇编完成的切换上下文函数extern uint32_t get_esp();void schedule();void create_thread(uint32_t tid,thread_function *func,void * args,uint32_t addr,uint32_t page_counte); //创建线程函数void threads_init();    //线程模块初始化 需要把主线程加入运行表中void exit();     //线程结束函数 关闭中断->移出执行链表->回收内存空间->开启中断
#endif

task_status_t枚举类定义了线程的状态,不过目前我们简单的实现当中只用到了RUNNING(正在运行中)。 context_t定义了线程上下文,主要是线程切换需要保存的几个寄存器。TCB_T定义了线程的TCB结构。

/kernel/init/threads.c

#include "threads.h"
#include "types.h"
#include "pmm.h"
#include "printk.h"
#define TIME_CONT  2 //默认时间片计数
TCB_t main_TCB;    //内核主线程TCB
TCB_t* cur_tcb;void threads_init(){TCB_t *tcb_buffer_addr = &main_TCB;tcb_buffer_addr->tid = 0;        //主线程的编号为0  tcb_buffer_addr->time_counter=0;tcb_buffer_addr->time_left=TIME_CONT;tcb_buffer_addr->task_status = TASK_RUNNING;tcb_buffer_addr->page_counte=0;   //主线程不会被回收内存 所以可以任意赋值tcb_buffer_addr->page_addr=0;tcb_buffer_addr->next = tcb_buffer_addr;tcb_buffer_addr->kern_stack_top=0;cur_tcb = tcb_buffer_addr;
}uint32_t create_TCB(uint32_t tid,uint32_t page_addr,uint32_t page_counte){TCB_t * tcb_buffer_addr = (TCB_t*)page_addr;tcb_buffer_addr->tid = tid;         tcb_buffer_addr->time_counter=0;tcb_buffer_addr->time_left=TIME_CONT;tcb_buffer_addr->task_status = TASK_RUNNING;tcb_buffer_addr->page_counte=page_counte; tcb_buffer_addr->page_addr=page_addr;tcb_buffer_addr->kern_stack_top=page_addr+page_counte*4096;return page_addr;
}void create_thread(uint32_t tid,thread_function *func,void *args,uint32_t addr,uint32_t page_counte){  asm volatile("cli");  //由于创建过程会使用到共享的数据 不使用锁的话会造成临界区错误 所以我们在此处关闭中断TCB_t * new_tcb = create_TCB(tid,addr,page_counte);TCB_t * temp_next = cur_tcb->next;cur_tcb->next = new_tcb;new_tcb->next = temp_next;*(--new_tcb->kern_stack_top)=args;     //压入初始化的参数与线程执行函数*(--new_tcb->kern_stack_top)=exit;*(--new_tcb->kern_stack_top)=func;new_tcb->context.eflags = 0x200;new_tcb->context.esp =new_tcb->kern_stack_top;asm volatile("sti");
}void schedule(){      //调度函数  检测时间片为0时调用此函数if(cur_tcb->next==cur_tcb){cur_tcb->time_left = TIME_CONT;    //如果只有一个线程 就再次给此线程添加时间片return ;}TCB_t *now = cur_tcb;TCB_t *next_tcb = cur_tcb->next;next_tcb->time_left = TIME_CONT;cur_tcb = next_tcb;get_esp();      //有一个隐藏bug 需要call刷新寄存器switch_to(&(now->context),&(next_tcb->context));
}void remove_thread(){asm volatile("cli");if(cur_tcb->tid==0)printk("ERRO:main thread can`t use function exitn");else{TCB_t *temp = cur_tcb;for(;temp->next!=cur_tcb;temp=temp->next);temp->next = cur_tcb->next;}
}void exit(){remove_thread();TCB_t *now = cur_tcb;TCB_t *next_tcb = cur_tcb->next;next_tcb->time_left = TIME_CONT;cur_tcb = cur_tcb->next;switch_to(&(now->context),&(next_tcb->context));//注意 暂时没有回收此线程页
}#undef TIME_CONT

main_TCB即目前正在运行的主线程的上下文,其初始化是在thread_init函数中实现的。cur_tcb是指向目前正在运行的线程的控制块的指针。schedule函数用于将线程切换到cur_tcb中的next对应的PCB的线程,最终线程的切换是通过switch_to实现的,这个函数定义在后边一个汇编文件中。其他剩下的函数是用来维护TCB链表的(增加 删除 修改等操作)。值得注意的一点:每个线程都有自己的栈,而栈的分配是在create_thread中通过pmm模块分配对应物理内存页。

但是,一些函数是不能用c语言实现的,比如上下文切换中会涉及到寄存器的访问。

/kernel/init/threads_asm.s

;内核线程模块的汇编函数文件
[bits 32]
[GLOBAL get_esp]
get_esp:mov eax,espret
[GLOBAL get_eflags]
get_eflags:pushfpop eaxret
[GLOBAL switch_to]
switch_to:;保存上下文mov [eax+28],espmov eax,[esp+4]     ;第一个参数 mov [eax],ebpmov [eax+4],ebxmov [eax+8],ecxmov [eax+12],edxmov [eax+16],esimov [eax+20],edipush ebxmov ebx,eaxpushfpop eaxmov [ebx+24],eaxmov eax,ebxpop ebx;加载上下文mov eax,[esp+8]      ;第二个参数mov esp,[eax+28]mov ebp,[eax]mov ebx,[eax+4]mov ecx,[eax+8]mov edx,[eax+12]mov esi,[eax+16]mov edi,[eax+20]add eax,24push dword [eax] ;eflagspopf    ;由于8259a设置的手动模式 所以必须给主片与从片发送信号 否则8259a会暂停;这个bug找了一下午才找到 顺便吐槽下 内核级的代码debug太难了(GDB在多线程与汇编级会失效 只有print调试法) mov al,0x20         out 0xA0,alout 0x20,alret                  ;执行下一个函数

switch_to的实现比较简单,函数传入的第一个参数是正在执行的线程的上下文,第二个参数是要切换到的函数的上下文。

当然,由于线程的切换是由时钟中断驱动的,所以要修改时钟中断的处理函数

extern TCB_t * cur_tcb;
//时钟中断函数 主要用于线程调度
void timer_server_func(void *args){if(cur_tcb->time_left!=0){(cur_tcb->time_left)--;(cur_tcb->time_counter)++;}else{schedule();}
}

在时钟中断时,会检查现在正在执行的线程的TCB时间片是否为0,为0的话就会切换到下一个函数执行,否者,就会将时间片-1

最后,来测试一下多线程:

/kernel/entry.c

#include "types.h"
#include "vga_basic.h"
#include "printk.h"
#include "init.h"
#include "pmm.h"
#include "threads.h"
void clear_screen();
void kputc(char);
void screen_uproll_once();
uint32_t get_eflags();
extern TCB_t * cur_tcb;
extern TCB_t main_TCB;
void kern_entry(){void func(void* args);vga_init();idt_init();  pmm_init();threads_init();create_thread(1,(thread_function *)func,0,pmm_alloc_one_page().addr,1);asm volatile ("sti");   //要在主线程加载完后开中断while(True){asm volatile("cli");printk("A");asm volatile("sti");}while(True)asm volatile ("hlt");
}void func(void* args){while(True){asm volatile("cli");printk_color("B",15,0);asm volatile("sti");}
}

最后的测试结果应该是交替打印出A和B字符(当然 此处没有放图 因为后续开发还在进行中 无法测试之前的代码了)

下一节预告:

实现虚拟内存管理,为实现用户进程做准备

多线程操作时操作系统时间片_从零开始自制操作系统(15):内核多线程相关推荐

  1. 操作系统源代码_计算机自制操作系统(八):仿生DOS操作系统源代码

    一.真机运行 我们已经完成了仿生DOS操作系统的制作,并在上一章的末尾给大家在虚拟机上做了演示.今天,我们要将该操作系统在真机上启动运行,是不是非常期待自己做出的第一款比较有意义的操作系统? 在&qu ...

  2. 《操作系统真象还原》从零开始自制操作系统 自写源码实现 (fs相关文件)

    文章目录 专栏博客链接 fs相关文件 编写完的dir.c 编写完的dir.h 编写完的file.c 编写完的file.h 编写完的fs.c 编写完的fs.h 编写完的inode.c 编写完的inode ...

  3. 【操作系统】30天自制操作系统--(27)文件操作

    本章主要介绍了对 _alloca 函数的兼容,日文的显示,以及着重介绍了文件系统操作. 一 对_alloca的支持 首先作者写了一个小应用程序,功能是找出并打印1000以内的质数: #include ...

  4. 《操作系统真象还原》从零开始自制操作系统 全流程记录

    文章目录 前引 章节博客链接 实现源码链接 前引 这本<操作系统真象还原>里面一共有十五个章节 大约760页 这些系列博客也是我在做完哈工大操作系统Lab之后 觉得还是有些朦朦胧胧 毅然决 ...

  5. 【操作系统】30天自制操作系统--(14)多任务1

    本章开始多任务的设计. 一 多任务的说明 多任务(multitask),指的是操作系统中,多个应用程序同时运行的状态.然而,对于单核CPU来说,同一个瞬间只能处理一个事情,不能做到左右互搏.一心二用的 ...

  6. 【操作系统】30天自制操作系统--(9)叠加处理

    这一章主要是处理之前遇到的图层叠加的问题.[操作系统]30天自制操作系统--(7)鼠标移动与32位切换 一 内存管理优化 上一章的内存管理虽然写好,但是还是有不完善的地方.因为如果不对申请内存的大小有 ...

  7. 【操作系统】30天自制操作系统--(18)应用程序

    本章主要介绍了文件处理的相关操作以及尝试制作第一个应用程序hlt. 一 type命令 与Linux里面的type命令不同,Windows命令行中的type命令是用来查看文件内容的.在自制的操作系统中, ...

  8. java多线程写在哪一层_面试知识点三:Java多线程

    35.并行和并发有什么区别? 36.线程和进程的区别? 37.守护线程是什么? 38.创建线程有哪几种方式? 39.说一下 runnable 和 callable 有什么区别? 40.线程有哪些状态? ...

  9. 操作系统原理_读懂操作系统之缓存原理(cache)(三)

    本节内容计划是讲解TLB与高速缓存的关系,但是在涉及高速缓的前提是我们必须要了解操作系统缓存原理,所以提前先详细了解下缓存原理,我们依然是采取循序渐进的方式来解答缓存原理,若有叙述不当之处,还请批评指 ...

最新文章

  1. Sql Server 2005 ROW_NUMBER 函数实现分页
  2. 国货之光业务增长背后的技术支持 - 完美日记的云原生实践
  3. ICMP协议抓包分析-wireshark
  4. python获取输入框内容长度_python3 tkinter 获取输入字符串长度
  5. java hive 查询语句,使用java连接hive,并执行hive语句详解
  6. 快速准备电子设计大赛
  7. C语言讲义——字符串
  8. mysqld_safe启动mysql
  9. 连接数据库是显示无法连接到服务器,数据库无法连接到服务器怎么办(解决服务器连接故障的技巧)...
  10. Sketch 52.2 轻量易用的矢量设计工具(下载) Sketch汉化
  11. 使用wps把word格式文件转换成pdf文件
  12. OpenCV开发笔记(六十一):红胖子8分钟带你深入了解Shi-Tomasi角点检测(图文并茂+浅显易懂+程序源码)
  13. 英语学习APP案例分析
  14. 703. 数据流中的第K大元素
  15. 一文带你深入了解,什么是深度学习及其工作原理
  16. 一款非常nice的国产U盘启动制作工具——Ventoy
  17. 受力分析软件_学了那么多力学,怎么还是不会做有限元分析?
  18. python2.7安装教程windowsxp_怎么在windows xp 下安装python 2.7
  19. pandas数据分析常用的一些方法
  20. 一个时代的终结:为什么是时候放弃ITOM四大巨头了?这对IT领导者来说意味着什么?

热门文章

  1. table数据表 边框特效
  2. [转]该学Java或.NET?
  3. 王道计算机网络 网络层整理 超详细版
  4. Java中文件的创建
  5. 2017年网易校招题 买苹果
  6. python爬去朋友圈_利用Python爬取朋友圈数据,爬到你开始怀疑人生
  7. C++ char数组和char*的输入
  8. QT学习笔记(八):顺序容器和关联容器
  9. String和STL的一些基础知识
  10. Dom4j完整教程~DOM4J简介