Lab2 【哈工大_操作系统】操作系统的引导

本节将更新哈工大《操作系统》课程第二个 Lab 实验 操作系统的引导。按照实验书要求,介绍了非常详细的实验操作流程,并提供了超级无敌详细的代码注释。文末附完整 bootsect.ssetup.s 标准答案代码以及超详细注释。

实验目的:

  • 熟悉 hit-oslab 实验环境;
  • 建立对操作系统引导过程的深入认识;
  • 掌握操作系统的基本开发过程;
  • 能对操作系统代码进行简单的控制,揭开操作系统的神秘面纱。

实验任务:

1、bootsect.s 能在屏幕上打印一段提示信息“XXX is booting…”,其中 XXX 是你给自己的操作系统起的名字,例如 LZJos、Sunix 等
2、bootsect.s 能完成 setup.s 的载入,并跳转到 setup.s 开始地址执行。而 setup.s 向屏幕输出一行"Now we are in SETUP"。
3、setup.s 能获取至少一个基本的硬件参数(如内存参数、显卡参数、硬盘参数等),将其存放在内存的特定地址,并输出到屏幕上。

实验工具准备:

文件名介绍
hit-操作系统实验指导书.pdf哈工大OS实验指导书
Linux内核完全注释(修正版v3.0).pdf赵博士对Linux v0.11 OS进行了详细全面的注释和说明
file1615.pdfBIOS 涉及的中断数据手册
hit-oslab-linux-20110823.tar.gzhit-oslab 实验环境
gcc-3.4-ubuntu.tar.gzLinux v0.11 所使用的编译器
Bochs 汇编级调试指令bochs 基本调试指令大全
最全ASCII码对照表0-255屏幕输出字符对照的 ASCII 码
x86_64 常用寄存器大全x86_64 常用寄存器大全

一、bootsect.s 的屏幕输出功能

需要实现:bootsect.s 能在屏幕上打印一段提示信息“XXX is booting…”,其中 XXX 是你给自己的操作系统起的名字,例如 LZJos、Sunix 等

参考赵博士《Linux 内核 0.11 完全注释(修正版 V3.0)》第六章,其中非常详细的解释了 Linux_v0.11 的启动引导过程。

就比如下面这张图,形象生动地概括了OS引动过程:

  1. 首先,80x86 架构CPU进入实模式(16位),从 0xfff0 开始自动执行代码,在地址0处初始化中断向量。
  2. 从系统软盘上将第一扇区 bootsect.s 程序读入物理地址 0x07c00 处(共512 bytes),并赋给 bootsect 控制权。
  3. 接下来 bootsect.s 将自身移动到 0x90000 处,为了腾出内存空间,以便给操作系统内核使用。
  4. 将 setup.s 加载到内存 0x90200 处,即 bootsect.s 512 bytes 之后,并赋给 setup.s 控制权。
  5. system 模块加载到 0x10000 处。(==不能移动到0x00000处原因:==因为在执行是 setup.s 模块时,需要利用 ROM BIOS 的中断调用来获取机器的参数,不能把中断向量表、BIOS数据区给覆盖掉)
  6. system 模块移动到 0x00000 处,进入保护模式(32位),并赋给 system 控制权。

在这里插入图片描述

1、编写 bootsect.s

需要注意的是,我们要从头开始编写 bootsect.s,而不是在 Linux0.11 的源码上进行修改,这样子无法深刻理解 bootsect 引导过程。

解压 hit-oslab-linux-20110823.tar.gz ,并命名为 os_lab_Lab1 ,在此源码基础上我们进一步完成实验。进入 os_lab_Lab1/linux-0.11/boot/ 文件夹下的 bootsect.s 文件,我们清空文件内容,重头开始编写

  • 获取光标位置
entry _start
_start:
! 输出一些信息! 读入光标位置mov ah, #0x03xor bh, bh             ! 显示页码设置int 0x10

其中,中断 int 10ah = 03H 功能以及参数 bh 可以在文件 file1615.pdf 查到,如下图所示。

在这里插入图片描述

  • 输出显示字符串。同理可以查表发现,0x10中断对应的 ah = 13 功能为向屏幕输出显示字符串,并且各个参数的功能也都有详细介绍,具体可以参照代码及注释。

