操作系统内核Hack:(四)内核雏形
操作系统内核Hack:(四)内核雏形
在本系列的前一篇文章《操作系统内核Hack:(三)BootLoader制作》中,我们制作出了一个两阶段引导BootLoader,并进入了一个内核的空壳main函数。本文我们继续完善引导程序和内核,让内核的内容一点点充实起来。本文的代码可以参考GitHub上的MiniOS分支kernel_prototype。
1.周边代码修改
1.1 常量和宏提取
像各个模块的内存这种常量,会经常被引导,所以就提取出一个单独的文件var.inc。同理,保护模式相关的常量和宏都提取到了pm.inc,这里主要是拷贝了Orange’s的代码和注释。
; var.inc
; ############################
; Constants
; ############################
SETUPLEN equ 4
BOOTSEG equ 0x07c0
INITSEG equ 0x9000
SETUPSEG equ 0x9020
SYSSEG equ 0x1000
NEWSYSSEG equ 0x0000
MEMSIZE equ 0 ; INITSEG:MEMSIZE
; pm.inc
; ############################
; Macros
; ############################
; 描述符类型
DA_32 equ 4000h ; 32 位段
DA_LIMIT_4K equ 8000h ; 段界限粒度为 4K 字节
; 存储段描述符类型
DA_DR equ 90h ; 存在的只读数据段类型值
DA_DRW equ 92h ; 存在的可读写数据段属性值
DA_C equ 98h ; 存在的只执行代码段属性值
DA_CR equ 9Ah ; 存在的可执行可读代码段属性值
; Descriptor macro
%macro Descriptor 3
dw %2 & 0FFFFh ; Limit 1
dw %1 & 0FFFFh ; Base addr 1
db (%1 >> 16) & 0FFh ; Base addr 2
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; Attr 1 + Limit 2 + Attr 2
db (%1 >> 24) & 0FFh ; Base addr 3
%endmacro
1.2 Makefile修改
因为include/的提取和后面第二阶段引导过程拆分的影响,所以Makefile也要做相应的修改:
- 新加编译参数ASINC,编译bootsect.asm、setup.asm和head.asm时都要用到
- 链接参数LDFLAGS中的entry地址改为startup_32,这也是head.asm中的代码起始地址
- SYSSIZE不再除以16,而是system的真实大小,后面bootsect.asm中会用到
ASINC = -I include/
ASFLAGS = -f elf
LD = ld
# -Ttext org -e entry -s(omit all symbol info)
# -x(discard all local symbols) -M(print memory map)
LDFLAGS = -Ttext 0 -e startup_32 --oformat binary -s -x -M
...
# SYSSIZE = system file size
boot1/bootsect: boot1/bootsect.asm include/var.inc system/system
(echo -n "SYSSIZE equ ";ls -l system/system | grep system \
| cut -d " " -f 5 | tr '\012' ' ') > tmp.asm
cat $< >> tmp.asm
$(AS) $(ASINC) -o $@ tmp.asm
rm -f tmp.asm
boot2/setup: boot2/setup.asm include/var.inc include/pm.inc
$(AS) $(ASINC) -o $@ $<
system/system: system/init/head.o system/init/main.o
$(LD) $(LDFLAGS) \
system/init/head.o \
system/init/main.o \
-o $@ > System.map
system/init/head.o: system/init/head.asm include/var.inc include/pm.inc
$(AS) $(ASFLAGS) $(ASINC) -o $@ $<
...
2.第一阶段引导完善
2.1 system加载
之前为了简化代码,避免被细枝末节干扰,所以从软盘加载system到内存的代码写的非常简单,只读取了一个扇区。随着我们的system模块越来越大,这样是肯定不行的,于是就想参考Linux源码做一下改进。
结果不看不知道,一细研究还真吓一跳每个磁道有18个扇区,每个磁头有80个磁道,根据要加载的数据大小,自己负责切换扇区、磁道、磁头号。就这样还不行,要加载到的内存位置是用[es:bx]表示的,也就是说bx偏移逐渐增加最后要溢出的时候,我们还得修改es重置bx,避免它溢出。原来想写一段通用的从软盘加载数据的代码这么费劲啊!先看看我写的,因为之前已经加载过bootsect和setup了,所以第一个磁道还需读取13个扇区,之后的每个磁道读取18个扇区,最后一个磁道根据还剩余多少扇区没读决定要读取多少,先不考虑磁头和bx溢出问题。结果已经来回调试改进了好几遍了,还是有问题。
; 4) Load system module at 0x10000
; Assume SYSSIZE < 1 head
; 1 track = 18 sectors * 512b = 9216(b)
; 1 head = 80 tracks * 9216 = 720(kb)
_Sector: db 0
_Track: db 0
Sector equ _Sector-$$
Track equ _Track-$$
SECT_PER_TRACK equ 18
LEFT_IN_TRACK1 equ SECT_PER_TRACK - 1 - SETUPLEN
load_system:
mov ax, SYSSEG
mov es, ax
mov bx, 0000h ; es:bx = target(es=1000h,bx=0)
mov dx, 0000h ; dx = driver(dh)/head(dl)
mov cx, 0006h ; cx = track(ch)/sector(cl)
mov ax, SYSSIZE
add ax, 511
shr ax, 9 ; al = (SYSSIZE + 511) / 512, sectors to read
mov byte [Sector], al
cmp al, LEFT_IN_TRACK1
jbe .loop
mov al, LEFT_IN_TRACK1 ; al = (al <= 13) ? al : 13
.loop
mov ah, 02h ; ah = service id(ah=02 means read)
int 13h ; ignore any error
sub byte [Sector], al ; remainingSector -= al
cmp byte [Sector], 0
je ok_load_system
xor ah, ah
shl ax, 9
add bx, ax ; offset += (al * 512)
add byte [Track], 1
mov ch, byte [Track] ; track++
mov cl, 1 ; start at first sector
xor ax, ax
mov al, byte [Sector]
cmp al, SECT_PER_TRACK
jbe .loop
mov al, SECT_PER_TRACK ; al = (al <= 18) ? al : 18
jmp .loop
最后发现一次读取跨磁道的扇区也没关系,Bochs的BIOS支持一次最多读取72个扇区。于是就放弃了,先读取最多72个吧,对于现阶段的system的规模是暂时够用了,到时再改吧。此外,要注意的是对要加载的扇区数的计算:这里SYSSIZE是system的实际大小,而不是Linux中所谓的click数(实际size加15后左移了4位)。并且为了避免丢失余数的差一问题,我们要先加上511。
; 4) Load system module at 0x10000
; Assume SYSSIZE < 72 sectors (36864)
; 1 track = 18 sectors * 512b = 9216(b)
; 1 head = 80 tracks * 9216 = 720(kb)
MAX_ONE_READ equ 72
load_system:
mov ax, SYSSEG
mov es, ax
mov bx, 0000h ; es:bx = target(es=1000h,bx=0)
mov dx, 0000h ; dx = driver(dh)/head(dl)
mov cx, 0006h ; cx = track(ch)/sector(cl)
mov ax, SYSSIZE
add ax, 511
shr ax, 9 ; al = (SYSSIZE + 511) / 512 sectors to read
cmp al, MAX_ONE_READ
jbe .loop
mov al, MAX_ONE_READ ; al = (al <= 72) ? al : 72
.loop
mov ah, 02h ; ah = service id(ah=02 means read)
int 13h ; ignore any error
2.2 关闭软驱马达
至此所有要加载的数据就都加载完了,所以我们可以关掉软驱的马达了。这样可以关闭软盘控制器FDC、禁止DMA和中断请求。具体细节有待深入研究。
; 5) Kill motor
ok_load_system:
mov dx, 0x3f2 ; floppy controller port
mov al, 0 ; floppy A
outb ; output al to dx port
3.第二阶段引导拆分
在上一篇文章中,在setup.asm中进入了保护模式,并执行了一段32位的代码。我们其实可以将进入保护模式之后的内核初始化工作继续填到setup.asm这段32位代码中,但这样做不如Linux 0.11的方式优雅,即将这部分工作放到system模块的头部去完成。缺点是可能引导过程有些零散,但优点就是因为system会被加载到0x0处,所以后续初始化的页目录表和页表、重放置后的GDT都会在低地址,安全、集中且易于管理,这在我们的上一篇文章中也提到了。
3.1 上半部:setup.asm
setup.asm首先读取BIOS中的有用信息保存到0x9000,即覆盖了bootsect的内存位置,因为它已经没有用了。然后将system拷贝到0x0低地址,进入保护模式后就跳转到system。
%include "var.inc"
%include "pm.inc"
; ############################
; Booting Process
; ############################
[SECTION .s16]
[BITS 16]
; 1) Read memory info from BIOS
mov ax, INITSEG
mov ds, ax ; save to bootsect space
mov ah, 0x88
int 0x15
mov [MEMSIZE], ax ; ax=3c00h (15360kb=15mb)
; 2) Move system to 0x0000
; round-1: 10000~1ffff => 0000~ffff
; ...
; round-5: 80000~8ffff => 70000~7ffff
;
; NOTE: 8000h word = 10000h byte
WordPerMove equ 8000h
move_system:
mov ax, 0h
.loop
mov es, ax
add ax, 1000h
mov ds, ax
mov cx, WordPerMove ; cx = counter
xor si, si ; ds:si = source
xor di, di ; es:di = target
rep movsw ; move
cmp ax, INITSEG
jne .loop
; 3) Enter protection mode
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
; 3.1) Load gdt to gdtr
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt base addr
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt base addr
lgdt [GdtPtr]
; 3.2) Disable interrupt
cli
; 3.3) Enable A20 addr line
in al, 92h
or al, 00000010b
out 92h, al
; 3.4) Set PE in cr0
mov eax, cr0
or eax, 1
mov cr0, eax
; 3.5) Jump to protective mode!
jmp dword SelectorSystem:0 ; 0x0000:0x0
[SECTION .gdt]
; Base Addr, Limit, Attribute
LABEL_GDT: Descriptor 0h, 0h, 0h
LABEL_DESC_SYSTEM: Descriptor 0h, 0ffffh, DA_CR | DA_32 | DA_LIMIT_4K
LABEL_DESC_DATA: Descriptor 0h, 0ffffh, DA_DRW | DA_32 | DA_LIMIT_4K
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW
GdtLen equ $ - LABEL_GDT
GdtPtr dw GdtLen - 1 ; GDT limit
dd 0 ; GDT base addr
SelectorSystem equ LABEL_DESC_SYSTEM - LABEL_GDT
SelectorData equ LABEL_DESC_DATA - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
读取BIOS中内存信息的方式有几种,目前setup.s中用的是最简单的一种,也是Linux 0.11中用的方式。代码只有三行,极其简单。获取结果保存在ax中,单位是KB,不是Byte:
mov ah, 0x88
int 0x15
mov [MEMSIZE], ax ; ax=3c00h (15360kb=15mb)
但这种方式的缺点也同样明显,就是最大只支持64MB。因为ax只有16位,最大能表示65536。所以在Orange’s中作者用了另一种更为强大的方式,ax=0xe820的int 0x15中断,除了能获得内存大小外,还能获得到内存分布。当然缺点就是代码比前面这种方式要麻烦多了,所以这里就不细说了,知道有多种获取BIOS中内存信息的方式就可以了。
3.2 下半部:head.asm
这就是我们新拆分出来的head.asm,它将各个段寄存器置为内核的代码段Selector后,就开始内核初始化的工作了:
- 直接写显存的方式打印一条消息
- 重置GDT的地址。至于GDT的内容则与setup.asm的一样
- 将main函数的地址压到栈上,这样setup_paging就会将它当做调用的返回地址,执行ret时就会跳转到main.c中的main函数了
- 开启分页管理
%include "var.inc"
%include "pm.inc"
extern main
global startup_32
pdt:
[SECTION .text]
ALIGN 32
[BITS 32]
startup_32:
mov ax, 16 ; SelectorData
mov ds, ax
mov es, ax
mov ss, ax
mov esp, TopOfStack
; 1) Print welcome message
mov ax, 24 ; SelectorVideo
mov gs, ax
mov ah, 0Ch
mov ebx, 0
mov ecx, len
mov edx, Message
.loop:
mov edi, ebx
add edi, (80 * 20) ; (80 * row + col) * 2
imul edi, 2
mov al, byte [edx]
mov [gs:edi], ax
inc ebx
dec ecx
inc edx
cmp ecx, 0h
jne .loop
; 2) Reset GDTR
lgdt [GdtPtr]
; 3) Prepare return address
push main
jmp setup_paging
; Temporary data and stack, will be overriden later
Message:
db "Welcome to MiniOS"
len equ $ - Message
; LinearAddr[31~22] = 10 bits = 1024 entry (* 4B = 4096B)
; So PDT has 1024 entries (1024 page tables, occupy 4096b totally)
; LinearAddr[21~12] = 10 bits = 1024 entry (* 4B = 4096B)
; So PD has 1024 entries (1024 pages, occupy 4096b totally)
; LinearAddr[11~0] = 12 bits = 4096 byte
; So offset (page size) is 4096b
PdtSize equ 1024
PtSize equ 1024
EntrySize equ 4
PageSize equ 4096
times PdtSize*EntrySize-($-$$) db 0
pg0:
times PtSize*EntrySize db 0
pg1:
times PtSize*EntrySize db 0
pg2:
times PtSize*EntrySize db 0
pg3:
times PtSize*EntrySize db 0
; 4) Setup paging
PgRw equ 111h
ALIGN 32
setup_paging:
; 4.1) Clear page space
mov ecx, PdtSize + PtSize ; counter = 5*1024
xor eax, eax
xor edi, edi
cld ; DF=0: edi move forward
rep stosd ; move eax => [es:edi] by dword
; 4.2) Fill page dir
mov dword [pdt], pg0 + PgRw ; 111h(7): read/write page
mov dword [pdt+04h], pg1 + PgRw
mov dword [pdt+08h], pg2 + PgRw
mov dword [pdt+0ch], pg3 + PgRw
; 4.3) Fill page table
; pg0~3 can represent 0h ~ fff000h (16MB) memory space
; 0x3ffc: start addr of last entry of last PT
mov edi, (pg3 + PtSize * EntrySize) - EntrySize
; 0xfff000: start addr of last page represented by last entry
mov eax, ((4 * PtSize * PageSize) - PageSize) + PgRw
std ; DF=1: edi move backward
.loop:
stosd ; move eax => [es:edi] by dword
sub eax, PageSize
jge .loop
; 4.4) Set cr3 (PDBR, Page-Dir Base address Register)
xor eax, eax
mov cr3, eax
; 4.5) Set PG bit of cr0 to enable paging
mov eax, cr0
or eax, 80000000h
mov cr0, eax
; 4.6) Transfer control to main()
ret
; Temporary stack space
times 100h db 0
TopOfStack equ $
;[SECTION .gdt]
; Base Addr, Limit, Attribute
LABEL_GDT: Descriptor 0h, 0h, 0h
LABEL_DESC_SYSTEM: Descriptor 0h, 0ffffh, DA_CR | DA_32 | DA_LIMIT_4K
LABEL_DESC_DATA: Descriptor 0h, 0ffffh, DA_DRW | DA_32 | DA_LIMIT_4K
times 253 dd 0x0, 0x0 ; space for LDT and TSS
GdtLen equ $ - LABEL_GDT
GdtPtr dw GdtLen - 1 ; GDT limit
dd LABEL_GDT ; GDT base addr
SelectorSystem equ LABEL_DESC_SYSTEM - LABEL_GDT
SelectorData equ LABEL_DESC_DATA - LABEL_GDT
代码有些长,但比较清晰,真正的难点在于我们之前没有接触到的分页管理机制。其实我们此刻不是必须开启分页管理,但为了避免麻烦,我们这次就多做一点,把分页给弄好,这样以后就没有后顾之忧了。
3.2.1 内存位置
可能大家刚才看上面代码时没有注意标签,现在后看一下就会发现精心放置好的标签,包括pdt、pg0~3、以及后面的栈空间和GDT。这些标签和代码在运行时对应的内存空间非常重要,都是内核最重要的数据,所以它们的位置绝对不是随意放置的:
- pdt放在最开头,使页目录表覆盖掉head.asm的部分代码
- setup_paging位于pg3后,避免自己把自己覆盖掉
- 栈放在setup_paging,作为临时的内核栈,因为main函数地址需要入栈
- GDT放在最后
--------pdt-----------
| 0x0000 | 0000 0000 |
| ... | ... |
| 0x0FFC | 0000 0000 |
|-------pg0----------|
| 0x1000 | 0000 0000 |
| ... | ... |
| 0x1FFC | 0000 0000 |
|-------pg1----------|
| 0x2000 | 0000 0000 |
| ... | ... |
| 0x2FFC | 0000 0000 |
|-------pg2----------|
| 0x3000 | 0000 0000 |
| ... | ... |
| 0x3FFC | 0000 0000 |
|-------pg3----------|
| 0x4000 | 0000 0000 |
| ... | ... |
| 0x4FFC | 0000 0000 |
| ... | ... |
| ... | ... |
|------stack---------|
| ... | ... |
|-------gdt----------|
| ... | ... |
3.2.2 代码解释
《Linux 0.11中的页目录表及页表内容分析》作者对Linux 0.11中setup_paging处的代码进行了详细分析,清晰易懂,非常棒!
首先解释一下cld和std两个命令的用处,其实很简单:“在字符串的比较、赋值、读取等一系列和rep连用的操作中,di或si是可以自动增减的而不需要人来加减它的值,cld即告诉程序si,di向前移动,std指令为设置方向,告诉程序si,di向后移动”。
下面就重点说一下PDT和PT的初始化过程,为了使代码尽可能的清晰,很多“魔数”都提取成了常量。
; 4.1) Clear page space
mov ecx, PdtSize + PtSize ; counter = 5*1024
xor eax, eax
xor edi, edi
cld ; DF=0: edi move forward
rep stosd ; move eax => [es:edi] by dword
; 4.2) Fill page dir
mov dword [pdt], pg0 + PgRw ; 111h(7): read/write page
mov dword [pdt+04h], pg1 + PgRw
mov dword [pdt+08h], pg2 + PgRw
mov dword [pdt+0ch], pg3 + PgRw
; 4.3) Fill page table
; pg0~3 can represent 0h ~ fff000h (16MB) memory space
; 0x3ffc: start addr of last entry of last PT
mov edi, (pg3 + PtSize * EntrySize) - EntrySize
; 0xfff000: start addr of last page represented by last entry
mov eax, ((4 * PtSize * PageSize) - PageSize) + PgRw
std ; DF=1: edi move backward
.loop:
stosd ; move eax => [es:edi] by dword
sub eax, PageSize
jge .loop
3.2.3 PDT和PT的样子
PDT和PT到底是什么样子呢?要是看完前面的代码解释还是觉得很抽象的话,我们就直观的看看初始化成功后,内存从低到高的模样!
--------pdt-----------
| 0x0000 | 0000 1111 | => pg0
| 0x0004 | 0000 2111 | => pg1
| 0x0008 | 0000 3111 | => pg2
| 0x000C | 0000 4111 | => pg3
| 0x0010 | 0000 0000 |
| ... | ... |
| 0x0FFC | 0000 0000 |
|-------pg0----------| Physical Address
| 0x1000 | 0000 0111 | => [00000000~00000FFF]
| 0x1004 | 0000 1111 | => [00001000~00001FFF]
| 0x1008 | 0000 2111 | => [00002000~00002FFF]
| ... | ... |
| 0x1FFC | 003F F111 | => [003FF000~003FFFFF]
|-------pg1----------|
| 0x2000 | 0040 0111 | => [00400000~00400FFF]
| 0x2004 | 0040 1111 | => [00401000~00401FFF]
| 0x2008 | 0040 2111 | => [00402000~00402FFF]
| ... | ... |
| 0x2FFC | 007F F111 | => [007FF000~007FFFFF]
|-------pg2----------|
| 0x3000 | 0080 0111 | => [00800000~00800FFF]
| 0x3004 | 0080 1111 | => [00801000~00801FFF]
| 0x3008 | 0080 2111 | => [00802000~00802FFF]
| ... | ... |
| 0x3FFC | 00BF F111 | => [00BFF000~00BFFFFF]
|-------pg3----------|
| 0x4000 | 00C0 0111 | => [00C00000~00C00FFF]
| 0x4004 | 00C0 1111 | => [00C01000~00C01FFF]
| 0x4008 | 00C0 2111 | => [00C02000~00C02FFF]
| ... | ... |
| 0x4FFC | 00FF F111 | => [00CFF000~00CFFFFF]
下面就运行起来Bochs,验证一下页表是否初始化成功了。我们查看几个关键位置就可以了,比如页目录表(0x0000),四个页表的开头部分(0x1000, 0x2000, 0x3000, 0x4000),以及pg4的最末尾部分(0x4ffc)。
(0) Breakpoint 1, 0x00005047 in ?? ()
Next at t=15473080
(0) [0x0000000000005047] 0008:00005047 (unk. ctxt): xor eax, eax ; 31c0
<bochs:3> xp /32bx 0x00000
[bochs]:
0x00000000 <bogus+ 0>: 0x11 0x11 0x00 0x00 0x11 0x21 0x00 0x00
0x00000008 <bogus+ 8>: 0x11 0x31 0x00 0x00 0x11 0x41 0x00 0x00
0x00000010 <bogus+ 16>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00000018 <bogus+ 24>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
<bochs:4> xp /32bx 0x01000
[bochs]:
0x00001000 <bogus+ 0>: 0x11 0x01 0x00 0x00 0x11 0x11 0x00 0x00
0x00001008 <bogus+ 8>: 0x11 0x21 0x00 0x00 0x11 0x31 0x00 0x00
0x00001010 <bogus+ 16>: 0x11 0x41 0x00 0x00 0x11 0x51 0x00 0x00
0x00001018 <bogus+ 24>: 0x11 0x61 0x00 0x00 0x11 0x71 0x00 0x00
<bochs:5> xp /32bx 0x02000
[bochs]:
0x00002000 <bogus+ 0>: 0x11 0x01 0x40 0x00 0x11 0x11 0x40 0x00
0x00002008 <bogus+ 8>: 0x11 0x21 0x40 0x00 0x11 0x31 0x40 0x00
0x00002010 <bogus+ 16>: 0x11 0x41 0x40 0x00 0x11 0x51 0x40 0x00
0x00002018 <bogus+ 24>: 0x11 0x61 0x40 0x00 0x11 0x71 0x40 0x00
<bochs:6> xp /32bx 0x03000
[bochs]:
0x00003000 <bogus+ 0>: 0x11 0x01 0x80 0x00 0x11 0x11 0x80 0x00
0x00003008 <bogus+ 8>: 0x11 0x21 0x80 0x00 0x11 0x31 0x80 0x00
0x00003010 <bogus+ 16>: 0x11 0x41 0x80 0x00 0x11 0x51 0x80 0x00
0x00003018 <bogus+ 24>: 0x11 0x61 0x80 0x00 0x11 0x71 0x80 0x00
<bochs:8> xp /4bx 0x04ffc
[bochs]:
0x00004ffc <bogus+ 0>: 0x11 0xf1 0xff 0x00
4.宝贵的参考资料
个人感觉底层编程的学习曲线非常陡峭,要积累好多知识,爬过好多的“坑”,才能走到这一步,所以好的学习资料是非常重要的。以下就是我学习过程中常用的资料,它们的用法是:以《Linux内核完全剖析》为主,如果看不懂就去找《Orange’s:一个操作系统实现》中对应的章节对比学习。渐渐熟悉Linux的代码后,就参照Linux实现我们的操作系统。如果碰到NASM语法的相关问题,就去看一下博古以通今的博客,作者用NASM重写的代码还是靠谱的,只不过下不到全部代码了。
4.1 Linux 0.11源码
《完全剖析》中给出的代码包不是很方便,有热心的网友已经做了优化,“一键”就可直接编译运行起来Linux 0.11。
4.2 用NASM重写Linux 0.11
《用nasm语言重新实现linux-0.11 bootsect.s(博古以通今)》
《用nasm语言重新实现linux-0.11 setup.s (博古以通今)》
《nasm重写linux-0.11 head.s (博古以通今)》
4.3 NASM汇编指令
官方文档提供了一份NASM汇编指令列表,令我惊讶的是这份列表貌似并不全,有些查不到的指令如jge也是可用的。
此外,汇编语言没有高级语言那些控制结构,所以jmp对于实现逻辑就非常重要了,这是一份总结的不错的各种跳转指令的列表。
操作系统内核Hack:(四)内核雏形的更多相关文章
- 操作系统内核Hack:(三)引导程序制作
操作系统内核Hack:(三)引导程序制作 关于本文涉及到的完整源码请参考MiniOS的v1_bootloader分支. 1.制作方法 现在我们已经了解了关于BootLoader的一切知识,让我们开始动 ...
- 操作系统内核Hack:(二)底层编程基础
操作系统内核Hack:(二)底层编程基础 在<操作系统内核Hack:(一)实验环境搭建>中,我们看到了一个迷你操作系统引导程序.尽管只有不到二十行,然而要完全看懂还是需要不少底层软硬件知识 ...
- 操作系统内核Hack:(一)实验环境搭建
操作系统内核Hack:(一)实验环境搭建 三四年前,心血来潮,入手<Orange's:一个操作系统的实现>学习操作系统内核,还配套买了王爽的<汇编语言(第二版)>和<80 ...
- Linux操作系统内核编译之NTFS文件系统模块支持案例
Linux操作系统内核编译之NTFS文件系统模块支持案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.内核编译概述 单内核体系设计.但充分借鉴了微内核设计体系的优点,为内核引 ...
- 专家解读Linux操作系统内核中的GCC特性
专家解读Linux操作系统内核中的GCC特性 Linux内核使用GNU Compiler Collection (GCC)套件的几个特殊功能.这些功能包括提供快捷方式和简化以及向编译器提供优化提示 ...
- 从操作系统内核看Java非阻塞IO事件检测
非阻塞服务器模型最重要的一个特点是,在调用读取或写入接口后立即返回,而不会进入阻塞状态.在探讨单线程非阻塞IO模型前必须要先了解非阻塞情况下Socket事件的检测机制,因为对于非阻塞模式最重要的事情是 ...
- C++通用WMI接口实现获取Windows操作系统内核版本号
作为一名Windows开发者,能熟练掌握WMI技术,在开发Windows应用程序的时候往往能够事半功倍.今天来给大家分享一个使用WMI来获取Windows操作系统内核版本号的例子. 首先我们打开WMI ...
- 用java做操作系统内核:软盘读写
在前两节,我们将一段代码通过软盘加载到了系统内存中,并指示cpu执行加入到内存的代码,事实上,操作系统内核加载也是这么做的.只不过我们加载的代码,最大只能512 byte, 一个操作系统内核,少说也要 ...
- Linux查看版本当前操作系统内核信息
1. # uname -a (Linux查看版本当前操作系统内核信息) 输出 Linux xxx --generic #~-Ubuntu SMP Wed Jul :: UTC x86_64 x86_6 ...
随机推荐
- POJ-2632 Crashing Robots模拟
题目链接: https://vjudge.net/problem/POJ-2632 题目大意: 在一个a×b的仓库里有n个机器人,编号为1到n.现在给出每一个机器人的坐标和它所面朝的方向,以及m条指令 ...
- datatables
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...
- Ubuntu16.04开机引导缺失Win10
Ubuntu正常开机的情况下: sudo update-grub # 如果grub丢失, 就先sudo apt install grub Ubuntu不能正常开下: 进入Ubuntu引导, 不要正常进 ...
- mysql如何选择合适的数据类型1:CHAR与VARCHAR
CHAR和VARCHAR类型类似,都用来存储字符串,但它们"保存"和"检索"的方式不同.CHAR属于"固定长度"的字符串,而VARCHAR属 ...
- jQuery中的for循环var与let的区别
今天在写jQuery请求接口中发现一个问题: 在用AJAX发送请求中又嵌套了一个AJAX请求,发现在内层请求的success中对第一次success中的循环变量 i 无法获取,具体代码如下: $.aj ...
- BAT 前端开发面试 —— 吐血总结
更好阅读,请移步这里 聊之前 最近暑期实习招聘已经开始,个人目前参加了腾讯和阿里的内推及百度的实习生招聘,在此总结一下 一是备忘.总结提升,二是希望给大家一些参考 其他面试及基础相关可以参考其他博文: ...
- MQTT客户端库-Paho GO
为了加深理解,本文是翻译文章.原文地址 Paho GO Client 语言 GO 协议 EPL AND EDL 官网地址 http://www.eclipse.org/paho/ API类型 As ...
- 【实验吧】CTF_Web_简单的SQL注入之3
实验吧第二题 who are you? 很有意思,过两天好好分析写一下.简单的SQL注入之3也很有意思,适合做手工练习,详细分析见下. http://ctf5.shiyanbar.com/web/in ...
- [HNOI 2002]彩票
Description 某地发行一套彩票.彩票上写有1到M这M个自然数.彩民可以在这M个数中任意选取N个不同的数打圈.每个彩民只能买一张彩票,不同的彩民的彩票上的选择不同. 每次抽奖将抽出两个自然数X ...
- 控制公司 Controlling Companies
题目描述 有些公司是其他公司的部分拥有者,因为他们获得了其他公司发行的股票的一部分.(此处略去一句废话)据说,如果至少满足了以下三个条件之一,公司A就可以控制公司B了: 公司A = 公司B. 公司A拥 ...