MacOS环境-手写操作系统-30-进程之间互相切换 原创

进程之间互相切换

1.简介

上一节 我们初步介绍了进程相关的具体概念

特别是讲解了进程切换相关的数据结构 也就是TSS

也实现了进程的自我切换

本节 我们看看如何从当前的进程切换到新进程 然后再切换回来

进程A -切换->进程B-切换->进程A.

2.代码

先看看进程B的实现 一个进程主要包含一个主函数 我们把进程B的主函数实现如下

void task_b_main(void) {
   showString(shtctl, sht_back, 0, 144, COL8_FFFFFF, "enter task b");

    struct FIFO8 timerinfo_b;
    char timerbuf_b[8];
    struct TIMER *timer_b = 0;

    int i = 0;

    fifo8_init(&timerinfo_b, 8, timerbuf_b);
    timer_b = timer_alloc();
    timer_init(timer_b, &timerinfo_b, 123);

    timer_settime(timer_b, 500);


    for(;;) {

       io_cli();
        if (fifo8_status(&timerinfo_b) == 0) {
            io_sti();
        } else {
           i = fifo8_get(&timerinfo_b);
           io_sti();
           if (i == 123) {
               showString(shtctl, sht_back, 0, 160, COL8_FFFFFF, "switch back");
               taskswitch7();
           }

        }

    }

}

进程B函数的逻辑是这样的

当进入到进程B后 通过它的主函数现在桌面上打印出一个字符串”enter task b”

当这个字符串出现在桌面时 表示进程完成了切换

然后它初始化一个时钟 这个时钟超时是五秒 五秒过后 它调用函数taskswitch7重新切回到进程A

进程A就是主入口函数CMain 既然要切换进程B

那显然 我们需要一个描述进程B的TSS结构并进行相应的初始化

代码如下

int addr_code32 = get_code32_addr();
 tss_b.eip =  (task_b_main - addr_code32);
    tss_b.eflags = 0x00000202; 
    tss_b.eax = 0;
    tss_b.ecx = 0;
    tss_b.edx = 0;
    tss_b.ebx = 0;
    tss_b.esp = 1024;//tss_a.esp;
    tss_b.ebp = 0;
    tss_b.esi = 0;
    tss_b.edi = 0;
    tss_b.es = tss_a.es;
    tss_b.cs = tss_a.cs;//6 * 8;
    tss_b.ss = tss_a.ss;
    tss_b.ds = tss_a.ds;
    tss_b.fs = tss_a.fs;
    tss_b.gs = tss_a.gs;

上面的代码需要详细解释下

首先我们把tss_b.eflags设置成0x202

这个值可以当做一个写死的值

然后 我们把进程B的段寄存器设置成跟A一样

我们看看进程A的各个段寄存器分别指向哪个全局描述符 tss_a.cs 的值是8

对应全局描述符表的下标就是1(数值要除以8,上一节讲解过)

下标为1的描述符是这样的

LABEL_DESC_CODE32:  Descriptor        0,      0fffffh,       DA_CR | DA_32 | DA_LIMIT_4K

这个描述符指向一段内存 这段内存的性质是可执行代码段

这段内存的起始地址在内核的汇编部分进行了初始化 如下

xor   eax, eax
     mov   ax,  cs
     shl   eax, 4
     add   eax, LABEL_SEG_CODE32
     mov   word [LABEL_DESC_CODE32 + 2], ax
     shr   eax, 16
     mov   byte [LABEL_DESC_CODE32 + 4], al
     mov   byte [LABEL_DESC_CODE32 + 7], ah

上面的代码把描述符指向的内存地址的起始位置设置为LABEL_SEG_CODE32

tss_a.ds 的值为24 除以8后为3 也就是对应描述符在全局描述符表中的下标是3 这个描述符内容如下

LABEL_DESC_VRAM:    Descriptor        0,         0fffffh,            DA_DRWA | DA_LIMIT_4K

这个描述符指向的内存起始地址是0 长度为0fffffh

这段内存的性质是可读写数据段 也就是从0到0fffffh 这段长度的内存是可读写的数据