在这里插入图片描述

BOOTSEG = 0x07c0;    ! bootsect 程序开始的地址! 输出一些信息! 设置开机启动字符串mov cx, #26            ! 字符串长度(20字符+3个回车+3个换行)mov bx, #0x0007        ! 设置 显示页号 和 字符属性(BL表示显示颜色 01:蓝色,03:青色,07:普通白色)mov ax, #BOOTSEG       ! 字符串 段地址mov es, ax             ! 不能将立即数直接赋给段寄存器,ax 充当临时寄存器mov bp, #msg1          ! 字符串 偏移地址mov ax, #0x1301        ! ah = 13H -> 显示, al = 01H -> 设置文本模式int 0x10               ! 显示服务中断! 无限循环
inf_loop:jmp inf_loop! 显示字符串
msg1:.byte    13,10         ! 回车 + 换行.ascii    "Joker is booting ...".byte    13,10,13,10.org 510                   ! 将当前位置设置为内存地址 510
! 设置引导扇区标记.word    0xAA55        ! 指定引导扇区的最后两个字节,bootsect必须以它结尾,才能引导

需要注意的是:在结尾处我们需要使用 .org 指令 ,设置最后两个字节为 bootsect 程序标识。

可能有不少读者不太了解 org 指令 ,在此处给出Unix操作系统指令大全:115个最常用的Linux命令行大全 - 知乎 (zhihu.com),大家遇到不懂的指令可以在该文中查到。

2、编译和运行

cd ~/my_space/OS_HIT/oslab_Lab1/linux-0.11/boot
as86 -0 -a -o bootsect.o bootsect.s   // 用as86编译器,编译生成目标文件 bootsect.o
ld86 -0 -s -o bootsect bootsect.o     // 用ld86链接器,将目标文件连接成可执行文件

直接在 linux-0.11 文件夹下用 make all 也可以,因为Makefile里面定义了这两条语句了

编译后,通过 ls -l 指令可以查看生成文件信息。

在这里插入图片描述

需要留意的文件是 bootsect 的文件大小是 544 字节,而引导程序必须要正好占用一个磁盘扇区,即 512 个字节。造成多了 32 个字节的原因是 ld86 产生的是 Minix 可执行文件格式,这样的可执行文件处理文本段、数据段等部分以外,还包括一个 Minix 可执行文件头部。

  • 因此,我们需要去掉这 32 个字节后,将生成的文件拷贝到 linux-0.11 目录下,并一定要命名为“Image”。
dd bs=1 if=bootsect of=Image skip=32   // 去掉前 32 个 bytes
cp ./Image ../Image                    // 移动到 linux-0.11 目录下
../../run                              // 运行

至此,实验任务一 就完成了,实现效果如下所示。

在这里插入图片描述

二、bootsect.s 导入 setup.s

需要实现:bootsect.s 能完成 setup.s 的载入,并跳转到 setup.s 开始地址执行。而 setup.s 向屏幕输出一行"Now we are in SETUP"。

1、编写 setup.s 屏幕输出

我们首先来编写 setup.s 向屏幕输出功能,同理需要重头开始编写 setup.s,与 bootsect.s 类似。

entry _start
_start:! 读入光标位置mov ah, #0x03xor bh, bhint 0x10! 设置开机启动字符串mov cx, #25            ! 字符串长度(19字符+3个回车+3个换行)mov bx, #0x0007        ! 设置 显示页号 和 字符属性mov ax, cs             ! 使用cs值获取 段地址mov es, ax             ! ax 充当临时寄存器mov bp, #msg2          ! 字符串 偏移地址mov ax, #0x1301        ! ah = 13H -> 显示, al = 01H -> 设置文本模式int 0x10! 无限循环
inf_loop:jmp inf_loop! 显示字符串
msg2:.byte    13,10         ! 回车 + 换行.ascii    "Now we are in SETUP".byte    13,10,13,10

2、在 bootsect.s 中载入 setup.s

在 bootsects 中载入 setup.s ,需要用到中断 int 0x1302H 号功能——从磁盘中载入数据到内存。

在这里插入图片描述

具体代码如下所示:(除了新增代码,我们还需要去掉在 bootsect.s 添加的无限循环)

