一、实模式寻址

段首地址×16+偏移量 = 物理地址
为什么要×16?因为在8086CPU中,地址线是20位,但寄存器是16位的,最高寻址64KB,它无法寻址到1M内存。于是,Intel设计了这种寻址方式,先缩小4位成16位放入到段寄存器,用到时候,再将其扩大到20位,这也造成了段的首地址必须是16的倍数的限制。

二、保护模式寻址

保护模式下 分段机制是利用一个称作段选择符的偏移量,从而到描述符表找到需要的段描述符,而这个段描述符中就存放着真正的段的物理首地址,再加上偏移量。公式:xxxx:yyyyyyyy

其中,xxxx也就是索引,yyyyyyyy是偏移量(因为32位寄存器,所以8个16进制)xxxx存放在段寄存器中。

1.GDT(全局描述符表)

GDT每一行都是一个8个字节的结构体,称作描述符。描述符里面存放着段基址,段界限,和段属性。

看一下这个Descriptor段描述符的内存模型:

; 高地址………………………………………………………………………低地址

; |     7     |     6     |     5     |     4     |     3     |     2     |     1     |     0      |
共 8 字节
; |——–========——–========——–========——–========|
; ┏━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┓
; ┃31..24  ┃     段属性            ┃       段基址(23..0)                ┃ 段界限(15..0)     ┃
; ┃             ┃                              ┃                                                ┃                               ┃
; ┃ 基址2 ┃                              ┃基址1b│     基址1a              ┃       段界限1        ┃
; ┣━━━╋━━━┳━━━╋━━━━━━━━━━━╋━━━━━━━┫
; ┃      %6 ┃    %5    ┃    %4    ┃    %3    ┃       %2                  ┃         %1                ┃
; ┗━━━┻━━━┻━━━┻━━━┻━━━━━━━┻━━━━━━━┛

所以,如果要往这个结构体传这三个参数%1,%2,%3,需要一些移位操作:

%macro Descriptor 3
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 (1 字节)
%endmacro ; 共 8 字节

2.段属性

接下来看看段选择子:段选择子,也就是数组的索引,但这时候的索引不在是高级语言中数组的下标,而是我们将要找的那个段描述符相对于数组首地址(也就是全局描述表的首地址)偏移位置。

3.寻址步骤

  • 1、段寄存器中存放段选择子Selector
  • 2、GDTR中存放着段描述符表的首地址
  • 3、通过选择子根据GDTR中的首地址,就能找到对应的段描述符
  • 4、段描述符中有段的物理首地址,就得到段在内存中的首地址
  • 5、加上偏移量,就找到在这个段中存放的数据的真正物理地址

4.GDTR

Global Descriptor Table Register(全局描述符表寄存器)但是这个寄存器有什么用呢 ? 大家想一下,段描述符表现在是存放在内存中,那CPU是如何知道它在哪里呢?所以,Intel 公司设计了一个全局描述符表寄存器,专门用来存放段描述符表的首地址,以便找到内存中段描述符表。这时,段描述符表地址被存到GDTR寄存器中了。

5.代码实现

段基地址    段界限 段属性
GDT_BEGIN: Descriptor 0,       0,    0
GDT_CODE32: Descriptor 0,    0,    DA_C
通过上面的macro Descriptor 3将三个参数传入描述符中规定的位置。
;下面是定义代码段选择子,它就是相对数组首地址的偏移量
SelectorCode32 equ    GDT_CODE32 – GDT_BEGIN
GDT_CODE32是相对于数据段的偏移量,
GDT_BEGIN也是相对于数据段的偏移量,虽然它是数组的首地址,说的罗索一些,GDT_BEGIN是数组的首地址,但是它是相对于数据段的偏移量
那么两个偏移量相减就是GDT_CODE32 相对于GDT_BEGIN的偏移量

6.保护模式背景