tss_a.ss 的值是32 除以8后得4 因此对应的是下标为4的描述符 该描述符的内容如下

LABEL_DESC_STACK:   Descriptor        0,             LenOfStackSection,        DA_DRWA | DA_32

它描述的是一段32位可读写的内存 长度为LenOfStackSection

它对应的这段内存是我们在内核的汇编部分分配的内存 具体如下

[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512  db 0
TopOfStack1  equ  $ - LABEL_STACK
times 512 db 0
TopOfStack2 equ $ - LABEL_STACK

LenOfStackSection equ $ - LABEL_STACK

上面分配了两个512字节 总共1024字节的内存

LABEL_STACK将会设置成下标为4的描述符所对应内存的起始地址

第一个512字节 作为进程A的堆栈

第二个512字节 将作为进程B的堆栈 上面tss_b的初始化代码中有这么一句:
tss_b.esp = 1024;

它的作用就是让进程把把堆栈指针指向第二个512字节的末尾处 大家要记得

堆栈是有高地址向低地址生长的 所以设置堆栈指针时 要把它指向内存的末尾

在内核的汇编部分 有代码将下标为4的描述符对应的内容起始地址设置为了LABEL_STACK

代码如下

     xor   eax, eax
     mov   ax,  cs
     shl   eax, 4
     add   eax, LABEL_STACK
     mov   word [LABEL_DESC_STACK + 2], ax
     shr   eax, 16
     mov   byte [LABEL_DESC_STACK + 4], al
     mov   byte [LABEL_DESC_STACK + 7], ah

最重要的三个段寄存器 cs, ds, ss,设置好

其余寄存器 设置成跟进程A一样即可

接下来最重要的设置是eip指针 这个指针将指向要执行代码的首地址

我们要执行的函数是task_b_main

因此eip应该指向这个函数 但注意 我们不能直接把这个函数的地址直接赋值给eip

eip指向的是相对于代码段起始地址的偏移 当前代码段的其实地址是LABEL_SEG_CODE32

因此我们需要把task_b_main的地址减去LABEL_SEG_CODE32

所得的结果就是相对偏移了

这也是eip初始化的逻辑

tss_b.eip = (task_b_main - addr_code32);

get_code32_addr是内核的汇编部分实现的行数

目的就是返回LABEL_SEG_CODE32对应的地址

实现如下

get_code32_addr:
        mov  eax, LABEL_SEG_CODE32
        ret

上一节 我们已经看到

我们通过代码 将一个描述符指向结构tss_b了

代码如下

set_segmdesc(gdt + 9, 103, (int) &tss_b, AR_TSS32);

指向tss_b结构的描述符下标是9 初始化好tss_b后

只要通过一个jmp语句 跳转到下标为9的描述符

那么就能将当前指向进程切换成运行task_b_main的进程了 这个跳转语句实现如下

taskswitch9:
        jmp 9*8:0
        ret

进程A运行的是CMain函数 它会创建一个5秒的计时器 一旦超时 则调用上面的函数实现任务切换

for(;;) {
.....
else if (fifo8_status(&timerinfo) != 0) {
           io_sti();
           int i = fifo8_get(&timerinfo);
           if (i == 10) {
               showString(shtctl, sht_back, 0, 176, COL8_FFFFFF, "switch to task b");
                //switch task 
               taskswitch9();
           }
.....
}

在跳转前 我们会在桌面上打印出一句switch to task b表示即将进行任务切换

task_b_main的实现我们已经看过了 进入task_b_main后

它会在桌面打印一条语句 表示跳转成功

然后启动一个5秒的计时器 五秒过后 通过taskswitch7重新跳转回进程A

一旦跳转到task_b_main 桌面会打印出相关字符串

然后光标会停止住 等5秒后

进程从task_b_main 切换回进程A,进程A恢复执行

于是在卡死5秒后 在跳转会进程A前

task_b_main会打印出一条语句”switch back”

当这条语句出现在桌面上时 控制器转回到进程A 于是光标会重新开始闪烁

这样的话 我们就实现了进程从A切换到B再从B切换回A的整个流程

3.编译运行

在这里插入图片描述

posted @   武子康  阅读(0)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示