当前位置: 首页 > news >正文

平度市建设局网站淘宝代理平台

平度市建设局网站,淘宝代理平台,jarvis主题wordpress,网站空间容量实验一#xff1a;系统软件启动过程 参考 重要文件 调用顺序 1. boot/bootasm.S | bootasm.asm#xff08;修改了名字#xff0c;以便于彩色显示#xff09;a. 开启A20 16位地址线 实现 20位地址访问 芯片版本兼容通过写 键盘控制器8042 的 64h端口 与 60h端口。b.…实验一系统软件启动过程 参考 重要文件 调用顺序 1. boot/bootasm.S | bootasm.asm修改了名字以便于彩色显示a. 开启A20 16位地址线 实现 20位地址访问 芯片版本兼容通过写 键盘控制器8042 的 64h端口 与 60h端口。b. 加载GDT全局描述符 lgdt gdtdescc. 使能和进入保护模式 置位 cr0寄存器的 PE位 (内存分段访问) PEPG分页机制movl %cr0, %eax orl $CR0_PE_ON, %eax 或操作置位 PE位 movl %eax, %cr0d. 调用载入系统的函数 call bootmain # 转而调用 bootmain.c 2. boot/bootmain.c - bootmain 函数a. 调用readseg函数从ELFHDR处读取8个扇区大小的 os 数据。b. 将输入读入 到 内存中以 进程(程序)块 proghdr 的方式存储c. 跳到ucore操作系统在内存中的入口位置kern/init.c中的kern_init函数的起始地址3. kern/init.ca. 初始化终端 cons_init(); init the console kernel/driver/consore.c显示器初始化 cga_init(); 串口初始化 serial_init(); keyboard键盘初始化 kbd_init();b. 打印内核信息 欢迎信息 print_kerninfo(); // 内核信息 kernel/debug/kdebug.ccprintf(%s\n\n, message);// 欢迎信息 const char *message “qwert”c. 显示堆栈中的多层函数调用关系 切换到保护模式启用分段机制grade_backtrace();d. 初始化物理内存管理pmm_init(); // init physical memory management kernel/mm/ppm.c---gdt_init(); // 初始化默认的全局描述符表e. 初始化中断控制器pic_init(); // 初始化 8259A 中断控制器 kernel/driver/picirq.cf. 设置中断描述符表idt_init(); // kernel/trap/trap.c // __vectors[] 来对应中断描述符表中的256个中断符 tools/vector.c中g. 初始化时钟中断使能整个系统的中断机制 8253定时器 clock_init(); // 10ms 时钟中断(1s中断100次) kernel/driver/clock.c---- pic_enable(IRQ_TIMER);// 使能定时器中断 h. 使能整个系统的中断机制 enable irq interruptintr_enable(); // kernel/driver/intr.c// sti(); // set interrupt // x86.hi. lab1_switch_test();// 用户切换函数 会 触发中断用户切换中断4. kernel/trap/trap.c trap中断(陷阱)处理函数trap() --- trap_dispatch() // kernel/trap/trap.c a. 10ms 时钟中断处理 case IRQ_TIMERif((ticks)%1000) print_ticks();//向终端打印时间信息1s打印一次b. 串口1 中断 case IRQ_COM1: 获取串口字符后打印c. 键盘中断 case IRQ_KBD: 获取键盘字符后打印d. 用户切换中断 实验目的 操作系统是一个软件也需要通过某种机制加载并运行它。 在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作。 为此我们需要完成一个能够切换到x86的保护模式并显示字符的bootloader为启动操作系统ucore做准备。 lab1提供了一个非常小的bootloader和ucore OS 整个bootloader执行代码小于512个字节这样才能放到硬盘的主引导扇区中。 通过分析和实现这个bootloader和ucore OS读者可以了解到1. 计算机原理 CPU的编址与寻址: 基于分段机制的内存管理CPU的中断机制外设串口/并口/CGA时钟硬盘2. Bootloader软件 编译运行bootloader的过程调试bootloader的方法PC启动bootloader的过程ELF执行文件的格式和加载外设访问读硬盘在CGA上显示字符串3. ucore OS软件 编译运行ucore OS的过程ucore OS的启动过程调试ucore OS的方法函数调用关系在汇编级了解函数调用栈的结构和处理过程中断管理与软件相关的中断处理外设管理时钟实验内容 lab1中包含一个bootloader和一个OS。 这个bootloader可以切换到X86保护模式能够读磁盘并加载ELF执行文件格式并显示字符。 而这lab1中的OS只是一个可以处理时钟中断和显示字符的幼儿园级别OS。项目组成 lab1的整体目录结构如下所示tree.├── bin // 编译后生成│ ├── bootblock // 是引导区│ ├── kernel // 是操作系统内核│ ├── sign // 用于生成一个符合规范的硬盘主引导扇区│ └── ucore.img // ucore.img 通过dd指令将上面我们生成的 bootblock 和 kernel 的ELF文件拷贝到ucore.img├── boot // bootloader 代码│ ├── asm.h // 是bootasm.S汇编文件所需要的头文件, 是一些与X86保护模式的段访问方式相关的宏定义.│ ├── bootasm.S // 0. 定义了最先执行的函数start部分初始化从实模式切换到保护模式调用bootmain.c中的bootmain函数│ └── bootmain.c // 1. 实现了bootmain函数, 通过屏幕、串口和并口显示字符串,加载ucore操作系统到内存然后跳转到ucore的入口处执行.| // 生成 bootblock.out | // 由 sign.c 在最后添加 0x55AA之后生成 规范的 512字节的├── kern // ucore系统部分│ ├── debug// 内核调试部分 │ │ ├── assert.h // 保证宏 assert宏在发现错误后调用 内核监视器kernel monitor│ │ ├── kdebug.c  // 提供源码和二进制对应关系的查询功能用于显示调用栈关系。│ │ ├── kdebug.h // 其中补全print_stackframe函数是需要完成的练习。其他实现部分不必深究。│ │ ├── kmonitor.c // 实现提供动态分析命令的kernel monitor便于在ucore出现bug或问题后│ │ ├── kmonitor.h // 能够进入kernel monitor中查看当前调用关系。│ │ ├── panic.c // 内核错误(Kernel panic)是指操作系统在监测到内部的致命错误,│ │ └── stab.h│ ├── driver //驱动│ │ ├── clock.c // 实现了对时钟控制器8253的初始化操作 系统时钟 │ │ ├── clock.h │ │ ├── console.c // 实现了对串口和键盘的中断方式的处理操作 串口命令行终端│ │ ├── console.h│ │ ├── intr.c // 实现了通过设置CPU的eflags来屏蔽和使能中断的函数│ │ ├── intr.h│ │ ├── kbdreg.h // │ │ ├── picirq.c // 实现了对中断控制器8259A的初始化和使能操作 │ │ └── picirq.h│ ├── init // 系统初始化│ │ └── init.c // ucore操作系统的初始化启动代码│ ├── libs│ │ ├── readline.c│ │ └── stdio.c│ ├── mm // 内存管理 Memory management│ │ ├── memlayout.h // 操作系统有关段管理段描述符编号、段号等的一些宏定义│ │ ├── mmu.h // 内存管理单元硬件 Memory Management Unit 将线性地址映射为物理地址,包括EFLAGS寄存器等段定义│ │ ├── pmm.c     // 设定了ucore操作系统在段机制中要用到的全局变量│ │ └── pmm.h // 任务状态段ts全局描述符表 gdt[],加载gdt的函数lgdt 初始化函数gdt_init│ └── trap // 陷阱trap 异常exception 中断interrupt 中断处理部分│ ├── trap.c // 紧接着第二步初步处理后继续完成具体的各种中断处理操作│ ├── trapentry.S // 紧接着第一步初步处理后进一步完成第二步初步处理| | // 并且有恢复中断上下文的处理即中断处理完毕后的返回准备工作│ ├── trap.h // 紧接着第二步初步处理后继续完成具体的各种中断处理操作│ └── vectors.S // 包括256个中断服务例程的入口地址和第一步初步处理实现。| // 此文件是由tools/vector.c在编译ucore期间动态生成的├── libs // 公共库部分│ ├── defs.h // 包含一些无符号整型的缩写定义│ ├── elf.h│ ├── error.h│ ├── printfmt.c│ ├── stdarg.h     // argument 参数│ ├── stdio.h // 标志输入输出 io│ ├── string.c│ ├── string.h│ └── x86.h // 一些用GNU C嵌入式汇编实现的C函数├── Makefile // 指导make完成整个软件项目的编译清除等工作。└── tools // 工具部分├── function.mk // mk模块 指导make完成整个软件项目的编译清除等工作。├── gdbinit     // gnu debugger 调试├── grade.sh├── kernel.ld├── sign.c // 一个C语言小程序是辅助工具用于生成一个符合规范的硬盘主引导扇区。| // 规范的硬盘主引导扇区大小为512字节结束符为0x55AA| // obj/bootblock.out( 500 ) 0x55AA - bootblock(512字节)└── vector.c // 生成vectors.S 中断服务例程的入口地址和第一步初步处理实现cpu mmu 内存 关系 页地址映射 中断异常陷阱 编译 cd lab1/ make # 变量定义 ( or : ) # 其中 和 : 的区别在于, # : 只能使用前面定义好的变量, 可以使用后面定义的变量 # 变量追加值 SRCS programD.c # 变量追加值 # 命令前缀 # 前缀 :: 只输出命令执行的结果, 出错的话停止执行 # 前缀 - :: 命令执行有错的话, 忽略错误, 继续执行makefile文件见 直接在硬件模拟器上运行 硬件模拟器qemu的安装 make qemu1. 从CPU加电后执行的第一条指令开始单步跟踪BIOS的执行 单步调试和查看BIOS代码如果你是想看BIOS的汇编可试试如下方法 练习2可以单步跟踪方法如下1. 修改 lab1/tools/gdbinit,set architecture i8086target remote :12342. 在 lab1目录下执行make debug这时gdb停在BIOS的第一条指令处 0xffff0: ljmp $0xf000,$0xe05b3 在看到gdb的调试界面(gdb)后执行如下命令就可以看到BIOS在执行了 si si ... 4 此时的CS0xf000, EIP0xfff0如果想看BIOS的代码x/2i 0xffff0应该可以看到0xffff0: ljmp $0xf000,$0xe05b0xffff5: xor %dh,0x322f 进一步可以执行x/10i 0xfe05b 可以看到后续的BIOS代码。首先在CPU加电之后CPU里面的ROM存储器会将其里面保存的初始值传给各个寄存器 其中CS:IP 0Xf000 : fff0CS代码段寄存器IP指令寄存器 这个值决定了我们从内存中读数据的位置PC 16*CS IP。此时系统处于实模式并且截止到目前为止系统的总线还不是我们平常的32位 这时的地址总线只有20位所以地址空间的总大小只有1M 而我们的BIOS启动固件就在这个1M的空间里面。BIOS启动固件需要提供以下的一些功能☆基本输入输出的程序☆系统设置信息☆开机后自检程序☆系统自启动程序在此我们需要找到CPU加电之后的第一条指令的位置然后在这里break单步跟踪BIOS的执行 根据PC 16*CS IP我们可以得到PC 0xffff0 所以BIOS的第一条指令的位置为0xffff0在这里因为此时我们的地址空间只有20位所以是0xffff0。在这里我们利用 make debug来观察BIOS的单步执行 所以我们首先通过Makefile文件来查看make debug的相关操作# Makefile # 利用make debug来观察BIOS的单步执行 # 首先是对qemu进行的操作 # sleep 2 等待一段时间 # 针对gdbinit文件进行的调试 debug: $(UCOREIMG)$(V)$(QEMU) -S -s -parallel stdio -hda $ -serial null $(V)sleep 2$(V)$(TERMINAL) -e gdb -q -tui -x tools/gdbinit# tools/gdbinit file bin/kernel target remote :1234 break kern_init continue可以看到电脑在运行到kern_init是会触发break然后又紧接着在下一步continue执行调试 这里需要安装 cgdb sudo apt-get install cgdb开始调试 make debug会出现一个新的终端分为上下两个窗口上面的窗口显示运行到的源码下面的窗口是gdb调试界面。 由上面的分析可知:BIOS的第一条指令的位置为0xffff0**查看 pc指针地址 对应的指令 前强制反汇编当前的指令 ** x/i $pc 显示1行 x/10i $pc 显示10行我们查看 0xffff0地址内的信息 x/i 0xffff0 0xffff0: ljmp $0x3630,$0xf000e05b可以看到BIOS的第一条指令是一条跳转指令 ljmp然后程序会跳转到0xf000e05b开始进行一系列的操作。在截图中我们看到pc0xfff0这是因为在x86的机器里面并没有pc这个寄存器所谓的pc值是通过CS:IP而得到的因此这里的PC所代表的是eip寄存器里面的值低 16位的值。设置断点 在gdb命令行中使用b *[地址]便可以在指定内存地址设置断点当qemu中的cpu执行到指定地址时便会将控制权交给gdb。n/s都是C语言级的断点定位。 s会进入C函数内部,但是不会进入没有定位信息的函数比如没有加-g编译的代码因为其没有C代码的行数标记没办法定位n不会。 ni/si都是汇编级别的断点定位。si会进入汇编和C函数内部,ni不会 归纳:当要进入没有调试信息的库函数调试的时候用si是唯一的方法。 当进入有调试信息的函数用si和s都可以但是他们不同si是定位到汇编级别的第一个语句但是s是进入到C级别的第一个语句.gdb的单步命令: next 单步到程序源代码的下一行不进入函数。 nexti 单步一条机器指令不进入函数。 step 单步到下一个不同的源代码行包括进入函数。 stepi 单步一条机器指令。2. bootloader进入保护模式的过程 我们最重要的是要理解三个问题 1、为何要开启A20以及如何开启A20 2、如何初始化GDT表 3、如何使能和进入保护模式。1、为何要开启A20以及如何开启A20 首先关于A20我们通过查询资料以及说明文档可以知道早期的8086CPU所提供的地址线只有20位 所以可寻址空间为0~2^20(1MB)但是8086的数据处理位宽16位无法直接访问1M的地址空间 所以8086提供了段地址加偏移地址的转换机制。 PC的寻址结构是segment:offsetsegment和offset都是16位寄存器 最大值是0ffffh所以换算成物理地址的计算方法是把segment左移4位再加上offset 所以segment:offset所能表示的最大为10ffefh而这个地址超过了1M 但是超过1M会发生“回卷”的现象不会报错 但是从下一代的80286开始地址线成为了24位所能访问的地址空间超过了1M 此时寻址超过1M时会报错出现了向下不兼容所以为了解决这个问题采用了A20机制。A20 Gate将A20地址线控制器 和 键盘控制器的一个输出进行AND操作 这样来控制A20地址线的打开与关闭所以在实模式下需要确保A20开关处于关闭状态这样可以防止访问大于1M的地址空间但 是在保护模式下我们需要访问更大的内存空间所以需要将A20的开关打开如果在保护模式下A20的开关未打开的话此时我们只能访问奇数兆的内存 即只能访问0—1M2—3M4—5M……所以如果我们要进入保护模式首先就需要把A20开关给打开。2、如何初始化GDT表 接下来我们需要了解下GDT表全局描述符表在整个操作系统中我们只有一张GDT表 GDT可以放在内存的任意位置但是CPU必须知道GDT的入口 在Intel里面有一个专门的寄存器GDTR用来存放GDT的入口地址 程序员将GDT设定在内存的某个位置之后 可以通过LGDT指令将GDT的入口地址加载到该寄存器里面 以后CPU就可以通过GDTR来访问GDT了。3、如何使能和进入保护模式 最后我们需要了解如何 使能 和 进入 保护模式 关于这一点我们需要了解一个寄存器CR0首先我们来看下CR0寄存器的各个位代表什么在这里由于我们需要进入保护模式所以暂时可以先不用管其他的位只需关注最低位的PE即可 PE是启用保护位(protection enable)当设置该位的时候即开启了保护模式 系统上电复位的时候该位默认为0于是便是实模式 当PE置1的时候进入保护模式实质上是开启了段级保护只是进行了分段没有开启分页机制 如果要开启分页机制的话我们需要同时置位PE和PG。有了初步了解之后我们便知道的开启保护模式的相关操作 首先开启A20 Gate其次加载全局描述符表GDT最后只需要将CR0寄存器的最低位置为1即可。接下来我们通过观察代码来查看UCore具体是如何实现相应的操作的# Enable A20:# For backwards compatibility with the earliest PCs, physical# address line 20 is tied low, so that addresses higher than# 1MB wrap around to zero by default. This code undoes this. seta20.1:inb $0x64, %al # Wait for not busy(8042 input buffer empty).testb $0x2, %al # 等待键盘控制器8042 0x64 端口 空闲64h端口中的状态寄存器的值为0x2jnz seta20.1 # 忙的话一直等待movb $0xd1, %al # 等到64h空闲之后我们会写入0xd1 0xd1 - port 0x64outb %al, $0x64 # 表明我们要向60h里面写入数据 0xd1 means: write data to 8042s P2 portseta20.2:inb $0x64, %al # 等待64h端口空闲 Wait for not busy(8042 input buffer empty).testb $0x2, %al # 64h端口中的状态寄存器的值为0x2jnz seta20.2 # 忙的话一直等待movb $0xdf, %al # 0xdf - port 0x60 等到空闲之后我们将0xdf写入60h端口至此来打开A20开关。outb %al, $0x60 # 0xdf 11011111, means set P2s A20 bit(the 1 bit) to 1 (第20位)首先是开启A20根据上文我们知道需要将第20位为1即可 但是我们需要知道在UCore里是如何将A20置为1的。 根据说明书我们可以知道A20地址线由键盘控制器8042进行控制 我们的A20所对应的是8042里面的P21引脚所以问题就变成了我们需要将P21引脚置1。对于8042芯片来说有两个端口地址60h和64h。 对于这两个端口来说 0x64用来发送一个键盘控制命令 0x60用来传递参数所以将P21引脚置1的操作就变成了 我们首先利用0x64端口传递一个写入的指令然后由0x60端口读进去相应的参数来将P21置1。 由以下的资料我们可以知道 我们首先要先向 64h 发送 0xd1 的指令写之前需要等待键盘控制器8042空闲 可以通过判断 64h端口中的状态寄存器的值0x2,来判断是否空闲。 然后向 60h 发送 0xdf 的指令。在这里可能有人会有疑问既然我们只需要将P21置为1就可以了 那么我们是不是可以传入多种不同的参数只需要对应的位为1就好了答案是不行的。我们传入的0xdf参数在这里也相当于一条指令通过这条指令我们可以将A20的开关打开。 在这里我们还需要注意一个问题就是当前端口(60h或者64h)是否空闲 只有当这两个端口空闲的时候我们才可以向其传入数据。boot/bootasm.S #include asm.h# Start the CPU: switch to 32-bit protected mode, jump into C. # The BIOS loads this code from the first sector of the hard disk into # memory at physical address 0x7c00 and starts executing in real mode # with %cs0 %ip7c00..set PROT_MODE_CSEG, 0x8 # kernel code segment selector .set PROT_MODE_DSEG, 0x10 # kernel data segment selector .set CR0_PE_ON, 0x1 # protected mode enable flag# start address should be 0:7c00, in real mode, the beginning address of the running bootloader .globl start start: .code16 # Assemble for 16-bit modecli # Disable interruptscld # String operations increment# 初始化 Set up the important data segment registers (DS, ES, SS).xorw %ax, %ax # Segment number zeromovw %ax, %ds # - Data Segmentmovw %ax, %es # - Extra Segmentmovw %ax, %ss # - Stack Segment# 1 Enable A20:# For backwards compatibility with the earliest PCs, physical# address line 20 is tied low, so that addresses higher than# 1MB wrap around to zero by default. This code undoes this. seta20.1:inb $0x64, %al # Wait for not busy(8042 input buffer empty).testb $0x2, %al # 等待键盘控制器8042 0x64 端口 空闲64h端口中的状态寄存器的值为0x2jnz seta20.1 # 忙的话一直等待movb $0xd1, %al # 等到64h空闲之后我们会写入0xd1, 0xd1 - port 0x64outb %al, $0x64 # 表明我们要向60h里面写入数据0xd1 means: write data to 8042s P2 portseta20.2:inb $0x64, %al # 等待64h端口空闲 Wait for not busy(8042 input buffer empty).testb $0x2, %al # 64h端口中的状态寄存器的值为0x2jnz seta20.2 # 忙的话一直等待movb $0xdf, %al # 等到空闲之后我们将0xdf写入60h端口至此来打开A20开关0xdf - port 0x60outb %al, $0x60 # 0xdf 11011111, means set P2s A20 bit(the 1 bit(第20位)) to 1# Switch from real to protected mode, using a bootstrap GDT# and segment translation that makes virtual addresses# identical to physical addresses, so that the# effective memory map does not change during the switch.# 2 load gdt 加载GDT全局描述符 在后面可以看到lgdt gdtdesc# 使能和进入保护模式 movl %cr0, %eax # 首先将cr0寄存器里面的内容取出来orl $CR0_PE_ON, %eax # 进行一个或操作, PE是启用保护位(protection enable),当设置该位的时候即开启了保护模式movl %eax, %cr0 # 最后将得到的结果再写回 cr0中 # 只是进行了分段没有开启分页机制如果要开启分页机制的话我们需要同时置位PE和PG。# Jump to next instruction, but in 32-bit code segment.# Switches processor into 32-bit mode.# 3 最后通过一个长跳转指令正式进入保护模式。ljmp $PROT_MODE_CSEG, $protcseg.code32 # Assemble for 32-bit mode protcseg:# Set up the protected-mode data segment registers 初始化保护模式的数据段寄存器movw $PROT_MODE_DSEG, %ax # Our data segment selectormovw %ax, %ds # - DS: Data Segmentmovw %ax, %es # - ES: Extra Segmentmovw %ax, %fs # - FSmovw %ax, %gs # - GSmovw %ax, %ss # - SS: Stack Segment# Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)movl $0x0, %ebp # 栈底指针movl $start, %esp # 栈顶指针call bootmain # 调用bootmain函数在bootmain.c中进行 加载ELF格式的操作系统OS# If bootmain returns (it shouldnt), loop. spin:jmp spin# Bootstrap GDT .p2align 2 # force 4 byte alignment gdt: # GDT表的入口地址 GDT全局描述符表由三个全局描述符组成SEG_NULLASM # null seg 第一个均为空描述符SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel 第二个为代码段描述符SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel 第三个为数据段描述符# gdt全局描述符表  它里面有两个参数 gdtdesc:.word 0x17 # 表示的是GDT表的大小 # sizeof(gdt) - 1.long gdt # 表示的是GDT表的入口地址 # address gdt3. 分析bootloader加载ELF格式的OS的过程。 上面 boot/bootasm.S 的末尾切换到保护模式初始化一些段寄存器后 会调用bootmain函数在bootmain.c中 进行 加载ELF格式的操作系统OS。ELF文件格式 读取elf 背景知识 对于硬盘来说我们知道是分成许多扇区的其中每个扇区的大小为512字节。 读取扇区的流程我们通过查询指导书可以看到1、等待磁盘准备好2、发出读取扇区的命令3、等待磁盘准备好4、把磁盘扇区数据读到指定内存。 接下来我们需要了解下如何具体的从硬盘读取数据 因为我们所要读取的操作系统文件是存在0号硬盘上的 所以我们来看一下关于0号硬盘的I/O端口在这里我们可以看到对于0号硬盘的读取操作是通过一系列的寄存器完成的 所以在读取硬盘时我们也是通过对这些硬盘进行操作从而得到相应的数据。 通过上面对硬盘知识的一些了解之后 我们开始观察具体的实现过程#include defs.h #include x86.h #include elf.h// elf文件格式定义 /* 通过bootmain函数从硬盘中 加载elf格式的 操作系统os 到内存中使用程序块方式存储1. 将一些OS的ELF文件从硬盘中读到内存的ELFHDR里面 格式在elf.h中定义2. 在加载操作开始之前我们需要对ELFHDR进行判断观察是否是一个合法的ELF头3. 通过循环读取每个段并且将每个段读入相应的虚存p_va 程序块中4. 最后调用ELF header表头中的内核入口地址, 实现 内核链接地址 转化为 加载地址无返回值。*/ #define SECTSIZE 512 // 一个扇区的大小 #define ELFHDR ((struct elfhdr *)0x10000)// scratch space 虚拟地址va(virtual address)/* 等待磁盘准备好 waitdisk - wait for disk ready */ static void waitdisk(void) {while ((inb(0x1F7) 0xC0) ! 0x40)//判断磁盘寄存器的状态为标志 // 0x1F7 0号硬盘 读时状态寄存器 // 0xC0 0x11000000 最高两位为1 // 0x40 0x01000000 01表示空闲 // 检查0x1F7的最高两位如果是01那么证明磁盘准备就绪跳出循环否则继续等待。/* do nothing */; }/* 读取一整块扇区 readsect - read a single sector at secno into dst */ static void readsect(void *dst, uint32_t secno) {// wait for disk to be readywaitdisk();// 1、等待磁盘准备好outb(0x1F2, 1); // 设置需要读取得参数 扇区计数 count 1outb(0x1F3, secno 0xFF);// 即读取相应的内容到寄存器里面outb(0x1F4, (secno 8) 0xFF);outb(0x1F5, (secno 16) 0xFF);outb(0x1F6, ((secno 24) 0xF) | 0xE0);outb(0x1F7, 0x20); // 2、发出读取磁盘命令 cmd 0x20 - read sectors// wait for disk to be readywaitdisk();// 3、等待磁盘准备好 // read a sectorinsl(0x1F0, dst, SECTSIZE / 4);// 4、把磁盘扇区数据读到指定内存。 }/* ** readseg - read count bytes at offset from kernel into virtual address va,* might copy more than asked.* 从0号硬盘上读入os文件* 第一个参数是一个虚拟地址va (virtual address)起始地址* 第二个是count我们所要读取的数据的大小 512*8* SECTSIZE的定义我们通过追踪可以看到是512即一个扇区的大小* 第三个是offset偏移量* */ static void readseg(uintptr_t va, uint32_t count, uint32_t offset) {uintptr_t end_va va count;//结束地址// round down to sector boundaryva - offset % SECTSIZE;// SECTSIZE512扇区单位长度 起始地址减去偏移 块首地址// translate from bytes to sectors; kernel starts at sector 1uint32_t secno (offset / SECTSIZE) 1;// 存储我们需要读取的磁盘的位置// 通过一个for循环一次从磁盘中读取一个整块for (; va end_va; va SECTSIZE, secno ) {// 继续对虚存va和secno进行自加操作直到读完所需读的东西为止。readsect((void *)va, secno);// 磁盘中读取一个整块 存到相应的虚存va中} }/* bootmain - the entry of bootloader bootmain 函数 加载 elf格式的os 操作系统文件是存在0号硬盘上的 读取扇区的流程我们通过查询指导书可以看到 1、等待磁盘准备好 2、发出读取扇区的命令 3、等待磁盘准备好 4、把磁盘扇区数据读到指定内存。 */ void bootmain(void) {// read the 1st page off disk// 从0号硬盘上读入os文件// 第一个参数是一个虚拟地址va(virtual address)// 第二个是count我们所要读取的数据的大小 512*8// 第三个是offset偏移量// SECTSIZE的定义我们通过追踪可以看到是512即一个扇区的大小 // 1. 将一些OS的ELF文件从硬盘中读到内存的ELFHDR里面 格式在elf.h中定义readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);// 调用readseg函数从ELFHDR处读取8个扇区的大小。// is this a valid ELF? // 2. 在加载操作开始之前我们需要对ELFHDR进行判断观察是否是一个合法的ELF头if (ELFHDR-e_magic ! ELF_MAGIC) {goto bad;//加载到错误得操作系统, 跳转到}struct proghdr *ph, *eph;//内存中 进程(程序)块 的存储方式, 在elf.h中定义// ph表示ELF段表首地址 , eph表示ELF段表末地址// load each program segment (ignores ph flags)ph (struct proghdr *)((uintptr_t)ELFHDR ELFHDR-e_phoff);//首地址基地址偏移量eph ph ELFHDR-e_phnum;// 末地址 首地址elf大小// 3. 通过循环读取每个段并且将每个段读入相应的虚存p_va 程序块中for (; ph eph; ph ) {readseg(ph-p_va 0xFFFFFF, ph-p_memsz, ph-p_offset);}// call the entry point from the ELF header // 4. 最后调用ELF header表头中的内核入口地址, 实现 内核链接地址 转化为 加载地址无返回值。((void (*)(void))(ELFHDR-e_entry 0xFFFFFF))();bad:outw(0x8A00, 0x8A00);outw(0x8A00, 0x8E00);/* do nothing */while (1); } 4. 实现函数调用堆栈跟踪函数 我们需要实现函数调用堆栈因此我们需要首先针对函数堆栈的操作做一些相关的了解对于函数堆栈来说可以分为以下三部分操作1、首先保存原相关寄存器的状态即将相关参数以及寄存器的当前状态压入栈2、其次在栈中进行函数操作即完成函数的相关功能3、最后释放栈空间回复原寄存器状态。要实现以上的相关操作我们就需要对函数栈的结构有相关的了解 堆栈是地址逆生长.在每次进行函数调用的时候 首先会将函数的参数 自右向左 压入栈中 所以从图中我们可以看到参数的顺序是从参数3到参数1 然后将返回地址压入栈即下条指令的地址压入栈 接着把原ebp的值压入栈便于稍后的恢复。 对于上一层ebp的压入 和 返回地址 和是通过两句汇编实现的pushl %ebp # 上一层的ebp的压入movl %esp, %ebp # %ebp 指向%esp栈顶(存储的是上一层的ebp也就是返回地址)当我们传完参数时我们进行push操作将原ebp的值压入栈 此时我们的ebp寄存器所指的位置是上一层ebp的位置 然后通过一个movl操作将返回地址压入对应的栈便实现了对函数栈的搭建。所以一般而言 ss:[ebp4]处为返回地址 ss:[ebp8]处为第一个参数值最后一个入栈的参数值此处假设其占用4字节内存 ss:[ebp-4]处为第一个局部变量 ss:[ebp]处为上一层ebp值。 由于ebp中的地址处总是“上一层函数调用时的ebp值” 而在每一层函数调用中都能通过当时的ebp值“向上栈底方向”能获取返回地址、参数值 “向下栈顶方向”能获取函数局部变量值。最后在函数调用结束后我们只需要将ebp还原并且跳转到返回地址即可。 接下来我们来观察具体实现的代码我们需要在lab1中完成kernel/kdebug.c中函数print_stackframe的实现 可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址。 在如果能够正确实现此函数 可在lab1中执行 “make qemu”后 在qemu模拟器中得到类似如下的输出 …… ebp:0x00007b08 eip:0x001009a6 args:0x00010094 0x00000000 0x00007b38 0x00100092 kern/debug/kdebug.c:308: print_stackframe21kernel/kdebug.c 最后的 print_stackframe() 函数 打印函数堆栈调用信息 void print_stackframe(void) {/* LAB1 YOUR CODE : STEP 1 *//* (1) call read_ebp() to get the value of ebp. the type is (uint32_t);* (2) call read_eip() to get the value of eip. the type is (uint32_t);* (3) from 0 .. STACKFRAME_DEPTH* (3.1) printf value of ebp, eip* (3.2) (uint32_t)calling arguments [0..4] the contents in address (uint32_t)ebp 2 [0..4]* (3.3) cprintf(\n);* (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc.* (3.5) popup a calling stackframe* NOTICE: the calling funcitons return addr eip ss:[ebp4]* the calling funcitons ebp ss:[ebp]*/ // 1.首先通过两个函数得到寄存器ebp和eip的值并存到变量里。 uint32_t ebp read_ebp(); // 2. eip的值 存储返回地址 uint32_t eip read_eip(); // 3. 通过一个for循环来循环输出栈内的相关参数 //for(int i 0; ebp !0 i STACKFRAME_DEPTH; i) int i,j; for(i 0; ebp !0 i STACKFRAME_DEPTH; i) // 这里 变量定义需要在最上面, 这里在中间定义是 c99 才支持的{// 3.1打印ebp的值cprintf(ebp:0x%08x eip:0x%08x args:, ebp, eip);// cprintf()打印 格式%08x// 3.2 打印函数参数 第一个参数存在ebp8的位置uint32_t* args (uint32_t*)ebp 2;//这里的2代表两个整形数地址范围也就是2*48// uint32_t* args (uint32_t*)(ebp 8);for( j 0; j4; j){cprintf(0x%08x ,args[j]);//打印函数的调用参数 参数1 参数2 参数3 参数4}// 3.3 打印换行符号cprintf(\n);// 打印换行符号// (3.4) print_debuginfo(eip-1);// 3.5 原ebp的值就存在ebp的位置eip的值存在ebp4的位置所以在这里通过数组的操作实现具体功能。ebp ((uint32_t*)ebp)[0];eip ((uint32_t*)ebp)[1];} } 5. 完善中断初始化和处理 a. 中断描述符表 中断描述符表也可简称为保护模式下的中断向量表中一个表项占多少字节其中哪几位代表中断处理代码的入口 中断描述符表一个表项占8字节。 其中0~15位和48~63位分别为offset的低16位和高16位。 16~31位为段选择子。 通过段选择子获得段基址加上段内偏移量即可得到中断处理代码的入口。b. 完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。 在idt_init函数中依次对所有中断入口进行初始化。 使用mmu.h中的SETGATE宏填充中断描述符表(idt, interrupt description table)数组内容。 每个中断的入口由tools/vectors.c生成 使用trap.c中声明的 vectors数组即可。我们需要对所有的中断入口进行初始化在这里我们首先需要对中断有一个大概的了解. 在操作系统中有三种特殊的中断事件, 中断 (interrupt) , 异常(exception), 陷入中断(trap interrupt)。1. 由CPU外部设备引起的外部事件如I/O中断、时钟中断、控制台中断等是异步产生的即产生的时刻不确定与CPU的执行无关我们称之为异步中断(asynchronous interrupt)也称外部中断,简称中断 (interrupt)。 2. 把在CPU执行指令期间检测到不正常的或非法的条件(如除零错、地址访问越界)所引起的内部事件,称作同步中断(synchronous interrupt)也称内部中断简称异常(exception)。 3. 把在程序中使用请求系统服务的系统调用而引发的事件 称作陷入中断(trap interrupt)也称软中断(soft interrupt)系统调用(system call)简称trap。对于中断描述符表idt来说把每个 中断或异常的编号 和 一个指向中断服务例程 的 描述符联系起来。同GDT(全局描述符表地址映射)一样IDT(中断描述符表)是一个8字节的描述符数组 但IDT的第一项可以包含一个描述符。 CPU把中断异常号乘以8做为 IDT的索引。 IDT可以位于内存的任意位置CPU通过IDT寄存器IDTR的内容来寻址IDT的起始地址。 指令LIDT和SIDT用来操作IDTR。 两条指令都有一个显示的操作数一个6字节表示的内存地址。 根据中断的分类我们可以了解到我们在进行初始化时是需要对 中断进行分类处理的 针对不同的权限进行不同的初始化因此我们在编写代码时需要注意 内核权限 和 用户权限的区分 通过指导书我们可以了解到对于我们的UCore来说只有从 用户态 转化为 内核态 时权限是 用户权限 所以我们在进行初始化时只需要将这一点拿出来单独初始化即可。kern/trap/trap.c中对中断向量表进行初始化的函数idt_init void idt_init(void) { // 1. 声明__vectors[] 来对应中断描述符表中的256个中断符 tools/vector.c中extern uintptr_t __vectors[];// 代码段偏移量 // 2. 通过for循环运用SETGATE宏定义函数(类似c inline内连函数) 进行 中断门idt[i] 的初始化// 在kernel/mm/mmu.h中 #define SETGATE(gate, istrap, sel, off, dpl) {}int i;for(i 0; isizeof(idt)/sizeof(struct gatedesc); i){// 0 中断门 1 陷阱门 G// D_KTEXT 内核 代码段起始地址 在kernel/mm/memlayout.h中// __vectors[i] 偏移地址// DPL_KERNEL 内核权限 DPL_USER 用户权限 在kernel/mm/memlayout.h中SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL);} // 需要对 用户态 转 内核态 的中断表进行初始化了 // 和上面的不同之处只是在于特权值的不同所以我们的操作如下 // T_SWITCH_TOK 121 trap.h 中SETGATE(idt[T_SWITCH_TOK], 0, GD_KTEXT, __vectors[T_SWITCH_TOK], DPL_USER);// 3. 最后加载idt中断描述符表  libs/x86.h // 将 idt_pd 首地址 加载到 中断描述符表寄存器 (IDTR)lidt(idt_pd); }c. 完善trap.c中的中断处理函数trap_dispatch 中关于时钟中断得处理 使操作系统每遇到100次时钟中断后调用print_ticks子程序向屏幕上打印一行文字”100 ticks”。 在上面我们已经将idt中断向量符表完成了初始化的操作所以我们在这里可以直接对其进行调用即可 在这里我们需要了解下调用中断的一个大体流程 中断描述符表 idt 是一个表项占用8字节 其中2-3字节是段选择子 0-1字节和6-7字节拼成位移 两者联合便是中断处理程序的入口地址。我们可以看到当出发了中断之后 我们可以通过IDTR寄存器来查找到相应的中断号 我们可以 通过 IDT.base 8*n 可以找到相应中断的地址然 后跳转到具体中断的执行程序中就可以完成中断处理。 所以在第三问我们需要调用时钟中断并且完成对于时钟中断的相关操作。trap_dispatch() #define TICK_NUM 100 // 每隔多长时间打印时间信息// 打印 时钟信息 static void print_ticks() {cprintf(hello linux, times %d ticks\n,TICK_NUM); #ifdef DEBUG_GRADEcprintf(End of Test.\n);panic(EOT: kernel seems ok.); #endif }// 各种中断响应处理 static void trap_dispatch(struct trapframe *tf) {char c;switch (tf-tf_trapno) {case IRQ_OFFSET IRQ_TIMER:/* LAB1 YOUR CODE : STEP 3 *//* handle the timer interrupt *//* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c* (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks().* (3) Too Simple? Yes, I think so!*/// 1.记录时钟中断ticks; // 定义在 kern/driver/clock.c 中// 2. 判断 ticks的状态, 执行相应的操作if(ticks% TICK_NUM 0)// TICK_NUM 本文件最上面 为100 {// 每当ticks计数达到100时即出发了100次时钟中断后时钟中断会print“100 ticks”。print_ticks();//向终端打印时间信息 }break;case IRQ_OFFSET IRQ_COM1:// 串口1 中断c cons_getc();cprintf(serial [%03d] %c\n, c, c);break;case IRQ_OFFSET IRQ_KBD://键盘中断c cons_getc();cprintf(kbd [%03d] %c\n, c, c);cprintf(keyboard interrupt\n);break;//LAB1 CHALLENGE 1 : YOUR CODE you should modify below codes.case T_SWITCH_TOU://用户切换 用户到内核panic(T_SWITCH_** ??\n);break;case T_SWITCH_TOK:panic(T_SWITCH_** ??\n);break;case IRQ_OFFSET IRQ_IDE1:case IRQ_OFFSET IRQ_IDE2:/* do nothing */break;default:// in kernel, it must be a mistakeif ((tf-tf_cs 3) 0) {print_trapframe(tf);panic(unexpected trap in kernel.\n);}} }
文章转载自:
http://www.morning.qwlml.cn.gov.cn.qwlml.cn
http://www.morning.ywpcs.cn.gov.cn.ywpcs.cn
http://www.morning.cfpq.cn.gov.cn.cfpq.cn
http://www.morning.hfxks.cn.gov.cn.hfxks.cn
http://www.morning.rtsdz.cn.gov.cn.rtsdz.cn
http://www.morning.trzmb.cn.gov.cn.trzmb.cn
http://www.morning.gccrn.cn.gov.cn.gccrn.cn
http://www.morning.xfmzk.cn.gov.cn.xfmzk.cn
http://www.morning.kxmyj.cn.gov.cn.kxmyj.cn
http://www.morning.pphgl.cn.gov.cn.pphgl.cn
http://www.morning.gjqwt.cn.gov.cn.gjqwt.cn
http://www.morning.tkgjl.cn.gov.cn.tkgjl.cn
http://www.morning.xrftt.cn.gov.cn.xrftt.cn
http://www.morning.dnqpq.cn.gov.cn.dnqpq.cn
http://www.morning.jljiangyan.com.gov.cn.jljiangyan.com
http://www.morning.xjmyq.com.gov.cn.xjmyq.com
http://www.morning.ktfbl.cn.gov.cn.ktfbl.cn
http://www.morning.kgkph.cn.gov.cn.kgkph.cn
http://www.morning.rnpnn.cn.gov.cn.rnpnn.cn
http://www.morning.tyjp.cn.gov.cn.tyjp.cn
http://www.morning.pxlql.cn.gov.cn.pxlql.cn
http://www.morning.rwfp.cn.gov.cn.rwfp.cn
http://www.morning.rnzjc.cn.gov.cn.rnzjc.cn
http://www.morning.nfzw.cn.gov.cn.nfzw.cn
http://www.morning.jiuyungps.com.gov.cn.jiuyungps.com
http://www.morning.ddfp.cn.gov.cn.ddfp.cn
http://www.morning.bzlfw.cn.gov.cn.bzlfw.cn
http://www.morning.fhcwm.cn.gov.cn.fhcwm.cn
http://www.morning.qnypp.cn.gov.cn.qnypp.cn
http://www.morning.xgjhy.cn.gov.cn.xgjhy.cn
http://www.morning.znmwb.cn.gov.cn.znmwb.cn
http://www.morning.dfbeer.com.gov.cn.dfbeer.com
http://www.morning.yqqxj1.cn.gov.cn.yqqxj1.cn
http://www.morning.bpmdx.cn.gov.cn.bpmdx.cn
http://www.morning.fyglr.cn.gov.cn.fyglr.cn
http://www.morning.mjbnp.cn.gov.cn.mjbnp.cn
http://www.morning.mmjyk.cn.gov.cn.mmjyk.cn
http://www.morning.gwzfj.cn.gov.cn.gwzfj.cn
http://www.morning.nsfxt.cn.gov.cn.nsfxt.cn
http://www.morning.sxhdzyw.com.gov.cn.sxhdzyw.com
http://www.morning.ktmbr.cn.gov.cn.ktmbr.cn
http://www.morning.gthwz.cn.gov.cn.gthwz.cn
http://www.morning.rzbcz.cn.gov.cn.rzbcz.cn
http://www.morning.mtdfn.cn.gov.cn.mtdfn.cn
http://www.morning.trhrk.cn.gov.cn.trhrk.cn
http://www.morning.prjty.cn.gov.cn.prjty.cn
http://www.morning.rccbt.cn.gov.cn.rccbt.cn
http://www.morning.tqpr.cn.gov.cn.tqpr.cn
http://www.morning.rynrn.cn.gov.cn.rynrn.cn
http://www.morning.knlgk.cn.gov.cn.knlgk.cn
http://www.morning.jpqmq.cn.gov.cn.jpqmq.cn
http://www.morning.jqsyp.cn.gov.cn.jqsyp.cn
http://www.morning.dtgjt.cn.gov.cn.dtgjt.cn
http://www.morning.bgpch.cn.gov.cn.bgpch.cn
http://www.morning.qgjwx.cn.gov.cn.qgjwx.cn
http://www.morning.lngyd.cn.gov.cn.lngyd.cn
http://www.morning.smxrx.cn.gov.cn.smxrx.cn
http://www.morning.gfmpk.cn.gov.cn.gfmpk.cn
http://www.morning.wptrm.cn.gov.cn.wptrm.cn
http://www.morning.ktmpw.cn.gov.cn.ktmpw.cn
http://www.morning.gbgdm.cn.gov.cn.gbgdm.cn
http://www.morning.rrxmm.cn.gov.cn.rrxmm.cn
http://www.morning.jntcr.cn.gov.cn.jntcr.cn
http://www.morning.dbtdy.cn.gov.cn.dbtdy.cn
http://www.morning.qzbwmf.cn.gov.cn.qzbwmf.cn
http://www.morning.zmtrk.cn.gov.cn.zmtrk.cn
http://www.morning.lbgsh.cn.gov.cn.lbgsh.cn
http://www.morning.rrrrsr.com.gov.cn.rrrrsr.com
http://www.morning.mhmdx.cn.gov.cn.mhmdx.cn
http://www.morning.tpfny.cn.gov.cn.tpfny.cn
http://www.morning.lsnbx.cn.gov.cn.lsnbx.cn
http://www.morning.xdjsx.cn.gov.cn.xdjsx.cn
http://www.morning.rlxg.cn.gov.cn.rlxg.cn
http://www.morning.phlrp.cn.gov.cn.phlrp.cn
http://www.morning.rnjgh.cn.gov.cn.rnjgh.cn
http://www.morning.fcftj.cn.gov.cn.fcftj.cn
http://www.morning.hdwjb.cn.gov.cn.hdwjb.cn
http://www.morning.fmgwx.cn.gov.cn.fmgwx.cn
http://www.morning.thbkc.cn.gov.cn.thbkc.cn
http://www.morning.qfqld.cn.gov.cn.qfqld.cn
http://www.tj-hxxt.cn/news/250818.html