当x86 CPU 工作在保护模式时,可以使用全部32根地址线访问4GB的内存,因为80386的所有通用寄存器都是32位的,所以用任何一个通用寄存器来间接寻址,不用分段就可以访问4G空间中任意的内存地址。也就是说我们直接可以用Eip寄存器就可以找到茫茫内存里面所有的值! 但这并不意味着,此时段寄存器就不再有用了[其实 还有部分原因是要与8086兼容] 。实际上,段寄存器更加有用了,虽然再寻址上没有分段的限制了,但在保护模式下,一个地址空间是否可以被写入,可以被多少优先级的代码写入,是不是允许执行等等涉及保护的问题就出来了。[想想吧,单单就是靠eip找到所有内存的值显然不够的,醒醒吧,我们到了80386时代了,我们需要保护模式,要指示出来那些内存段是操作系统核心用的,那些是你打游戏时用的,打游戏时的cpu不能访问到操作系统核心所用的内存段。我们需要分出”级别”来] 。  要解决这些问题,必须对一个地址空间定义一些安全上的属性。段寄存器这时就派上了用场。但是设计属性和保护模式下段的参数,要表示的信息太多了,要用64位长的数据才能表示。我们把着64位的属性数据叫做段描述符。

段物理首地址、段界限、段属性  80386的段寄存器是16位(注意:通用寄存器在保护模式下都是32位,但段寄存器没有被改变,比如cs还是16位的,16位的段寄存器怎么可能装下一个64位的段描述符)的,无法放下保护模式下64位的段描述符。如何解决这个问题呢? 方法是把所有段的段描述符顺序存放在内存中的指定位置,组成一个段描述符表(Descriptor Table);而段寄存器中的16位用来做索引信息,这时,段寄存器中的信息不再是段地址了,而是段选择子(Selector)。可以通过它在段描述符表中“选择”一个项目已得到段的全部信息。 也就是说我们在另一个地方把段描述符放好,然后通过选择子来找到这个段描述符。

三、实模式到保护模式之间的跳转

=====================================================================
;实现从实模式到保护模式之间的跳转
;参考:《自己动手写操作系统》
———————————————————————-
%include “pm.inc”

org 0100h
jmp LABEL_BEGIN

[SECTION .gdt]
GDT_BEGIN: Descriptor 0,    0,     0
GDT_CODE32: Descriptor 0,    LenOfCode32 – 1, DA_C + DA_32
GDT_VIDEO: Descriptor 0B8000H, 0FFFFH,     DA_DRW

GdtLen    equ    $ – GDT_BEGIN
GdtPtr    dw    GdtLen – 1
dd    0

;定义段选择子
SelectorCode32 equ    GDT_CODE32 – GDT_BEGIN
SelectorVideo equ    GDT_VIDEO – GDT_BEGIN

[SECTION .main]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax

;初始化32位代码段选择子
;我们可以在实模式下通过段寄存器×16 + 偏移两 得到物理地址,
;那么,我们就可以将这个物理地址放到段描述符中,以供保护模式下使用,
;因为保护模式下只能通过段选择子 + 偏移量

xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_CODE32
mov word [GDT_CODE32 + 2],ax
shr eax, 16
mov byte [GDT_CODE32 + 4],al
mov byte [GDT_CODE32 + 7],ah

;得到段描述符表的物理地址,并将其放到GdtPtr中
xor eax, eax
mov ax, ds
shl eax, 4
add eax, GDT_BEGIN
mov dword [GdtPtr + 2],eax

;加载到gdtr,因为现在段描述符表在内存中,我们必须要让CPU知道段描述符    表在哪个位置
;通过使用lgdtr就可以将源加载到gdtr寄存器中
lgdt [GdtPtr]

;关中断
cli

;打开A20线
in al, 92h
or al, 00000010b
out 92h, al

;准备切换到保护模式,设置PE为1
mov eax, cr0
or eax, 1
mov cr0, eax

;现在已经处在保护模式分段机制下,所以寻址必须使用段选择子:偏移量来    寻址

;跳转到32位代码段中
;因为此时偏移量位32位,所以必须dword告诉编译器,不然,编译器将阶段    成16位
jmp dword SelectorCode32:0;跳转到32位代码段第一条指令开始执行

[SECTION .code32]
[BITS 32]
LABEL_CODE32:
mov ax, SelectorVideo
mov es, ax

