构造一个简单的操作系统内核,详解进程切换细节

2023-11-07

(1)基本功能介绍

如题,本文将介绍如何构造一个简单的操作系统内核(基于内核版本3.9.4 )。它有以下功能:

1:进程的管理

2:进程的初始化

3 : 进程基于时间片的调度

(2)实操步骤

1 安装qemu, 以ubuntu为例:    

    sudo apt-get install qemu

    sudo ln -s /usr/bin/qemu-system-i386 /usr/bin/qemu

2 下载linux内核源代码

    wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.9.4.tar.xz 

3  解压源码    

   xz -d linux-3.9.4.tar.xz

   tar -xvf linux-3.9.4.tar

4 下载制作好的patch_for_mykernel

链接:https://pan.baidu.com/s/164qQa4PfyQZjoP8F7eT-cw 
提取码:7387 

5 打上patch   

   cd linux-3.9.4   

   patch -p1 < patch_for_mykernel

 6 编译运行    

   make allnoconfig

   make

   qemu -kernel arch/x86/boot/bzImage

   运行效果视频:   

操作系统进程调度

 (3) 代码概述:

  1 my_start_kernel: my kernel的入口, 初始化了10个进程,并且启动0号进程。

  2 用tPCB去保存进程的信息,包括id, state, 调用栈起始地址,入口函数,ip (instruct point, 指令指针), sp (stack point, 栈顶指针)。所有的进程通过tPCB的链表链在一起。

  3 my_timer_handler: 时钟中断处理函数, 每触发2000次, 将my_need_sched 设置成 1, 表  示要进行一次调度。

 4 my_process:进程的入口函数。每执行10000000次,看一下是否需要调度,如果是,则调用my_schedule进行调度。

5 my_schedule:调度函数的实现。保存当前进程的上下文现场,并切换到下一个进程。这里下一个进程的选择用的是简单的方法:tPCB struct 的 next指向的进程。

(4)关键代码详解:

void __init my_start_kernel(void)
{
    int pid = 0;
    /* Initialize process 0*/
    task[pid].pid = pid;
    task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */
    // set task 0 execute entry address to my_process
    task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
    task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
    task[pid].next = &task[pid];
    /*fork more process */
    for(pid=1;pid<MAX_TASK_NUM;pid++)
    {
        memcpy(&task[pid],&task[0],sizeof(tPCB));
        task[pid].pid = pid;
        task[pid].state = -1;
        task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
        task[pid].priority=get_rand(PRIORITY_MAX);//each time all tasks get a random priority
        task[pid].next = task[pid-1].next;
        task[pid-1].next = &task[pid];        //所有的线程用链表链接起来
    }
        //task[MAX_TASK_NUM-1].next=&task[0];
    printk(KERN_NOTICE "\n\n\n\n\n\n                system begin :>>>process 0 running!!!<<<\n\n");
    /* start process 0 by task[0] */
    pid = 0;
    my_current_task = &task[pid];
asm volatile(    //嵌入式汇编
     "movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */ //esp=task[pid].thread.sp
     "pushl %0\n\t" /* push task[pid].thread.ip */ //task[pid].thread.ip 
     "ret\n\t" /* pop task[pid].thread.ip to eip */ // eip = task[pid].thread.ip    
     :
     : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)      /* input c or d mean %ecx/%edx*/
);

