jjzjj

操作系统ucore实验——lab1

wh0@am1 2023-09-20 原文

** ## 操作系统ucore实验——lab1 **

紧急更新实验用的源代码在lab0中的有误改为:

链接:https://pan.baidu.com/s/1RLCG57xDSydH8oQD-JwgPQ
提取码:9i15

中科大的源可能遇见无法使用的情况,换官方的

https://releases.ubuntu.com/focal/ubuntu-20.04.5-desktop-amd64.iso

练习1:理解通过make生成执行文件的过程

首先打开lab1文件观察一下,然后有makefile,make V=生成make编译过程中详细的过程和参数,和bin文件夹,里面即是编译生成的内核文件夹。ucore.img就在其中。

用Source Insight审计makefile代码

看一下正文,小白肯定看不懂,这个是linux shell的脚本语言,这里推荐大家一本学习linux的一本书《鸟哥的Linux私房菜》图书馆里应该有。那么接下来将对makefile进行一一讲解。

首先学一种语言其实只需要将输入输出,判断循环,结构定义学会基本就掌握七八了。以下是基本符号说明

=即是简单的赋值
:=变量不存在或值为空时才对其赋值
echo输出
num>&num输出重定向符号0表示标准输入,1表示标准输出,2标准错误输出 1>&2即是将标准输出定向到标准错误输出这里
ifndef…endif判断是否定义,中间夹带定义内容
if,elif,then与python类似的then即是if或者elif为真时启用

其余的可看这篇:

https://blog.csdn.net/tangyuanzong/article/details/78595854?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166609720716800184180982%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=166609720716800184180982&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-78595854-null-null.142v59pc_rank_34_2,201v3control_2&utm_term=ucore%20lab1&spm=1018.2226.3001.4187

可以看出ucore是基于qemu开发的操作系统

问题1:操作系统镜像文件ucore.img是如何一步一步生成的?(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)

make V=(对每条命令进行了精简)后的内容:
如果没有则说明你以前编译过但没清除缓存解决方法:

方法1.
make clean #清除上一次make命令生成的文件
make distclean #清除上一次make以及configure命令生成的文件
方法2.
将文件删除,重新下一个

开始分析:
第一步

  1. +cc kern/init/init.c #编译初始化代码init.c
    • gcc -Ikern/init/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/init/init.c -o obj/kern/init/init.o #编译方法这里大概就是对-kern/init/文件目录下开了保护 可调试 栈保护等等一系列操作…详情请看gcc说明手册,gcc --help即可

第二步

2.+ cc kern/libs/stdio.c #编译输入输出文件,这里并不是BIOS,BIOS是放在ROM固定的,是什么输入输出以后到源码分析会说。
gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/stdio.c -o obj/kern/libs/stdio.o

第三步

  • cc kern/libs/readline.c #编译readline.c文件
    gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/readline.c -o obj/kern/libs/readline.o

第四步

  • cc kern/debug/panic.c#编译panic.c文件
    gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/panic.c -o obj/kern/debug/panic.o

第五步

  • cc kern/debug/kdebug.c #编译kdebug.c文件
    gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kdebug.c -o obj/kern/debug/kdebug.o

第六步

  • cc kern/debug/kmonitor.c #编译kmonitor.c gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kmonitor.c -o obj/kern/debug/kmonitor.o

…此处开始一个文件夹举例说明
第七步

  • cc kern/driver/clock.c#编译时钟代码clock.c
    gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/clock.c -o obj/kern/driver/clock.o


第十一步

  • cc kern/trap/trap.c #编译陷门中断代码
    gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trap.c -o obj/kern/trap/trap.o

第12步

  • cc kern/trap/vectors.S #编译终端向量的汇编代码
    gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/vectors.S -o obj/kern/trap/vectors.o

第14步

  • cc kern/mm/pmm.c #编译pmm.c文件
    gcc -Ikern/mm/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/mm/pmm.c -o obj/kern/mm/pmm.o

数不来了…

ld -m elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel obj/kern/init/init.o obj/kern/libs/stdio.o obj/kern/libs/readline.o obj/kern/debug/panic.o obj/kern/debug/kdebug.o obj/kern/debug/kmonitor.o obj/kern/driver/clock.o obj/kern/driver/console.o obj/kern/driver/picirq.o
obj/kern/driver/intr.o obj/kern/trap/trap.o obj/kern/trap/vectors.o
obj/kern/trap/trapentry.o obj/kern/mm/pmm.o obj/libs/string.o
obj/libs/printfmt.o #动态链接生成bin/kernel

  • ld bin/bootblock //根据sign规范生成bootblock
    ld -m elf_i386 -nostdlib -N -e start -Ttext 0x7C00
    obj/boot/bootasm.o obj/boot/bootmain.o
    -o obj/bootblock.o

    //创建大小为10000个块的ucore.img,初始化为0,每个块为512字节 dd if=/dev/zero of=bin/ucore.img count=10000
    //把bootblock中的内容写到第一个块 dd if=bin/bootblock of=bin/ucore.img conv=notrunc
    //从第二个块开始写kernel中的内容 dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc

通过分析,不仅仅可以在makefile中找到编译命令,并且可以得到ucore.img的生成过程为

1.将所有源代码编译为可执行文件,生成产生bin/kernel所需文件
2.链接生成bin/kernel
3 编译bootasm.S bootmain.c sign.c
4.根据sign规范生成obj/bootblock.o
5.创建10000个块的ucore,将bootblock写入第0块,然后将bin/kernel写入后面的块

问题2:一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
这里找了一下想了想,既然是起规范作用的,那么在前面说了sign可以规范bootblock。那么这片代码一定在sign.c里面。
果然:

则符合符合规范的硬盘主引导扇区的特征

1.扇区大小为512字节
2.多余空间为0
3.第510字节=0x55
4.第511字节=0xAA

练习2:使用qemu执行并调试lab1中的软件

1.从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行

首先我们看看在用qemu启动内核时我们给内核埋下的调试断点在lab1/tools/gdbinit文件内

意思就是说,我在文件 bin/kernel执行时开一个端口1234给别人交互,并在kern_init函数下断点,运行继续。我们知道CPU加电后是先启动BIOS的那么这个断点是不能看见的所以修改为。

set architecture i8086
target remote :1234
就行了,方便我们将来的调试

然后这里用qemu拉起ucore并设为可调试

qemu-system-i386 -S -s -hda ./bin/ucore.img -monitor stdio
或者
qemu -S -s -hda ./bin/ucore.img -monitor stdio


1.本地调试法
新启一个终端,在lab1文件下

make debug#启动调试

然后输入:

si #单步跟踪
x /20i $pc #查看20次pc程序计数器所指的内容

2.远程调试法
新起一个终端,然后启动gdb
gdb
target remote ip+端口(本地用ip127.0.0.1)
命令

这样就可以让别人帮你远程调试了,也意识到这其实是一个很危险的后门,如果我隐藏在其他不知名文件里面,你的系统将随时被我干掉。

并且第一个断点是0x0000fff0即BIOS的启动点,可看见是成功的。

2.在初始化位置0x7c00设置实地址断点,测试断点正常。

这里将前面启动的全部关闭,然后重新拉起一下ucore.img让他以BIOS开始

新起一个终端,然后本地或者远程调试。
然后设置断点在0x7c00处,然后运行查看程序计数器

b *0x7c00
c
x /20i $pc

由此可知改断点是正常的

这里就可以得到结论,在这个内存空间中BIOS和加载程序的内存分布的分部是,
————————————— <-4G
| 空闲空间 |
—————————————<-1MB=0xffff0
|BIOS固件 |
—————————————<-640KB
|… |
—————————————
|内核加载程序(bootloader) | <-第一块
—————————————<-0x7c00 CS:IP=0000:7c00
|BIOS数据块 |
——————————————0x0

3.从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较
20个20个的看
调试的

bootasm.S的

bootblock.asm

结论:都是一样的
那么他究竟干了什么事呢?这里就直接给出分析的结果,具体细节请大家自己通过汇编来理解。
汇编指令大全:

https://blog.csdn.net/linlibest/article/details/8904624

1.终端处理,然后启动bootloader(512字节)
CLI 清中断允许位(防止在启动的时候被终端打扰)
CLD 清方向标志位
然后进行一系列的寄存器赋值,即是处理系统中断,保存了ds,es,s等,然后跳转执行。

2.等待磁盘扇区,读取磁盘

3.进入保护模式,启动系统,然后用局部内存表(LDT)


至此我们就可以知道,操作系统启动的完全步骤是

1.启动BIOS,将bootblock写入第一块
2.将权限转到bootblock,然后写入操作系统
3.将权限转到操作系统,启动

此时的全局内存表(GDT)
————————————— <-4G
| 空闲空间 |
——————————————
|操作系统 |
—————————————<-1MB=0xffff0
|BIOS固件 |
—————————————<-640KB
|… |
—————————————
|内核加载程序(bootloader) | <-第一块
—————————————<-0x7c00 CS:IP=0000:7c00
|BIOS数据块 |
——————————————0x0

这也是为什么电脑的运存4G,而使用时最多是3G的原因,操作系统自己就占了1G

4.自己找一个bootloader或内核中的代码位置,设置断点并进行测试

随便搞

练习3:分析bootloader进入保护模式的过程。

看来不得不细讲bootasm.S了

