精华内容
下载资源
问答
  • 软盘分为两面,每一个面有80(0-79)个柱面(内到外一共分80圈),也叫簇 每一个柱面有18(1-18)个扇区(把圆分为了18个弧形面),每一个扇区里面可以存512字节 所以软盘一共可存储18802*512字节数据,约等于1.44...

    软盘的基本结构

    在这里插入图片描述
    软盘分为两面,每一个面有80(0-79)个柱面(从内到外一共分80圈),也叫簇
    每一个柱面有18(1-18)个扇区(把圆分为了18个弧形面),每一个扇区里面可以存512字节
    所以软盘一共可存储18802*512字节数据,约等于1.44MB存储(1000KB=1M的算法)
    每一个扇区以0x55 0xaa结尾 所以可写数据是510字节。

    软盘的数据读取
    软盘的读取数据的磁头有上下两个,读取数据顺序是先读上面柱面的数据,然后读下面对应柱面的数据(一圈一圈的读)。

    系统启动过程

    电脑通电->CPU复电->CPU加载BIOS程序(CPU里面有一个ROM内存,存着BIOS引导程序)->BIOS执行硬件自检-> 跳转到内存的0x7c00处

    写代码测试实模式

    org 0x7c00  ;BIOS跳过来的入口点
    
    entry:
    	mov ax,cs ;把用到的所有段寄存器base改为0
    	mov ds,ax
    	mov es,ax;
    	mov ss,ax;
    	mov si,msg
    printStr:
    	mov al,[si] ;al中代表要打印的字符
    	inc si 
    	cmp al,0
    	jz fin;
    	
    	mov ah,0xe;ah中的值代表打印方式
    	mov bx,15; bx是以什么来打印
    	int 0x10;  0x10中断  上面提供的是int 0x10的参数
    	jc fin;   打印出错会把CF位置1  jc指令就是cf位为1 跳转
    	jmp printStr;
    
    fin:
        HLT;让cpu一直睡眠  所以下面一个跳转不会运行
        jmp fin;
    
    [SECTION .data]
    msg:
    	db "hello world111111111111111111111111111";在汇编中编译器会自动加上\0结尾
    

    使用nasm编译此汇编. -o 指定文件名

    nasm os.asm -o os.bin
    

    烧录映像文件

    floppy.exe -wf os.bin 0 0 2 os.bin   //0正面还是反面  0簇(哪一圈)  2扇区
    

    运行img文件

    使用bochs虚拟机,能够调试实模式程序

    下载好bochs后,在安装文件里面新建一个文件夹dos
    在dos文件加下创建两个批处理文件
    run.bat

    cd "C:\Program Files\Bochs-2.6.7\dos" 
    ..\Bochs.exe -q -f Bochsrc.bxrc 
    

    rundbg.bat

    cd "C:\Program Files\Bochs-2.6.7\dos" 
    ..\Bochsdbg.exe -q -f Bochsrc.bxrc 
    

    新建一个配置文件 ,名为Bochsrc.bxrc

    # 在一行的最前面加上“#”表示这一行是注释行。 
    # 内存,以MB为单位,对于dos来说最大可以访问16MB 
    # 的内存,所以我就给了他16MB,你可以根据自己的机器来调整 
    megs: 16 
    # 下面两句一般是不可以改的,至于干什么用的就不用我说 
    # 了。从他们的文件名就可以看出来。 
    romimage: file=$BXSHARE/BIOS-bochs-latest
    vgaromimage: file=../VGABIOS-lgpl-latest 
    # 这个还用说吗?当然是软驱了,我想我们写操作系统肯定是先 
    # 把操作系统放在软盘(或映像)里面吧?在Bochs里面是可 
    # 以使用任意大小的软驱映像的。可以是1.442.88,我一般使 
    # 用2.88。还有就是Bochs里面可以使用两个软驱。不过好像 
    # 我们并不经常这样做。 
    #floppya: 2_88=test.img, status=inserted   在这里制定要启动的映像文件
    floppya: 1_44=boot.img, status=inserted    
    # 下面是硬盘,很简单,还有就是Bochs也是可以支持多个硬 
    # 盘的。那么,硬盘文件是怎么生成的呢?我们可以发现硬盘是 
    # img格式的。你注意没有在Bochs文件夹里有一个工具叫 
    # bximage.exe,我想你应该猜出来了。他就是用来生成这个硬盘 
    # 文件的工具。我在这儿还想说的是硬盘分三种格式的,最好选 
    #用growing类型。这种有一个好处就是节省硬盘空间,不过使用 
    #这种类型的硬盘还需要在下面加上mode = growing这个选项。 
    #ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 
    #ata0-master: type=disk, path="dos.img", cylinders=306, heads=4, spt=17 
    # 下面这个就是光驱,没什么好说的。如果你想使用物理光驱, 
    # 只要让path=E:(我们假设E盘是光驱) 
    #ata0-slave: type=cdrom, path="dos.iso", status=inserted 
    # 这个是启动设备,可以使用cdrom(光驱)、c(硬盘)或floppy(软 
    # 驱)。 
    #boot: cdrom 
    #boot: c 
    boot: floppy 
    # 这一句可以不要,他只是指定用来保存日志的文件。如果不指定的 
    # 话他就会输出到命令控制台上。 
    log: Bochsout.txt 
    # 这一句是设置在开机时是否激活鼠标,Bochs对于鼠标的控制不是# 很好。建议如果不是特别需要的话不要激活他。在运行期间也可以点窗口右上角的鼠标图标来激活他。 
    mouse: enabled=0 
    
    

    最后一步把映像文件赋值到这个文件夹中,点击run或者rundbg就可以运行了
    在这里插入图片描述

    调试命令
    b +地址 下断点
    例如:我们的程序是写在0x7c00

    b 0x7c00
    

    单步执行:n
    单步执行:s 会进入函数
    继续执行:c

    进入保护模式代码

    load.asm

    org 0x7c00
    
    RUN_CODE equ 0x8000
    
    entry:
    	 mov ax,cs;
    	 mov es,ax;
    	 mov ds,ax;
    	 mov ss,ax;
    	 mov si,RUN_CODE
    	
    putCode:
    	 mov dh,0x0 ;磁道正面
    	 mov ch,0x0 ;第一个柱面
    	 mov cl,0x2; 第二个扇区
    	 
    	 mov bx,si
    	 
    	 mov dl,0x0 ;A光驱
    	 mov ah,0x2; 读
    	 mov al,0x1
    	 
    	 int 0x13;
    	 jc fin;
    	 
    	 jmp RUN_CODE;
    	  
    fin:
    	HLT;
    	jmp fin;
    

    struct.inc

    %macro GDT_UTYPE_Descriptor 3   ;代表有3个参数,用这3个参数来构造GDT  参数1 base 参数2 limit 参数3 attr
    	dw %2 & 0xffff   ;取第二个参数
    	dw %1 & 0xffff	 ;取第一个参数	
    	db (%1 >> 16) & 0xff
    	dw ((%2 >> 8) & 0x0f00) | (%3 & 0xf0ff)
    	db (%1 >> 24) & 0xff
    %endmacro
    
    CODE_32 equ 0x4000  ;DB
    CODE_32_G equ 0x8000  ;DB
    CODE_SEGMENT_32 EQU 0x9A
    DATA_SEGMENT_32 EQU 0x92
    

    os.asm

    %include "struct.inc"
    org 0x8000
    jmp begin;
    
    [SECTION gdt]
    GDT_LABEL 	: 	GDT_UTYPE_Descriptor 0,0,0 ;base 0 limit 0 attr 0  GDT表中第一项是0
    GDT_CODE32	: 	GDT_UTYPE_Descriptor 0,0xfffff,CODE_32 + CODE_32_G + CODE_SEGMENT_32 ;c09a
    GDT_CODE_VIDEO: GDT_UTYPE_Descriptor 0xb8000,0xffff,CODE_32 + DATA_SEGMENT_32
    
    GDT_LEN equ $ - GDT_LABEL
    CODE_32_SELECTOR equ GDT_CODE32 - GDT_LABEL
    VIDEO_32_SELECTOR equ GDT_CODE_VIDEO - GDT_LABEL
    
    GDT_PTR:
    	dw GDT_LEN -1
    	dd 0
    
    [SECTION .s16]
    [BITS 16]
    begin:
    	mov ax,cs;
    	mov es,ax;
    	mov ds,ax;
    	mov ss,ax;
    	mov sp,0x100;
    	
    	;加载GDT
    	mov eax,GDT_LABEL
    	mov dword [GDT_PTR+2],eax;
    	lgdt [GDT_PTR]   ;  加载GDT描述符到GDTR寄存器
    	
    	;
    	cli
    	in al,0x92  ;0x92端口拿1字节数据
    	or al,2;	打开此端口才能使用控制寄存器  使用cr0开启保护模式
    	out 0x92,al; 写回92端口
    	
    	mov eax,cr0;
    	or eax,1;   ;pe为设置为1  开启保护模式
    	mov cr0,eax;
    	sti;
    	jmp CODE_32_SELECTOR:segment32    ;类似于jmp  0008:eip
    
    [SECTION .s32]
    [BITS 32]
    segment32:
    		
    		mov ax ,VIDEO_32_SELECTOR   ;显卡设置到gs
    		mov gs,ax
    		mov si,msg
    		mov ebx,0x10
    		mov ecx,2
    showMsg:
    		mov edi,(80 * 11); 8011列
    		add edi,ebx
    		mov eax,edi
    		mul ecx
    		mov edi,eax
    		mov ah,0xc
    		mov al,[si]
    		cmp al,0
    		je fin
    		inc ebx
    		inc si
    		mov [gs:edi],ax
    		jmp showMsg
    		
    	
    
    fin:
    	HLT
    	jmp $
    
    msg:
    	db "my is hackflae see os!!!"
    

    使用nasm编译,

    nasm os.asm -o os.bin  
    nasm load.asm -o load.bin
    

    然后使用floppy烧录即可

    floppy.exe -wf load.bin 0 0 2 os.bin
    

    点击下载相关工具
    提取码:avzk

    展开全文
  • 操作系统学习笔记01 从实模式进入保护模式总结新的改变为何引入保护模式保护模式与实模式的不同从实模式进入保护模式的汇编代码 总结 不知不觉已经接近年底,心中比较愤恨自己。因为说好的一周更新一次博客,已经一...

    总结

    不知不觉已经接近年底,心中比较愤恨自己。因为说好的一周更新一次博客,已经一个半月没有更新过了。操作系统方面的知识我十分感兴趣,所以肯定会连续更新下去。

    新的改变

    我把参考书籍从《30天自制操作系统》变为了《自己动手写一个操作系统》等书,值得参考的书籍还有《操作系统真相还原》《64位操作系统的实现》《IA-32 Intel开发手册第三卷》。开发手册几乎是必备的。他标志着从80386处理器开始,x86系列cpu里程碑式的彻底进入保护模式的世界。

    为何引入保护模式

    如果我们活生生的人作为操作系统去处理计算机中各种繁杂的事务。我们首先要考虑的肯定不是我们的内存,或者进程的调度算法什么的。更不应该考虑自己的外部硬件配置是怎么怎么样。首先要保证的就是我们自身的安全,也就是操作系统自身,在内存中可以安全的存在而不会被随意的修改。
    可是在实模式中,用户的程序,和操作系统平起平坐,像天王老子一样。这肯定是我们OS开发者不想看到的。于是,我们需要引入保护模式。

    保护模式与实模式的不同

    首先几个概念值得强调,16位模式时,所有寄存器都为16位,而地址总线却有20根,这短短20根线的寻址能力也就是2的20次方 = 1MB。小小的1MB在当时都是很不够用的,并且一部分还被BIOS的程序拿了去。如图所示
    在这里插入图片描述

    直到80386,我们的通用寄存器变为了32位,例如ax有了自己的拓展,变为eax。当然还有其它的一些寄存器(除了段寄存器仍然是16位)。并且总线也变为了32根。所以,寻址能力也达到了4GB.
    在这里插入图片描述

    接下来我会用自己的话解释以下几个名词:
    GDT
    Global Descriptor Table,全局描述符表。在有了4gb的寻址能力后,我们不再需要去委屈寄存器通过[段地址x16+偏移地址]这样的方式去寻址了。我们似乎只要使用一个寄存器就可以访问内存中的所有单元,但是,不可以想一出是一出,要保持对16位寄存器与寻址模式的兼容性。那么我们此时又不需要段地址,又要保持兼容性。只能给段寄存器中的内容赋予不同的意义.
    于是,我们将段地址作为索引,也就是作为所谓的段选择子。每一个段地址,都要拿着他的索引,去GDT中找到相应的段地址存放的地方。找到这个真实的段地址后,与偏移地址相加,即可得到物理地址。
    在这里插入图片描述

    这个GDT,是要存在内存中的。而每一个其中的段描述符结构如下。一个描述符64位。其中的信息有:段的基地址,段界限(有了段界限,我们就可以在寻址时检查用户程序对内存的访问是否合法)。还有一些关键信息。
    在这里插入图片描述
    段选择子
    段选择子在上文已经提到过,说白了是一个GDT这张表的索引,通过它去找到单个的段描述符。为16位。
    在这里插入图片描述

    段描述符
    就是GDT表中的一个一个项了。
    GDTR
    CPU切换到保护模式前,需要准备好GDT数据结构,并执行LGDT指令,将GDT基地址和界限传入到GDTR寄存器。
    GDTR寄存器长度为6字节(48位),前两个字节为GDT界限,后4个字节为GDT表的基地址。所以说,GDT最多只能拥有8192个描述符(65536 / 8)。
    在这里插入图片描述
    段描述符中的属性
    多说无益,各个位的属性值都在这里了。可以看出,代码段都是必须可执行的(不可写的),也就是11位这列。而数据段都是必须可读的。看英文文档的能力是必须要有的。
    在这里插入图片描述

    从实模式进入保护模式的汇编代码

    DA_32 EQU	4000h;32位
    DA_C EQU 98h; 只执行代码段的属性
    DA_DRW	EQU 92h;可读写的数据段
    DA_DRWA EQU 93h;存在的已访问的可读写的
    
    %macro Descriptor 3			;定义描述符宏,三个参数分别为 段基址(32位)段界限(20位)属性(12位)
    	dw %2 & 0FFFFh	;段界限1 (2字节)
    	dw %1 & 0FFFFh	;段基址1 (2字节)
    	db (%1 >> 16) & 0FFh	;段基址2 (1字节)
    	dw ((%2 >> 8) & 0F00h) | (%3 &0F0FFh) ;属性1 + 段界限2+属性2 (2字节)
    	db (%1 >> 24) & 0FFh	;段基址3
    %endmacro
    ;以上内容可以根据表格推出来。
    
    	org 0100h 	;因为我们dos下调试程序,那么0100是可用区域
    	jmp PM_BEGIN	;跳入到标号为PM_BEGIN的代码段开始推进
    	
    	
    [SECTION .gdt]
    ;GDT
    ;								段基址,段界限,属性
    PM_GDT:				Descriptor		0,		0,		0
    PM_DESC_CODE32:		Descriptor		0,		SegCode32Len -1,	DA_C+DA_32
    PM_DESC_DATA:		Descriptor		0,		DATALen-1, 			DA_DRW	
    PM_DESC_STACK:		Descriptor		0,		TopOfStack,			DA_DRWA+DA_32
    PM_DESC_TEST:		Descriptor		0200000h,0ffffh,			DA_DRW
    PM_DESC_VIDEO:		Descriptor		0B8000h, 0ffffh, 			DA_DRW
    ;end of definiton gdt
    GdtLen equ $ - PM_GDT			;gdt表的总长度,用来提交给gdtr寄存器
    GdtPtr dw GdtLen - 1			;在这里面的 LABEL_DESC_CODE32 和 GdtPtr 都要-1,
    							;因为访问指定表项所对应的段基址的时候都是包含基址的,长度自然要-1
    dd  0 ; GDT 基地址 待填
    
    ;GDT 选择子
    SelectoerCode32	equ PM_DESC_CODE32 - PM_GDT	 ;32位的代码段
    SelectoerDATA	equ PM_DESC_DATA - PM_GDT	
    SelectoerSTACK	equ PM_DESC_STACK - PM_GDT	
    SelectoerTEST	equ PM_DESC_TEST - PM_GDT	;测试用段
    SelectoerVideo	equ PM_DESC_VIDEO - PM_GDT				
    ;END of [SECTION .gdt]
    
    [SECTION .data1]
    ALIGN 32
    [BITS 32]r
    PM_DATA:
    PMMessage : db "Honey!I'm in Protect Mode!", 0;
    OffsetPMessage equ PMMessage - $$
    DATALen equ $- PM_DATA
    ;END of [SECTION .data]
    
    ;全局的堆栈段
    [SECTION .gs]
    ALIGN 32
    [BITS 32]
    PM_STACK:
    	times 512 db 0
    TopOfStack equ $ - PM_STACK -1
    ;END of STACK	
    
    [SECTION .s16]
    [BITS 16]
    PM_BEGIN:
    	mov ax,cs
    	mov ds,ax
    	mov es,ax
    	mov ss,ax
    	mov sp,0100h
    	
    	;初始化32位的代码段
    	xor eax,eax
    	mov ax,cs
    	shl eax,4		;这里左移4位,与下面的偏移地址相加,形成32位代码段的物理地址,装入段描述符
    	add eax,PM_SEG_CODE32
    	mov word[PM_DESC_CODE32+2],ax
    	shr eax,16
    	mov byte [PM_DESC_CODE32+4],al
    	mov byte [PM_DESC_CODE32+7],ah
    	
    	
    	;初始化32位的数据段
    	xor eax,eax
    	mov ax,ds
    	shl eax,4
    	add eax,PM_DATA
    	mov word[PM_DESC_DATA+2],ax
    	shr eax,16
    	mov byte [PM_DESC_DATA+4],al
    	mov byte [PM_DESC_DATA+7],ah
    	
    	;初始化32位的stack段
    	xor eax,eax
    	mov ax,ds
    	shl eax,4
    	add eax,PM_STACK
    	mov word[PM_DESC_STACK+2],ax
    	shr eax,16
    	mov byte [PM_DESC_STACK+4],al
    	mov byte [PM_DESC_STACK+7],ah
    	
    	;加载GDTR
    	xor eax,eax
    	mov ax,ds
    	shl eax,4
    	add eax,PM_GDT	;GDT的第一条记录的物理地址
    	mov dword [GdtPtr +2 ],eax
    	lgdt [GdtPtr]
    	
    	;关闭中断
    	cli
    					;通过A20快速门方式使用I/O 0x92号端口处理A20信号线。
    	in al,92h
    	or al,00000010b
    	out 92h,al
    	
    	;切换到保护模式
    	mov eax,cr0
    	or eax,1
    	mov cr0,eax
    	
    	jmp dword SelectoerCode32:0 	;这里使用了保护模式的寻址方式:gdt,跳到32位代码段
    
    
    
    [SECTION .s32]	;32位的代码段
    [BITS 32]
    PM_SEG_CODE32 :
    	mov ax,SelectoerDATA	;通过数据段的选择子放入ds寄存器,就可以用段+偏移进行寻址
    	mov ds,ax
    	
    	mov ax,SelectoerTEST	;通过测试段的选择子放入es寄存器,就可以用段+偏移进行寻址
    	mov es,ax
    	
    	mov ax,SelectoerVideo
    	mov gs,ax
    	
    	mov ax,SelectoerSTACK
    	mov ss,ax
    	mov esp,TopOfStack
    	
    	mov ah,0xca
    	xor esi,esi
    	xor edi,edi
    	mov esi,OffsetPMessage			;MSG中写的是字符串,在上文里
    	mov edi,(80*10 +0) *2
    	cld
    	
    .1:
    	lodsb				;将 以esi为段地址,edi为偏移地址的内存块加载入al寄存器
    	test al,al			;test 相与操作,如果结果为0,则eflag寄存器中的ZF为1
    	jz .2				;jump if zero ,如果结果为0。去.2段
    	mov [gs:edi],ax
    	add edi,2
    	jmp .1
    	
    .2: ;显示完毕
    
    	;测试段的寻址
    	mov ax, 'C'
    	mov [es:0],ax
    	mov ax,SelectoerVideo
    	mov gs,ax 
    	mov edi,(80*15 +0) *2
    	mov ah,0Ch
    	mov al,[es:0]
    	mov [gs:edi],ax
    
    	jmp $
    	
    
    SegCode32Len equ $ - PM_SEG_CODE32
    

    其中有一点十分费解,就是GdtPtr = GdtLen -1
    可以通过画图解决
    在这里插入图片描述

    从内存1到128总共128个单元(例如)两个段描述符。
    所以在这了128-1 = 127的GDT长度。
    然后Gdt的初始指针指向 127 +2 = 129位置处。

    展开全文
  • 进入保护模式。 1.什么是保护模式? 最经典的 8086CPU 是只有实模式的。实模式下有很多的不足,所以有了保护模式。 实模式的缺点: 操作系统和用户程序处于同一特权级。 用户程序所引用的地址都是指向真实的物理...

    本文接上一篇博文,在 loader.S中干点正事儿。进入保护模式。

    1.什么是保护模式?

    最经典的 8086CPU 是只有实模式的。实模式下有很多的不足,所以有了保护模式。
    实模式的缺点:

    1. 操作系统和用户程序处于同一特权级。
    2. 用户程序所引用的地址都是指向真实的物理地址。
    3. 用户程序可以自行修改段基址,可以访问所有内存。
    4. 访问超过 64KB 的内存区域要切换段基址。
    5. 一次只能运行一个程序。
    6. 共 20 条地址线,最大可用内存 1 MB 。

    实模式是 32 位的 CPU 运行在 16 位模式下的状态。而不是CPU变为16位的了。

    保护模式下,很多东西都扩展了。(因为变为32位了嘛)

    1. 寄存器扩展
      在这里插入图片描述
      但是 CS、 DS、 ES、 FS、 GS、 SS 段寄存器还是16位的。

    2. 寻址扩展
      在这里插入图片描述

    3. 指令扩展
      实模式下,它的操作数可以是 8 位、16位,
      而在保护模式下,它不仅要支持 8 位、16位,还得支持 32 位的操作数。

    add al, cl ; 8位
    add ax, cx ;16位
    add eax, ecx ;32位

    2.全局描述符表(GDT)

    在实模式下,地址用 段基址:偏移地址 来表示。段基址左移 4 位加上偏移地址。只能寻址 1MB的地址空间。

    为了兼容性,地址也要用 段基址:偏移地址 来表示。但是这个段基址不是用来左移 4 位。
    准确来说,段寄存器中存的不是段基址,而是选择子。

    保护模式下的寻址:

    1. 在内存中有一部分区域是 GDT(全局描述符表),表中每个表项是段描述符
    2. 段描述符 是一个 8 字节的表项,包含一个段的 基址、界限、段的属性等与段相关的信息。(这样就安全了,所以称作保护模式)。
    3. 段寄存器中是选择子,选择 GDT 中的 段描述符。
    4. 根据选择子找到段描述符中的基址,再加上偏移地址,就可以得到真正的地址。
      在这里插入图片描述

    但是全局描述符表( GDT)在哪呢? 我们怎么找到它?
    有一个专门的寄存器来存储 GDT 相关的信息。
    就是 GDTR寄存器。 48位的寄存器,低16 位是GDT 界限(指示总共存了多少段描述符),高 32 位是 GDT内存起始地址。
    如下图。
    在这里插入图片描述
    GDTR 需要一个专门的指令来加载。

    lgdt 48位内存数据

    3.进入保护模式

    要进入保护模式,要完成 3 步:

    1. 设置 GDT 表。
    2. 打开 A20 地址线。
    3. 将 cr0 寄存器的 pe 位置 1 。

    上一篇博文中,有三个文件, boot.inc,mbr.S,loader.S。
    本文接上一篇继续,稍微改动 mbr.S,boot.inc。大改loader.S。

    1)首先改mbr.S,第40行

    	mov cx, 1
    	call rd_disk_m_16				;调用函数,把 loader 从硬盘中的 LOADER_START_SECTOR 读到内存中 LOADER_BASE_ADDR
    

    改为

    	mov cx, 4
    	call rd_disk_m_16				;调用函数,把 loader 从硬盘中的 LOADER_START_SECTOR 读到内存中 LOADER_BASE_ADDR
    

    仅仅把 1 改为 4。因为上一篇中确定loader.S是小于512字节的,所以就只读一个扇区,这次读4个扇区。当然随着loader.S的增大,可以读更多的扇区。

    2) 改boot.inc
    增加一些宏定义。这样对段描述符,就直接采用宏定义。

    ;-------------	 loader和kernel   ----------
    
    LOADER_BASE_ADDR equ 0x900 
    LOADER_START_SECTOR equ 0x2
    
    ;--------------   gdt描述符属性  -------------
    DESC_G_4K   equ	  1_00000000000000000000000b   
    DESC_D_32   equ	   1_0000000000000000000000b
    DESC_L	    equ	    0_000000000000000000000b	;  64位代码标记,此处标记为0便可。
    DESC_AVL    equ	     0_00000000000000000000b	;  cpu不用此位,暂置为0  
    DESC_LIMIT_CODE2  equ 1111_0000000000000000b
    DESC_LIMIT_DATA2  equ DESC_LIMIT_CODE2
    DESC_LIMIT_VIDEO2  equ 0000_000000000000000b
    DESC_P	    equ		  1_000000000000000b
    DESC_DPL_0  equ		   00_0000000000000b
    DESC_DPL_1  equ		   01_0000000000000b
    DESC_DPL_2  equ		   10_0000000000000b
    DESC_DPL_3  equ		   11_0000000000000b
    DESC_S_CODE equ		     1_000000000000b
    DESC_S_DATA equ	  DESC_S_CODE
    DESC_S_sys  equ		     0_000000000000b
    DESC_TYPE_CODE  equ	      1000_00000000b	;x=1,c=0,r=0,a=0 代码段是可执行的,非依从的,不可读的,已访问位a清0.  
    DESC_TYPE_DATA  equ	      0010_00000000b	;x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写的,已访问位a清0.
    
    DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0x00
    DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00
    DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0b
    
    ;--------------   选择子属性  ---------------
    RPL0  equ   00b
    RPL1  equ   01b
    RPL2  equ   10b
    RPL3  equ   11b
    TI_GDT	 equ   000b
    TI_LDT	 equ   100b
    
    

    3)loader.S

       %include "boot.inc"
       section loader vstart=LOADER_BASE_ADDR		; 0x900
       LOADER_STACK_TOP equ LOADER_BASE_ADDR		;栈底也定位 0x900
       jmp loader_start					;跳转到下面 loader_start 处执行
       
    ;构建gdt及其内部的描述符
       GDT_BASE:   dd    0x00000000 			;第 0 个描述符,GDT的第 0 个描述符不可用,所以设为 0
    	       		dd    0x00000000
    
       CODE_DESC:  dd    0x0000FFFF 			;第 1 个描述符的低 4 位,看名称是代码段的段描述符
    	    		dd    DESC_CODE_HIGH4			;第 1 个描述符的低高 4 位
    
       DATA_STACK_DESC:  dd    0x0000FFFF		;数据段和栈段的描述符
    		    		 dd    DESC_DATA_HIGH4
    
       VIDEO_DESC: dd    0x80000007	       ;limit=(0xbffff-0xb8000)/4k=0x7;指向显存的位置,根据之前实模式布局,其基址要位 0xb8000
    	       		dd    DESC_VIDEO_HIGH4  ; 此时dpl已改为0
    
       GDT_SIZE   equ   $ - GDT_BASE			;GDT的大小
       GDT_LIMIT   equ   GDT_SIZE -	1 			; GDT的大小减一就是 GDT 的界限,就是填在GDTR中的低16位
       times 60 dq 0					 ; 此处预留60个描述符的slot
       SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0         ; 相当于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0; 宏定义的选择子
       SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0	 ; 同上; 宏定义的选择子
       SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0	 ; 同上 ; 宏定义的选择子
    
       ;以下是定义gdt的指针,前2字节是gdt界限,后4字节是gdt起始地址
    
       gdt_ptr  dw  GDT_LIMIT 	;GDTR的低16位
    	    dd  GDT_BASE		; GDTR的高 32 位,GDT的基地址。;gdt_ptr相当于指针。因为gdt_ptr是存放这48位的地址。
    loader_start:
    	mov ax, 0xb800
    	mov gs, ax
       
    	mov byte [gs:0x0c],'9'
    	mov byte [gs:0x0d],0xa4
    	
    	mov byte [gs:0x0e],'9'
    	mov byte [gs:0x0f],0xa4		;在屏幕随便显示一些字符
    
    
    ;----------------------------------------   准备进入保护模式   ------------------------------------------
    									;1 打开A20
    									;2 加载gdt
    									;3 将cr0的pe位置1
    
       ;-----------------  打开A20  ----------------
       in al,0x92
       or al,0000_0010B
       out 0x92,al
    
       ;-----------------  加载GDT  ----------------
       lgdt [gdt_ptr]			;注意!这里用的是 [] ,就是取地址为gdt_ptr的内存中的数据,就是那48位数据
    
    
       ;-----------------  cr0第0位置1  ----------------
       mov eax, cr0
       or eax, 0x00000001
       mov cr0, eax
    
       ;jmp dword SELECTOR_CODE:p_mode_start	     ; 刷新流水线,避免分支预测的影响,这种cpu优化策略,最怕jmp跳转,
       jmp  SELECTOR_CODE:p_mode_start	     ; 刷新流水线,避免分支预测的影响,这种cpu优化策略,最怕jmp跳转,
    					     ; 这将导致之前做的预测失效,从而起到了刷新的作用。
    
    [bits 32]
    p_mode_start:
       mov ax, SELECTOR_DATA
       mov ds, ax
       mov es, ax
       mov ss, ax
       mov esp,LOADER_STACK_TOP
       mov ax, SELECTOR_VIDEO
       mov gs, ax
    
       mov byte [gs:160], 'P'
    
       jmp $
    
    

    实验结果:
    在这里插入图片描述

    展开全文
  • 这个1994年开始的dos兼容开源操作系统。实际上,它就是dos,可以说它是MS-DOS的续命者。 非常令人震惊,2016年至今,这个Freedos竟然依然活跃。我以前竟然不知道这个。 我一直以为1994年和现在的201X年对于计算机...

    台风夜基本是不会睡觉的,写点有意思的。此时,2019年8月10日 2:20.

    酷爱历史,于是在主音吉他手的推荐下,在历史的垃圾堆里找到了Freedos。这个从1994年开始的dos兼容开源操作系统。实际上,它就是dos,可以说它是MS-DOS的续命者。

    非常令人震惊,2016年至今,这个Freedos竟然依然活跃。我以前竟然不知道这个。

    我一直以为1994年和现在的201X年对于计算机这个特定的领域而言简直就是两个时代,中间的技术代差堪比希腊火,伏火雷这种和现代洲际导弹之间的差异,没想到1994年的系统,竟然在今天还能玩到原汁原味的延续下来的东西,不错!同样的1990年代初的Linux,你看看5.3和0.01之间的差异就知道了。

    索性就装了一个。先是在VirtualBox上装的,后来改到了bochs。

    对于可以执行指令的机器而言,最纯粹的玩法就是眼睁睁看着这些指令被执行,而不是去折腾什么库,SDK,中间件,编译器之类,这是原教旨主义的看法,当然,我只是说随便玩玩,不提赚钱,那是程序员的事。

    我一直都想直接在一个屏幕上直接敲代码,然后机器给我反馈,但是总是被所谓 操作系统内核 所阻止,然后我改成了写内核模块,然而又被各种烦人的panic,oops等困扰,接下来重启一遍机器恢复一次环境需要一根烟的工夫。为了吃顿牛肉,养了一头牛,这简直糟糕透顶。

    大多数时候我就想看看在一个指令序列执行后机器发生了什么变化,却需要安装并学习大量的工具,然后望而却步,空留了一个想法。说实话,我憎恨复杂的工具。

    现如今的工具并不像它们声称的那般简单,相反,它们在故弄玄虚,显得复杂就是高端。

    Freedos让事情变好了很多。它更纯粹!

    Freedos自带的经典的debug程序,基本就是瑞士军刀了。我先用它写个hello程序,看看效果:
    在这里插入图片描述
    不用单独的汇编器,也不像往常那般先写一个文本的代码,然后编译器汇编器将它转成可执行的二进制文件,debug可以逐行汇编你的指令,然后直接执行。当然了,你也可以将它保存到文件里:
    在这里插入图片描述
    就是这么简单,而且你可以随时改二进制代码,先用 -u $段内地址 查看反汇编,然后想改哪个地址了,直接 -a $段内地址 修改即可了,非常方便。


    我准备用debug将这个dos系统带入到保护模式,搞不好能将它变成Linux呢。
    dos把vmlinuz读入内存,准备好rootfs,jmp过去…

    这种想法并不奇怪,LinuxBIOS可以,Grub可以,Lilo可以,为什么dos不可以,无非就是一个控制权转交的问题而已。但不同的是,LinuxBIOS,Grub这些引导程序一开始在上电启动的第一步就已经进保护模式了,因为它们的目标非常明确,就是引导真正的系统,在保护模式下更安全编程更灵活,比如可以访问大内存,所以它们在启动时,无一例外都会一步跨过千年的历史,直接进入保护模式,就连Linux本身也是如此,进保护模式这件事基本都是刻录在512字节的引导扇区的,这是第一步要做的事。

    dos与此不同,它本身就是一个实模式下的完整操作系统(Freedos可以使用保护模式,但我还是进实模式更好玩些),它在实模式下提供了一个操作系统应该提供的几乎所有功能,在特定的历史条件下,它没有进保护模式的动机,除非有人带它进。

    这就需要 运行时进入保护模式 ,而不是引导时进入保护模式。

    看看我写的最初的代码:
    在这里插入图片描述

    显然是错的,为什么呢?有我自己的原因也有debug程序的原因。

    我自己显然是不怎么会编程的,我没有系统学过任何语言的语法规则,都是需要解决一个问题时慢慢死磕的,所以写出 jmp -1,jmp 8:b13e 这种代码也就不足为奇了。

    此外,debug程序不支持标号,也可能是它支持,但我不知道怎么用,就当不支持吧,所以我必须把标号化作硬编码的地址,这也无可厚非,如果写对了,代码是可以运行产生预期结果的,但是很难写对,比如我上面程序的:

    lgdt [b138]
    

    这个就写错了。

    在实模式下,程序里的地址都是逻辑地址,代码里是看不见段寄存器的,所以lgdt的操作数就应该是GDTR的相对地址,也就是 0018,而不是 B120<<4+0018=B138 这个地址,B138这个地址是机器负责转换的,就像分页保护模式下程序看到的都是虚拟地址,然后机器通过MMU机制负责转化为物理地址一样。

    那么修改了上述错误呢?还是报错,机器直接崩溃。

    是的,谭浩强式的崩溃!
    【上大学时,谭浩强的C语言教程里说要是指针搞错了,就会系统崩溃,可是我怎么也不会把系统搞崩溃,顶多是程序崩溃。后来知道,原来我在保护模式下工作,如果在实模式下,比如原始dos系统,指针错了系统真的就会崩溃。谭浩强老师是对的】

    所以说,我准备先用nasm汇编一个 正儿八经的汇编程序

    所有的工具都在Linux下,我需要将Linux汇编而成的.com放进Freedos的磁盘中,有万种方法我都想不中,这使我不得开心颜。我不希望在Freedos里折腾网络。

    VirtualBox很难操作虚拟磁盘,想从外部放进去一个文件不知道该怎么办,于是我换到了bochs,如果只是为了玩而不是为了用,那么bochs显然要比VBox好太多,这也是听了主音吉他手的建议。
    【主音吉他手是一位猛士,就职于阿里巴巴,与就职于腾讯的温州皮鞋厂老板针锋相对!】

    Ubuntu里装bochs显然要比Centos里装bochs方便很多,因为Ubuntu是自带桌面的。
    【suse也是,但不很。我第一次接触ubuntu是在2007年冬天,姓赵的一位同事展示了ubuntu的立体桌面】

    自带桌面当然自带X11,而Centos往往都是纯命令行操作,即便是有什么图形界面,我一般也都不装或者装了之后卸掉。

    以下的命令可以初始化一个虚拟磁盘:

    root@name-VirtualBox:~/bochs# bximage
    ========================================================================
                                    bximage
      Disk Image Creation / Conversion / Resize and Commit Tool for Bochs
             $Id: bximage.cc 13481 2018-03-30 21:04:04Z vruppert $
    ========================================================================
    
    1. Create new floppy or hard disk image
    2. Convert hard disk image to other format (mode)
    3. Resize hard disk image
    4. Commit 'undoable' redolog to base image
    5. Disk image info
    
    0. Quit
    
    Please choose one [0] 1
    
    Create image
    
    Do you want to create a floppy disk image or a hard disk image?
    Please type hd or fd. [hd]
    
    What kind of image should I create?
    Please type flat, sparse, growing, vpc or vmware4. [flat]
    
    Choose the size of hard disk sectors.
    Please type 512, 1024 or 4096. [512]
    
    Enter the hard disk size in megabytes, between 10 and 8257535
    [10] 200
    
    What should be the name of the image?
    [c.img] freedos1.img
    
    Creating hard disk image 'freedos1.img' with CHS=406/16/63 (sector size = 512)
    
    The following line should appear in your bochsrc:
      ata0-master: type=disk, path="freedos.img", mode=flat
    root@name-VirtualBox:~/bochs#
    

    然后将freedos.iso挂在cdrom上,将系统装入这个freedos.img虚拟磁盘:

    # 编辑bochs配置文件,添加虚拟磁盘和虚拟光驱
    ata0-master: type=disk, mode=flat, path=/home/name/bochs/freedos.img
    # 一旦安装完成,注释掉虚拟光驱以表示断开
    #ata0-slave: type=cdrom, path=/home/name/bochs/freedos.iso, status=inserted
    

    接下来就可以通过以下命令挂载虚拟磁盘到Linux本地目录了:

    mount -t msdos -o loop,offset=32256 freedos.img /mnt/dos
    

    然后就可以随便拷贝文件进出了。

    现在需要写一个汇编程序,实现进入保护模式的功能,我想从最最最简单的做起,先仅仅完成段式保护模式,先不分页。在进入保护模式后,执行死循环。我的汇编程序代码如下:

    ; COM程序在dos中的加载位置,这个必须写100h,不写的话会报错,我也是第一次知道
    org 100h
    
    ;第一行指令跳转到GDT之下
    jmp main
    
    ;GDT
    gdt_start:
        dd 0
        dd 0
    ; 仅仅一个段,平坦模式
    gdt_code:
        dw 0xFFFF
        dw 0
        db 0
        db 0b10011010   ;9a
        db 0b11001111   ;cf
        db 0
    gdt_end:
    
    ;GDTR
    gdtr:
        dw gdt_end-gdt_start-1
        dd gdt_start
    
    ;保护模式下的死循环代码
    _loop_:
    jmp $
    
    ;主程序
    main:
    ; 必须手工自己计算地址:段<<4+offset
    mov eax,cs
    shl eax,4
    add [gdtr+2],eax
    add eax, _loop_
    
    ; 这里有个技巧,采用堆栈实现跨段长跳。这需要对jmp指令比较熟悉,进入保护模式的长跳需要重新加载段寄存器,所以需要的jmp指令为:
    ; 0x66 0xea $4字节的段内偏移 $2字节的段选择子
    ; 所以在jmp指令的操作数中要构造6个字节的数据,这里采用了堆栈
    push dword 0x08
    push eax
    mov bx, sp
    
    ; 进入保护模式的核心3步骤:
    ; 1. 加载准备好的GDTR
    ; 2. 开启A20
    ; 3. 开启cr0保护模式
    ; 暂时不准备分页。
    lgdt [gdtr]
    
    cli
    in al, 0x92
    or al, 0x02
    out 0x92, al
    
    mov eax,cr0
    or eax,1
    mov cr0,eax
    
    ; 按照准备好的6字节地址和段选择子,执行66 ea长跳!
    jmp dword far [bx]
    

    用nasm编译之:

    nasm -f bin rtime.asm -o pro.com
    

    将这个pro.com放入freedos的虚拟磁盘,umount虚拟磁盘后开启bochs虚拟机,进入实模式dos系统,执行 pro.com,死循环!

    然后,我尝试着将这个rtime.asm代码逐行手敲到debug的命令行,结果debug无法识别最后一步的 jmp dword far [bx]

    于是准备将其等价转换一下,决定手工写指令,即写入:

    db 0x66
    db 0xea
    dd $4字节偏移
    dw 8
    

    问题就在于这4字节偏移,这个偏移显然是在运行时才能算出来的,所以不能事先dd,需要在运行时填充,于是标号注之:

    org 100h
    
    jmp main
    
    gdt_start:
        dd 0
        dd 0
    gdt_code:
        dw 0xFFFF
        dw 0
        db 0
        db 0b10011010   ;9a
        db 0b11001111   ;cf
        db 0
    gdt_end:
    
    gdtr:
        dw gdt_end-gdt_start-1
        dd gdt_start
    
    _loop_:
    jmp $
    
    main:
    mov eax,cs
    shl eax,4
    mov ecx,eax
    add [gdtr+2],eax
    add eax, _loop_
    
    ; 填充4字节的偏移地址
    add [addr], eax
    
    lgdt [gdtr]
    
    cli
    in al, 0x92
    or al, 0x02
    out 0x92, al
    
    mov eax,cr0
    or eax,1
    mov cr0,eax
    
    ; 直接插入指令的方式
    db 0x66
    db 0xea
    addr:dd 0x00000000
    dw 0x08
    

    非常不错,这下可以手敲进debug了。但是由于debug程序里逐行汇编无法使用标号,所以需要把所有的标号转化为固定的地址偏移,于是如下:

    org 100h
    
    ;jmp main
    jmp 150
    
    ; 110:GDT载入110
    gdt_start:
        dd 0
        dd 0
    gdt_code:
        dw 0xFFFF
        dw 0
        db 0
        db 0b10011010   ;9a
        db 0b11001111   ;cf
        db 0
    gdt_end:
    
    ; 130:GDTR载入130
    gdtr:
        dw gdt_end-gdt_start-1
        dd gdt_start
    
    ; 140:32位程序载入140
    _loop_:
    jmp $
    
    ; 150:main载入150
    main:
    mov eax,cs
    shl eax,4
    mov ecx,eax
    add [gdtr+2],eax
    add eax, _loop_
    
    ; 填充4字节的偏移地址
    add [addr], eax
    
    lgdt [gdtr]
    
    cli
    in al, 0x92
    or al, 0x02
    out 0x92, al
    
    mov eax,cr0
    or eax,1
    mov cr0,eax
    jmp 190
    
    ; 190:为了确定addr的地址,这里将jmp far指令单独载入到固定的地址,短跳到达
    db 0x66
    db 0xea
    ; 192:addr自然就是192咯
    addr:dd 0x00000000
    dw 0x08
    

    接下来就要把这些逐行敲入debug了:
    在这里插入图片描述
    每写完一段,可以用 -u $段内偏移 看一下对不对:
    在这里插入图片描述
    如果哪个地址的指令不对,可以用 -a $地址 来修改,比如你写指令的时候,加不加dword修饰符可能结果是不一样,如果真的不行就需要手工写入指令了,比如用db,dd,dw这种,或者直接用debug的 -e $地址 来写指令。

    此外,还要注意的是,我们把标号化作了地址偏移,但是我们自己规定的130,140,150这种可能只是一厢情愿,由于有偏移问题,必须用 -u 指令反汇编确认,如果有问题,则需要用 -a 指令来修正。

    一切结束后,用 -n -rcx 指令存文件,执行,死循环。这意味着成功了。


    以上这些其实是要被主音吉他手耻笑鄙视的,因为原教旨主义者不使用nasm工具,只能用debug,然而我还是用了,其实我是先用nasm把程序调通,然后照着调通之后的程序的反汇编抄到debug命令行逐行汇编的,然后把抄的过程隐藏,就留下一个手工敲进debug命令行的指令的截图。

    然后显得我好像是直接逐行汇编的,但其实把截图多截宽一点,旁边还有呢
    在这里插入图片描述
    哈哈。


    本文只是做到了进入保护模式,没有做任何事情,这没有意义。
    但其实,进入保护模式后,世界就是你的了,打印个字符串那是教科书式的,更没有意义,而且代码还非常冗长,所以我什么也不打印,只是执行 jmp $

    你可以再准备一个页表,然后开启分页,打印一些东西,然后呢…厄… 再准备一个页表,执行打印一些别的东西,厄…准备一个时钟中断处理程序,每一次时钟中断到来,CR3在两个页表间切换,同时save/restore寄存器上下文…厄…这就是个多任务现代操作系统了。

    以为我会写下去吗?写上面这段话的内容?看上去很有吸引力,也很有成就感,但我不会去写的。

    我面试过会写这些的人,也和很多会写这种的人纯粹的技术交流无关利益,貌似很懂操作系统的样子,能说明白段式,页式,段页式,平坦内存等等X86系列兼容处理器一系列复杂且恶心的特性,也确实有作品,所谓的《自己动手写操作系统》这种,从一个MBR引导进保护模式,交叉打印不同的字符,也许很多人都觉得,太厉害了…

    然而他不懂如何评价进程调度算法,不知道LRU的实质,无法写出一个高效的并发链表,更不懂网络。操作系统的精髓不是X86处理如何引导进保护模式,操作系统的精髓是进程,内存,IO等一系列时间空间的分配和调度问题,这是个及其复杂的运筹学博弈问题,背后有复杂的博弈论,运筹学,哲学,算法等等看上去很虚但又很难企及的东西。

    就不说那些博士才操心的算法方面的事了,同样是在工程领域,写个引导进保护模式交叉打印字符远不如写个LinuxBIOS,Grub这种引导其它程序的程序(而不是自己在那无聊地打印字符串)更加有意义。


    那么本文的意义何在?

    本文的意义在于 运行时进入保护模式 。这不同于机器刚上电系统启动就进入保护模式。不同点在哪里呢?系统初启时的内存是空的,这意味着引导程序可以将其他的代码任意设置位置,机器的状态寄存器也是唯一由引导程序决定的,可是运行时却不同,运行时的dos系统已经有一个内核在内存里了,替换它需要格外小心,幸亏dos是单任务的,否则便更加麻烦,不信你把Linux变身到Windows试试?鸠占鹊巢正在执行期间,万一来了个调度事件,就会全盘皆输。

    所以运行时进保护模式会 更麻烦些

    这就是本文的全部意义。

    话说,既然一般的系统在上电初启时都会进入保护模式,为啥要有实模式?这又是Intel在搞事情。

    先不回答这个问题,先看看 “既然有简单的分页并且工作的还那么好,为什么还要有分段?” 而且,大部分的操作系统,比如Linux,Windows都是例行公事用平坦模式绕开分段的,这么看来分段显得毫无意义。

    这全是Intel为了兼容老旧处理器而搞的。也就是说,如果说最初的8086有一个机制或者特性,8088上就要支持,随后的286,386都要支持,P4,Core i5/i7大概率都要支持,即前一代的处理支持什么,后面的处理器大概率都要支持,最终的效果就是Intel处理器越发臃肿。

    后果是什么?我就不说为操作系统的实现增加复杂性了,另一个重要的毒副作用就是为很多人炫耀技巧提供了及其广阔的空间,不然也就没人觉得能背下段寄存器格式的人很厉害了,毕竟本来无一物,何处惹尘埃。

    Intel作为个人计算机独钟的微处理器提供商,推进了近40年个人计算机产业的发展,我们有目共睹的是,个人计算机从零到100的发展速度远远快于大型机,工作站以及服务器,这是因为个人计算机的需求是繁杂的,特别是游戏,互联网等等客户端App需要处理器在各个方面不断进化。与此相对,工作站,服务器则只需要保持高并发高吞吐即可。

    个人计算机的不断进化不断复杂意味着向后兼容的压力非常大,但又不得不做,因此Intel处理器就成了现在的样子。

    很多别的处理器一开始就是保护模式的,根本就没有实模式一说,这种专门迎合Multics/UNIX的设计,因为UNIX一开始就是进程保护隔离的,UNIX并非从CP/M这种及其简单的系统进化而来,它一开始就是设计出来的。

    既然操作系统提供进程间内存的隔离保护,那么处理器做到即可,不多也不少,这种简洁的设计和Intel的风格是相对的。


    写完了,2019年8月10日 5:40. 超强台风利奇马已经登陆,杭州风雨大作,但是雨量没有达到预期。迷茫一会儿。


    现在7:40,独坐窗前看风起云涌,一家人都醒了,疯子在做早饭,小小在刷抖音,安安在学步车里欢呼,嘟嘟乱跑,我依然在作文。

    超强台风即将登陆前的八小时,800019年8月9日 周五,下午六点,皮鞋厂工人都在等着下班。
    温州皮鞋厂wu老板发了邮件:
    由于此次台风非常强烈,登陆地点离本厂非常近,对本厂影响巨大,或对本厂业务产生严重影响!
    于此,特公告,所有车间,所有工人,务必值守岗位,按照日常制度执行,若有急事需请假,电话务必保持24小时畅通,请随时处理紧急事务。
    货单照常发货,值班邮件请查收,如遇突发事件按流程请报工伤。
    经理与你同在!
    – 皮鞋二厂/三厂,行政处
    – 钦此!

    【800019年8月10日,皮鞋厂厂房在台风登陆时轰然崩摧,wu老板发文,经理穿着湿皮鞋逃跑,若干工人death has no place】
    在这里插入图片描述
    杭州看不见了,因为在台风云下面…


    浙江温州皮鞋湿,下雨进水不会胖!
    皮鞋湿了,是祸,然而皮鞋不胖,是福报!
    浙江温州皮鞋湿,下雨进水不会胖!

    展开全文
  • [2] haribote asmhead.nas 从实模式进入保护模式程序阅读注释 ; haribote os(haribote.sys) ; |---------------------------------------------------| ; | | header added | bootpack.hrb | ; | asmhead.nas | |...
  • 从实模式进入保护模式其实经历三个步骤就可以了,第一步,加载gdt,第二步,打开A20,第三步,置cro为1. gdt介绍 首先简单介绍一下保护模式下的分段机制 由于在保护模式下,访问地址也是通过段基址加段内偏...
  • 06.实模式进入保护模式

    千次阅读 2018-10-30 21:17:30
    上一节我们实现了内核加载器中加载其它扇区代码并执行,但始终工作在实模式状态下。内存寻址方式和8086相同,由16位段寄存器的内容乘以16(10H)当做段基地址,加上16位偏移地址形成20位的物理地址,最大寻址空间1...
  • 实模式到保护模式,如何进入保护模式 在还是16位CPU的天下时,并没有实模式这一说。直到CPU发展到32位,推出保护模式后,为了区别两种模式,便将之前的模式称为实模式。处于向下兼容的考虑,现代的CPU依然保持对16...

空空如也

空空如也

1 2 3 4 5 ... 12
收藏数 228
精华内容 91
关键字:

从实模式进入保护模式