系列blogs,目的在于建立起对linux内核的框架性理解,如果能有所帮助,开心。内容将随本人的理解,不定性地扩充和更改,欢迎指正!
linux内核
为方便用户辨识,linux提供了一套编号方案。linux内核版本格式为“x.yy.zz”,x介于0至9之间,yy及zz介于0至99之间。通常数字俞高版本俞新。其中x的不同号码标志着内核在设计或实现上的重大改变,yy一方面表示版本的变迁,一方面标志着版本的种类,即yy为偶数表示相对稳定,已经发行的版本。若为奇数则表示正在开发中,目前还不太稳定,或在运行中可能会出现比较大问题的版本。zz表示内核增加的内容不多,改动不是很大的变迁,只能算是同一版本。“发行版”和“开发版”的zz是独立编号的,因此并没有固定的对应关系。例如,开发版2.3的版本号达到2.3.99时,相应的发行版还可只是2.2.18。另外,一些版本号若具有pNN字样,NN介于0至20之间。代表对某一版内核“打补丁”或修订次数。如0.99p15,表示对版本0.99内核版本的第15次修订。
Intel X86 CPU系列的寻址方式
在X86系列中,8086和8088是16位处理器,而从80386开始为32位处理器,本blogs主要学习i386。首先,i386有哪些基本的资源:
8个16位通用寄存器:ax,bx,cx,dx,sp,bp,si,di
8个32位通用寄存器:eax,ebx,ecx,edx,esp,ebp,esi,edi
8个调试寄存器:dr0,dr1,dr2,dr3,dr5,dr6,dr7,dr8
6个16位段寄存器:cs,ds,ss,es
4个32位控制寄存器:cr0,cr1,cr2,cr3
4个系统地址寄存器:GDTR,IDTR,LDTR,TR
其实若是到i386这样的32位处理器,代码逻辑地址大可不必再来一次段式管理,当然我们知道这是在向下兼容,毕竟是一个系列的产品。不妨举个例子,以便更直观理解和简述:
mov ax, 1000h ; 段selector值为1000h
mov ds, ax ; 更新DS.base = 1000h << 4 = 10000h
mov ax, [100h] ; ax = DS.base + 100h
上示,涉及到实地址模式下的段基址计算,向DS寄存器加载1000h值,DS.base的值将是1000h << 4 = 10000h(20位值),后面对100h地址读,那么ax的值将是地址10100h里的值。
由上可知段管理基本规则便是:
segment:offset –> segment << 4 + offset
逻辑地址 线性地址
段基址为0x1000,偏移为0x100。上述实例为intel汇编,以后基本上接触的是linux下针对i386的AT&T汇编,上例若使用AT&T汇编会是这样的:
movw $0x1000, %ax
movw %ax, %ds
movw $0, %si
movw 0x100(%si), %ax
这里不妨bochs调试做些验证,也补充下bochs使用,有关bochs调试源码获取和bochs的搭建参考上篇:
http://blog.csdn.net/Je930502/article/details/79520639
因为调试时发现0x10100处是无数据的,需我得先写点数据下去,这就麻烦了。0x7c00处肯定是有指令数据的,使用si或di都行,下面使用di。总之为调试方便同时结果更直观,使用段基址为0x0000,偏移为0x7c00。即:
movw $0x0000, %ax
movw %ax, %ds
movw $0, %di
movw 0x7c00(%di), %ax
看附录bochs调试信息可知,0x7c00物理地址处存放两指令字节0xd9e9,最后获取到的ax寄存器的内容也是0xd9e9,简单验证完毕!
实模式地址与虚模式地址
在实模式中,通过段机制转化而来的线性地址,其实就是物理地址。这里涉及到三个地址,逻辑地址:1000h及100h,线性地址:10100h,物理地址:10100h。BTW,物理地址通俗的说就是实际存储器数据总线上的bits。
就i386而言,实模式中能访问的数据范围20bits,即0-1M,又分段管理段寄存器16位64k对齐。据段寄存器内容确定的基地址,一个进程总是能访问从该基址开始的连续的64k地址空间,修改寄存器也没有指令特权这方面的限制,这样通过修改寄存器就能访问任何内存单元。8086只运行在便是类似i386实模式方式中,无所谓特权指令(特权指令,像i386中访问GDTR的指令等),内存寻址缺乏对内存空间的保护。一个操作系统缺乏对寄存器及内存的访问控制,可想而知,都不具备现代操作系统的基本要求。
那么,在保护模式下,上述汇编实例,会是如何个原理呢,有哪些访问控制保护?
在保护模式下改变段寄存器的功能,可使其从单纯的基地址(变相的基地址)变成指向这样一个数据结构的指针。这样,当一条访内指令发出一个内存地址时,CPU就可以这样归纳出实际上放上数据总线的地址:
1 根据指令的性质来确定应该使用哪一个段寄存器,例如转移指令中的地址在代码段,而取数据指令的地址在数据段。这与实地址模式相同。
2 根据段寄存器的内容,找到相应的“地址段描述结构”。
从地址段描述结构中得到基地址
3 将指令中发出的地址作为位移,与段描述结构中规定的段长度相比,看是否越界。
5 根据指令的性质和段描述符中的访问权限来确定是否越权
6 将指令中发出的地址作为位移,与基地址相加而得出实际的“物理地址”。
上述,6中得出的是实际的物理地址,而非线性地址或说此时两者是相等的,是因为在进入保护模式之前,实模式中需要做些工作,一当然是使能分段管理,二其实可以选择性的使能分页管理。若在实模式中没有使能,那进入保护模式中使能分页管理前,还是直接采用分段管理内存访问。不过此时的段式内存访问,采用了间接的地址段描述结构管理,而不是直接使用段寄存器内容计算基址,再者当然就是添加了内存访问控制。
小结
简单介绍了linux内核的版本管理方式;linux内核学习i386平台的一些实例测试,今后会可能用到的工具;也延伸说到了有关虚或实地址的概念。
附bochs调试 log
<bochs:1> b 0x7c00 #跳到0x7c00处执行
<bochs:2> trace-reg on #每次命令执行打印寄存器值
Register-Tracing enabled for CPU0
<bochs:3> c #继续执行
(0) Breakpoint 1, 0x00007c00 in ?? ()
Next at t=13162205
eax: 0x0000aa55 43605
ecx: 0x00090000 589824
edx: 0x00000000 0
ebx: 0x00000000 0
esp: 0x0000ffd6 65494
ebp: 0x00000000 0
esi: 0x000e0000 917504
edi: 0x0000ffac 65452 #edi低位即di目前值
eip: 0x00007c00
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
(0) [0x0000000000007c00] 0000:7c00 (unk. ctxt): jmp .+217 (0x00007cdc) ; e9d900 #0xe9 0xd9两字节指令值
<bochs:4> n #下条指令执行
Next at t=13162206
eax: 0x0000aa55 43605 #特殊值,0xaa55 主引导记录
ecx: 0x00090000 589824
edx: 0x00000000 0
ebx: 0x00000000 0
esp: 0x0000ffd6 65494
ebp: 0x00000000 0
esi: 0x000e0000 917504
edi: 0x0000ffac 65452
eip: 0x00007cdc
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
(0) [0x0000000000007cdc] 0000:7cdc (unk. ctxt): mov ax, cx ; 89c8
<bochs:5> n
Next at t=13162207
eax: 0x00000000 0
ecx: 0x00090000 589824
edx: 0x00000000 0
ebx: 0x00000000 0
esp: 0x0000ffd6 65494
ebp: 0x00000000 0
esi: 0x000e0000 917504
edi: 0x0000ffac 65452
eip: 0x00007cde
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
(0) [0x0000000000007cde] 0000:7cde (unk. ctxt): mov ds, ax ; 8ed8
<bochs:6> n
Next at t=13162208
eax: 0x00000000 0
ecx: 0x00090000 589824
edx: 0x00000000 0
ebx: 0x00000000 0
esp: 0x0000ffd6 65494
ebp: 0x00000000 0
esi: 0x000e0000 917504
edi: 0x0000ffac 65452
eip: 0x00007ce0
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
(0) [0x0000000000007ce0] 0000:7ce0 (unk. ctxt): mov es, ax ; 8ec0
<bochs:7> n
Next at t=13162209
eax: 0x00000000 0
ecx: 0x00090000 589824
edx: 0x00000000 0
ebx: 0x00000000 0
esp: 0x0000ffd6 65494
ebp: 0x00000000 0
esi: 0x000e0000 917504
edi: 0x0000ffac 65452
eip: 0x00007ce2
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
(0) [0x0000000000007ce2] 0000:7ce2 (unk. ctxt): mov ax, 0x0000 ; b80000
<bochs:8> n
Next at t=13162210
eax: 0x00000000 0
ecx: 0x00090000 589824
edx: 0x00000000 0
ebx: 0x00000000 0
esp: 0x0000ffd6 65494
ebp: 0x00000000 0
esi: 0x000e0000 917504
edi: 0x0000ffac 65452
eip: 0x00007ce5
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
(0) [0x0000000000007ce5] 0000:7ce5 (unk. ctxt): mov ds, ax ; 8ed8
<bochs:9> n
Next at t=13162211
eax: 0x00000000 0
ecx: 0x00090000 589824
edx: 0x00000000 0
ebx: 0x00000000 0
esp: 0x0000ffd6 65494
ebp: 0x00000000 0
esi: 0x000e0000 917504
edi: 0x0000ffac 65452
eip: 0x00007ce7
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
(0) [0x0000000000007ce7] 0000:7ce7 (unk. ctxt): mov di, 0x0000 ; bf0000
<bochs:10> n
Next at t=13162212
eax: 0x00000000 0
ecx: 0x00090000 589824
edx: 0x00000000 0
ebx: 0x00000000 0
esp: 0x0000ffd6 65494
ebp: 0x00000000 0
esi: 0x000e0000 917504 #esi低位即si,调试用di不用si因为值低位全为0,再置0,值改无体现。 :)
edi: 0x00000000 0 #edi低位即di值置为0x0000
eip: 0x00007cea
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
(0) [0x0000000000007cea] 0000:7cea (unk. ctxt): mov ax, word ptr ds:[di+31744] ; 8b85007c
<bochs:11> n
Next at t=13162213
eax: 0x0000d9e9 55785 #eax低位即ax置为0xd9e9
ecx: 0x00090000 589824
edx: 0x00000000 0
ebx: 0x00000000 0
esp: 0x0000ffd6 65494
ebp: 0x00000000 0
esi: 0x000e0000 917504
edi: 0x00000000 0
eip: 0x00007cee
eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
(0) [0x0000000000007cee] 0000:7cee (hunk. ctxt): call .-238 (0x00007c03) ; e812ff
<bochs:12> xp /64xb 0x7c00 #打印0x7c00地址处开始的64个字节,可知起始两字节为0xe9,0xd9,即0xd9e9
[bochs]:
0x00007c00 <bogus+ 0>: 0xe9 0xd9 0x00 0xb4 0x06 0xb0 0x00 0xb5
0x00007c08 <bogus+ 8>: 0x00 0xb5 0x00 0xb6 0x18 0xb2 0x4f 0xb7
0x00007c10 <bogus+ 16>: 0x07 0xcd 0x10 0xc3 0xb4 0x00 0xb0 0x13
0x00007c18 <bogus+ 24>: 0xcd 0x10 0xc7 0x06 0x04 0x7d 0x13 0x00
0x00007c20 <bogus+ 32>: 0xc7 0x06 0x06 0x7d 0x40 0x01 0xc7 0x06
0x00007c28 <bogus+ 40>: 0x08 0x7d 0xc8 0x00 0x66 0xc7 0x06 0x0a
0x00007c30 <bogus+ 48>: 0x7d 0x00 0x80 0x0b 0x00 0xc3 0xb8 0x00
0x00007c38 <bogus+ 56>: 0x08 0x8e 0xc0 0x8e 0xd8 0x31 0xff 0xb4
<bochs:13>
资料参考:
linux内核情景分析
x86_64体系探索及编程
https://www.cnblogs.com/BoyXiao/archive/2010/11/20/1882716.html