SETUPSEG = 0x07e0;               ! setup 程序开始的地址(没有移动到 0x9000 处)
SETUPLEN = 4;                    ! setup 占用扇区数量! 加载 setup.s 程序
load_setup:mov    dx,#0x0000            ! 设置驱动器和磁头(drive 0, head 0)mov    cx,#0x0002            ! 设置扇区号和磁道(sector 2, track 0)mov    bx,#0x0200            ! 偏移地址为 512 bytesmov    ax,#0x0200+SETUPLEN   ! ah=02H:从磁盘读数据到内存;al=04H,读入四个扇区int    0x13                  ! 低级磁盘服务中断jnc    ok_load_setup         ! 加载成功,跳转(无进位时跳转)!加载错误mov    dx,#0x0000mov    ax,#0x0000            ! 复位软驱int    0x13jmp load_setup               ! 再次尝试ok_load_setup:jmpi 0,SETUPSEG              ! 赋予 setup.s 控制权! 无限循环
! inf_loop:
!    jmp inf_loop

3、编译和运行

为了加快编译速度,避免一个一个手动编译,我们将借助 makefile 来实现编译。我们将用到 linux-0.11/tools/build.c 文件来实现。注意:在使用 make BootImage 之前,我们需要修改一下 build.c 代码,因为我们还没有编写内核文件 kernel,所以会出现报错。

  • 注释掉 tool/build.c 中的部分代码:
//     if ((id=open(argv[3],O_RDONLY,0))<0)
//         die("Unable to open 'system'");
// //    if (read(id,buf,GCC_HEADER) != GCC_HEADER)
// //        die("Unable to read header of 'system'");
// //    if (((long *) buf)[5] != 0)
// //        die("Non-GCC header of 'system'");
//     for (i=0 ; (c=read(id,buf,sizeof buf))>0 ; i+=c )
//         if (write(1,buf,c)!=c)
//             die("Write call failed");
//     close(id);
//     fprintf(stderr,"System is %d bytes.\n",i);
//     if (i > SYS_SIZE*16)
//         die("System is too big");return(0);
  • 即可实现编译和运行
cd linux-0.11
make BootImage
../run

至此,实验任务二 就完成了,实现效果如下所示。

在这里插入图片描述

三、setup.s 获取硬件参数并输出显示

需要实现:setup.s 能获取至少一个基本的硬件参数(如内存参数、显卡参数、硬盘参数等),将其存放在内存的特定地址,并输出到屏幕上。

1、获取硬件参数

获取硬件参数的代码大致都一样,我们将主要介绍 获取内存大小获取磁盘参数表,其他参数获取参考文末代码。

  • 要获取内存大小,我们需要用到中断 int 0x15,调用 ah = 0x88 功能实现。具体代码如下:
BOOTSEG = 0x07c0;         ! bootsect 读入段地址
INITSEG = 0x9000;         ! 初始数据段存放的位置
SETUPSEG = 0x07e0;        ! setup 程序开始的地址(没有移动到 0x90200 处)! 设置段地址,将硬件参数取出来放在内存 0x90000mov ax, #INITSEGmov ds, ax            ! 数据段地址 ds = 0x9000! 读光标位置,存入 dxmov ah, #0x03         ! AH = 3 -> 读光标位置xor bh, bh            ! 显示页数 = 0int 0x10! 将光标位置写入 0x90000.mov [0], dx! 读入内存大小位置mov ah, #0x88         ! AH = 0x88 -> 读入内存大小int 0x15              ! 内存大存入 AXmov [2], ax           ! 存入 9000 后面两个偏移
  • 获取磁盘参数表,。在 PC 机中 BIOS 设定的中断向量表中 int 0x41 的中断向量位置 4*0x41 = 0x0000:0x0104存放着第一个硬盘的基本参数表。第二个硬盘的基本参数表入口地址存于 int 0x46 中断向量位置处。每个硬盘参数表有 16 个字节大小。