1.为何开启A20,以及如何开启A20
初始时A20为0,访问超过1MB的地址时,就会从0循环计数,将A20地址线置为1之后,才可以访问4G内存。A20地址位由8042控制,8042有2个有两个I/O端口:0x60和0x64。(A20即是address 20(地址线),他的空间4G)

打开流程:

等待8042键盘控制不忙,即输入字符为空;
发送Write 8042 Output Port (P2)命令到8042 Input buffer;
等待8042 Input buffer为空;
将8042 Output Port(P2)得到字节的第2位置1,然后写入8042 Input buffer;


2.如何初始化GDT表
1.载入GDT

lgdt gdtdesc

2.进入保护模式
通过将cr0寄存器设为1,即进入保护模式

movl %cr0, %eax #将cr0给eax
orl $CR0_PE_ON, %eax #给eax赋值1
movl %eax, %cr0 #给cr0赋值1

3.通过长跳转更新cs的基地址:
上面已经打开了保护模式,所以这里需要用到逻辑地址。$PROT_MODE_CSEG的值为0x8

跳转到protcseg这。

4 设置段寄存器,并建立堆栈

可以看到的是此时DS数据寄存器,ES扩展寄存器,FS标志寄存器,GS全局寄存器,ss堆栈寄存器。都初始指向0x0
具体作用:

https://blog.csdn.net/u011488769/article/details/119488993

esp栈顶指针指向0x7c,ebp指向0x0
5.转到保护模式完成,调用bootmain函数

3.如何使能和进入保护模式
将cr0寄存器置数为1

练习4:分析bootloader加载ELF格式的OS的过程。

1.bootloader如何读取硬盘扇区的?
在前面已经说过,当控制权给到bootloader时,进入保护模式后,bootloader会将操作系统写到BIOS上面
那么bootloader肯定是将硬盘扇区已经编译生产的OS,进行读取,怎么个读取法,就在bootmain.c中的readsect()函数

这里了解一下这个PC的I O端口地址表 (I/O端口地址表)

https://www.cnblogs.com/chulia20002001/archive/2011/01/11/1932772.html

我们知道我们是使用qemu拉起来的操作系统,它默认操作系统存放在0号硬盘中。(有错误,打算问完老师后修改)
outb的定义:

void outb (uint16_t port , uint8_t data);#内嵌汇编读/写数据,x86外设读取方式

然后用readseg()函数封装,就可以从设备读/写任意长度的内容。

以上就是bootloader如何读/写硬盘扇区的。

2.bootloader是如何加载ELF格式的OS?

在文件elf.h中找到efl的定义:

然后直接看bootmain的读取

所以步骤是:

1.从磁盘里读去8个扇区存到内存0x10000处(刚好在BIOS上面),并强制转换为elf格式使用
2.检验幻数
3.根据偏移量分别把程序段的数据读取到内存中

练习5:实现函数调用堆栈跟踪函数

这里分别使用x86.h中的read_ebp()函数,和kdebug.c中的read_eip()函数

这样函数print_stackframe就是将这些信息输出就行
然后在lab1中

make qemu #就可得到一下结果

#这里的具体分析…打算弄明白了再说.

练习6:完善中断初始化和处理

1.中断描述符表(也可简称为保护模式下的中断向量表(IDT))中一个表项占多少字节?其中哪几位代表中断处理代码的入口?

在mm/mmu.h查看中断向量表的定义

中断机制

中断向量表项

看代码将所有数加起来64位=8字节

2.请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。在idt_init函数中,依次对所有中断入口进行初始化。使用mmu.h中的SETGATE宏,填充idt数组内容。每个中断的入口由tools/vectors.c生成,使用trap.c中声明的vectors数组即可

SETGATE宏定义

中断向量表初始定义

idt_init实现

3.请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100 ticks”

先在print_ticks函数中写自己想要的输出

然后修改trap_dispatch()函数,让他满足mod100就调用。

然后make qemu运行就可以了

现在回顾一下ucore整个启动过程。
1.CPU初始化CS:IP,启动位于ROM的BIOS(实模式)
2.BIOS通过主引导记录到主引导扇区然后读取bootblock(加载程序)
3.bootblock进入保护模式调用,初始化GDT,调用bootmain
4.bootmain读取磁盘扇区,将系统的elf文件写入运存中,然后找到程序入口,将权限给操作系统。
5.操作系统初始化中断向量表(idt_init函数),然后根据中断向量表开始内核的初始化(kern_init函数)

扩展练习

这篇文章很不错。

https://www.cnblogs.com/whileskies/p/13427877.html

参考bolg:

https://blog.csdn.net/tangyuanzong/article/details/78595854?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166609720716800184180982%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=166609720716800184180982&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-78595854-null-null.142v59pc_rank_34_2,201v3control_2&utm_term=ucore%20lab1&spm=1018.2226.3001.4187

