操作系统是如何工作的

基础知识

1、计算机的三个法宝:存储程序计算机、函数调用堆栈机制、中断。

2、堆栈的具体作用:记录函数调用框架、传输函数参数、保存返回值的地址、提供函数内部局部变量的存储空间。

3、堆栈相关寄存器:

ESP:堆栈指针,指向栈顶。

EBP:基址指针,指向栈底。C语言中记录当前函数的调用基址。

CS:EIP:指向下一条的指令地址。

EAX:保存返回值或返回地址。

3、堆栈操作:

push:入栈,栈顶地址减少4个字节(32位),将操作数放入栈顶存储单元。

pop:出栈,栈顶地址增加4个字节(32位),将栈顶存储单元的内容取出放入操作数。

4、在Linux中,可以使用objdump工具进行反汇编。

5、中断机制:当一个中断信号发生时,CPU将当前所执行程序的EIP和ESP压入内核堆栈,然后使EIP指向中断处理程序入口,保存现场之后执行其他程序,执行完再恢复现场,恢复EIP和ESP,继续执行中断前的程序。

6、内嵌汇编中寄存器前会多一个转义符号%。%加一个数字表示第二部分输出、第三部分输入以及第四部分破坏描述的编号。 总感觉这句话有点抽象,不是很理解,看完视频中的例子也是明白个大概,希望老师可以讲解一下(〃'▽'〃)

在mykernel基础上构造一个简单的操作系统内核

1、第一部分实验

  • 在实验楼的虚拟机上打开shell,并进行如下操作:
cd LinuxKernel/linux-3.9.4
qemu -kernel arch/x86/boot/bzImage

结果发现并没有出现预想中的每隔一段时间输出结果,如下图所示。



于是重新输入以下命令,进行make 编译后结果正确。

cd LinuxKernel/linux-3.9.4
make allnoconfig
make
qemu -kernel arch/x86/boot/bzImage

内核成功启动出来之后,发现不会退出QEMU(T_T)

查找资料之后发现很多人说用Ctrl+A按X,但我试了一下并没有什么反应。然后我按了Ctrl+Alt+A+X就退出了,不知道是不是对的!

  • 查看mymain.c和myinterrupt.c的内容

内核不停的执行my_start_kernel(),每隔一段时间被my_timer_handler()中断,然后执行一条打印语句:printk(KERN_NOTICE “\n>>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<\n\n”)。这里发现内嵌汇编的输出语句是printk而不是printf。

2、第二部分实验

  • 参考https://github.com/mengning/mykernel上的mypcb.h、mymain.c、myinterrupt.c进行代码修改

    修改代码后重新进行make编译,结果如下:

3、第二部分实验代码分析

在mykernel基础上构造的操作系统内核的调度的主要实现主要是靠三个文件完成:mypcb.h、mymain.c、myinterrupt.c。

mypcb.h :定义进程控制块PCB即定义进程结构体。mymain.c:初始化内核的各个组成部分并启动0号进程。myinterrupt.c:时钟中断处理和进程调度算法。

mymain.c

#include <linux/types.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/tty.h>
#include <linux/vmalloc.h>
#include "mypcb.h"
tPCB task[MAX_TASK_NUM]; /*pcb的数组*/
tPCB * my_current_task = NULL;
volatile int my_need_sched = 0; /*是否需要调度的标志*/
void my_process(void)
void __init my_start_kernel(void) /*内核入口。初始化并启动0号进程*/
{
int pid = 0;
int i;
/* Initialize process 0*/
task[pid].pid = pid;
task[pid].state = 0; /* -1 未运行, 0 正在运行, >0 停止运行 */
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]; /*next指向自己*/
/*fork more process */ /*初始化更多进程*/
for(i=1;i<MAX_TASK_NUM;i++)
{
memcpy(&task[i],&task[0],sizeof(tPCB));
task[i].pid = i;
task[i].state = -1;
task[i].thread.sp = (unsigned long)(&task[i].stack[KERNEL_STACK_SIZE-1]);
task[i].next = task[i-1].next; /*将创建的进程加到进程列表的尾部*/
task[i-1].next = &task[i];
}
/* start process 0 by task[0] */
pid = 0; /*开始执行0号进程*/
my_current_task = &task[pid];
asm volatile(
"movl %1,%%esp\n\t" /*将ESP指向进程0的堆栈栈底,输出部分和输入部分从0开始编号,所以1%指task[pid].thread.sp*/
"pushl %1\n\t" /*将EBP的值入栈,因为是空栈,EBP=ESP,所以将task[pid].thread.sp入栈即可*/
"pushl %0\n\t" /*将EIP的值入栈,这里是初始化的值即my_process(void)的位置*/
"ret\n\t" /*将my_process(void)的位置放入EIP*/
"popl %%ebp\n\t"
:
: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)
);
} void my_process(void)
{
int i = 0;
while(1)
{
i++;
if(i%10000000 == 0) /*循环1000万次才有一次机会判断是否需要调度*/
{
printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);
if(my_need_sched == 1)
{
my_need_sched = 0;
my_schedule();
}
printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
}
}
}
  • 上述代码中启动第一进程的关键汇编代码asm volatile()执行过程中,堆栈和寄存器的变化过程:

myinterrupt.c

#include <linux/types.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/tty.h>
#include <linux/vmalloc.h>
#include "mypcb.h" extern tPCB task[MAX_TASK_NUM];
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = 0; void my_timer_handler(void) /*设置时间片大小,时间片用完时设置一下调度标志*/
{
#if 1
if(time_count%1000 == 0 && my_need_sched != 1)
{
printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
my_need_sched = 1; /*调度执行my_schedule(void)*/
}
time_count ++ ;
#endif
return;
} void my_schedule(void) /*进程上下文的切换*/
{
tPCB * next;
tPCB * prev; if(my_current_task == NULL || my_current_task->next == NULL)
{
return;
}
printk(KERN_NOTICE ">>>my_schedule<<<\n");
/* schedule */
next = my_current_task->next;
prev = my_current_task;
if(next->state == 0) /* next对应进程曾经执行过 */
{
asm volatile(
"pushl %%ebp\n\t" /* 将当前进程的EBP入栈 */
"movl %%esp,%0\n\t" /* 将当前进程的ESP保存到PCB */
"movl %2,%%esp\n\t" /* 将next进程的栈顶地址放入ESP */
"movl $1f,%1\n\t" /* 保存当前进程的EIP */
"pushl %3\n\t" /* 把即将进行的进程的代码位置标号1入栈 */
"ret\n\t" /* 出栈标号1到EIP*/
"1:\t" /* 标号1,next进程开始执行的位置 */
"popl %%ebp\n\t" /* 恢复EBP的值*/
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
my_current_task = next;
printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
}
else /* next对应进程第一次被执行*/
{
next->state = 0;
my_current_task = next;
printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
/* switch to new process */
asm volatile(
"pushl %%ebp\n\t" /* 将当前进程的EBP入栈 */
"movl %%esp,%0\n\t" /* 将当前进程的ESP保存到PCB */
"movl %2,%%esp\n\t" /* 将next进程的栈顶地址放入ESP */
"movl %2,%%ebp\n\t" /* 将next进程的栈底地址放入EBP */
"movl $1f,%1\n\t" /* 将当前EIP的值放入PCB */
"pushl %3\n\t" /* 把即将进行的进程的代码入口地址入栈 */
"ret\n\t" /* 把即将进行的进程的代码入口地址存入EIP */
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
}
return;
}
  • 假设有两个进程,从进程1被调度开始,堆栈和寄存器的变化过程:

总结

操作系统的内核有一个起始位置,从这个起始位置开始执行进行初始化操作。然后开始运行第一个进程,进程间会通过调度算法(比如时间片轮转)进行切换,进程切换时利用内核堆栈保存进程对应的%esp,%eip寄存器中的值即进行现场保存,然后将CPU分配给下一个进程并开始执行。

2019-2020-1 20199319《Linux内核原理与分析》第三周作业的更多相关文章

  1. 2019-2020-1 20199329《Linux内核原理与分析》第九周作业

    <Linux内核原理与分析>第九周作业 一.本周内容概述: 阐释linux操作系统的整体构架 理解linux系统的一般执行过程和进程调度的时机 理解linux系统的中断和进程上下文切换 二 ...

  2. 2019-2020-1 20199329《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 一.上周问题总结: 未能及时整理笔记 Linux还需要多用 markdown格式不熟练 发布博客时间超过规定期限 二.本周学习内容: <庖丁解 ...

  3. 20169212《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 这一周学习了MOOCLinux内核分析的第一讲,计算机是如何工作的?由于本科对相关知识的不熟悉,所以感觉有的知识理解起来了有一定的难度,不过多查查资 ...

  4. 20169210《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 本周作业分为两部分:第一部分为观看学习视频并完成实验楼实验一:第二部分为看<Linux内核设计与实现>1.2.18章并安装配置内核. 第 ...

  5. 2018-2019-1 20189221 《Linux内核原理与分析》第九周作业

    2018-2019-1 20189221 <Linux内核原理与分析>第九周作业 实验八 理理解进程调度时机跟踪分析进程调度与进程切换的过程 进程调度 进度调度时机: 1.中断处理过程(包 ...

  6. 2017-2018-1 20179215《Linux内核原理与分析》第二周作业

    20179215<Linux内核原理与分析>第二周作业 这一周主要了解了计算机是如何工作的,包括现在存储程序计算机的工作模型.X86汇编指令包括几种内存地址的寻址方式和push.pop.c ...

  7. 2019-2020-1 20209313《Linux内核原理与分析》第二周作业

    2019-2020-1 20209313<Linux内核原理与分析>第二周作业 零.总结 阐明自己对"计算机是如何工作的"理解. 一.myod 步骤 复习c文件处理内容 ...

  8. 2018-2019-1 20189221《Linux内核原理与分析》第一周作业

    Linux内核原理与分析 - 第一周作业 实验1 Linux系统简介 Linux历史 1991 年 10 月,Linus Torvalds想在自己的电脑上运行UNIX,可是 UNIX 的商业版本非常昂 ...

  9. 《Linux内核原理与分析》第一周作业 20189210

    实验一 Linux系统简介 这一节主要学习了Linux的历史,Linux有关的重要人物以及学习Linux的方法,Linux和Windows的区别.其中学到了LInux中的应用程序大都为开源自由的软件, ...

  10. 2018-2019-1 20189221《Linux内核原理与分析》第二周作业

    读书报告 <庖丁解牛Linux内核分析> 第 1 章 计算工作原理 1.1 存储程序计算机工作模型 1.2 x86-32汇编基础 1.3汇编一个简单的C语言程序并分析其汇编指令执行过程 因 ...

随机推荐

  1. golang 使用reflect反射结构体

    "反射结构体"是指在程序执行时,遍历结构体中的字段以及方法. 1.反射结构体 下面使用一个简单的例子说明如何反射结构体. 定义一个结构体,包括3个字段,以及一个方法. 通过refl ...

  2. 对docker一些认知

    关于docker(应用容器引擎) docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化.容器是完全使用 ...

  3. vue2创建webpack项目build之后无法正常显示页面的问题

    最近在做vue项目的时候,项目正常运行,但是当我打包上线之后,却出现无法出现页面空白的情况,打开控制台,发现无法加载到css和js文件. 仔细观察发现路径中少了一个dis文件夹,于是我加上dist文件 ...

  4. CTF—攻防练习之HTTP—PUT上传漏洞

    主机:192.168.32.152 靶机:192.168.32.159 中间件PUT漏洞 包括apache,tomcat,IIS等中间件设置支持的HTTP方法(get,post,head,delete ...

  5. 【PyTorch】PyTorch中的梯度累加

    PyTorch中的梯度累加 使用PyTorch实现梯度累加变相扩大batch PyTorch中在反向传播前为什么要手动将梯度清零? - Pascal的回答 - 知乎 https://www.zhihu ...

  6. 插入排序--python

    import random def insert_sort(nums): # 排序趟数 for i in range(1, len(nums)): current = nums[i] pre_inde ...

  7. Spring依赖模块

    1.spring maven依赖 https://www.cnblogs.com/nwu-edu/p/9542074.html

  8. [DS+Algo] 011 哈希

    目录 1. hash 函数 2. 哈希表 3. 密码存储 1. hash 函数 关键词 任意长度输入 固定长度输出 特征 理论上输入跟输出并不是一对一 实际使用假定不会出现碰撞或者冲突 常用算法 (M ...

  9. TIDB学习资料

    TiDB 源码阅读系列文章(一)序 TiDB 源码阅读系列文章(二)初识 TiDB 源码 TiDB 源码阅读系列文章(三)SQL 的一生 TiDB 源码阅读系列文章(四)Insert 语句概览 TiD ...

  10. 使用rsync在linux(服务端)与windows(客户端)之间同步

    说明: 1.RsyncServer服务端 系统:CentOS 6.8 IP地址:192.168.247.141 2.Rsync客户端 系统:Windows10 实现目的: Rsync客户端同步服务端/ ...