! 获取磁盘参数表,从 0x41 处拷贝 16 个字节mov ax, #0x0000       ! 不允许直接将立即数加载到段寄存器,需要使用通用寄存器axmov ds, ax            ! 数据段地址 ds = 0x0000lds    si,[4*0x41]    ! 取中断向量 41 的值,即 hd0 参数表的地址 ds:simov    ax,INITSEGmov    es,axmov    di,#0x0080     ! 传输的目的地址: es:di = 9000:0080mov    cx,#0x10       ! 重复执行次数 = 16rep                   ! 重复执行movsb, DS:SI -> ES:DImovsb                 ! 每执行一次 movsb 指令,源地址和目的地址的偏移都会自动递增

2、显示获得的参数

为了以 16进制 的形式显示获取的参数,我们需要编写显示函数。(ASCII码请参考:最全ASCII码对照表0-255)

以十六进制方式显示比较简单。这是因为十六进制与二进制有很好的对应关系(每 4 位二进制数和 1 位十六进制数存在一一对应关系),显示时只需将原二进制数每 4 位划成一组,按组求对应的 ASCII 码送显示器即可。分为两种情况

  • 数字 0~9 : 对应 ASCII 码为 0x30~0x39,故显示时加上 0x30
  • 数字 a~f :对应ASCII码为 0x41~0x46,故显示时在原先加 0x30 的基础上,还要添加 0x07
  • 相关显示函数如下所示。将临时寄存器 ax 中存放的16位二进制数,通过4位十六进制的ASCII字符进行显示。其中使用循环左移 rol 不断获取高位数据 -> 并通过 and 操作保留第四位数据 -> 再统一加上 0x30 之后判断是否大于数字9,若大于再加上 0x07。
! 以 16 进制 打印寄存器 ax 中的16位数
print_hex:mov cx, #4        ! 循环次数smov dx,ax         ! 将ax所指的值放入dx中,ax作为参数传递寄存器
print_digit:rol dx,#4         ! 将 DX 寄存器中的值向左循环移位 4 位,相当于将 最高的4位 移到 最低的4位。后面取出最低四位,打印的时候实现高位在前mov ax,#0x0e0f     ! ah = 0eh -> 显示字符, al = 字符:半字节(4个比特)掩码。and al,dl         ! 取 dl 的低4比特值。add al,#0x30      ! 给al数字加上十六进制 0x30, 0~9 + 0x30 = "0~9"cmp al,#0x3a      ! 判断 al 是否大于 字符 9jl outp           ! al < 数字"9"后面的字符,说明是 0~9,则跳转add al,#0x07      ! al > "9", 是a~f,要多加7h
outp:int 0x10          ! 打印字符存储在 al 中loop print_digit  ! cx = 4,循环四次ret               ! 函数返回指令,表示函数执行结束,返回到调用该函数的位置! 打印显示回车换行
print_nl:mov ax, #0x0e0dint 0x10mov al, #0x0aint 0x10ret

回车和换行的区别
回车(13):表示光标移动到当前行的开头,但不改变行号
换行(10):表示光标移动到下一行,但不会移动到行首

  • 调用显示函数来实现硬件参数显示:
! 开始显示参数
! 前面修改了ds数据段寄存器,这里将其设置为0x9000mov ax,#INITSEG        ! 设置数据段地址,后续显示硬件参数使用mov ds,ax              ! 0x9000mov ax,#SETUPSEG       ! 设置附加段地址,后续显示字符串使用mov    es,ax           ! 0x07e0! 显示 光标位置! 获取光标位置mov    ah,#0x03        xor    bh,bhint    0x10! 显示字符串mov bp, #cur           ! 串 偏移地址mov cx, #11            ! 串 长度mov bx, #0x0003        ! 页号 = 0 + 颜色设置(03:青色,07:普通白色)mov ax, #0x1301        ! 显示字符串 + char...int 0x10! 显示数值mov ax, [0]            ! 打印函数参数存入 axcall print_hex         ! 调用打印寄存器函数call print_nl          ! 打印回车换行! 显示 内存大小! 获取光标位置mov    ah,#0x03        xor    bh,bhint    0x10! 显示字符串提示mov bp, #memmov cx, #12mov bx, #0x0007        mov ax, #0x1301        ! 显示字符串 + char...int 0x10! 显示数值mov ax, [2]call print_hex! 显示 KBmov    ah,#0x03        ! read cursor posxor    bh,bhint    0x10mov    cx,#6mov    bx,#0x0007      ! page 0, attribute c mov    bp,#cylmov    ax,#0x1301      ! write string, move cursorint    0x10! 无限循环
inf_loop:jmp inf_loop! 显示字符串
msg2:.byte    13,10         ! 回车 + 换行.ascii    "Now we are in SETUP".byte    13,10,13,10! 光标位置
cur:.ascii    "Cursor POS:"! 内存大小
mem:.ascii    "Memory SIZE:"! 提示信息
cyl:.ascii "KB".byte 13,10,13,10