xor edi, edi
mov edi, (80 * 10 + 10)

mov ah, 0ch
mov al, ‘G’

mov [es:edi],ax

jmp $
LenOfCode32 equ $ – LABEL_CODE32

这段代码的大概意思是:
先在16位代码段,实模式下运行,在实模式下,通过段寄存器×16+偏移量得到32位代码的真正物理首地址,并将放入到段描述符表中,以供在保护模式下使用,上面说过了,保护模式下寻址,是通过段选择子,段描述符表,段描述符一起工作寻址的。所以在实模式下所做的工作就是初始化段描述符表里的所有段描述符。
我们来看一下段描述符表,它有3个段:
GDT_BEGIN
GDT_CODE32
GDT_VIDEO

GDT_BEGIN,遵循Intel公司规定,全部置0
GDT_CODE32,32位代码段描述符,供保护模式下使用
GDT_VIDEO,显存段首地址,我们知道,显存首地址是0B8000H.

回想一下,我们在实模式下往显示器上输出文字时,我们设置段寄存器为
0B800h,(注意后面比真正物理地址少一个0)。
而我们现在在保护模式下访问显存,那么0B8000h就可以直接放到段描述符中即可。因为段描述符中存放的是段的真正的物理地址。

下面我们来逐行分析该代码
org    0100h
这句话告诉加载器,将这段程序加载到偏移段首地址0100h处,即:偏移256字节处,为什么要加载到偏移256个字节处呢 ?这是因为,在DOS中,需要留下256个字节和DOS系统进行通信。
jmp    LABEL_BEGIN
执行这句话就跳转到LABEL_BEGIN处开始执行。好,我们看一下LABEL_BEGIN在那块,也就是16位代码段

[SECTION .main]
[BITS 16]
LABEL_BEGIN:
这样程序就从.main节的第一段代码开始执行。我们看一下上面的代码,[BITS 16]告诉编译器,这是一个16位代码段,所使用的寄存器都是16位寄存器。该代码段初始化所有段描述符表中的段物理首地址

首先在实模式下计算出32位代码段的物理首地址
对照    段值 × 16 + 偏移量    = 物理地址

1    mov ax,    cs
2    shl eax, 4    ;向左移动4位,不就是×16吗?呵呵
;到现在为止,eax就是代码段的物理首地址了,那么。。。看
3    add eax, LABEL_CODE32
;为eax (代码段首地址)加上 LABEL_CODE32偏移量,得到的不就是LABEL_CODE32的真正物理地址了吗 ?LABEL_CODE32在程序中,不就是32位代码段的首地址吗 ?

上面说过,代码中,使用的变量,或者标签 都是相对程序物理首地址的偏移量。

由于历史原因,段描述符的内存排列不是按照 段基地址 段界限 段属性 这样的来排列的,所以我们现在要想一种办法,把eax里所存放的物理首地址拆开,分别放到2,3,4,7字节处
那么很显然,我们可以将eax寄存器中的ax先放到2,3字节处
mov    word [GDT_CODE32 + 2],ax
因为在偏移2个字节处,所以,首地址 + 2,才能定位到下标为2的字节开头处
而,word 告诉编译器,我要一次访问2个字节的内存

好,简单的搞定了,那么再看,我们现在要将eax高16字节分别放到下标为4,7字节处。
虽然eax的ax代表低16位,但是Intel并没有给高位一个名字定义,(不会是high ax,呵呵),所以,我们没有办法去访问高位。但是我们可以将高16位放到低16位中,因为这时,低16位我们已经不关心它的值了。
好,看代码
shr    eax,    16
这句代码就将eax向右移动16位,低位被抛弃,高位变成了低位。呵呵。。。

现在好办了,低16位又可以分为al,和 ah,那么现在我们就将al放到4位置,ah放到7位置吧
mov    byte [GDT_CODE32 + 4], AL
mov    byte [GDT_CODE32 + 7], AH
不用我再解释这段代码了,自己去分析为什么吧。。。。