void my_schedule(void)
{
    tPCB * next;
    tPCB * prev;
    // if there no task running or only a task ,it shouldn't need schedule
    if(my_current_task == NULL
        || my_current_task->next == NULL)
    {
	printk(KERN_NOTICE "                time out!!!,but no more than 2 task,need not schedule\n");
     return;
    }
    /* schedule */

    //next = get_next();
    next = my_current_task->next;

    prev = my_current_task;
    if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */ //下一个线程是运行过的
    {//save current scene
     /* switch to next process */
     asm volatile(	
         "pushl %%ebp\n\t" /* save ebp */    
         "movl %%esp,%0\n\t" /* save esp */ 
         "movl %2,%%esp\n\t" /* restore esp */ 
         "movl $1f,%1\n\t" /* save eip */	
         "pushl %3\n\t" 
         "ret\n\t" /* restore eip */ 
         "1:\t" /* next process start here */
         "popl %%ebp\n\t"
         : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
         : "m" (next->thread.sp),"m" (next->thread.ip)
     );
     my_current_task = next;//switch to the next task
    printk(KERN_NOTICE "                switch from %d process to %d process\n                >>>process %d running!!!<<<\n\n",prev->pid,next->pid,next->pid);

  }
    else  //下一个进程是从来没有运行过的
    {
        next->state = 0;
        my_current_task = next;
    printk(KERN_NOTICE "                switch from %d process to %d process\n                >>>process %d running!!!<<<\n\n\n",prev->pid,next->pid,next->pid);

     /* switch to new process */
     asm volatile(	
         "pushl %%ebp\n\t" /* save ebp */
         "movl %%esp,%0\n\t" /* save esp */
         "movl %2,%%esp\n\t" /* restore esp */ 
         "movl %2,%%ebp\n\t" /* restore ebp */ 
         "movl $1f,%1\n\t" /* save eip */	
         "pushl %3\n\t"   
         "ret\n\t" /* restore eip */ //eip = next->thread.ip
         : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
         : "m" (next->thread.sp),"m" (next->thread.ip)
     );
    }
    return;	
}//end of my_schedule

1 0号进程的启动代码:

1.1 “movl %1 %%esp\n\t” ===》esp = task[0].thread.sp 

将0号进程栈顶的地址存入ESP寄存器,因为一开始栈是空的,EBP等于ESP,所以EBP也等于task[0].thread.sp 

1.2 ’pushl %0\n\t' ===> push task[0].thread.ip

将0号进程的入口(ip)压栈

 1.3 'ret\n\t'