至此,实验任务三 就完成了,实现效果如下所示。

在这里插入图片描述

至此,Lab2 实验介绍完毕,文末将附上 bootsect.ssetup.s 完整代码及详细注释。

实验 Lab2 代码与 Linux-0.11 代码的区别在于:

  • Lab2 无需将 bootsect.s 从 0x07c00 移动到 0x90000,故 setup.s 存放在其后 0x07e00 段位置处,也没有移动到 0x90200

四、完整代码汇总

bootsect.s

! 声明了几个全局符号,用来标识程序的代码段、数据段和未初始化数据段的起始和结束位置
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text                 ! 接下来的指令是 代码段
begtext:              ! 标识代码段的起始位置
.data                 ! 接下来的指令是 数据段
begdata:              ! 标识数据段的起始位置
.bss                  ! 接下来的指令是 未初始化数据段
begbss:               ! 标识未初始化数据段的起始位置
.text                 ! 接下来的指令是 代码段BOOTSEG = 0x07c0;     ! bootsect 程序开始的地址
SETUPSEG = 0x07e0;    ! setup 程序开始的地址(没有移动到 0x9000 处)
SETUPLEN = 4;         ! setup 占用扇区数量! 指定程序的入口点 为 _start
entry _start
_start:! 从go标号处开始执行,并设置代码段 cs = BOOTSEGjmpi    go,BOOTSEG
! 设置 ds=es=cs
go:    mov    ax,csmov    ds,ax  mov    es,ax! 输出一些信息! 读入光标位置mov ah, #0x03xor bh, bh             ! 显示页码设置int 0x10! 设置开机启动字符串mov cx, #26            ! 字符串长度(20字符+3个回车+3个换行)mov bx, #0x0007        ! 设置 显示页号 和 字符属性mov ax, #BOOTSEG       ! 字符串 段地址mov es, ax             ! 不能将立即数直接赋给段寄存器,ax 充当临时寄存器mov bp, #msg1          ! 字符串 偏移地址mov ax, #0x1301        ! ah = 13H -> 显示, al = 01H -> 设置文本模式int 0x10               ! 显示服务中断! 加载 setup.s 程序
load_setup:mov    dx,#0x0000            ! 设置驱动器和磁头(drive 0, head 0)mov    cx,#0x0002            ! 设置扇区号和磁道(sector 2, track 0)mov    bx,#0x0200            ! 偏移地址为 512 bytesmov    ax,#0x0200+SETUPLEN   ! ah=02H:从磁盘读数据到内存;al=04H,读入四个扇区int    0x13                  ! 低级磁盘服务中断jnc    ok_load_setup         ! 加载成功,跳转!加载错误mov    dx,#0x0000mov    ax,#0x0000            ! 复位软驱int    0x13jmp load_setup               ! 再次尝试! 加载成功后,开始执行setup代码
ok_load_setup:jmpi 0,SETUPSEG! 显示字符串
msg1:.byte    13,10               ! 回车 + 换行.ascii    "Joker is booting ...".byte    13,10,13,10! 将当前位置设置为内存地址 510 字节处
.org 510                
! 设置引导扇区标记.word    0xAA55               ! 指定引导扇区的最后两个字节,bootsect必须以它结尾,才能引导.text            
endtext:        ! 标识代码段的结束位置
.data
enddata:        ! 标识数据段的结束位置
.bss
endbss:         ! 标识未初始化数据段的结束位置

setup.s