好了,32位代码段描述符设置好了,其界限设置看代码吧,为什么要那样设置,很简单的,界限 = 长度 - 1,段属性:
DA_C: 98h      可执行
DA_32: 4000h 32位代码段
是个常量,换算成二进制位,对照段描述符属性位置去看吧,参考任意一本保护模式书。

段描述符设置好了,但是,先段描述符表,还在内存中,我们必须想办法放到寄存器中,这时,就用到了gdtr(Golbal Descriptor Table Register),使用一条指令
lgdtr [GdtPtr]

就可以将GdtPtr加载到gdtr中
而gdtr的内存模型是:

——————————————————-
高字节                                   低字节

——————————————————-

但GdtPtr是什么呢 ?
就是我们定义的和这个寄存器内存模型一摸一样的结构体:
GdtLen    equ    $ – LABEL_BEGIN
GdtPtr    dw    GdtLen – 1     ;界限
dd    0       ;真正物理地址
那现在我们就要计算GdtPtr第二个字节 也就是真正物理地址了
xor eax, eax
mov ax,    ds
shl eax, 4
add eax, GDT_BEGIN
mov dword [GdtPtr + 2],eax
自己分析吧,和计算32位段首地址基本一样的,
搞定后,使用lgdt [GdtPtr]就将此加载到寄存器GDTR中了

然后关中断
cli    实模式下的中断和保护模式下的中断处理不一样,那就关吧,规矩
开启A20线
in al, 92h
or al, 00000010b
out 92h, al
如果不开启A20线,就无办法访问1M之上的内存,没办法,开启吧,规矩,想知道历史了,去查吧

然后设置CR0的PE位
mov eax, cr0
or eax, 1
mov cr0, eax
这个简单说一下,以后再详细
CR0也是一个寄存器,其中有个PE位,如果为0,就说明为实模式,
如果置1,说明为保护模式。现在我们要进入保护模式下工作,那么就要设置PE为1。

好了,看一下这个main节中的最后一个代码
jmp    dword SelectorCode32 : 0
哈哈,现在已经再保护模式下了,当然要使用段选择子 + 偏移量来寻址啊,这样不就是寻址到了32位代码段中去了吗,偏移量为0不就说明从第一个代码开始执行。
不是吗 ?呵呵,那dword了?
因为现在的代码段是16位,编译器只能将它编译位16位,但处于保护模式下,它的偏移量应该是32位,所以,要显示告诉编译器,我这里使用的是32位,把我这块给编译成32位的!!!
如果不加dword,
jmp    SelectorCode32:0
这句话不会出什么问题,16位的0是0,32位的0还是0,但如果这样呢?:
jmp    SelectorCode32:0x12345678
跳转到偏移0x12345678中,这时就错了
如果不将dword,编译器就将该地址截断成16位,取低位,变成了0x5678
你说对吗 ?哈哈
所以我们必须这样做:
jmp    dword SelectorCodde32:0x12345678

OKEY,我们继续追击,执行完上面那个跳转后,
代码就跳到了32位代码段的中,开始执行第一条指令
mov ax, SelectorVideo
再看
mov es,ax
呵呵,实模式下,放的是16位的段值,而现在呢,不就是要将段选择子放到段寄存器里吗 ?然后通过段选择子(偏移量)找到描述符表中对应的段描述符的吗 !!!!
继续看下面代码
xor edi, edi
mov edi, (80 * 10 + 10)

mov ah, 0ch
mov al, ‘G’
跟实模式下差不多,设置目标10行10列
设置现实字符:G
mov [es:edi],ax
也和实模式下一样,
只不过实模式是这样来寻址 :
es×16 + edi
而保护模式下呢
es是一个偏移,根据这个偏移找到段描述符表中的对应显存段,然后这个显存段里存放的就是0B8000h,然后在加上偏移 不就的了吗!!!
哈哈 。。。。程序分析完毕,细节之处,自己体会去

总结:
1. 注意程序中使用的全部是偏移地址。注意两种偏移地址

A 对于程序的起始地址来说,所有变量和标签都是相对于整个程序的偏移量
B 对于段中定义的代码,有两种偏移:
相对于程序起始地址的偏移
相对于段标签的偏移。

