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

093b4198c362c58b70c7ba4805503997.png

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字符(当然 此处没有放图 因为后续开发还在进行中 无法测试之前的代码了)

下一节预告:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/503025.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

打开git界面_使用 Gitea 快速搭建私有 Git 版本控制服务

1. 前言分布式版本控制工具 Git 已经是现代软件源代码版本控制首选方案之一。公有 Git 服务提供商 国外知名如 GitHub 国内网络延迟高,Gitlab 涉嫌对中国的歧视不推荐。国内有 Gitee、Coding 生态还不错。但是一般公司的源代码除非开源项目是不会放在公有 Git 服务上的。所以我…

dev项目属性按钮是灰色_Spring Boot 中的项目属性配置

阅读本文约需要5分钟大家好,我是你们的导师,我每天都会在这里给大家分享一些干货内容(当然了,周末也要允许老师休息一下哈)。上次老师跟大家分享了Spring Boot 如何使用 SLF4J 进行日志记录,今天跟大家分享一下 Spring Boot 中的项…

diskgenius扩容c盘重启电脑卡住_电脑开机显示:reboot and select proper boot device怎么办?...

今天就碰到有一个知友问,自己电脑开机就提示:reboot and select proper boot device,整个人都懵了,不知道怎么办?其实对于电脑出现问题,大家不要着急,坚哥就来为大家分析下原因以及试着去解决。第一种原因…

大并发下程序出错_Python并发编程理论篇

前言其实关于Python的并发编程是比较难写的一章,因为涉及到的知识很复杂并且理论偏多,所以在这里我尽量的用一些非常简明的语言来尽可能的将它描述清楚,在学习之前首先要记住一个点: 并发编程永远的宗旨就是提高程序的运行效率&am…

月薪30K程序员花了一个小时,用c++做出经典扫雷游戏 !

上次发过一个俄罗斯方块的游戏源码,由于是通过Easy X实现的,但是很多和我一样的新手,一开始不知道Easy X是什么,到时源码拿过去之后,运行报错,我这次发的扫雷, 也是通过Easy X实现,…

用python写web网页_从零开始,使用python快速开发web站点(1) | 学步园

环境:ubuntu 12.04 python版本: 2.73 ok,首先,既然是从零开始,我们需要的是一台可以运行的python的计算机环境,并且假设你已经安装好了python, 然后,既然是快速开发,必不可少的需要用到框架,py…

修改so_货代、海运操作、船务操作还分不清?船公司SO文件看不懂?

货代是货物代理(freight forwarding agent)的简称,是指经营受他人委托,为其提供代办运输手续,代提、代发、代运货物服务的业务。货物代理,有些是中间商就是自己没有船或者飞机的或者船公司、航空公司&#…

一行代码蒸发64亿人民币!黑客盯上区块链漏洞!Python真的变态!

此前认为,区块链技术由于分布存储、加密算法等技术的应用,拥有了不可篡改、可追溯等被认为是“万无一失”的特性。然而,该特性主要针对存储在区块中的信息来说,以文中开头的案例为例,区块链技术保障了可以追溯到这64亿…

太吾绘卷第一世攻略_建平中学高二数学周练卷(2020.09)

试卷图片仅供学习交流使用,答案仅供参考【往期内容】高一是坎, 高二是坡, 高三是峰! 最全学习攻略新高一数学教材必修第一册第一章习题答案往年高中9月开学考月考数学试卷汇总2020上海高考复交综评录取率top202020北京大学强基计划数学试题2020上海16区零志愿、名额…

自动补足算法是什么_数据、算法岗的几点经验分享!

learners | 作者Datawhale | 来源目录有哪些好的秋招经验分享?机器学习中常用的最优化方法有哪些?想通过数据竞赛来提升实践能力,作为小白有什么入门经验?(今日问题)有哪些好的秋招经验分享?1李玲 - 携程算法工程师(…

我精心珍藏的Python代码技巧

01.****简洁的表达式 image 点评:Python因为简洁高效而出名,就是因为语法非常简单,而且内置了很多强大的数据结构: 比如我们可以大量用推导列表来生成很多简洁的代码 比如我们可以用if else组合,本来需要2-3行代码写…

python函数和类的区别_Python中类中的方法还有区别?

上一篇,我们讲到Python类中的属性是有区别的,爱思考的小伙伴们可能就会问了,Python中的方法是不是也有区别呢?是的,而且Python中的方法区别更大,让我们来看看吧~ Python类中的三种方法 Python中的方法可以分…

python 获取昨天的日期_利用Python来实现报表的自动发送,解放你时间去做更有意思的事情...

前言在日常工作中你可能因为每天都被各种各样的数据数据报表搞得焦头烂额,老板的,运营的、产品的等等。而且大部分报表都是重复性的工作,这篇文章就是帮助大家如何用Python来实现报表的自动发送,解放你的劳动力,可以让…

大数据 深度 分页_机器学习、深度学习、大数据 ?傻傻分不清楚?

提起机器学习四个字,不知你的脑海中是否会有一丝印象?毕竟身处信息时代,在日常生活中,无论通过什么媒介,接触到这个名词概念的机会还是挺大的。与之类似,还有以下这些名词概念:数据分析、数据挖…

jieba结巴分词--关键词抽取_结巴中文分词原理分析2

作者:白宁超,工学硕士,现工作于四川省计算机研究院,著有《自然语言处理理论与实战》一书,作者公众号:机器学习和自然语言处理(公众号ID:datathinks)结巴分词详解1中文分词介绍中文分词特点词是最…

cad动态块制作翻转_定制橱柜家具中CAD门型动态块制作方式图文讲解加视频

在定制橱柜家具中,我们用CAD我们经常会用一些门型放置到绘图当中,来展示CAD图纸的美观性,如下图:那么这些门型要是一点点画却是麻烦,所以我们可以把门型做成动态快的形式,对门型进行任意拉伸,方…

python json库安装_python怎么安装requests库

requests是python实现的简单易用的HTTP库,使用起来比urllib简洁很多 因为是第三方库,所以使用前需要cmd安装pip install requests 安装完成后import一下,正常则说明可以开始使用了。 基本用法: requests.get()用于请求目标网站&am…

32获取外部中断状态_Linux中断一网打尽(1) — 中断及其初始化

1中断是什么既然叫中断, 那我们首先就会想到这个中断是中断谁?想一想计算机最核心的部分是什么?没错, CPU, 计算机上绝大部分的计算都在CPU中完成,因此这个中断也就是中断CPU当前的运行,让CPU转而先处理这个…

文件夹_【教程】创建透明文件夹(非隐藏文件夹哦)

Hello 今天 不夜君 来教大家如何创建 一个 透明的 文件夹 效果类似这样哦~~是不是什么都看不到呢 大家不要慌 下面来看一下选中后的样子呀~~怎么样 是不是十分的酷炫呢其实这个方法的原理 我也不是很懂 所以就不献丑了 直接附上教程吧!!当然 有多种实现方…

山东栋梁机器人比赛_谁是最强“移动机器人”?来深技师这场全国大赛一决高下!...

9月24日第一届全国技能大赛世赛移动机器人项目全国机械行业选拔赛在深圳技师学院闭幕!为期三天的赛程中来自全国职业院校、机械行业相关企业的40支参赛队伍、130多名选手和专家教练相聚在深技师美丽的鹤鸣湖畔展开了激烈的比拼~点击边框调出视频工具条 ▲ 点击视频&…