BOOTSEG = 0x07c0;        ! bootsect 读入段地址
INITSEG = 0x9000;        ! 初始数据段存放的位置
SETUPSEG = 0x07e0;       ! setup 程序开始的地址(没有移动到 0x9000 处)! 指定程序的入口点 为 _start
entry _start
_start:! 从go标号处开始执行,并设置代码段 cs = SETUPSEGjmpi    go,SETUPSEG
! 设置 ds=es=cs
go:    mov    ax,csmov    ds,ax  mov    es,ax! 输出一些信息! 读入光标位置mov ah, #0x03xor bh, bhint 0x10! 设置开机启动字符串mov cx, #25            ! 字符串长度(20字符+3个回车+3个换行)mov bx, #0x0007        ! 设置 显示页号 和 字符属性mov ax, cs             ! 使用cs值获取 段地址mov es, ax             ! ax 充当临时寄存器mov bp, #msg2          ! 字符串 偏移地址mov ax, #0x1301        ! ah = 13H -> 显示, al = 01H -> 设置文本模式int 0x10! 设置数据段ds,将硬件参数取出来放在内存 0x90000mov ax, #INITSEGmov ds, ax             ! 数据段地址 ds = 0x9000! 读光标位置,存入数据段mov ah, #0x03          ! AH = 3 -> 读光标位置xor bh, bh             ! 显示页数 = 0int 0x10! 将光标位置写入 0x90000.mov [0], dx! 读入内存大小位置mov ah, #0x88          ! AH = 0x88 -> 读入内存大小int 0x15mov [2], ax            ! 存入 9000 后面两个偏移! 获取磁盘参数表,从 0x41 处拷贝 16 个字节mov ax, #0x0000        ! 不允许直接将立即数加载到段寄存器,需要使用通用寄存器axmov ds, ax             ! 数据段地址 ds = 0x0000lds    si,[4*0x41]     ! 取中断向量 41 的值,即 hd0 参数表的地址 ds:simov    ax,INITSEGmov    es,axmov    di,#0x0080      ! 传输的目的地址: es:di = 9000:0080mov    cx,#0x10        ! 重复执行次数 = 16rep                    ! 重复执行movsb, DS:SI -> ES:DImovsb                  ! 每执行一次 movsb 指令,源地址和目的地址的偏移都会自动递增! 开始显示参数
! 前面修改了ds数据段寄存器,这里将其设置为0x9000mov ax,#INITSEG        ! 设置数据段地址,后续显示硬件参数使用mov ds,ax              ! 0x9000mov ax,#SETUPSEG       ! 设置附加段地址,后续显示字符串使用mov    es,ax           ! 0x07e0! 显示 光标位置! 获取光标位置mov    ah,#0x03        xor    bh,bhint    0x10! 显示字符串mov bp, #cur           ! 串 偏移地址mov cx, #11            ! 串 长度mov bx, #0x0003        ! 页号 = 0 + 颜色设置(03:青色,07:普通白色)mov ax, #0x1301        ! 显示字符串 + char...int 0x10! 显示数值mov ax, [0]            ! 打印函数参数存入 axcall print_hex         ! 调用打印寄存器函数call print_nl          ! 打印回车换行! 显示 内存大小! 获取光标位置mov    ah,#0x03        xor    bh,bhint    0x10! 显示字符串提示mov bp, #memmov cx, #12mov bx, #0x0007        mov ax, #0x1301        ! 显示字符串 + char...int 0x10! 显示数值mov ax, [2]call print_hex! 显示 KBmov    ah,#0x03        ! read cursor posxor    bh,bhint    0x10mov    cx,#6mov    bx,#0x0007      ! page 0, attribute c mov    bp,#cylmov    ax,#0x1301      ! write string, move cursorint    0x10! 无限循环
inf_loop:jmp inf_loop! 以 16 进制 打印寄存器 ax 中的16位数
print_hex:mov cx, #4             ! 循环次数smov dx,ax              ! 将ax所指的值放入dx中,ax作为参数传递寄存器
print_digit:rol dx,#4              ! 将 DX 寄存器中的值向左循环移位 4 位,相当于将 高4位 移到 低4位mov ax,#0xe0f          ! ah = 0eh -> 显示字符, al = 字符:半字节(4个比特)掩码。and al,dl              ! 取 dl 的低4比特值。add al,#0x30           ! 给al数字加上十六进制 0x30, 0~9 + 0x30 = "0~9"cmp al,#0x3a           ! 判断 al 是否大于 字符 9jl outp                ! al < 数字"9"后面的字符,说明是 0~9,则跳转add al,#0x07           ! al > "9", 是a~f,要多加7h
outp:int 0x10               ! 打印字符存储在 al 中loop print_digit       ! cx = 4,循环四次ret                    ! 函数返回指令,表示函数执行结束,返回到调用该函数的位置! 打印回车换行
print_nl:mov ax, #0x0e0dint 0x10mov al, #0x0aint 0x10ret! 显示字符串
msg2:.byte    13,10        ! 回车 + 换行.ascii    "Now we are in SETUP".byte    13,10,13,10! 光标位置
cur:.ascii    "Cursor POS:"! 内存大小
mem:.ascii    "Memory SIZE:"! 提示信息
cyl:.ascii "KB".byte 13,10,13,10

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/144001.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