2.不管是实模式下的物理地址,还是保护模式下的物理地址,反正他们都是物理地址,呵呵,实模式下求的物理地址,也能在保护模式下使用,只是他们不同的是,如何寻址的方式不一样。

3.一个程序中可以包含多个不同位的段,32位或者16位,他们之间也可以互相跳转,只是32位段用的是32位寄存器,16位代码段用的是16位寄存器,如果要在16位段下使用32位寄存器,必须象高级语言中强制类型转换一样,显示的定义 dword

org 07c00h
mov ax, cs
mov ds, ax
mov es, ax
call DispStr
jmp $
DispStr:
mov ax, BootMessage
mov bp, ax
mov cx, 16
mov ax, 01301h
mov bx, 000ch
mov dl, 0
int 10h
ret
BootMessage: db “Hello, OS world!”
times 510-($-$$) db 0
dw 0xaa55
这个代码段是存于磁盘引导扇区的系统引导(自举)程序。若存于硬盘第一个扇区,则这段代码称为:硬盘主引导记录;若存于硬盘某个分区的第一个扇区,则称为:分区引导记录;若存于软盘的第一个扇区,则称为:软盘引导记录。称为磁盘引导记录的重要标志是:

dw 0xaa55

磁盘的一个扇区是512字节,标志0xaa55存于这个扇区的最后一个字(两字节,偏移地址为:1FEH),其余空间用于存储指令代码和一些参数、提示信息等。磁盘引导记录由ROM BIOS的INT 19H(引导加载程序,相当于热启动系统,对应的快捷键为:Ctrl+Alt+Del),固定装入内存的0000:7C00H,然后将控制权交给磁盘引导程序,相当于开始执行下面的程序段。下面详细解答一下这个程序段的功能:

org 07c00h ;调整偏移量伪指令ORG,指定下面的指令从7c00h处开始,因为BIOS一旦发现引导扇区,就会将这512字节装载到内存0000:7c00处
mov ax, cs ;数据传送指令,将代码段寄存器cs的内容赋给通用寄存器ax
mov ds, ax ;ax→ds,使数据段与代码段在同一个段
mov es, ax ;ax→es,使附加段与代码段在同一个段
call DispStr ;调用子程序DispStr,显示字符串信息:Hello, OS world!
jmp $ ;$表示当前地址,实现死循环
DispStr: ;子程序:显示字符串
mov ax, BootMessage ;BootMessage的首地址给ax
mov bp, ax ;BootMessage首地址给堆栈指针BP
mov cx, 16 ;要显示的字符数
mov ax, 01301h ;AH=13h,int 10h,即视频中断13h号功能:写字符串;AL=01H,表示写完字符串后,更新光标位置
mov bx, 000ch ;BH=0,页号(视频缓冲区是分页的),初学者暂时不必理会;BL=0CH,字符显示属性,以黑底亮红显示字符
mov dl, 0 ;DH、DL=写串的光标位置,DH=行号,DL=列号
int 10h ;调用视频中断
ret ;子程序返回指令,返回调用者
BootMessage: db “Hello, OS world!” ;要显示的字符串信息
times 510-($-$$) db 0 ;$是当前地址,$$是首地址,总体意思就是从此处一直到510都用0填充
dw 0xaa55 ;磁盘引导记录重要标志 ,最后2个字节代表这是引导扇区。

ubuntu10.04 lucid 去掉了sun-java6-jre,sun-java6-jdk的源,所以如果是直接apt-get install 提示是

现在没有可用的软件包 sun-java6-jdk,但是它被其它的软件包引用了。

这可能意味着这个缺失的软件包可能已被废弃,

或者只能在其他发布源中找到

E: 软件包 sun-java6-jdk 还没有可供安装的候选者

解决办法(选择一个即可):

1、系统->系统管理->软件源->“其它软件”下添加一个  deb http://archive.canonical.com/ lucid partner

之后,再执行apt-get install

2、自己从sun网站下载相应的Jre,JDK安装即可

3、从新立德软件管理器中search openJDK,用openJDK代替

一、症状

在点击安装程序的时候,无论重启多少次都提示“You must reboot after previous operation”导致无法安装。 装完又重启,重启又安装,如此反复,就是无法到下一步。

