`

《30天自制操作系统》样章 多任务(1)——挑战任务切换(harib12a)

阅读更多

多任务(1) --挑战任务切换( harib12a)

  “话说,多任务到底是啥呢?”我们今天的内容,就从这个问题开始吧。

  多任务,在英语中叫做 “multitask”,顾名思义就是“多个任务”的意思。简单地说,在Windows等操作系统中,多个应用程序同时运行的状态(也就是同时打开好几个窗口的状态)就叫做多任务。

  对于生活在现代社会的各位来说,这种多任务简直是理所当然的事情。比如你会一边用音乐播放软件听音乐一边写邮件,邮件写到一半忽然有点东西要查,便打开 Web浏览器上网搜索。这对于大家来说这些都是家常便饭了吧。可如果没有多任务的话会怎么样呢?想写邮件的时候就必须关掉正在播放的音乐,要查东西的时候就必须先保存写到一半的邮件,然后才能打开 Web浏览器……光想象一下就会觉得太不方便了。

  然而在从前,没有多任务反倒是普遍的情形(那个时候大家不用电脑听音乐,也没有互联网)。在那个年代,电脑一次只能运行一个程序,如果要同时运行多个程序的话,就得买好几台电脑才行。

  就在那个时候,诞生了昀初的多任务操作系统,大家都觉得太了不起了。从现在开始,我们也要准备给“纸娃娃系统”添加执行多任务的能力了。连这样一个小不点儿操作系统都能够实现多任务,真是让人不由地感叹它生逢其时呀。

  稍稍思考一下我们就会发现,多任务这个东西还真是奇妙,它究竟是怎样做到让多个程序同时运行的呢?如果我们的电脑里面装了好多个 CPU的话,同时运行多个程序倒也顺理成章,但实际上就算我们只有一个 CPU,照样可以实现多任务。

  其实说穿了,这些程序根本没有在同时运行,只不过看上去好像是在同时运行一样:程序 A运行一会儿,接下来程序 B运行一会儿,再接下来轮到程序 C,然后再回到程序 A……如此反复,有点像日本忍者的“分身术”呢(笑)。

  为了让这种分身术看上去更完美,需要让操作系统尽可能快地切换任务。如果 10秒才切换一次,那就连人眼都能察觉出来了,同时运行多个程序的戏码也就穿帮了。再有,如果我们给程序 C发出一个按键指令,正巧这个瞬间系统切换到了程序 A的话,我们就不得不等上 20秒,才能重新轮到程序 C对按键指令作出反应。这实在是让人抓狂啊(哭)。




  在一般的操作系统中,这个切换的动作每 0.01~0.03秒就会进行一次。当然,切换的速度越快,让人觉得程序是在同时运行的效果也就越好。不过, CPU进行程序切换(我们称为“任务切换”)这个动作本身就需要消耗一定的时间,这个时间大约为 0.0001秒左右,不同的 CPU及操作系统所需的时间也有所不同。如果 CPU每0.0002秒切换一次任务的话,该 CPU处理能力的 50%都要被任务切换本身所消耗掉。这意味着,如果同时运行 2个程序,每个程序的速度就只有单独运行时的1/4,这样你会觉得开心吗?如果变成这种结果,那还不如干脆别搞多任务呢。

  相比之下,即便是每 0.001秒切换一次任务,单单在任务切换上面也要消耗 CPU处理能力的 10%。大概有人会想, 10%也没什么大不了的吧?可如果你看看速度快 10%的CPU卖多少钱,说不定就会恍然大悟,“对啊,只要优化一下任务切换间隔,就相当于一分钱也不花,便换上了比现在更快的 CPU嘛……”(笑),你也就明白了浪费 10%也是很不值得的。正是因为这个原因,任务切换的间隔昀短也得 0.01秒左右,这样一来只有 1%的处理能力消耗在任务切换上,基本上就可以忽略不计了。

  关于多任务是什么的问题,已经大致讲得差不多了,接下来我们来看看如何让 CPU来处理多任务。

  当你向CPU发出任务切换的指令时, CPU会先把寄存器中的值全部写入内存中,这样做是为了当以后切换回这个程序的时候,可以从中断的地方继续运行。接下来,为了运行下一个程序, CPU会把所有寄存器中的值从内存中读取出来(当然,这个读取的地址和刚刚写入的地址一定是不同的,不然就相当于什么都没变嘛),这样就完成了一次切换。我们前面所说的任务切换所需要的时间,正是对内存进行写入和读取操作所消耗的时间。

  接下来我们来看看寄存器中的内容是怎样写入内存里去的。下面这个结构叫做“任务状态段”(task status segment),简称TSS。TSS有16位和32位两个版本,这里我们使用 32位版。顾名思义, TSS也是内存段的一种,需要在 GDT中进行定义后使用。



  参考上面的结构定义,TSS共包含26个int成员,总计 104字节(摘自 CPU的技术资料),我特意把它们分成 4行来写。从开头的 backlink起,到 cr3为止的几个成员,保存的不是寄存器的数据,而是与任务设置相关的信息,在执行任务切换的时候这些成员不会被写入( backlink除外,某些情况下是会被写入的)。后面的部分中我们会用到这里的设定,不过现在你完全可以先忽略它。

  第2行的成员是 32位寄存器,第 3行是16位寄存器,应该没必要解释了吧……不对, eip好像到现在还没讲过呢。 EIP的全称是“extended instruction pointer”,也就是“扩展指令指针寄存器”的意思。这里的“扩展”代表它是一个 32位寄存器,也就是说其对应的 16位版本叫做 IP,类比一下的话,跟 EAX与AX之间的关系是一样的。

  EIP是CPU用来记录下一条需要执行的指令位于内存中哪个地址的寄存器,因此它才被称为
“指令指针”。如果没有这个寄存器,记性不好的 CPU就会忘记自己正在运行哪里的程序,于是程序就没办法正常运行了。每执行一条指令, EIP寄存器中的值就会自动累加,从而保证一直指向下一条指令所在的内存地址。

  说点题外话, JMP指令实际上是一个向 EIP寄存器赋值的指令。 JMP 0x1234这种写法, CPU会解释为 MOV EIP,0x1234,并向 EIP赋值。也就是说,这条指令其实是篡改了 CPU记忆中下一条该执行的指令的地址,蒙了 CPU一把。这样一来, CPU在读取下一条指令时,就会去读取 0x1234这个地址中的指令。你看,这不就相当于是做了一个跳转吗?

  对了,如果你在汇编语言里用 MOV EIP,0x1234这种写法是会出错的,还是不要尝试的好。在汇编语言中,应该使用 JMP 0x1234来代替MOV EIP,0x1234。


  如果在TSS中将EIP寄存器的值记录下来,那么当下次再返回这个任务的时候, CPU就可以明白应该从哪里读取程序来运行了。

  按照常识,段寄存器应该是 16位的才对,可是在 TSS数据结构中却定义成了 int(也就是 DWORD)类型。我们可以大胆想象一下,说不定英特尔公司的人将来会把段寄存器变成 32位的,这样想想也挺有意思的呢(笑)。

  第4行的ldtr和iomap也和第1行的成员一样,是有关任务设置的部分,因此在任务切换时不会被CPU写入。也许你会想,那就和第 1行一样,暂时先忽略好了——但那可是绝对不行的!如果胡乱赋值的话,任务就无法正常切换了,在这里我们先将ldtr置为0,将iomap置为0x40000000就好了。

  关于TSS的话题暂且先告一段落,我们回来继续讲任务切换的方法。要进行任务切换,其实还得用JMP指令。 JMP指令分为两种,只改写 EIP的称为near模式,同时改写 EIP和CS的称为far模式,在此之前我们使用的 JMP指令基本上都是 near模式的。不记得 CS是什么了? CS就是代码段(code segment)寄存器啦。

  说起来我们其实用过一次 far模式的JMP指令,就在 asmhead.nas的“bootpack启动”的昀后一句(见8.5节)。 MP DWORD 2*8:0x0000001b 这条指令在向 EIP存入0x1b的同时,将CS置为2*8(=16)。像这样在 JMP目标地址中带冒号( :)的,就是far模式的 JMP指令。

  如果一条 JMP指令所指定的目标地址段不是可执行的代码,而是 TSS的话, CPU就不会执行通常的改写 EIP和CS的操作,而是将这条指令理解为任务切换。也就是说, CPU会切换到目标 TSS所指定的任务,说白了,就是 JMP到一个任务那里去了。

  CPU每次执行带有段地址的指令时,都会去确认一下 GDT中的设置,以便判断接下来要执行的 JMP指令到底是普通的 far-JMP,还是任务切换。也就是说,从汇编程序翻译出来的机器语言来看,普通的 far-JMP和任务切换的 far-JMP,指令本身是没有任何区别的。

  好了,枯燥的讲解就到这里,让我们实际做一次任务切换吧。我们准备两个任务:任务 A和任务B,尝试从 A切换到B。

  

  现在两个TSS都创建好了,该进行实际的切换了。

  我们向TR寄存器存入3 * 8这个值,这是因为我们刚才把当前运行的任务定义为 GDT的3号。 TR寄存器以前没有提到过,它的作用是让 CPU记住当前正在运行哪一个任务。当进行任务切换的时候,TR寄存器的值也会自动变化,它的名字也就是“ task register”(任务寄存器)的缩写。我们每次给 TR寄存器赋值的时候,必须把 GDT的编号乘以 8,因为英特尔公司就是这样规定的。如果你有意见的话,可以打电话找英特尔的大叔投诉哦(笑)。

  给TR寄存器赋值需要使用 LTR指令,不过用 C语言做不到。唉,各位是不是都已经见怪不怪了啊?啥?你早就料到了?(笑)所以说,正如你所料,我们只能把它写进 naskfunc.nas里面。






  对了,LTR指令的作用只是改变 TR寄存器的值,因此执行了 LTR指令并不会发生任务切换。要进行任务切换,我们必须执行 far模式的跳转指令,可惜 far跳转这事 C语言还是无能为力,这种语言还真是不方便啊。没办法,这个函数我们也得在 naskfunc.nas里创建。



  也许有人会问,在 JMP指令后面写个 RET有意义吗?也对,通常情况下确实没意义,因为已经跳转到别的地方了嘛,后面再写什么指令也不会被执行了。不过,用作任务切换的 JMP指令却不太一样,在切换任务之后,再返回这个任务的时候,程序会从这条 JMP指令之后恢复运行,也就是执行JMP后面的RET,从汇编语言函数返回,继续运行 C语言主程序。

  另外,如果 far-JMP指令是用作任务切换的话,地址段(冒号前面的 4*8的部分)要指向 TSS这一点比较重要,而偏移量(冒号后面的 0的部分)并没有什么实际作用,会被忽略掉,一般来说像这样写 0就可以了。

  现在我们需要在 HariMain的某个地方来调用 taskswitch(),可到底该写在哪里呢?唔,有了,就放在显示“ 10[sec]”的语句后面好了。也就是说,程序启动 10秒以后进行任务切换。



大功告成了?不对,我们还没准备好 tss_b呢。在任务切换的时候需要读取 tss_b的内容,因此我们得在TSS中定义好寄存器的初始值才行。




  乍看之下,貌似会有很多看不懂的地方吧,我们从后半段对寄存器赋值的地方开始看。这里我们给cs置为GDT的2号,其他寄存器都置为 GDT的1号,asmhead.nas的时候也是一样的。也就是说,我们这次使用了和bootpack.c相同的地址段。当然,如果你用别的地址段也没问题,不过这次我们只是想随便做个任务切换的实验而已,这种麻烦的事情还是以后再说吧。

  继续看剩下的部分,关于 eflags的赋值,如果把 STI后的EFLAGS的值通过 io_load_eflags赋给变量的话,该变量的值就显示为 0x00000202,因此在这里就直接使用了这个值,仅此而已。如果还有看不懂的地方,大概就是 eip和esp的部分了吧。

。。。。。。

 在eip中,我们需要定义在切换到这个任务的时候,要从哪里开始运行。在这里我们先把 task_b_main这个函数的内存地址赋值给它。



  这个函数只执行了一个 HLT,没有任何实际作用,后面我们会对它进行各种改造,现在就先这样吧。

  task_b_esp是专门为任务 B所定义的栈。有人可能会说,直接用任务 A的栈不就好了吗?那可不行,如果真这么做的话,栈就会混成一团,程序也无法正常运行。



  总之先写成这个样子了。我们为任务B的栈分配了64KB的内存,并计算出栈底的内存地址。
请各位回忆一下向栈PUSH数据(入栈)的动作,ESP中存入的应该栈末尾的地址,而不是栈开
头的地址。

  好了,我们已经讲解得够多了,现在总算是万事俱备啦,马上“make run”一下吧。这个程
序如果运行正常的话应该是什么样子呢?嗯,启动之后的10秒内,还是跟以前一样的,10秒一到便执行任务切换,task_b_main开始运行。因为task_b_main只有一句HLT,所以接下来程序就全部停止了,鼠标和键盘也应该都没有反应了。

  唔……这样看起来好像很无聊啊,算了,总之我们先来“make run”吧。10秒钟的等待还真
是漫长……哇!停了停了!

  看来我们的首次任务切换获得了圆满成功。

分享到:
评论

相关推荐

    30天自制操作系统综合大设计harib12g.zip

    30天自制操作系统最后的实验验收,将近两千行的代码,两点在于开机动画的帧数多,密码,双击,时钟绘制的好看,直线的绘制,可以用拖动的方式绘制矩形,还可以通过输入的方式改变绘制的大小,以及窗口颜色来改变颜色

    harib11i.rar

    本程序是修改原书30天自制操作系统第14天中的最后一个代码,增加了一些自己的图层,实现了五子棋人机对战、时钟计时、开机动画等功能。

    harib02e.zip

    纸娃娃操作系统源代码,可打开!cons_nt.bat后输入make run运行,可改编(这个就是改编的,原作者是川合秀实,原作是纸娃娃系统,增加了一些功能)。

    UPS、蓄电池、空开、电缆配置计算方法.pptx

    5G通信行业、网络优化、通信工程建设资料

    node-v7.4.0.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    Unity mesh减面工具 Mesh Simplify 1.12

    Unity mesh减面工具 Mesh Simplify 1.12

    基于Springboot+Vue酒店客房入住管理系统-毕业源码案例设计.zip

    网络技术和计算机技术发展至今,已经拥有了深厚的理论基础,并在现实中进行了充分运用,尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代,所以对于信息的宣传和管理就很关键。系统化是必要的,设计网上系统不仅会节约人力和管理成本,还会安全保存庞大的数据量,对于信息的维护和检索也不需要花费很多时间,非常的便利。 网上系统是在MySQL中建立数据表保存信息,运用SpringBoot框架和Java语言编写。并按照软件设计开发流程进行设计实现。系统具备友好性且功能完善。 网上系统在让售信息规范化的同时,也能及时通过数据输入的有效性规则检测出错误数据,让数据的录入达到准确性的目的,进而提升数据的可靠性,让系统数据的错误率降至最低。 关键词:vue;MySQL;SpringBoot框架 【引流】 Java、Python、Node.js、Spring Boot、Django、Express、MySQL、PostgreSQL、MongoDB、React、Angular、Vue、Bootstrap、Material-UI、Redis、Docker、Kubernetes

    JAVA扫雷游戏程序+源码

    扫雷游戏是一款经典的计算机游戏,它的目标是在一个方格矩阵中找出所有隐藏的地雷。玩家需要通过点击方格来揭示其内容,如果方格中有地雷,则游戏结束;如果没有地雷,则会显示周围8个方格中地雷的数量。玩家需要根据这些信息来判断哪些方格是安全的,并继续点击其他方格。 在JAVA扫雷游戏程序中,我们使用了一个二维数组来表示游戏的方格矩阵。每个方格可以包含以下三种状态之一:未被揭示、有地雷或安全。我们还使用了一些辅助变量来跟踪游戏中的状态,例如已揭示的方格数量和剩余的地雷数量。 当玩家点击一个方格时,程序会检查该方格是否已经被揭示。如果是,则不做任何操作;否则,程序会揭示该方格的内容,并根据其是否包含地雷来更新游戏状态。如果方格中有地雷,则游戏结束;否则,程序会递归地揭示周围的方格,直到遇到已经揭示的方格为止。 为了提高游戏的可玩性,我们可以添加一些额外的功能,例如计时器、难度级别选择和自定义方格大小等。此外,我们还可以使用图形用户界面(GUI)来美化游戏界面,使其更加友好和易于操作。

    python-3.8.19-amd64-full.exe

    仅供个人娱乐使用,不要乱用造成侵权,搬运自git删hub.co除m/ad中ang1345/Pyt文honW部ind分ows

    网络安全网络安全视频教程56讲全在线地址.txt

    【网络安全】网络安全视频教程56讲全在线地址.txt

    299-企业数字化转型方案20220804.pptx

    299-企业数字化转型方案20220804.pptx

    参考资料-人工智能对劳动力市场的影响机制研究.pdf

    参考资料-人工智能对劳动力市场的影响机制研究.pdf

    3D Facial Expressions

    3D Facial Expressions through Analysis-by-Neural-Synthesis

    AI设计工具-美图设计室:一键生成海报宣传画.txt

    AI设计工具-美图设计室:一键生成海报宣传画

    node-v10.12.0-linux-armv6l.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    基于Springboot+Vue的商业辅助决策系统的设计与实现-毕业源码案例设计.zip

    网络技术和计算机技术发展至今,已经拥有了深厚的理论基础,并在现实中进行了充分运用,尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代,所以对于信息的宣传和管理就很关键。系统化是必要的,设计网上系统不仅会节约人力和管理成本,还会安全保存庞大的数据量,对于信息的维护和检索也不需要花费很多时间,非常的便利。 网上系统是在MySQL中建立数据表保存信息,运用SpringBoot框架和Java语言编写。并按照软件设计开发流程进行设计实现。系统具备友好性且功能完善。 网上系统在让售信息规范化的同时,也能及时通过数据输入的有效性规则检测出错误数据,让数据的录入达到准确性的目的,进而提升数据的可靠性,让系统数据的错误率降至最低。 关键词:vue;MySQL;SpringBoot框架 【引流】 Java、Python、Node.js、Spring Boot、Django、Express、MySQL、PostgreSQL、MongoDB、React、Angular、Vue、Bootstrap、Material-UI、Redis、Docker、Kubernetes

    5MHz 函数发生器使用说明书

    5MHz 函数发生器使用说明书

    大学生就业平台微信小程序+ssm后端毕业源码案例设计.zip

    网络技术和计算机技术发展至今,已经拥有了深厚的理论基础,并在现实中进行了充分运用,尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代,所以对于信息的宣传和管理就很关键。系统化是必要的,设计网上系统不仅会节约人力和管理成本,还会安全保存庞大的数据量,对于信息的维护和检索也不需要花费很多时间,非常的便利。 网上系统是在MySQL中建立数据表保存信息,运用SpringBoot框架和Java语言编写。并按照软件设计开发流程进行设计实现。系统具备友好性且功能完善。 网上系统在让售信息规范化的同时,也能及时通过数据输入的有效性规则检测出错误数据,让数据的录入达到准确性的目的,进而提升数据的可靠性,让系统数据的错误率降至最低。 关键词:vue;MySQL;SpringBoot框架 【引流】 Java、Python、Node.js、Spring Boot、Django、Express、MySQL、PostgreSQL、MongoDB、React、Angular、Vue、Bootstrap、Material-UI、Redis、Docker、Kubernetes

    通信驻地网施工组织方案.doc

    5G通信、网络优化与通信建设

    管道顶管工程施工技术.doc

    5G通信、网络优化与通信建设

Global site tag (gtag.js) - Google Analytics