C语言中的assert断言

Assert断言 断言是程序中处理异常的一种高级形式。可以在任何时候启用和禁用断言验证&#xff0c;因此可以在测试时启用断言&#xff0c;而在部署时禁用断言。同样&#xff0c;程序投入运行后&#xff0c;最终用户在遇到问题时可以重新启用断言。 用法&#xff1a; #…

AD域控服务器

1.AD域控服务器安装 2.客户端Windows10加入域环境 3.组织单位OU和域用户创建 目的是分部门管理用户和使用域用户登录客户端 4.域用户安全策略 5.当客户端密码锁住了,管理员解锁账户。 6.只允许域用户使用自己的电脑 7.域策略 7.1统一客户端桌面壁纸 7.2重定向用户配置文件路径…

软件设计画图,流程图、甘特图、时间轴图、系统架构图、网络拓扑图、E-R图、思维导图

目录 一、流程图 二、甘特图 三、时间轴图 四、系统架构图 五、网络拓扑图 六、E-R图 七、思维导图 一、流程图 是一种用符号表示算法、工作流或流程的图形。用不同的图形表示不同含义&#xff0c;如椭圆表示开始和结束、菱形表示判断等。 画图工具WPS office 应用市场…

如何使用ssm实现基于vue.js的购物商场的设计与实现+vue

TOC ssm616基于vue.js的购物商场的设计与实现vue 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着电脑和笔记本…

如何使用ssm实现基于ssm框架的车辆出租管理系统+vue

TOC ssm643基于ssm框架的车辆出租管理系统vue 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思想上不可跨域…

LeetCode_sql_day28(1767.寻找没有被执行的任务对)

描述&#xff1a;1767.寻找没有被执行的任务对 表&#xff1a;Tasks ------------------------- | Column Name | Type | ------------------------- | task_id | int | | subtasks_count | int | ------------------------- task_id 具有唯一值的列。 ta…

7iDU AMP田岛绣花机驱动器维修0J2100400022

7iDU AMP神州田岛绣花机驱动器维修0J2101300000绣花机控制器等全系列型号均可处理。 田岛7iDU AMP是田岛绣花机中使用很广的一种5相驱动器&#xff0c;在田岛平绣车TMEF-H&#xff0c;TMFD中应用&#xff0c;在链条车TMCE112S&#xff0c;和盘带车TMLG中大量使用。其采用的东芝…

面试或工作中的经典问题共享

前言&#xff1a;大家可以把面试或工作中遇到了经典问题共享一下&#xff0c;我们共同学习成长&#xff0c;金九银十&#xff0c;祝各位都能步步高升&#xff01; 文档大家可以往下追加&#xff0c;请勿删除他人编写的内容哦。我会把问题的相关资料进行补充&#xff0c;以便各…

HTB-Blue(永恒之蓝漏洞复现)

前言 各位师傅大家好&#xff0c;我是qmx_07&#xff0c;今天给大家讲解Blue靶机 渗透过程 信息搜集 服务器开放了smb服务&#xff0c;漏洞探测显示 具有ms17_010(永恒之蓝漏洞) 利用永恒之蓝 搜索永恒之蓝漏洞 use使用永恒之蓝漏洞 rhost //对方主机 lhost //回连主机 …

[学习笔记]树链剖分(简易版) 及其LCA

树链剖分 先讲解一下一些基础定义(都是在树上) 重儿子: 一个节点中所有儿子中子树大小最大的一个儿子(每个节点最多有一个重儿子)轻儿子: 一个节点除重儿子外所有的节点重链: 若干个重儿子组成的链链顶: 一条链中深度最小的节点 以下图为例子 (红色连续线段为重链) 对于节点…