有关操作系统ucore实验——lab1的更多相关文章

  1. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  2. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  3. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  4. ruby - 如何使用 Selenium Webdriver 根据 div 的内容执行操作? - 2

    我有一个使用SeleniumWebdriver和Nokogiri的Ruby应用程序。我想选择一个类,然后对于那个类对应的每个div,我想根据div的内容执行一个Action。例如,我正在解析以下页面:https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies这是一个搜索结果页面,我正在寻找描述中包含“Adoption”一词的第一个结果。因此机器人应该寻找带有className:"result"的div,对于每个检查它的.descriptiondiv是否包含单词“adoption

  5. ruby-on-rails - 如何处理 Grape 中特定操作的过滤器之前? - 2

    我正在我的Rails项目中安装Grape以构建RESTfulAPI。现在一些端点的操作需要身份验证,而另一些则不需要身份验证。例如,我有users端点,看起来像这样:moduleBackendmoduleV1classUsers现在如您所见,除了password/forget之外的所有操作都需要用户登录/验证。创建一个新的端点也没有意义,比如passwords并且只是删除password/forget从逻辑上讲,这个端点应该与用户资源。问题是Grapebefore过滤器没有像except,only这样的选项,我可以在其中说对某些操作应用过滤器。您通常如何干净利落地处理这种情况?

  6. ruby-on-rails - 在 Ruby on Rails 中发送响应之前如何等待多个异步操作完成? - 2

    在我做的一些网络开发中,我有多个操作开始,比如对外部API的GET请求,我希望它们同时开始,因为一个不依赖另一个的结果。我希望事情能够在后台运行。我找到了concurrent-rubylibrary这似乎运作良好。通过将其混合到您创建的类中,该类的方法具有在后台线程上运行的异步版本。这导致我编写如下代码,其中FirstAsyncWorker和SecondAsyncWorker是我编写的类,我在其中混合了Concurrent::Async模块,并编写了一个名为“work”的方法来发送HTTP请求:defindexop1_result=FirstAsyncWorker.new.async.

  7. ruby - 在没有基准或时间的情况下用 Ruby 测量用户时间或系统时间 - 2

    因为我现在正在做一些时间测量,我想知道是否可以在不使用Benchmark类或命令行实用程序time的情况下测量用户时间或系统时间。使用Time类只显示挂钟时间,而不显示系统和用户时间,但是我正在寻找具有相同灵active的解决方案,例如time=TimeUtility.now#somecodeuser,system,real=TimeUtility.now-time原因是我有点不喜欢Benchmark,因为它不能只返回数字(编辑:我错了-它可以。请参阅下面的答案。)。当然,我可以解析输出,但感觉不对。*NIX系统的time实用程序也应该可以解决我的问题,但我想知道是否已经在Ruby中实

  8. ruby - 在 Ruby 中是否有一种惯用的方法来操作 2 个数组? - 2

    a=[3,4,7,8,3]b=[5,3,6,8,3]假设数组长度相同,是否有办法使用each或其他一些惯用方法从两个数组的每个元素中获取结果?不使用计数器?例如获取每个元素的乘积:[15,12,42,64,9](0..a.count-1).eachdo|i|太丑了...ruby1.9.3 最佳答案 使用Array.zip怎么样?:>>a=[3,4,7,8,3]=>[3,4,7,8,3]>>b=[5,3,6,8,3]=>[5,3,6,8,3]>>c=[]=>[]>>a.zip(b)do|i,j|c[[3,5],[4,3],[7,6],

  9. ruby-on-rails - 如何让 Rails View 返回其关联的操作名称? - 2

    我有一个非常简单的Controller来管理我的Rails应用程序中的静态页面:classPagesController我怎样才能让View模板返回它自己的名字,这样我就可以做这样的事情:#pricing.html.erb#-->"Pricing"感谢您的帮助。 最佳答案 4.3RoutingParametersTheparamshashwillalwayscontainthe:controllerand:actionkeys,butyoushouldusethemethodscontroller_nameandaction_nam

  10. ruby - 以毫秒为单位获取当前系统时间 - 2

    在Ruby中,以毫秒为单位获取自纪元(1970)以来的当前系统时间的正确方法是什么?我试过了Time.now.to_i,好像不是我想要的结果。我需要结果显示毫秒并且使用long类型,而不是float或double。 最佳答案 (Time.now.to_f*1000).to_iTime.now.to_f显示包含十进制数字的时间。要获得毫秒数,只需将时间乘以1000。 关于ruby-以毫秒为单位获取当前系统时间,我们在StackOverflow上找到一个类似的问题:

随机推荐