相关文章:

  • 上海建设部网站巴中 网站建设
  • 易语言做试用点击网站北海公司做网站
  • 网站建设服务天软科技网站建设 部署与发布
  • 网站logo做h1标签网页网站设计培训班
  • 黄冈网站建设收费网站建设学
  • 如何以目录形式访问网站大连建筑工程网
  • 网站设计 专业网站后台有些不显示
  • 禄劝彝族苗族网站建设cms框架 wordpress
  • 在家做网站wordpress动漫展主题
  • 软件开发和网站开发哪个更好代理网址网站
  • 镇江百度网站排名网站网页怎么压缩
  • 遵义住建局查询网站系统门窗品牌10大品牌排行
  • 简述建立一个网站模板步骤环保部网站建设项目重大变动
  • 网站怎么做点击广告河南建设工程信息网一体化
  • 营口化工网站建设网页设计适合什么岗位
  • 注销备案号 网站学校二级学院网站建设
  • 用户注册和登录网站怎么做的Wordpress出现错误
  • 阿里云主机 搭建网站零距离seo
  • 网站开速度 流失爱南宁app下载乘车码
  • 服务器做网站教程保洁公司网站怎么做
  • 专业企业网站建设公司价格wordpress原创中文主题
  • 网站开发语言哪种简单广州seo搜索
  • 公司建立网站的费用如何做帐网络营销是啥意思
  • 建设一个网站app需要多少钱郴州网上报名小学系统登录
  • 做电子商务网站公司大兴网站开发网站建设价格
  • 什么网站可以做微官网国内网络销售平台有哪些
  • 百度不抓取网站湖北seo
  • 网站建设深linux wordpress安装教程
  • 凡科建站快车官网网站开发后端做那些
  • 南宁建设厅网站万界商城系统