【LabVIEW】事件结构的用法

本篇文章记录我学习LabVIEW的事件结构用法&#xff0c;希望我的分享对你有所帮助&#xff01; 目录 一、案例说明 1、 LabVIEW实现“YAXBXC的计算” 2、添加事件结构 一、案例说明 在LabVIEW实现“YAXBXC的计算”的基础上&#xff0c;加上事件结构&#xff0c;实现单击一次按…

分布式锁总结2 - redis实现分布式锁并解决常见问题

目录 1. redis分布式锁 1.1基本原理图示如下 1.2 Redis通过一个lock变量实现一个最简单的分布式锁实现代码&#xff1a; 2 升级简单分布式锁&#xff08;实现原子加锁与安全删锁&#xff09; 2.1 但1中的简单分布式锁存在几个问题&#xff1a; 2.1.1 问题1. 如果加完锁执…

Vue.js魔法书:前端开发者的终极指南----指令篇续篇

​个人名片&#xff1a; &#x1f60a;作者简介&#xff1a;一个为了让更多人看见许舒雅的宝贝的小白先生 &#x1f921;个人主页&#xff1a;&#x1f517; 许舒雅的宝贝 &#x1f43c;座右铭&#xff1a;深夜两点半的夜灯依旧闪烁&#xff0c;凌晨四点的闹钟不止你一个。 &am…

计算机网络32——Linux-文件io-2文件系统

1、阻塞和非阻塞 想要将文件以非阻塞方式打开&#xff0c;有两种方式 &#xff08;1&#xff09;需要将文件关闭&#xff0c;再用非阻塞方式打开 &#xff08;2&#xff09;fctnl函数&#xff0c;先获取旧属性&#xff0c;再添加一个新属性 阻塞函数 阻塞函数一直在等待输入…

MATLAB系列09:图形句柄

MATLAB系列09&#xff1a;图形句柄 9. 图形句柄9.1 MATLAB图形系统9.2 对象句柄9.3 对象属性的检测和更改9.3.1 在创建对象时改变对象的属性9.3.2 对象创建后改变对象的属性 9.4 用 set 函数列出可能属性值9.5 自定义数据9.6 对象查找9.7 用鼠标选择对象9.8 位置和单位9.8.1 图…

使用微信小程序唤起导航的常用方式

1.微信内置地图 可以使用小程序的wx.openLocation方法&#xff0c;该方法可以打开微信内置地图&#xff0c;并显示指定的位置坐标。如果用户手机上安装了其他地图应用&#xff0c;可能会出现选择使用哪个地图应用进行导航的提示。 wx.openLocation({latitude: 目标地点纬度,lo…

滑动窗口(7)_串联所有单词的字串

目录 1. 题目链接: 2. 题目描述 : 3. 解法 : 题目解析: 算法思路 : 图解流程: 代码展示 : 结果分析 : 1. 题目链接: OJ链接:串联所有单词的字串 2. 题目描述 : 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 w…

Llama 3.1 Omni:颠覆性的文本与语音双输出模型

你可能听说过不少关于语言模型的进展,但如果告诉你,有一种模型不仅能生成文本,还能同时生成语音,你会不会觉得特别酷?今天咱们就来聊聊一个相当前沿的项目——Llama 3.1 Omni模型。这个模型打破了传统的文字生成边界,直接让文本和语音同时输出,实现了真正的"多模态…

【uni-app】小兔鲜项目-基础架构-请求和上传文件拦截器

注意事项 uni.request 请求封装 请求和上传文件拦截器 uniapp 拦截器&#xff1a; uni.addInterceptor 接口说明&#xff1a;接口文档 实现需求 拼接基础地址设置超时时间添加请求头标识添加 token 参考代码 // src/utils/http.ts// 请求基地址 const baseURL https://pca…

fastadmin 部署后前台会员中心出现404错误

访问前台会员中心出现404错误。 解决&#xff1a;nginx访问站点增加伪静态 location / {if (!-e $request_filename){rewrite ^(.*)$ /index.php?s$1 last; break;} }在phpstydy中增加伪静态&#xff0c;如图&#xff1a;