pop栈顶entry到eip, 此时栈顶entry即是之前一步刚压栈的task[0.thread.ip, 所以EIP = taks[0].thread.ip

到此0号进程初始化完毕: esp = eip = task[0].thread.sp, eip = taks[0].thread.ip

接下来分析进程切换部份,为了简便,假设系统只有两个进程,分别是0号进程处1号进程。进程0由内核启动时初始化执行,然后需要进行进程调度,开始执行1号进程。由于1号进程是一个从未执行过的进程,那么进入代码的else部分。

2.由0号进程切换到1号进程

2.1 pushl %%ebp\n\t

保存当前进程EBP到堆栈

 2.2 ’movl %%esp, %0\n\t‘===>prev->thread.sp = esp

保存当前进程ESP到内存(prev->threads.sp)

 2.3 'movl %2,%%esp\n\t' ===>esp = next->thread.sp

载入next进程的栈顶地址到ESP寄存器,此时EPS切换到process 1的栈顶

2.4 ’movl %2,%%ebp\n\t‘ ===>ebp = next->thread.sp 

载入next进程的堆栈基地址到EBP寄存器

 2.5 ’movl $1f, %1\n\t‘

保存当前EIP寄存器值到内存(pre->thread.ip), $1f是一种特殊的语法,后面会介绍

2.6 pushl %3\n\t===>push next->thread.ip

把next进程的代码入口(ip)入栈

 2.7 ’ret \n\t‘  ===> pop to eip

pop栈顶entry到eip,即在上一步push到栈的next->thread.ip. 所以EIP = next ->thread.ip

 到此0号进程切换到1号进程完毕!

经过一段时间之后,1号进程会切换回0号进程,由于0号进程是已经执行过的进程,那么走的是if这个分支

3.由1号进程切换回0号进程

3.1 'pushl %%ebp \n\t'

保存当前EBP到栈中

 3.2 move %%esp,%0\n\t===> pre->thread.sp = esp

保存当前ESP到内存中

3.3 ‘move %2,%%esp\n\t’===> esp = next->thread.sp

将next进程的堆栈栈顶保存到ESP寄存器,此时已经切换回0号进程的调用栈

3.4 'pushl %3'===>push ext->thread.ip

将next进行继续执行的代码位置($1f)压栈

 3.5 'ret\n\t' ===>pop to EIP

将栈顶entry pop(next 进程继续执行的代码位置)到EIP

  3.6 '1:\t'

定义标号1的位置,即next进程开使执行的位置

 3.7 ‘popl %%ebp\n\t’

恢复EBP寄存器的值

 到此1号进程切换回0号进程完毕!

关于文中$1f的使用再补充一点说明:

 $1f的含义是前的标号1(forwarding label 1), 在这个例子中指的是

  "ret\n\t" /* restore eip */ 
   "1:\t" /* next process start here */
   "popl %%ebp\n\t"

即next开始执行的入口。if中有标号1,else中没有标号1。这里可能会有疑问。else中$1f只是将其存入prev-thread.ip.并没有使用$1ff, 但当进程被重新调度执行时,prev->thread.ip变成了next->thread.ip.此时进入了if代码中会将next->thread.ip压栈,并由ret出栈到EIP寄存器存中,这时才实际使用了$1F,因此将执行if代码块中的标号1处的代码,所有else中没有标号1也就不奇怪了。

4总结:

0号进程初始化包括两个步

    --ESP和EBP的赋值(进程调用栈地址)

    --EIP的赋值(进程入口)

进程切换

    --保存当前线程的ESP和EBP以及EIP

    --load下一个进程的sp,bp以及ip. 如果下一个进程从未执行过,那么其运行的调用栈是空的,那么不用恢复EBP. (此时EBP=ESP)。它的入口直接是代码。

    --如果下一个进程已经执行过了,那么它的入口是恢复ebp, 然后再执行真正的代码。

嵌入式汇编语法可以参考:

嵌入式汇编的基本格式_突围-CSDN博客

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

构造一个简单的操作系统内核,详解进程切换细节 的相关文章

  • 使用 inotify 的正确方法是什么?

    我想使用inotifyLinux 上的机制 我希望我的应用程序知道文件何时aaa被改变了 您能给我提供一个如何做到这一点的示例吗 文档 来自监视文件系统活动 inotify https developer ibm com tutorials
  • 配置:错误:无法运行C编译的程序

    我正在尝试使用 Debian Wheezy 操作系统在我的 Raspberry Pi 上安装不同的软件 当我运行尝试配置软件时 我尝试安装我得到此输出 checking for C compiler default output file
  • 如何使用 echo 写入非 ASCII 字符?

    如何写非ASCII http en wikipedia org wiki ASCII使用 echo 的字符 是否有转义序列 例如 012或类似的东西 我想使用以下方法将 ASCII 字符附加到文件中 echo gt gt file 如果您关
  • 找出 Linux 上的默认语言

    有没有办法从C语言中找出Linux系统的默认语言 有 POSIX API 可以实现这个功能吗 例如 我想要一个人类可读格式的字符串 即德语系统上的 German 或 Deutsch 法语系统上的 French 或 Francais 等 有类
  • 从 php/linux 获取 pdf 的布局模式(横向或纵向)

    给定一个 PDF 如何使用 PHP lib 或 Linux 命令行工具获取 PDF 的布局模式 或相对宽度 高度 Using http www tecnick com public code cp dpage php aiocp dp tc
  • 如何在gnuplot中将字符串转换为数字

    有没有办法将表示数字 以科学格式 的字符串转换为 gnuplot 中的数字 IE stringnumber 1 0e0 number myconvert stringnumber plot 1 1 number 我可能使用 shell 命令
  • 错误:命令“c++”失败,退出状态为 1

    所以我尝试按照以下说明安装 Pyv8https andrewwilkinson wordpress com 2012 01 23 integrating python and javascript with pyv8 https andre
  • 使用 gcc 理解共享库

    我试图理解 C 中共享库的以下行为 机器一 cat one c include
  • 如何重命名 .tar.gz 文件而不提取内容并在 UBUNTU 中创建新的 .tar.gz 文件?

    我有一个命令将创建一个新的 tar gz现有文件中的文件 sudo tar zcvf Existing tar gz New tar gz 该命令将创建一个新的New tar gz从现有的文件Existing tar gz file 谁能告
  • 在 debian wheezy amd64 上安装 ia32-libs

    我正在使用 Debian 7 喘息 amd64 uname a Linux tzwm debian 3 2 0 4 amd64 1 SMP Debian 3 2 51 1 x86 64 GNU Linux 我想安装ia32 libs在我的
  • 如何反汇编、修改然后重新组装 Linux 可执行文件?

    无论如何 这可以做到吗 我使用过 objdump 但它不会产生我所知道的任何汇编器都可以接受的汇编输出 我希望能够更改可执行文件中的指令 然后对其进行测试 我认为没有任何可靠的方法可以做到这一点 机器代码格式非常复杂 比汇编文件还要复杂 实
  • 提高mysql导入速度[关闭]

    Closed 这个问题是与编程或软件开发无关 help closed questions 目前不接受答案 我有一个很大的数据库22GB 我曾经用过进行备份mysqldumpgzip 格式的命令 当我提取 gz 文件时 它会生成 sql文件的
  • 为什么默认情况下不启用 arp 忽略/通告 [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我有一个需要经验才能回答的具体问题 为什么 arp ignore arp announce 在 Linux 安装 例如 debian 上默认不启用 有
  • Bash - 比较 2 个文件列表及其 md5 校验和

    我有 2 个列表 其中包含带有 md5sum 检查的文件 即使文件相同 列表也具有不同的路径 我想检查每个文件的 md5 和 我们正在讨论数千个文件 这就是为什么我需要脚本来仅显示差异 第一个列表是普通列表 第二个列表是文件的当前状态 我想
  • 运行 shell 命令并将输出发送到文件?

    我需要能够通过 php 脚本修改我的 openvpn 身份验证文件 我已将我的 http 用户设置为免通 sudoer 因为这台机器仅在我的家庭网络中可用 我目前有以下命令 echo shell exec sudo echo usernam
  • 用于获取特定用户 ID 和进程数的 Bash 脚本

    我需要 bash 脚本来计算特定用户或所有用户的进程 我们可以输入 0 1 或更多参数 例如 myScript sh root deamon 应该像这样执行 root 92 deamon 8 2 users has total proces
  • 亚马逊 Linux - 安装 openjdk-debuginfo?

    我试图使用jstack在 ec2 实例上amazon linux 所以我安装了openjdk devel包裹 sudo yum install java 1 7 0 openjdk devel x86 64 但是 jstack 引发了异常j
  • 如何回忆上一个 bash 命令的参数?

    Bash 有没有办法回忆上一个命令的参数 我通常这样做vi file c其次是gcc file c Bash 有没有办法回忆上一个命令的参数 您可以使用 or 调用上一个命令的最后一个参数 Also Alt can be used to r
  • 如何使用 VSCode 调试 Linux 核心转储?

    我故意从我使用 VSCode 编写的 C 应用程序生成核心转储 我不知道如何调试核心转储 有没有人愿意分享这方面的经验 更新 我相信我现在已经可以使用了 我为核心文件创建了第二个调试配置 我需要添加指向生成的转储文件的 coreDumpPa
  • 在构建内核模块时为什么需要 /lib/modules? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 在Kbuild树中 当我们编写一个简单的hello ko程序时 为什么我们需要在构建规则中使用 C lib module 为什么需要这样做

随机推荐

  • Flink之水位线(Watermark)

    在流数据处理应用中 一个很重要 也很常见的操作就是窗口计算 所谓的 窗口 一般就是划定的一段时间范围 也就是 时间窗 对在这范围内的数据进行处理 就是所谓的窗口计算 所以窗口和时间往往是分不开的 接下来我们就深入了解一下 Flink 中的时
  • 20220129---CTF刷题---WEB--代码审计

    20220129 CTF刷题 WEB 代码审计 刷题网站 世界攻防 https adworld xctf org cn WEB方向 新手区第5题 一道简单的代码审计题 首先通过get方法传参 a要转化成数字是0 但是不能直接传0 否则 a就
  • Dynamics 365 UI Controls 用Calendar View来查看数据

    Dynamics 365 在升级到new UI之后 支持一种calendar 形式来查看view数据 今天我们来说一下怎么在一个特定的view上来实现用calendar查看 1 首先进入Customization页面 找到你想开启Calen
  • redis中api理解与使用(二)

    4 列表 list 列表类型是用来存储多个有序的字符串 可以重复 一个列表最多存储2 32 1个元素 redis中可以对列表两端插入和弹出 还可以获取指定范围的元素列表 获取指定索引下标的元素等 4 1 常用命令 操作类型 操作 添加 rp
  • android studio 将已有的项目 以module Library的 形式引入到 自己的项目中

    1 什么是Module Library android 将项目 分成 project 和 Module module 其实也是一个项目 他里面也有 res java AndroidManifiest等文件 其实也是一个可以独立运行的项目 只
  • samba搭建(基于centos7)

    samba 基础 组管理 文件权限管理 基本命令 安装及配置 samba linux连接 samba 配置详解 global 配置特定目录共享 shell 自动创建用户 基础 SMB Server Message Block 服务器消息块
  • [转]video视频解码硬解和软解的区别及如何选择

    如果你认为本系列文章对你有所帮助 请大家有钱的捧个钱场 点击此处赞助 赞助额0 1元起步 多少随意 声明 本文只用于个人学习交流 若不慎造成侵权 请及时联系我 立即予以改正 锋影 email 174176320 qq com 硬解 字面上理
  • 百度引流推广怎么做?个人如何做百度推广

    个人如何做百度推广 相对于中小型企业 个人或者微商朋友在网络推广预算比较紧张 做网络营销推广的预算不会太多 因此 更需要在有限的推广费用预算 做出更好的推广效果 无疑 精准引流成为了个人做百度推广的首选 一 什么是百度推广 百度推广可以简单
  • 只出现一次的数字(异或运算^)

    给定一个非空整数数组 除了某个元素只出现一次以外 其余每个元素均出现两次 找出那个只出现了一次的元素 说明 你的算法应该具有线性时间复杂度 你可以不使用额外空间来实现吗 示例 1 输入 2 2 1 输出 1 示例 2 输入 4 1 2 1
  • 践行社会责任的路上,中概股们看到了怎样的风景?

    谈起社会责任 你会想到什么 绿色经济 双碳 目标 共同富裕 乡村振兴 慈善活动 ESG 环境 社会和公司治理 这些名词肯定少不了 当下 全球企业正越发强调社会责任 这或许是商业发展到一定阶段的必然结果 但也离不开公司们对社会事业的特别关注
  • PHP异常处理中的finally

    0x01 异常处理 在做代码分析的时候发现了一个有意思的点 样例代码如下 我们知道finally会在return之前执行 那么上
  • Packing(石板切割问题)回溯算法

    一 问题描述 给定一个最大的总切割目标石块 再给定一系列我们需要的样板石块 寻找切割方法使得我们从目标石块上切割出的所需样板石块的面积和最大 即对目标石块的利用率最高 限制切割为一刀切 即一次切割必须把一块石板一分为二 不能只切一段 左边为
  • 方差公式【数论】

    对于今天打的一道题 非常有感想 然后花了很久很久打了这个函数超多的方差公式 哎 来吧来吧 推导 首先我们知道方差的公式是 K i
  • 架构--网络关键指标公式

    架构 网络关键指标公式 一 经典公式1 估算系统的平均并发用户数和并发用户数峰值 1 1 公式 1 1 1 平均并发用户数 C nL T 参数说明 C 平均并发用户数 通过计算出来的 参数说明 n login session的数量 也就是
  • 告警与恢复告警原理及实现

    一 背景 自 双碳 政策提出以来 KaiwuDB 聚焦 数字能源 领域 为用户打造数字能源管理平台 旨在提升综合能源和碳资产管理能力 数字能源管理平台是以 KaiwuDB 为核心建设的云 边 端一体化数据服务平台 致力于为 IoT 工业互联
  • 多目标灰狼算法(MOGWO)的Matlab代码详细注释及难点解释(佳点集改进初始种群的MOGWO)

    目录 一 外部种群Archive机制 二 领导者选择机制 三 多目标灰狼算法运行步骤 四 MOGWO的Matlab部分代码详细注释 五 MOGWO算法难点解释 5 1 网格与膨胀因子 5 2 轮盘赌方法选择每个超立方体概率 为了将灰狼算法应
  • Ubuntu软件包升级失败的终极修复方法

    升级失败 apt upgrade y 尝试修复 apt autoremove Reading package lists Done Building dependency tree Reading state information Don
  • Centos7.6重置root密码

    启动Centos 7 虚拟机 三秒之内在这个系统boot引导界面迅速按e键进入boot编辑模式 如果没有在3秒内按写e 系统正常启动就不会进入到boot编辑模式了 找到以 linux16 开头的行 将从ro开始 ro不要删 往后到下一行前内
  • synchronized原理之前置知识

    一 Monitor概述 一 Java 对象头以 32 位虚拟机为例 一 普通对象 Object Header 64 bits Mark Word 32 bits Klass Word 32 bits 这个可以找到对象 二 数组对象
  • 构造一个简单的操作系统内核,详解进程切换细节

    1 基本功能介绍 如题 本文将介绍如何构造一个简单的操作系统内核 基于内核版本3 9 4 它有以下功能 1 进程的管理 2 进程的初始化 3 进程基于时间片的调度 2 实操步骤 1 安装qemu 以ubuntu为例 sudo apt get