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

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
公网NAT网关,每月750个小时 15CU
简介: 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.编译运行

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
高可用应用架构
欢迎来到“高可用应用架构”课程,本课程是“弹性计算Clouder系列认证“中的阶段四课程。本课程重点向您阐述了云服务器ECS的高可用部署方案,包含了弹性公网IP和负载均衡的概念及操作,通过本课程的学习您将了解在平时工作中,如何利用负载均衡和多台云服务器组建高可用应用架构,并通过弹性公网IP的方式对外提供稳定的互联网接入,使得您的网站更加稳定的同时可以接受更多人访问,掌握在阿里云上构建企业级大流量网站场景的方法。 学习完本课程后,您将能够: 理解高可用架构的含义并掌握基本实现方法 理解弹性公网IP的概念、功能以及应用场景 理解负载均衡的概念、功能以及应用场景 掌握网站高并发时如何处理的基本思路 完成多台Web服务器的负载均衡,从而实现高可用、高并发流量架构
目录
相关文章
|
24天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
55 1
|
28天前
|
调度 开发者 Python
深入浅出操作系统:进程与线程的奥秘
在数字世界的底层,操作系统扮演着不可或缺的角色。它如同一位高效的管家,协调和控制着计算机硬件与软件资源。本文将拨开迷雾,深入探索操作系统中两个核心概念——进程与线程。我们将从它们的诞生谈起,逐步剖析它们的本质、区别以及如何影响我们日常使用的应用程序性能。通过简单的比喻,我们将理解这些看似抽象的概念,并学会如何在编程实践中高效利用进程与线程。准备好跟随我一起,揭开操作系统的神秘面纱,让我们的代码运行得更加流畅吧!
|
26天前
|
C语言 开发者 内存技术
探索操作系统核心:从进程管理到内存分配
本文将深入探讨操作系统的两大核心功能——进程管理和内存分配。通过直观的代码示例,我们将了解如何在操作系统中实现这些基本功能,以及它们如何影响系统性能和稳定性。文章旨在为读者提供一个清晰的操作系统内部工作机制视角,同时强调理解和掌握这些概念对于任何软件开发人员的重要性。
|
25天前
|
Linux 调度 C语言
深入理解操作系统:从进程管理到内存优化
本文旨在为读者提供一次深入浅出的操作系统之旅,从进程管理的基本概念出发,逐步探索到内存管理的高级技巧。我们将通过实际代码示例,揭示操作系统如何高效地调度和优化资源,确保系统稳定运行。无论你是初学者还是有一定基础的开发者,这篇文章都将为你打开一扇了解操作系统深层工作原理的大门。
|
26天前
|
存储 算法 调度
深入理解操作系统:进程调度的奥秘
在数字世界的心脏跳动着的是操作系统,它如同一个无形的指挥官,协调着每一个程序和进程。本文将揭开操作系统中进程调度的神秘面纱,带你领略时间片轮转、优先级调度等策略背后的智慧。从理论到实践,我们将一起探索如何通过代码示例来模拟简单的进程调度,从而更深刻地理解这一核心机制。准备好跟随我的步伐,一起走进操作系统的世界吧!
|
26天前
|
算法 调度 开发者
深入理解操作系统:进程与线程的管理
在数字世界的复杂编织中,操作系统如同一位精明的指挥家,协调着每一个音符的奏响。本篇文章将带领读者穿越操作系统的幕后,探索进程与线程管理的奥秘。从进程的诞生到线程的舞蹈,我们将一起见证这场微观世界的华丽变奏。通过深入浅出的解释和生动的比喻,本文旨在揭示操作系统如何高效地处理多任务,确保系统的稳定性和效率。让我们一起跟随代码的步伐,走进操作系统的内心世界。
|
27天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
30天前
|
算法 Linux 调度
深入浅出操作系统的进程管理
本文通过浅显易懂的语言,向读者介绍了操作系统中一个核心概念——进程管理。我们将从进程的定义出发,逐步深入到进程的创建、调度、同步以及终止等关键环节,并穿插代码示例来直观展示进程管理的实现。文章旨在帮助初学者构建起对操作系统进程管理机制的初步认识,同时为有一定基础的读者提供温故知新的契机。
|
29天前
|
消息中间件 算法 调度
深入理解操作系统之进程管理
本文旨在通过深入浅出的方式,带领读者探索操作系统中的核心概念——进程管理。我们将从进程的定义和重要性出发,逐步解析进程状态、进程调度、以及进程同步与通信等关键知识点。文章将结合具体代码示例,帮助读者构建起对进程管理机制的全面认识,并在实践中加深理解。
|
1月前
|
负载均衡 算法 调度
深入理解操作系统:进程管理与调度
在数字世界的心脏,操作系统扮演着至关重要的角色。它如同一位精明的指挥家,协调着硬件资源和软件需求之间的和谐乐章。本文将带你走进操作系统的核心,探索进程管理的艺术和调度策略的智慧。你将了解到进程是如何创建、执行和消亡的,以及操作系统如何巧妙地决定哪个进程应该在何时获得CPU的青睐。让我们一起揭开操作系统神秘的面纱,发现那些隐藏在日常计算背后的精妙机制。