二、安装错误导致的无法安装

此时,只需要将注册表的值删除即可。

进入注册表(运行regedit),展开到如下:HKEY_LOCAL_MACHINE\Software\19659239224e364682fa4b af72c53ea4 或者HKEY_LOCAL_MACHINE\Software\14919ea49a8f3b4aa3cf10 58d9a64cec,找到任意一个就可以将其整个项都从注册表删除。

然后再运行DT的安装程序,这个提示就不会再出现,可以正确安装

三、sptd驱动问题

1、删除注册表的键值,和上步一样。

2、到http://www.disc-tools.com/download/sptd 下载sptd,然后安装,可能要重启。(如果不能安装,运行注册表,展开进入:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\sptd ,修改其中Start的键值,改为4 ,重启后,运行注册表,展开进入HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ ,将下面的sptd项删除,如果系统提示没有足够的权限删除,那么请修改sptd项下面cgf项(HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\sptd\cgf)的权限为完全可控制,再进行删除。删除后,再以管理员身份运行第一步下载的SPTD程序,点击Install,如果系统提示“Failed to open Services”,在“HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\”下面新建一个名为sptd的项,如果没有这个提示那最好。完成后重启计算机。 )

3、重新安装Daemon Tools

写COOKIE:

HttpCookie acookie = New HttpCookie(user);

acookie.Values.Add(“user”, HttpUtility.UrlEncode(user)); ‘写COOKIE时进行编码

acookie.Values.Add(“pass”, HttpUtility.UrlEncode(pass));

Response.Cookie(acookie);

读COOKIE:

HttpCookie acookie = Request.Cookies(user);

Session[“user”] = HttpUtility.UrlDecode(acookie.Values[“user”].ToString());

Session[“pass”] = HttpUtility.UrlDecode(acookie.Values[“pass”].ToSring()) ; ‘读COOKIE时进行解码

同步文件夹

在移动硬盘与硬盘之间、本地与网络(FTP,SSH等)之间备份数据、同步文件夹的需求对于Linux用户是很常见的。在Windows中,有Total Commander等工具同步文件夹的功能很强大,那么Linux中有哪些同步文件夹的软件呢?

rsync和grsync

rsync是Linux中重要的文件同步工具,支持本地和远程的文件同步工具,而grsync则是它的图形界面。

grsync界面比较简单,但可以完成基本的同步任务。还有一些rsync的强大功能没有在grsync中体现出来,如果需要这些功能,就需要看一下rsync的man文档了。

unison

unison是一个既可以用于Windows,也可以用于Linux的开源文件夹同步工具,也支持本地和远程的文件同步。(网站: http://www.cis.upenn.edu/~bcpierce/unison/)

注意:

如果要用unison通过ssh和远程的电脑同步,远程的电脑也要安装unison。

安装所需的 CAB 文件‘Sql.cab’ 已损坏,不能使用。这可能表示网络错误、读光盘错误或此软件包错误。

网上说法一大堆,真没有发现哪个能解决问题的。

我挨着试了一遍,发现真没有一个可以用。最后重新下载了一个,终于安装成功了。。

期间卸载挺麻烦,你可以先关掉Sql相关服务,然后在控制面板中将Sql2005的相关软件都删除,然后将注册表里面的相关键值都删除,然后删掉安装文件。这样比起下载软件删更简单,而且干净点。。

下次安装的时候,最好重新命名一个实例,以免上次安装的实例造成安装失败。

分享Archlinux源中国最快的几个源

Server = http://mirrors.163.com/archlinux/$repo/os/i686
Server = http://mirrors.sohu.com/archlinux/$repo/os/i686

更新方法

用root权限修改/etc/pacman.d/mirrorlist文件,将里面的内容换成上面的即可,然后

pacman -Sy 同步一些本地库即可,注意也要用root权限。

如果要安装yaourt,需要在/etc/pacman.conf末尾加上

[archlinuxfr]

Server = http://repo.archlinux.fr/i686

如果是64位,则加上

[archlinuxfr]

Server = http://repo.archlinux.fr/x86_64