公告

Gentoo交流群:87709706 欢迎您的加入

#551 新手园地 » Gentoo的哲学 » 2022-08-20 01:39:24

batsom
回复: 0

我之所以创建Gentoo是因为之前我没有办法找到一种我喜欢的Linux发行版。我用Linux发行版时, 其中最重要的一件事情是发行版里管理整个系统的系统工具本来是应该让用户工作更容易, 它似乎一直希望用户能够感应到这点并且能够让系统去做用户要做的工作。但是当我告诉系统我想要做些什么的时候, 似乎它更在意并且不断的传输我一个信息:“它想让我做什么!”

       所以我创立了Gentoo Linux并且开始设计了一种比以往的系统管理工具更为强大的Portage工具。为了完成这个目标, 我必须让它能够满足“我想要做”的事情 还要让它知道并且满足其他我想到的事情。

       如果想要知道一个软件包是如何建立的,你可以看一下相对简单易懂的ebuild文件。如果你想知道如何有效的创建软件包,你可以参考“USE”变量。如果你想加一个软件包, 你可以考虑在现有的“树”下建立一个新的ebuild。如果你想用其中一个软件包, 最简单的方法就是emerge, 而软件包的依赖性可以自动解决。

       很多人都青睐Portage的工作原理, 这也是GENTOO能在短时间成长起来的原因。GENTOO系统已经成为一种“from source”的发行版,但是要注意的是GENTOO的核心工作原理并非“from source(依赖来源)”。 虽然“from source(依赖来源)”是GENTOO的重要特征之一, 将来也是GENTOO必要运行的因素之一, 可是它不是唯一兼最基本的特征。而真正的最基本特征是让所有人没有拘限地做他们想做的工作。

       总结一下GENTOO的“核心”特征, 我们可以想象一位用户坐在一台LINUX系统前面。“他/她想要做什么?”GENTOO的工作原理就是在没有一个指定的方法下, 让这个用户可以随心所欲的做他/她要做的事情。

       在GENTOO刚刚诞生的时候, 用户说明里缺乏一个简便的可以从来源建立软件包的方法。而现在,我们已经将它实现并且运用的很好。但是现在我们仍然缺乏一个有效的非创建型软件包的支持方安, 虽然PORTAGE系统在一开始就已经支持了二位元包, 而这个正是我们在努力的工作。

       有一点很重要需要说明的是我们的工具支持二位元包是因为它在Linux社区里广泛的使用和需求。如果我们的工具不支持二位元包, 那么我们就无法向外宣称我们的工具是为了让任何一个用户做他想要做的任何工作。如果我们有意的排除二位元文件的支持, 那么事实上我们是在干涉用户自己想要解决问题的方法,从而将我们自己的愿望或者我们想要用户解决问题的方法强加到用户端。同样如果我们不建立二位元包,我们也无法肯定工具可以和二位元包兼容, 也无法确定用户可以建立二位元包, 也不能够论证我们的工具可以兼容二位元包。除了原理因素外, 还有很多实际因素使我们必须要考虑建立二位元包。

       GENTOO原理如果用一段文字表达就是这样。每个用户都有他/她需要做的事情。GENTOO的目标或者主旨就是设计出一系列工具和系统,从而可以让用户愉快并且有效率的做完他们的事情。我们的工具让用户用上去愉快,并且让他们知道和感慨LINUX和自由软件社区是多样化和自由软件的弹性。而这个目标只有在本身工具可以直接反应出用户自己的愿望, 然后把所有的可能性公开成一个最终的实体里(源码)。 如果工具是要用户使用一个特定的方法来工作的话, 那么它是违反而不是符合用户而工作。我们发现还是有一些情形, 当中将一些“我们”的愿望强加在用户上。这个是其中的缺点, 是违反了GENTOO的原理。

       从另一方面来说, GENTOO的原理是创建一个更好的工具。当工具出色的工作的时候, 你几乎可以感觉不到它的存在, 因为它不会干涉也不会让你知道它的存在。 如果你不想和它互动, 它也不会强迫。

       将来GENTOO的目标就是继续努力创造接近完美的工具。工具可以适应不同用户的需求而变的无比强悍。当你发现一个工具可以做你想要做的任何事情的时候, 你会不喜欢它吗?这种感觉不会棒吗?我们的任务就是将这种感觉带给更多的人们。

#553 入门必备 » 期中课程设计 » 2022-08-18 21:52:16

batsom
回复: 0

俺想了半日 还是不能独立完成 只好去参考答案了
不过经过数次参详 貌似理解了不少 发现王哥真是牛人啊
但是还是有几个地方不甚理解 而且我原原本本的copy上答案的代码后MASMPlus 和 debug调试
都无法显示最终效果
[local]1[/local]
下面是程序的源代码 比较长 不过程序都有比较多的注释 而且两个子函数基本可以不看
程序代码:


assume cs:code,ds:data,es:table

table segment

    db '1975','1976','1977','1978','1979','1980','1981','1982','1983'

    db '1984','1985','1986','1987','1988','1989','1990','1991','1992'

    db '1993','1994','1995'

    dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514

    dd 345980,590827,803530,1183000,1843000,2795000,3753000,4649000,5937000

    dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226

    dw 11542,14430,15257,17800

    dw 5,3,42,104,85,210,123,111,105,125,140,136,153,211,199,209,224,239

    dw 260,304,333

table ends

data segment

    db 32 dup (0)

data ends

code segment

start:   mov ax,data

     mov ds,ax

     mov ax,table

     mov es,ax

     mov bx,0

     mov si,0

     mov di,0

     mov cx,21

     mov dh,2

     mov dl,30

g:   push cx

     push dx

     mov ax,es:[bx]

     mov [si],ax

     mov ax,es:[bx].2

     mov [si].2,ax ;年份入ds:si

     add si,6


     mov ax,es:[bx].84

     mov dx,es:[bx].86

     call dtoc2    ;收入转成十进制字符入ds:si

     add si,10

     mov ax,es:[di].168

     mov dx,0

     call dtoc2    ;人员数转成十进制字符入ds:si

     add si,6

     mov ax,es:[di].210

     mov dx,0

     call dtoc2    ;人均收入转成十进制字符入ds:si

     mov si,0      ;设置ds:si指向需显示字符首地址

b:   mov cx,29       这里有个问题 b:应该是多余的吧 程序里没有对他的跳转

c:   push cx         为什么我在MASMPlus里使用c做一个段不行 ?改成其他的就可以比如n

     mov cl,[si]     即便是该了以后 程序能运行但是无法在屏幕上显示想要的效果?

     jcxz f          ;(ds:si)=0转到f执行

d:   inc si

     pop cx

     loop c

     inc si

     mov al,0

     mov [si],al   ;设置结尾符0

     mov si,0      ;设置ds:si指向需显示字符首地址

     pop dx

     mov cl,2

     call show_str

     add bx,4      ;dword数据指向下一数据单元

     add di,2      ;word数据指向下一数据单元

     add dh,1      ;指向显存下一行

     pop cx

     loop g

     mov ax,4c00h

     int 21h

f:   mov al,20h

     mov [si],al   ;(ds:si)=0的数据改成空格

     jmp d




;名称:dtoc2

;功能:将dword型数据转变为表示十进制的字符串,字符串以0为结尾符。

;参数:(ax)=dword型数据的低16位;

;      (dx)=dword型数据的高16位;

;      ds:si指向字符串首地址。

;返回:无。

dtoc2:

     push ax

     push bx

     push cx

     push dx

     push si

     push di

     mov di,0

d20: mov cx,10     ;除数为10

     call divdw

     add cx,30h    ;余数+30h,转为字符

     push cx       ;字符入栈

     inc di        ;记录字符个数

     mov cx,ax


     jcxz d21      ;低位商=0时,转到d21检测高位商

     jmp d20

d21: mov cx,dx

     jcxz d22      ;高低位商全=0时,转到d22执行

     jmp d20

d22: mov cx,di

d23: pop ax        ;字符出栈

     mov [si],al

     inc si        ;ds:si指向下一单元

     loop d23

     mov al,0

     mov [si],al   ;设置结尾符0

     pop di

     pop si

     pop dx

     pop cx

     pop bx

     pop ax

     ret




;名称:divdw

;功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型。

;参数:(ax)=dword型数据的低16位;

;      (dx)=dword型数据的高16位;

;      (cx)=除数。

;返回:(dx)=结果的高16位;

;      (ax)=结果的低16位;

;      (cx)=余数。

divdw:

     push si

     push bx

     push ax

     mov ax,dx

     mov dx,0

     div cx        ;被除数的高位/cx

     mov si,ax

     pop ax


     div cx        ;(被除数高位的商+低位)/cx

     mov cx,dx     ;余数入cx

     mov dx,si     ;高位的商入dx

     pop bx

     pop si

     ret




;名称:show_str

;功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。

;参数:(dh)=行号(取值范围0~24);

;      (dl)=列号(取值范围0~79);

;      (cl)=颜色;

;      ds:si指向字符串的首地址。

;返回:无。

show_str:

     push ax

     push bx

     push es

     push si

     mov ax,0b800h

     mov es,ax

     mov ax,160

     mul dh

     mov bx,ax     ;bx=160*dh

     mov ax,2

     mul dl        ;ax=dl*2

     add bx,ax     ;mov bx,(160*dh+dl*2)设置es:bx指向显存首地址

     mov al,cl     ;把颜色cl赋值al

     mov cl,0

show0:

     mov ch,[si]

     jcxz show1    ;(ds:si)=0时,转到show1执行   


     mov es:[bx],ch

     mov es:[bx].1,al

     inc si        ;ds:si指向下一个字符地址

     add bx,2      ;es:bx指向下一个显存地址

     jmp show0

show1:

     pop si

     pop es

     pop bx

     pop ax

     ret




code ends

end start

#554 入门必备 » 汇编学习路 » 2022-08-18 21:50:47

batsom
回复: 0

汇编学习路
    我是为学C来学汇编预热下的,教材是王爽的那本,之所以把学习笔记写这里,一方面给自己留点纪念,更重要的是希望有老鸟来看看来指正一下,因为是自学,没老师没同学,所以错了可能自己也不知道。
    第一章1.1——1.10
计算机有3中语言,机器语言、汇编语言和N多的高级语言。
机器语言就是一条条机器指令的集合。
汇编语言的主体是汇编指令。
汇编指令和机器指令本质是一样的,差别在于指令的表示方法上,一个茴字有几种下法的问题,本质都是茴这个字。
寄存器简单讲是CPU中可以存储数据的器件。寄存器和CPU的关系可以近似的看做是内存和PC机的关系。
一个CPU有多个寄存器,AX、BX只是其中2个不同寄存器的代号。
计算机可以识别的只有二进制和逻辑运算符,所以一个汇编指令要执行必须由编译器将汇编指令翻译成机器指令发送给计算机。
汇编语言由3类指令构成:汇编指令(机器指令的另一种写法)
                       伪指令和其他符号(这2中由编译器翻译成逻辑运算符交给计算机)
CPU要工作必须有指令和数据,指令和数据在存储器中存放。
CPU无法直接读取磁盘的数据,需先调入内存中,再由内存和CPU进行数据交换。
存储在计算机中的一切数据都是二进制信息,指令或数据只是应用上的概念。
存储器划分成若干个单元,从0开始顺序编号。
一个存储单元存放一个字节。一字节有8个二进制位。
1KB = 1024B    1MB = 1024KB    1GB = 1024MB    1T = 1024GB
CPU要进行数据的读写,必须和外部器件(芯片)进行3类数据的交换:
存储单元的地址(地址信息)。
器件的选择,读或写的命令(控制信息)。
读或写的数据(数据信息)。
换成江湖上的说法,要知道目标是谁,位置在哪里,是打死还是打残。
CPU和外部芯片依靠总线连接并进行数据交换。
总线有3类:地址总线、数据总线和控制总线。
计算机存放的都是二进制信息,依靠3种总线区别数据或指令。从地址总线传输的都是地址信息,从数据总线传输的都是数据信息,从控制总线传输的都是控制命令。
一个CPU有N根地址总线,则可以说这个CPU的地址总线宽度为N,最多可以寻找2的N次方个地址单元。
数据总线决定了CPU的速度,一根地址总线一次可以传送一位二进制数据。16根地址总线则可以一次传送16位二进制数据,即2个字节。
控制总线是一些不同控制线的集合,控制总线的宽度决定了CPU对外部器件的控制能力。
监测点1.1
(1)寻址能力位8KB,即8192b,开平方之后得出结果为13.
(2)1KB有1024个存储单元,编号从0到1023.
(3)1KB的存储器可以存储1024*1024个位,1024个byte。
(4)1KB是1024个byte,1MB是1024*1024byte,1GB是1024*1024*1024byte。结果懒的算了。
(5)8080的地址总线宽度为16,寻址能力则为2的16次方字节,8088的地址总线宽度为20,则寻址能力为2的20次方字节。其余类推,换算就不算了,麻烦。
(6)8080的数据总线宽度为8,则一次可以传输8位二进制数据,即1B。8086为16根,则可以传输16位二进制数据,即2B.其余类推。
(7)8086数据总线宽度为16,则一次可以传输16位二进制数据,即2B。从内存中读取1024字节,至少要读512次。
     80386数据总线宽度为32,则一次可以传输32位二进制数据,即4B。从内存中读取1024字节,至少要读256次。
(8)在存储器中,数据和程序以二进制形式存放。
2012年8月17日23:19:12,还早,继续奋斗。

第一章1.11——1.15
CPU可寻到的内存单元就构成这个CPU的内存地址空间。
随机存储器从读写属性分为:随即存储器RAM和只读存储器ROM。
随机存储器可读可写,但断电后存储内容消失。
只读存储器只能读不能写,断电后不会丢失存储内容。
从功能划分:
主随机存储器一般存放供CPU使用的大部分程序和数据,包括装在主板上的RAM(CPU二级缓存之类)和插在扩展插槽上的RAM(内存条)。
装有BIOS的ROM。如主板上ROM的系统BIOS,显卡上ROM的BIOS.
某些接口卡需要对大批量输入输出数据进行暂时的存储,也装有RAM。如显卡的RAM,显存。
像显存中写入任何数据都将显示在显示器上。
CPU将各个存储器看做一个逻辑整体,每个存储器在逻辑整体中占有一段地址。CPU向某段地址进行读写数据,实际上就是对该地址对应的存储器进行数据读写。
内存地址空间的大小受CPU地址总线宽度的限制,而CPU将所有的存储器看做一个逻辑整体,而这个逻辑整体实际上就是CPU的内存地址空间。所以我们所能利用的容量便受它控制,对于一个内存大小只有1MB的CPU来说,即使插上一个1G的RAM也只能用到1MB.
2012年8月18日0:13:54

第二章2.1——2.3
CPU由运算器、控制器、寄存器组成。依靠内部总线连接。
运算器进行信息处理,寄存器进行信息存储,控制器控制各个器件运行。
8086CPU有14个寄存器,每个寄存器都是16位。可以存放2个字节。
AX、BX、CX、DX是4个通用寄存器,存放一般性数据。
N位寄存器所能存储的最大值为2的N次方减1。(好像是这样,但原理不太明白,先跳过)
8088CPU寄存器是8位,为保证兼容,8086CPU的4个通用寄存处可以分成2个独立使用的8位寄存器。如AX可分为AH(高位)和AL(低位)。
1个字节(byte)由8个二进制位组成,可以存放在8位寄存器中。
1个字(word)由2个字节组成,分为高位字节和低位字节,分别存放在16位寄存器的高位寄存器和低位寄存器中。
4E20H 十六位进制    1000 十进制    01110010B 二进制
mov ax, 18    将18送人寄存器ax
mov ah, 78    将78送入寄存器ah
add ax, 8     将寄存器ax中的值加上8再存放到ax
mov ax, bx    将寄存器bx中的值存放到ax
add ax, bx    将寄存器ax中的值和bx的值相加,再存放到ax中
汇编不区分大小写
add ax, bx 等同于 ADD AX, BX
在进行数据传送或运输时,要注意指令的两个操作对象的位数应当是一致的。
检测点2.1
mov ax, 62627    ax = F4A3H  (62627是十进制,转换成十六进制位F4A3H)
mov ah, 31H      ax = 31A3H  (指令的意思是将31H送到AX的高8位寄存器AH中,所以结果为31A3H)
mov al, 23H      ax = 3123H   (同上,将23H送到AX的低8位寄存器AL中,所以结果为3123H)
add ax, ax       ax = 6246H   (将AX的值加上AX的值再存放到AX中)
mov bx, 826CH    bx = 826CH   (将826CH存放到BX中)
mov cx, ax       cx = 6246H   (将AX的值存放到CX中)
mov ax, bx       ax = 826CH   (将BX的值存放到AX中)
add ax, bx       ax = 04D8H    (指令的意思是把BX的值加上AX的存放到AX中,结果为104D8H,由于AX是16位寄存器,只能存放4位16进制数,故而结果为04D8H)
mov al, bh       ax = 0482H    (指令的意思是将BH的值存放到AL中,所以结果为0482H)
mov ah, bl       ax = 6C82H    (指令的意思是将BL的值存放到AH中,所以结果为6C82H)
add ah, ah       ax = D882H    (指令的意思是将AH的值加上AH的值存放到AH中,所以结果为D882H)
add al, 6        ax = D888H    (指令的意思是将AL的值加6存放到AL中,所以结果D888H)
add al, al       ax = D810H    (指令的意思讲AL的值加AL的值存放到AL中,由于AL为8位寄存器,而结果为110H,所以AX=D810H)
mov ax, cx       ax = 6246H    (将CX的值存放到AX中)

第二章2.4——
CPU访问内存单元需要给出内存单元的地址。
对CPU而言,将所有的内存单元看做一个逻辑线性整体。每个内存单元都有一个唯一的物理地址。
CPU只能通过地址总线将物理地址送入寄存器。
8086CPU为16位CPU,具有3方面特性:
运算器一次最多可以处理16位的数据
寄存器的最大宽度为16位(指通用寄存器)
寄存器和运算器之间的通路位16位。
8086CPU有20位地址总线,可以传送20位地址,寻址能力为2的20此方,1MB。
8086CPU为16位结构,在内部一次性处理、传输、暂时存储的地址为16位,寻址能力只有2的16此方,64KB。
为充分利用20位地址总线,8086CPU采用在内部用2个16位地址合成20位地址的方法。
公式   物理地址 = 段地址*16 + 偏移地址  (这里的地址均为16进制)
例:段地址为1230,偏移地址为00C8,按公式计算,段地址1230*16=12300,12300+00C8=123C8,则123C8就是物理地址。
一个N进制的数据乘以N,相当于该数据左移1位。例:一个十进制的数据乘以十,相当于该数据左移一位。
段地址*16 + 偏移地址 = 物理地址的本质就是  基础地址 + 偏移地址 = 物理地址
之所以采用这个办法,是因为8086CPU为16位结构,无法一次性传输处理20位数据。换个不太准确的说法,就好比一辆汽车,他要从A到B,但汽油不够,只能先跑一段路,再加一次油跑到B点。
8086也是如此,无法一次给出20位物理地址,便先给出16位的段地址,将其乘以16之后变成20位的基础地址,然后再给出一个偏移地址,最后相加得到物理地址。
内存并不分段,段的划分来自于CPU,是人类假想出来便于管理内存的一个概念。
在编程时,可以根据需要将若干地址连续的内存单元看做一个段。
如:10000H——100FFH这个内存地址区间,既可以看做是10000H——100FFH一个段,也可以看做是10000H——1007FH,10080H——100FFH这2个段。
10000H——100FFH这个段,起始地址(基础地质)为10000H,段地址为1000H,大小为100H。
大小的计算方法,100FFH - 10000H = FFH,这个结果即是大小,转换成十进制就是255。由于计算机从0开始编号,所以0——255,有256个数。将256转换成16进制,结果就是100H。
用段地址*16来定位段的起始地址(基础地质),所以一个段的起始地址一定是16的倍数。
用偏移地址来定位段中的内存单元,而偏移地址为16位,16位的寻址能力为64KB,所以一个段的长度最大为64KB。
CPU可以用不同的段地址和偏移地址来形成同一个物理地址。
检测点2.2
给定段地址位0001H,仅通过变化偏移地址寻址,CPU的寻址范围是00010H到1000FH。
有一段数据存放在20000H内存单元中,现给定段地址为SA,若想用偏移地址寻到此单元,则SA应满足的条件是:最小为
                                                                                                  最大为2000H
这道题很纳闷,偏移地址的变化范围是0H——FFFFH,最大好理解,段地址最大偏移地址就最小,根据SA*16 + 偏移地址 = 20000H来计算,SA最大应该是2000H,但SA最小偏移地址最大时,SA*16 + FFFFH = 20000H,算到的SA*16=10001H,这个东西要怎么写,高手求解,谷歌了老半天了不得其解啊。
当段地址给定为SA<=1000H,或SA>=2000H时,CPU无论怎么变化偏移地址都无法寻到20000H这个单元。
2012年8月19日5:26:55    晚上脑子不好使,把问题单独发一下,明天早上再去百度看看。好纠结。

物理地址=SA*16+EA 
EA的变化范围为0h~ffffh 
物理地址范围为(SA*16+0h)~(SA*16+ffffh) 
现在SA=0001h,那么寻址范围为 
(0001h*16+0h)~(0001h*16+ffffh) 
=0010h~1000fh 

物理地址=SA*16+EA 
20000h=SA*16+EA 
SA=(20000h-EA)/16=2000h-EA/16 
EA取最大值时,SA=2000h-ffffh/16=1001h,SA为最小值 
EA取最小值时,SA=2000h-0h/16=2000h,SA为最大值
找到答案居然兴奋的睡不着了。。。。。。。。。。。2012年8月19日5:46:53
突然间发现自己刚才走了岔路,题目求的是SA的最大最小值而不是EA的,我在算到1001H的时候居然鬼使神差的去和EA的最大值相加,1001H*16+FFFFH=.........
2012年8月19日5:55:32。。。。。。。。。。。。。。明天熬夜要带咖啡,不然脑子不清晰

#556 入门必备 » 入门的阶乘算法 » 2022-08-18 21:40:27

batsom
回复: 0

int b,d;
    scanf("%d",&b);
    if(b>=1 && b<=9)
        {
             d=b;
        }
     while(d>=1)
        {
            b=b*d;
            d=d-1;
        }
    printf("%d",b);
    system("pause");
    return 0;

#557 入门必备 » C++ 实例分享 《猴子吃桃》 » 2022-08-18 21:39:28

batsom
回复: 0

猴子吃桃

一只小猴子一天摘了许多桃子,第一天吃了一半,然后忍不住又吃了一个;
第二天又吃了一半,再加上一个;后面每天都是这样吃。到第10天的时候,
小猴子发现只有一个桃子了。问小猴子第一天共摘了多少个桃子。

#include <iostream>

using namespace std;


int main()
{
    int day_peac[11]={0};

    for(int day=10; day>=1; day--)
        day_peac[day] = day>9 ? 1 : (day_peac[day+1] + 1) * 2;
    cout << endl<< "    第一天共摘的桃子数量为 "<<day_peac[1] <<"个." << endl;
    cout  << endl<< "    这是什么猴子呀??? 猪一天也吃不了 "<<day_peac[2]+2 <<"个桃子呀!!!"<< endl;
    return 0;
}


设第i天剩余 x 颗,则第i天的前一天剩余 2*x+2 颗。
即 f(1)=x; f(i+1)=2*f(i)+2
故有 f(i) = x*2^(i-1) + 2^i-2


#include <stdio.h>

int main( void )
{
    // 已知:第i天剩余x颗
    const unsigned i = 10;
    const unsigned x = 1;

    // 则第一天采摘量: x*2^(i-1) + 2^i-2
    printf( "%u\n", x*(1u<<(i-1))+(1u<<i)-2 );
}

#558 入门必备 » 简易卡尔曼滤波(仅供学习) » 2022-08-18 21:37:39

batsom
回复: 0

#include <stdio.h>


float Q_angle=0.001;//过程噪声的协方差
float Q_gyro=0.003;//0.003 过程噪声的协方差 过程噪声的协方差为一个一行两列矩阵
float R_angle=0.5;// 测量噪声的协方差 既测量偏差
float dt=0.005;//注意:dt的取值为kalman滤波器采样时间;
float P[2][2] = {{ 1, 0 },{ 0, 1 }};
float Angle,Angle_dot;
float Pdot[4] ={0,0,0,0};
const char C_0 = 1;
float Q_bias, Angle_err, PCt_0, PCt_1, E, K_0, K_1, t_0, t_1;

/**************************************************************************
函数功能:简易卡尔曼滤波
入口参数:加速度、角速度
返回  值:无
**************************************************************************/
void Kalman_Filter(float Angle_m,float Gyro_m) //gyro_m:gyro_measure
{
    Angle+=(Gyro_m-Q_bias) * dt;//先验估计误差协方差的微分
    Pdot[0]=Q_angle - P[0][1] - P[1][0];// Pk-先验估计误差协方差的微分
    Pdot[1]=- P[1][1];
    Pdot[2]=- P[1][1];
    Pdot[3]=Q_gyro;
   
    P[0][0] += Pdot[0] * dt;// Pk- 先验估计误差协方差微分的积分
    P[0][1] += Pdot[1] * dt;// = 先验估计误差协方差
    P[1][0] += Pdot[2] * dt;
    P[1][1] += Pdot[3] * dt;
   
    Angle_err = Angle_m - Angle;//zk-先验估计
   
    PCt_0 = C_0 * P[0][0];
    PCt_1 = C_0 * P[1][0];
   
    E = R_angle + C_0 * PCt_0;
   
    K_0 = PCt_0 / E;//Kk
    K_1 = PCt_1 / E;
   
    t_0 = PCt_0;
    t_1 = C_0 * P[0][1];
   
    P[0][0] -= K_0 * t_0;//后验估计误差协方差
    P[0][1] -= K_0 * t_1;
    P[1][0] -= K_1 * t_0;
    P[1][1] -= K_1 * t_1;
   
    Angle += K_0 * Angle_err;//后验估计
    Q_bias += K_1 * Angle_err;//后验估计
    Angle_dot = Gyro_m-Q_bias;//输出值(后验估计)的微分 = 角速度
}

#560 应用程序与桌面环境 » GNOME 简介、使用、技巧、快捷键 » 2022-08-16 17:49:53

batsom
回复: 0

一.GNOME简介

1.什么是GNOME

GNOME(The GNU Network Object Model Evironment)是基于GPL的完全开放的软件,可以使用用户很容易地使用和配置计算机。GNOME是一个友好的环境桌面,它的图形驱动环境是十分强大的,对用户来说有很优势,它几乎可以不用任何字符界面来使用和配置机器。

也是一种支持多种平台的开发&桌面环境,可以运行在包括GNU/Linux(通常叫做Linux),Solaris,HP-UX,BSD 和 Apple's Darwin系统上。 GNOME 拥有很多强大的特性,如:高质量的平滑文本渲染,首个国际化和可用性支持,并且包括对反向文本的支持(注: 有些国家的文字是从右到左的排版的)。

2.GNOME历史

GNOME计划是1997年8月由Miguel de Icaza和Federico Mena发起,作为KDE的替代品。

使用孟加拉国语的GNOMEKDE是一个基于Qt部件工具箱自由的桌面环境,而QT是由Trolltech开发,当时并未使用自由软件许可。GNU项目的成员关注于使用象这样的一种工具箱构造自由的软件桌面和应用软件,从而发起两个项目:一个是作为纯粹Qt库替代品的“Harmony”;还有就是目的在于使用完全与Qt无关的自由软件构造桌面系统的GNOME项目。

在GNOME变得实用和普及之后,2000年9月Trolltech在GNU GPL和QPL(去掉了大多数争论多年的内容)双重许可证下发布了GNU/Linux版的QT库。但是Qt的许可证还是在许多人中间有争议,因为GPL用于库时对与之链接的代码-例如的KDE框架和任何为其编写的程序-都施加了许可证限制。

GIMP Toolkit(GTK+)被选中做为Qt toolkit的替代,担当GNOME桌面的基础。GTK+使用GNU宽通用公共许可证(LGPL,一个自由软件许可证),允许链接到它的软件——例如GNOME的应用程序——使用任意的许可证。GNOME桌面的库使用LGPL,而GNOME计划内的应用程序使用GPL许可证。

GNOME桌面系统使用C语言编程,但也存在一些其它语言的绑定使得能够使用其它语言编写GNOME应用程序,例如C++, Java, Ruby, C#, Python, Perl 等等。

3.架构

GNOME桌面由许多不同的项目构成,部分最重要的如下所示:
ATK——可达性工具包
Bonobo——复合文档技术
GObject——用于C语言的面向对象框架
GConf——保存应用软件设置
GNOME VFS——虚拟文件系统
GNOME Keyring——安全系统
GNOME Print——GNOME软件打印文档
GStreamer——GNOME软件的多媒体框架
GTK+——构件工具包
Cairo——复杂的2D图形库
Human Interface Guidelines——Sun微系统公司提供的使得GNOME应用软件易于使用的研究和文档
LibXML——为GNOME设计的XML库
ORBit——使软件组件化的CORBAORB
Pango——i18n文本排列和变换库
Metacity——窗口管理器

4.发布版本

组成GNOME计划的每一部分(见架构)都有自己的版本号和发布规划,通过各模块的维护者之间的定期协调(6个月),创建一个完整的GNOME发布版本。下边的发布版本列表分类属于稳定版。提供给测试和开发者的不稳定版本并未列入。

版本
   

日期
   

说明


   

1997年8月
   

发起GNOME开发

1.0
   

1999年3月
   

发布第一个主要GNOME版本

1.0.53
   

1999年10月
   

"October"

1.2
   

2000年5月
   

"Bongo"

1.4
   

2001年4月
   

"Tranquility"

2.0
   

2002年6月
   

基于GTK2的重要升级,并引入了人性化界面指导

2.2
   

2003年2月
   

多媒体和文件管理器的改进

2.4
   

2003年9月
   

Epiphany,可达性支持

2.6
   

2004年3月
   

转向spatial文件管理器,新文件对话框

2.8
   

2004年9月
   

改进可移动设备的支持,增加Evolution

2.10
   

2005年3月
   

一般性优化,新applets(设备挂载和卸载),增加Totem和Sound Juicer

2.12
   

2005年9月
   

改进了Nautilus;改进了在程序之间的剪切/粘贴功能以及加强了对freedeskop.org 的集成。增加了:Evince PDF 文件阅读器;开始菜单编辑器;keyring manager与管理工具。建基于支持Cairo的GTK 2.8

2.14
   

2006年3月
   

性能提升。增加:Ekiga 原先叫做GNOME Meeting,GNOME 的语音和 IP 视频客户端;任务栏搜索工具;Pessulus 锁定管理器(允许管理员方便地禁用 GNOME 桌面的特定特性);快速的用户切换;Sabayon 配置编辑器(允许管理员在实时和互动的 GNOME 会话中设置用户配置文件).

2.16
   

2006年9月
   

性能提升。新增:Tomboy 智能的wiki笔记本程序;Baobab 磁盘存储份额管理;屏幕保护的全屏预览支持;Totem 有一些轻微地改进;改进了 Nautilus;集成了支持笔记本的电源管理功能;Metacity添加compositing支持;新的图标主题。基于GTK+2.10库,新的打印对话框和它的API.

2.22
   

2008年3月
   

Epiphany浏览器换用WebKit引擎;Evince文档浏览器内部转换效应;Cheese可用网络摄像头拍摄照片和记录视频,并可应用特效。多媒体方面,Totem能搜索和播放YouTube视频,连接到MythTV服务,观看录像或实时TV。Rhythmbox能利用FM无线电收听器,集成进新的歌词网站,改进了Podcast feed支持。提供了独立的Flash播放器,支持从文件浏览器中预览flash[1]。

2.24
   

2008年9月
   

Better GNOME panel theming, integration of bookmarks and browsing history for GNOME-wide access, and new applet library for GNOME panel,[2] 新版Ekiga崭新的用户界面,SIP支持, Empathy即时通讯客户端采用Telepathy通讯框架,Nautilus可以选择纵行和列表视图, GnomeVFS到GIO的移植部分完成。[3]

2.26
   

2009年3月
   

新刻录工具Brasero,简单文件共享,媒体播放器改进,提供多显示器和指纹识别支持。

2.28
   

2009年9月
   

加入GNOME蓝牙模块;改进时间跟踪器;改进即时聊天工具Empathy;改进Epiphany Web 浏览器;媒体播放器增加DVD回放功能;网络摄像头工具Cheese;Evince文档查看器新增批注功能;声音控制程序增加淡入淡出控制支持;Orca 屏幕阅读器;WebKit 辅助功能支持等。

2.30
   

2010年3月
   

Nautilus 包含新的分割视图模式,现在默认预设为浏览器模式;Empathy 获得了许多新的功能;Tomboy 现在可以在后台自动同步你的笔记,改善了启动速度,强化了复制和粘贴功能;GNOME 系统工具现在能配合 PolicyKit 使用解锁按钮来验证;Time Tracker applet 加入了全新的独立运行模式;Evince 增强了打印支持,可在 PDF 文档附件中加上注释; Epiphany 现在可使用 gnome-keyring 来记住密码;Vinagre 加入了 SSH 隧道支持;其他细微改进及调整:新的字体可在 Nautilus 中通过双击完成安装、选择多种键盘配置时状态图示会自动出现在通知区里、Brasero 现在会使用 cdda2wav 来复制音乐 CD 等。

2.32
   

2010年9月
   

改善联系系统,对 GNOME 的即时消息和通讯应用进行了增强;改进对 PDF 支持;对 Nautilus 进行了更新与修复,很大的提高了工作效率;改善 Gnome 图形支持;增强对 Gnome 辅助工具的支持;新增 Mousetweaks 鼠标辅助系统;更新 Glib 2.26,Anjuta 等开发组件;更新翻译,Gnome 现已支持50多种语言。


   


   


5.官方网站

官方网站:
学习如何使用GNOME:
GNOME入门:
GNOME维基:

6.gnome的单词义

英语单词词义:gnome[nəum] n. [C]地精,土地神(传说居于地下守护财宝的年老侏儒)。


二.GNOME的使用

首次启动GNOME 桌面会话时,您会看见一个默认的启动屏幕,其中带有面板、窗口和各种图标。Linux系统初始化默认桌面是如下图所示的GNOME环境。


GNOME 桌面的主要组件如下:

# 面板
面板就是GNOME 桌面上的区域,通过这些区域您可以访问所有的系统应用程序和菜单。面板可自由配置。

# 菜单
可以通过菜单访问所有GNOME 桌面功能。您可以使用“应用程序”菜单访问几乎所有标准功能、命令和配置选项。您可以通过“主菜单”及“菜单栏”小程序访问“应用程序”菜单。您可以向面板中添加“主菜单”和“菜单栏”小程序。

“菜单栏”小程序包含一个“操作”菜单。“操作”菜单中包含用于执行各种功能的命令(如“查找文件”和“注销”)。“操作”菜单中的菜单项位于“主菜单”的顶层。

# 窗口
可以同时显示多个窗口。在每个窗口中都可以运行不同的应用程序。窗口管理器为窗口提供框架和按钮。窗口管理器使您可以执行诸如移动、关闭和改变窗口大小这些标准操作。

# 工作区
您可以将GNOME 桌面分为几个独立的工作区。工作区是指您在其中工作的离散区域。您可以指定GNOME 桌面上的工作区数量。您可以切换到不同的工作区,但是您每次只能显示一个工作区。

# Nautilus 文件管理器
Nautilus 文件管理器提供了一个集成的访问点,可以访问文件和应用程序。您可以在文件管理器窗口内显示文件内容,或者从文件管理器中用相应的应用程序打开文件。您可以使用文件管理器管理文件和文件夹。

# 桌面
桌面位于桌面上所有其它组件的后面。桌面是用户界面的活动组件。将对象放在桌面上可以快速访问文件和目录,或启动常用的应用程序。也可以在桌面上右击打开一个菜单。

# 首选项
GNOME 桌面包含专用的首选项工具。每一个工具控制GNOME 桌面行为的一个特定部分。要启动首选项工具,请从“主菜单”中选择“首选项”。从子菜单中选择要配置的项目。

1.GNOME桌面

默认的GNOME桌面上共有3个图标,如下图所示,分别是:用户主目录的文件夹图标,用于存放登录用户的资料和文件;计算机图标,相当于Windows中“我的电脑”图标,可以存放Linux系统下所有的文件和资料;还有回收站图标,用于放置暂时删除的文件。

当然用户也可以把文件、程序和菜单拖到桌面上,创建新的图标。

2.GNOME面板

面板图标有快捷工具按钮、通知警告/通知区域、显示当前用户名、显示日期和时间、控制音量、显示桌面、任务栏、工作区切换器。

(1)快捷工具按钮
通过单击快捷工具按钮可以快速启动程序,如下图所示。

(2)通知警告/通知区域

(3)显示用户名
当前登录用户名或其他用户名,如下图所示。

(4)显示日期和时间
显示时间,如下图所示。

(5)控制音量
调整音量,如下图所示。



(6)任务栏
任务栏是显示任意虚拟桌面上运行的应用程序,如下图所示。它在最小化应用程序的时候很有用,因为该程序会从桌面消失,一旦它消失了,可以单击其在任务条上的名称使其重新回到桌面上。

此外。右键单击面板还会发现其他一些操作,如新建面板、删除面板、向面板上添加新的按钮与程序启动器、更改面板属性等,如下图所示。

3.菜单系统

菜单系统有应用程序、位置、系统。

(1)应用程序
用户可以通过单击菜单上的应用程序来运行程序,还可以来添加/删除软件,如下图所示。

(2)位置
主要用于查找文件或文件夹,如下图所示。

(3)系统
可用来进行系统管理、注销和关机等任务,如下图所示。

4.工作区

您可以同时在GNOME桌面上显示多个窗口。您的窗口显示在GNOME桌面的分区中,这些分区被称为工作区。工作区是指您在其中工作的离散区域。

每个工作区都包含相同的桌面、面板和菜单。但是,您可以在每个工作区中运行不同的应用程序和打开不同的窗口。在GNOME桌面上一次只能显示一个工作区,但是您可以在其它工作区中打开窗口。

当同时运行许多应用程序时,您可以使用工作区来组织GNOME 桌面。如果当前工作区内挤满了窗口,您可以将工作转移到另一个工作区。您也可以切换到另一个工作区,然后启动更多的应用程序。

共分成4工作区,即4个虚拟桌面,每个虚拟桌面有自己的程序窗口。用户可以通过单击工作切换器或使用快捷键[Ctrl+Alt+→]和[Ctrl+Alt+←]进行切换,如下图所示。

5.桌面首选项

使用首选项工具可以配置几乎所有的GNOME 桌面功能。每一个工具控制GNOME桌面行为的一个特定部分。GNOME默认的是显示GNOME桌面。

您可以通过以下任何一种方式启动首选项工具:
(1)从“主菜单”中选择“首选项”,然后选择所需项。
(2)在桌面上双击“我的电脑”对象。在“我的电脑”位置将打开一个Nautilus 窗口。双击“首选项”对象,以显示首选项工具。双击所需的项目。

6.Nautilus文件管理器

Nautilus文件管理器提供了一个集成的访问点,可以访问文件、应用程序、FTP 站点和URI。

三.GNOME的技巧

1.终端

终端窗口是从Linux窗口管理器中打开的一个特殊窗口,其外观和操作系统方式都与简单的字符操作终端相似,它为用户提供一个标准的命令行接口,用户可在窗口显示的提示符中输入带有选项和参数的命令。

打开终端窗口的方法:

(1)单击桌面的控制面板上的“应用程序 → 系统工具 → 终端”,如下图所示。


(2)使用快捷键“Alt+F2”,在弹出的“运行应用程序”对话框中输入“gnome-terminal”,然后单击“运行”按钮,即可打开终端。

2.搜索文件

在使用Linux系统过程中,用户有时需要使用系统中的某一个文件,但这个文件可能并不容易找到,这时就需要使用系统中的搜索文件功能。

搜索文件方法如下:

(1)单击“位置 → 搜索文件”,桌面上将会弹出“搜索文件”对话框,如下图所示。

“搜索文件”对话框主要分为搜索区与搜索结果显示区两部分。当用户搜索文件时先在“名称包含”文本框中输入想要搜索的文件名或扩展名等。

(2)在“搜索文件夹”下拉列表框中选择搜索范围,如下图所示。

如搜索范围较详细,可单击“搜索文件夹”下拉列表框中的“其它”选项,会弹出“浏览”对话框,如下图所示。在“浏览”对话框中可以打开更深层的目录,选好要搜索的具体范围后单击“打开”按钮,系统会回到“搜索文件”对话框,这样便缩小了搜索范围,提高搜索效率。

(3)有时用户可能对要寻找的文件的名称记忆并不准确,但对于该文件的一些其他特征如文件大小、修改时间和文件所属用户等记得比较清楚,此时可以通过单击“选择更多选项”和“添加”按钮来对这些特征加以约束,“删除”按钮用于删除不需要的约束条件,如下图所示。

经过上述操作,只需单击“查找”按钮即可对所需要的文件进行搜索。

3.虚拟桌面设置(工作区)

虚拟桌面是一般有4个。系统默认进第一个虚拟桌面,如需进入第二个虚拟桌面,需用鼠标单击第二个虚拟桌面。这样就会看到第一个虚拟桌面的程序不再显示,再单击第一个虚拟桌面就又可以看到在第一个虚拟桌面运行的程序。

虚拟桌面的个数也是可以改变,方法如下:

右键单击虚拟桌面会出现设置选项,单击“首选项”会出现配置界面,如下图所示。

可以设置虚拟桌面的数量,也可以设置虚拟桌面是位于一行内还是两行。

4.面板上添加小程序

小型GNOME应用程序被称为小程序(applets),它们可以直接在GNOME面板中运行。小程序并添加到面板可以通过下列步骤实现。

(1)右键单击面板空白位置,弹出面板菜单。

(2)选择“添加到面板”,出现一个“添加到面板”窗口。

(3)窗口中列出一些小程序,包括:Geys、Modem Lights、时钟、查字典、气象报告,注销,锁住屏幕等。选择一个然后单击“添加”按钮,小程序就会出现在面板上,并且马上可以使用,如下图所示。


添加小程序之后,右键单击小程序图标可以查看它的其他选项。移动小程序的位置可以右键单击图标,选择“移动”,再将鼠标移动到想放置图标的地方,然后单击设定新的位置。

也可以删除小程序,右键单击图标,选择“从面板上删除”。如果面板上没有空余的位置,可以在桌面其他地方新增一个面板。

5.添加面板

GNOME桌面可以存在很多个面板,用户可以在屏幕的两侧增加面板,与顶部和底部的面板一起工作,添加面板的步骤如下:

(1)在面板的空余位置单击鼠标右键。

(2)选择“新建面板”,一个新的面板会出现在屏幕右侧,如下图所示。


(3)右键单击新面板的空白位置,选择“属性”。

(4)从“面板属性”对话框中,选择用户需要面板出现在屏幕的哪个方向(顶部、底部、左部或右部)


新增面板后,用户可以像在默认的面板上那样添加小程序或者是应用程序启动器。如果想删除面板,在面板空白位置单击鼠标右键弹出菜单,选择“删除该面板”。

6.面板添加应用程序

顶部的面板中包含WEB浏览器和几个办公自动化工具,用户可以添加自己的图标来启动应用程序,步骤如下:

(1)在面板的空余位置单击鼠标右键。

(2)选择“添加到面板”,再选择“应用程序启动器”,单击“前进”按钮,然后应用程序中的所有分类就出现在窗口中。

(3)单击想展开的分类前面的小箭头,选择要添加的应用程序,然后单击“添加”按钮。一个代表程序的图标会出现在面板上,如下图所示。


如果需启动的应用程序没有在“应用程序”一栏中,可以照下面的步骤创建一个。

(1)在面板的空余位置单击鼠标右键。

(2)选择“添加到面板”,再选择“自定义应用程序启动器”,单击“添加”按钮,出现“创建启动器”。

(3)添加要添加的应用程序的信息。

(4)单击图标框,选择一个列出的图标,或者浏览文件系统添加其他图标。完成后单击“确定”。


7.在GNOME启动时自动运行程序

这类似Windows中的开机启动程序,我们选择“系统 → 首选项 → 会话”,选择启动程序分页,用添加、 编辑和删除按钮来管理要在登录时启动的程序。

四.GNOME快捷键

1.通用快捷键

Alt + F1 类似Windows下的Win键,在GNOME中打开"应用程序"菜单(Applications)

Alt + F2 类似Windows下的Win + R组合键,在GNOME中运行应用程序

Ctrl + Alt + D 类似Windows下的Win + D组合键,显示桌面

Ctrl + Alt + L 启动屏幕保护程序

Alt + Tab 同Windows下的Alt + Tab组合键,在不同程序窗口间切换

PrintScreen 全屏抓图

Alt + PrintScreen 当前窗口抓图

Ctrl + Alt + → / ← 在不同工作台间切换

Ctrl + Alt + Shift + → / ← 移动当前窗口到不同工作台

2.窗口操作快捷键

Alt + ESC 在不同程序窗口间切换

Alt + Tab 为了兼容Windows下的Alt + Tab组合键习惯,提供的第二个“在不同程序窗口间切换”

Alt + F4 关闭窗口

Alt + F5 取消最大化窗口 (恢复窗口原来的大小)

Alt + F7 移动窗口 (注: 在窗口最大化的状态下无效)

Alt + F8 改变窗口大小 (注: 在窗口最大化的状态下无效)

Alt + F9 最小化窗口

Alt + F10 最大化窗口

Alt + Space 打开窗口的控制菜单 (点击窗口左上角图标出现的菜单)

3.应用程序中的常用快捷键

(注: 并不适用于所有程序)

Ctrl+N 新建窗口

Ctrl+X 剪切

Ctrl+C 复制

Ctrl+V 粘贴

Ctrl+Z 撤销上一步操作

Ctrl+Shift+Z / Ctrl+Y 重做刚撤销的一步操作

Ctrl+S 保存

Ctrl+B 书签

4.文件浏览器(部分)

Ctrl+H 显示隐藏文件(切换键)

Ctrl+T 新建标签

Ctrl+W 关闭标签

Ctrl+Page Up 上一个标签

Ctrl+Page Down 下一个标签

Alt+N 切换到第N个标签(N为数字)

5.鼠标操作

Shift + 中键 拖拽文件图标,移动菜单

Alt + 左键 拖拽移动窗口

Alt + Shift + 左键 拖拽移动窗口并且可以使窗口停留在边缘位置

Alt + 右键 弹出窗口管理器菜单

Alt + 中键 拖拽改变窗口大小

#561 应用程序与桌面环境 » 轻量级linux桌面环境,Linux发行版最为轻量级的桌面环境之一Xfce 桌面 » 2022-08-16 17:40:04

batsom
回复: 0

开源多样性应该是 Linux 最好的特性之一,用户可以不断尝试各种自己喜欢的新鲜玩法与花样,并从中选择最适合自己的应用。无论你是 Linux 新人还是老鸟,层出不穷的应用和桌面环境可能都会让我们应接不暇,特别是尝试不同的 Linux 桌面环境,可以说是一件非常有趣但也耗时的事情。

出于几个原因(包括好奇心),几周前我开始使用Xfce作为我的Linux桌面。原因之一是后台守护进程占用了我非常强大的主工作站上所有的CPU和I/O带宽。当然,有些不稳定可能是因为我删除了一些提供后台守护进程的RPM包。然而,即使在我移除RPM之前,事实是KDE是不稳定的,并且会导致性能和稳定性问题。我需要使用不同的桌面来避免这些问题。

作为我研究的一部分,我搜索了一下Xfce的含义。有对XForms公共环境的历史引用,但是Xfce不再使用XForms工具。几年前,我发现了对“Xtra精细计算环境”的引用,我非常喜欢它。我将使用它(尽管无法再次找到页面引用)。


我非常喜欢Xfce,并且比我想的更喜欢它的速度和轻盈。

推荐Xfce的八个原因

1.轻型建筑

与其他一些台式机(如KDE和GNOME)相比,Xfce占用的内存和CPU使用量非常小。在我的系统中,组成Xfce桌面的程序为如此强大的桌面占用了很小的内存。非常低的CPU使用率也是Xfce桌面的一个标志。由于内存占用这么少,Xfce也非常节省CPU周期,这一点我并不感到特别惊讶。

2.简约

Xfce桌面很简单,而且没有乱七八糟的东西。基本桌面有两个面板,左边有一条垂直的图标线。面板0位于底部,由一些基本的应用程序启动程序以及应用程序图标组成,该图标提供对系统上所有应用程序的访问。面板1位于顶部,具有一个应用程序启动程序以及一个允许用户在多个工作区之间切换的工作区开关程序。这些面板可以通过附加的项目来修改,比如新的发射器,或者通过改变它们的高度和宽度。

桌面左侧的图标由Home目录和回收站图标组成。它还可以显示完整的文件系统目录树和任何连接的可插拔USB存储设备的图标。这些图标可用于挂载和卸载设备,以及打开默认文件管理器。如果您愿意,也可以隐藏它们,并且文件系统、垃圾和主页目录图标分别是可控的。可移动驱动器可以隐藏或显示为一个组。

3.档案管理

Thunar是Xfce的默认文件管理器,它简单、易于使用和配置,而且非常容易学习。虽然不像Konqueror或Dolphin这样的文件管理器那么花哨,但它是相当有能力和非常快的。Thunar不能在其窗口中创建多个窗格,但它确实提供了选项卡,因此可以同时打开多个目录。Thunar还有一个非常好的侧边栏,它和桌面一样,为完整的文件系统目录树和任何连接的USB存储设备显示相同的图标。可以安装和卸载设备,并且可以弹出诸如CD这样的可移动介质。Thunar还可以使用助手应用程序(如Ark)在单击存档文件时打开它们。可以查看档案,如ZIP、TAR和RPM文件,并可从中复制单个文件。使用了许多不同的应用程序文件管理系列我必须说,我喜欢Thunar,因为它简单易用。使用侧栏很容易导航文件系统。

4.稳定度

Xfce桌面非常稳定。新版本似乎是在一个三年的周期,虽然更新是必要的。目前的版本为4.12,于2015年2月发布。Xfce桌面的坚固特性在KDE出现问题后非常令人放心。Xfce桌面从未为我崩溃过,也从未产生过占用系统资源的守护进程。它就在那里工作-这就是我想要的。

5.典雅

Xfce非常优雅。在我的新书里,系统管理员的Linux哲学将于今年秋季推出,我将讨论简单性的许多优点,包括简单性是优雅的标志之一这一事实。显然,编写和维护Xfce及其组件应用程序的程序员都非常喜欢简单。这种简单性很可能是Xfce如此稳定的原因,但它也带来了干净的外观、响应性的界面、易于导航的结构,感觉很自然,以及总体的优雅,使它成为一种使用的乐趣。

6.终端仿真

Xfce4终端模拟器是一个功能强大的模拟器,它使用制表符在一个窗口中允许多个终端,就像许多其他终端模拟器一样。与像Tilix、Terminator和Konsole这样的仿真器相比,这个终端模拟器很简单,但是它完成了工作。可以更改选项卡名,可以通过拖放、使用工具栏上的箭头图标或在菜单栏上选择选项来重新排列选项卡。关于Xfce终端模拟器上的选项卡,我特别喜欢的一点是,它们显示它们连接到的主机的名称,而不管连接到的主机有多少个,例如,host1==>host2==>host3==>host4正确显示host4在账单上。其他模拟器显示host2充其量。

它的其他方面的功能和外观可以很容易地配置,以满足您的需要。与其他Xfce组件一样,这个终端模拟器在系统资源方面的使用非常少。

7.可配置性

在其范围内,Xfce是非常可配置的。虽然没有像KDE这样的桌面提供更多的可配置性,但它比GNOME要可配置的多(而且更容易)。我发现Settings Manager是配置Xfce所需的所有东西的入口。单独的配置应用程序是单独可用的,但是Settings Manager会将它们全部收集到一个窗口中,以便于访问。桌面的所有重要方面都可以配置成满足我的需求和偏好。

8.模块化

Xfce有许多单独的项目组成了整个系统,并不是Xfce的所有部分都是由您的发行版安装的。Xfce的项目页面列出主要项目,以便您可以找到可能要安装的其他部件。当我安装Xfce组时,我的Fedora 28工作站上没有安装的项目主要是页面底部的应用程序。

还有一个文件页,还有一个名为wiki的Xfce Goodie项目列出其他与Xfce相关的项目,这些项目为Thunar和Xfce面板提供应用程序、艺术品和插件。

结论

Xfce桌面又薄又快,具有整体的优雅,因此很容易搞清楚如何做事情。它的轻量级结构节省了内存和CPU周期。这使得它理想的老主机,很少的资源,以节省一个桌面。但是,Xfce具有足够的灵活性和强大功能,可以满足我作为超级用户的需求。

我已经了解到,改变到一个新的Linux桌面可能需要一些工作来配置它,我想要的-所有我最喜欢的应用程序启动程序在面板上,我喜欢的壁纸,等等。这些年来,我已经换了很多次新的台式机或旧的台式机的更新。这需要时间和耐心。

我想这就像我在工作时搬动了隔间或办公室。有人把我的东西从旧办公室搬到新办公室,我把电脑连接起来,打开箱子,把它们放在新办公室的适当位置。移动到Xfce桌面是我做过的最简单的一步。

总结

其实不同 Linux 桌面环境的开发目的和性质都是有所不同的,不同的版本有不同的侧重点,对于不同用户需求来说,哪款桌面对自己适用才是王道,有了以上的介绍,大家不妨自己进行一下试用

#562 入门必备 » 汇编学习软件推荐 - 汇编金手指 » 2022-08-15 22:28:56

batsom
回复: 0

汇编的学习只要理解底层操作即可,在调试期间很有用,开发还是使用高级语言,所以很多指令不用背,推荐一个查询的软件
这款软件可以直接查询需要的指令,有详细的介绍和使用方法
编写汇编软件:debug软件见之前的文章:

下载地址:

http://oneindex.iegum.com/%E5%B7%A5%E5% … %E5%85%B7/

#563 入门必备 » ASCII编码与二进制 » 2022-08-15 22:24:34

batsom
回复: 0

二进制ASCII码  控制字符 二进制 十进制 十六进制 缩写 解释
   0000 0000 0 00 NUL 空字符(Null) 
  0000 0001 1 01 SOH 标题开始 
  0000 0010 2 02 STX 正文开始
   0000 0011 3 03 ETX 正文结束
    0000 0100 4 04 EOT 传输结束
    0000 0101 5 05 ENQ 请求 
   0000 0110 6 06 ACK 收到通知
    0000 0111 7 07 BEL 响铃
    0000 1000 8 08 BS 退格 
   0000 1001 9 09 HT 水平制表符
    0000 1010 10 0A LF 换行键
    0000 1011 11 0B VT 垂直制表符 
   0000 1100 12 0C FF 换页键 
   0000 1101 13 0D CR 回车键 
   0000 1110 14 0E SO 不用切换
    0000 1111 15 0F SI 启用切换
    0001 0000 16 10 DLE 数据链路转义
    0001 0001 17 11 DC1 设备控制1 
   0001 0010 18 12 DC2 设备控制2   
  0001 0011 19 13 DC3 设备控制3 
   0001 0100 20 14 DC4 设备控制4 
   0001 0101 21 15 NAK 拒绝接收   
  0001 0110 22 16 SYN 同步空闲 
   0001 0111 23 17 ETB 传输块结束 
   0001 1000 24 18 CAN 取消 
   0001 1001 25 19 EM 介质中断 
   0001 1010 26 1A SUB 替补 
   0001 1011 27 1B ESC 溢出 
   0001 1100 28 1C FS 文件分割符 
   0001 1101 29 1D GS 分组符 
   0001 1110 30 1E RS 记录分离符 
   0001 1111 31 1F US 单元分隔符
   0111 1111 127 7F DEL 删除 
可显示字符 二进制 十进制 十六进制 字符
   0010 0000 32 20 空格 
   0010 0001 33 21 ! 
   0010 0010 34 22 " 
   0010 0011 35 23 # 
   0010 0100 36 24 $ 
   0010 0101 37 25 % 
   0010 0110 38 26 & 
   0010 0111 39 27 
   0010 1000 40 28 ( 
   0010 1001 41 29 ) 
   0010 1010 42 2A * 
   0010 1011 43 2B + 
   0010 1100 44 2C , 
   0010 1101 45 2D - 
   0010 1110 46 2E . 
   0010 1111 47 2F / 
   0011 0000 48 30 0 
   0011 0001 49 31 1 
   0011 0010 50 32 2 
   0011 0011 51 33 3 
   0011 0100 52 34 4 
   0011 0101 53 35 5 
   0011 0110 54 36 6 
   0011 0111 55 37 7 
   0011 1000 56 38 8 
   0011 1001 57 39 9 
   0011 1010 58 3A : 
   0011 1011 59 3B ; 
   0011 1100 60 3C < 
   0011 1101 61 3D = 
   0011 1110 62 3E > 
   0011 1111 63 3F ? 
   0100 0000 64 40 @ 
可显示字符 二进制 十进制 十六进制 字符
   0100 0001 65 41 A 
   0100 0010 66 42 B 
   0100 0011 67 43 C 
   0100 0100 68 44 D 
   0100 0101 69 45 E 
   0100 0110 70 46 F 
   0100 0111 71 47 G 
   0100 1000 72 48 H 
   0100 1001 73 49 I 
   0100 1010 74 4A J 
   0100 1011 75 4B K 
   0100 1100 76 4C L 
   0100 1101 77 4D M 
   0100 1110 78 4E N 
   0100 1111 79 4F O 
   0101 0000 80 50 P 
   0101 0001 81 51 Q 
   0101 0010 82 52 R 
   0101 0011 83 53 S 
   0101 0100 84 54 T 
   0101 0101 85 55 U 
   0101 0110 86 56 V 
   0101 0111 87 57 W 
   0101 1000 88 58 X 
   0101 1001 89 59 Y 
   0101 1010 90 5A Z 
   0101 1011 91 5B [ 
   0101 1100 92 5C \ 
   0101 1101 93 5D ] 
   0101 1110 94 5E ^
   0101 1111 95 5F _ 
   0110 0000 96 60 `
可显示字符 二进制 十进制 十六进制 字符
   0110 0001 97 61 a 
   0110 0010 98 62 b 
   0110 0011 99 63 c 
   0110 0100 100 64 d 
   0110 0101 101 65 e 
   0110 0110 102 66 f 
   0110 0111 103 67 g 
   0110 1000 104 68 h 
   0110 1001 105 69 i 
   0110 1010 106 6A j 
   0110 1011 107 6B k 
   0110 1100 108 6C l 
   0110 1101 109 6D m 
   0110 1110 110 6E n 
   0110 1111 111 6F o 
   0111 0000 112 70 p 
   0111 0001 113 71 q 
   0111 0010 114 72 r 
   0111 0011 115 73 s 
   0111 0100 116 74 t
    0111 0101 117 75 u 
   0111 0110 118 76 v 
   0111 0111 119 77 w 
   0111 1000 120 78 x 
   0111 1001 121 79 y 
   0111 1010 122 7A z 
   0111 1011 123 7B { 
   0111 1100 124 7C | 
   0111 1101 125 7D } 
   0111 1110 126 7E ~

#564 内存管理模块 » 操作系统——内存管理 » 2022-08-09 00:14:08

batsom
回复: 0

文章目录

    1 内存管理的概念
        1.1 内存管理的基本原理和要求
        1.2 覆盖与交换
            1.2.1 覆盖
            1.2.2 交换
        1.3 连续分配管理方式
            1.3.1 单一连续分配(无外部碎片,有内部碎片)
            1.3.2 固定分区分配(无外部碎片,有内部碎片)
            1.3.3 动态分区分配(没有内部碎片,但有外部碎片)
        1.4 非连续分配管理方式
            1.4.1 基本分页存储管理方式
            1.4.2 基本分段存储管理方式
            1.4.3 段页式管理方式
    2. 虚拟内存管理
        2.1 虚拟内存的基本概念
            2.1.1 传统存储管理方式的特征
            2.1.2 局部性原理
            2.1.3 虚拟存储器的定义和特性
            2.1.4 虚拟内存技术的实现
        2.2 请求分页管理方式
            2.2.1 页表机制
            2.2.2 缺页中断机构
            2.2.3 地址变换机构
        2.3 页面置换算法
            2.3.1 最佳置换算法(OPT)
            2.3.2 先进先出页面置换算法(FIFO)
            2.3.3 最近最久未使用置换算法(LRU)
            2.3.4 时钟置换算法(CLOCK)
        2.4 页面分配策略
        2.5 抖动
        2.6 工作集

1 内存管理的概念
1.1 内存管理的基本原理和要求

计算机不可能将所有用户进程和系统所需要的全部程序和数据放入主存,因此操作系统必须对内存空间进行合理的划分和有效的动态分配。内存管理的概念就是操作系统对内存的划分和动态分配。

内存管理功能:

    内存空间的分配与回收:由操作系统完成主存储器空间的分配和管理,是程序员摆脱存储分配的麻烦,提高编程效率。
    地址转换:将逻辑地址转换成相应的物理地址。
    内存空间的扩充:利用虚拟存储技术或自动覆盖技术,从逻辑上扩充主存。
    存储保护:保证各道作业在各自的存储空间内运行,互不干扰。

创建进程首先要将程序和数据装入内存。将用户源程序变为可在内存中执行的程序,通常需要以下几个步骤:

    编译:由编译程序将用户源代码编译成若干目标模块(把高级语言翻译成机器语言)
    链接:由链接程序将编译后形成的一组目标模块及所需的库函数连接在一起,形成一个完整的装入模块(由目标模块生成装入模块,链接后形成完整的逻辑地址)
    装入:由装入程序将装入模块装入内存运行,装入后形成物理地址

程序的链接有以下三种方式:

    静态链接:在程序运行之前,先将各目标模块及它们所需的库函数连接成一个完整的可执行文件(装入模块),之后不再拆开。
    装入时动态链接:将各目标模块装入内存时,边装入边链接的链接方式。
    运行时动态链接:在程序执行中需要该目标模块时,才对它进行链接。其优点是便于修改和更新,便于实现对目标模块的共享。

内存的装入模块在装入内存时,有以下三种方式:

FluxBB bbcode 测试

重定位:根据内存的当前情况,将装入模块装入内存的适当位置,装入时对目标程序中的指令和数据的修改过程称为重定位。

    静态重定位:地址的变换通常是在装入时一次完成的。一个作业装入内存时,必须给它分配要求的全部内存空间,若没有足够的内存,则不能装入该作业。此外,作业一旦装入内存,整个运行期间就不能在内存中移动,也不能再申请内存空间。
    动态重定位:需要重定位寄存器的支持。可以将程序分配到不连续的存储区中;在程序运行之前可以只装入它的部分代码即可投入运行,然后在程序运行期间,根据需要动态申请分配内存。

内存分配前,需要保护操作系统不受用户进程的影响,同时保护用户进程不受其他用户进程的影响。内存保护可采取如下两种方法:

    在CPU中设置一对上、下限寄存器,存放用户作业在主存中的上限和下限地址,每当CPU要访问一个地址时,分别和两个寄存器的值相比,判断有无越界。
    采用重定位寄存器(或基址寄存器)和界地址寄存器(又称限长存储器)来实现这种保护。重定位寄存器包含最小的物理地址值,界地址寄存器含逻辑地址的最大值。每个逻辑地址值必须小于界地址寄存器;内存管理机构动态得将逻辑地址与界地址寄存器进行比较,若未发生地址越界,则加上重定位寄存器的值后映射成物理地址,再送交内存单元。

1.2 覆盖与交换

覆盖与交换技术是在多道程序环境下用来扩充内存的两种方法,目的是减少程序占用的主存空间来扩充内存。
覆盖是在同一个程序或进程中的,交换是在不同进程之间的。
1.2.1 覆盖

覆盖技术的基本思想:将程序分为多个段,常用的段常驻内存,不常用的段在需要时调入内存。

特点:打破了必须将一个进程的全部信息装入主存后才能运行的限制,解决了程序大小超过物理内存总和的问题。但对用户不透明,增加了编程的负担
1.2.2 交换

交换技术的基本思想:内存空间紧张时,系统将内存中的某些进程暂时换出外存,把外存中某些已具备运行条件的进程换入内存。

交换的时机选择的策略:

    进程不用或很少再用的就换出
    内存空间不够或者有不够的危险时,启动交换程序换出

1.3 连续分配管理方式

连续分配方式是指为一个用户程序分配一个连续的内存空间。主要包括单一连续分配、固定分区分配和动态分区分配。

    内部碎片:分配给某进程的内存区域中,有些部分没用上
    外部碎片:是指内存中的某些空闲分区由于太小而难以利用

1.3.1 单一连续分配(无外部碎片,有内部碎片)

在单一连续分配方式中,内存被分为系统区和用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关数据;用户区用于存放用户进程相关数据。
内存中只能有一道用户程序,用户程序独占整个用户区空间。

FluxBB bbcode 测试

    优点:实现简单;无外部碎片;可以采用覆盖技术扩充内存;不一定需要采取内存保护 。
    缺点:只能用于单用户、单任务的操作系统中;有内部碎片;存储器利用率极低。

1.3.2 固定分区分配(无外部碎片,有内部碎片)

固定分区分配是最简单的一种多道程序存储管理方式,它将用户内存空间划分为若干固定大小的分区,每个分区只装入一道作业,当有空闲分区时,便可再从外存的后备队列中选择适当大小的作业装入该分区。

固定分区分配分为分区大小相等和分区大小不等两种方式。

FluxBB bbcode 测试

    分区大小相等:适用于利用一台计算机去控制多个相同对象的场合,缺乏灵活性。程序太小则浪费内存,程序太大的分区装不下。
    分区大小不等:划分多个较小的分区、适量的中等分区和少量的大分区,增加了灵活性。

1.3.3 动态分区分配(没有内部碎片,但有外部碎片)

动态分区分配不预先分配内存,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。系统中分区的大小和数目是可变的。

动态分区分配会产生外部碎片,克服外部碎片可以采用紧凑技术来解决,即操作系统不时地对进程进行移动和整理。但这需要重定位寄存器的支持,且相对费时。

FluxBB bbcode 测试

动态分区分配的策略有以下几种算法:

    首次适应算法:按地址从小到大为序,分配第一个符合条件的分区。
    最佳适应算法:按空间从小到大为序,分配第一个符合条件的分区。
    最坏适应算法:按空间从大到小为序,分配第一个符合条件的分区。
    邻近适应算法:与首次适应相似,从上次查完的结束位置开始查找。

其中,首次适应算法不仅是最简单的,而且通常也是最好和最快的,但是会使得内存的低地址部分出现很多小的空闲分区,每次分配查找时,都要经过这些分区,因此增加了查找的开销。邻近适应算法试图解决这个问题,但实际上,它常常导致在内存的末尾分配空间分裂成小碎片,通常比首次适应算法的效果要差。
最佳适应算法会产生最多的外部碎片。
最坏适应算法会很快导致没有可用的大内存块。

FluxBB bbcode 测试

1.4 非连续分配管理方式

非连续分配允许一个程序分散地装入不相邻的内存分区,但这也需要额外的存储空间去存储它们的索引,使得非连续分配方式的存储密度低于连续存储方式。
非连续分配管理方式根据分区的大小是否固定,分为分页存储管理方式和分段存储管理方式。在分页存储管理方式中,又根据运行作业时是否要把作业的所有页面都装入内存才能运行,分为基本分页存储管理方式和请求分页存储管理方式。
1.4.1 基本分页存储管理方式

不会产生外部碎片,只会产生少量的内部碎片
分页的基本思想:把主存空间划分为大小相等的块,块相对较小,作为主存的基本单位。每个进程也以块为单位进行划分,进程在执行时,以块为单位逐个申请主存中的块空间。

分页存储的基本概念如下:

    页面和页面大小。进程中的块称为页,内存中的块称为页框,外存也以同样的单位进行划分,直接称为块。
    页面大小应该适中,页面太小会使进程的页面数过多,这样页表就会过长,占用大量内存,而且也会增加硬件地址转换的开销,降低页面换入/换出的效率;页面太大又会使页内碎片增多,降低内存利用率。
    地址结构。地址结构包含两部分,前一部分为页号P,后一部分为页内偏移量W。地址结构决定了虚拟内存的寻址空间有多大。
    页号 = 逻辑地址 / 页面长度
    页内偏移量 = 逻辑地址 % 页面长度
    页表。记录进程页面和实际存放的内存块之间的对应关系。页表由页表项构成,一般存放在内存中。页表的作用是实现从页号到物理块号的映射。页表项的作用是找到该页在内存中的位置。
    页表寄存器。存放页表的起始地址和页表长度。

逻辑地址到物理地址的变换过程如下:

    计算页号Р和页内偏移量W,P=A/L,W=A%L
    比较页号P和页表长度M,若P≥M,则产生越界中断,否则继续执行。(注意:页号是从0开始的,而页表长度至少是1,因此 P=M时也会越界)
    查询页表,找到页号对应的页表项,确定页面存放的内存块号
    用内存块号和页内偏移量得到物理地址
    访问目标内存单元

分页管理方式存在的两个主要问题:

    每次访存操作都需要进行逻辑地址到物理地址的转换,地址转换过程必须足够快,否则访存速度会降低。
    每个进程引入页表,用于存储映射机制,页表不能太大,否则内存利用率会降低。

具有快表的地址变换机构:

    时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。(因为程序中存在大量的循环)
    空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。(因为很多数据在内存中都是连续存放的)

若页表全部放在内存中,则存取一个数据或一条指令至少要访问两次内存,第一次是访问页表,确定所存取的数据或指令的物理地址;第二次是根据该地址存取数据或指令。
在地址变换机构中增设一个具有并行查找能力的高速缓冲存储器——快表,又称相联存储器(TLB),用来存放当前访问的若干页表项,以加速地址变换过程。

引入快表后的地址变换过程如下:

    CPU给出逻辑地址,由某个硬件算得页号、页内偏移量,将页号与快表中的所有页号进行比较。
    如果找到匹配的页号,说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表命中,则访问某个逻辑地址仅需一次访存即可。
    如果没有找到匹配的页号,则需要访问内存中的页表,找到对应页表项,得到页面存放的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表未命中,则访问某个逻辑地址需要两次访存(注意:在找到页表项后,应同时将其存入快表,以便后面可能的再次访问。但若快表已满,则必须按照一定的算法对旧的页表项进行替换)

两级页表
单级页表存在的问题:

    所有的页表必须连续存放,页表过大时需要很大的连续空间
    在一段时间并非所有的页面都用得到,因此没必要让整个页表常驻内存

为了压缩页表,采用二级页表机制。在进程执行时,只需要将这一页的上一级页表调入内存即可,进程的页表和进程本身的页面可在后面的执行中再调入内存。

建立多级页表的目的在于建立索引,以便不用浪费主存空间去存储无用的页表项,也不用盲目地顺序式查找页表项。若采用多级页表机制,则各项页表的大小不能超过一个页面。
1.4.2 基本分段存储管理方式

不会产生内部碎片,会产生外部碎片

    分页管理方式是从计算机地角度考虑设计的,目的是提高内存利用率,提升计算机的性能。分页通过硬件机制实现,对用户完全透明。
    分段管理方式的提出则考虑了用户和程序员,以满足方便编程、信息保护和共享、动态增长及动态链接等多方面的需要。

分段存储管理的相关概念如下:

    分段
    段式管理按照用户进程中的自然段划分逻辑空间,段内要求连续,段间不要求连续,整个作业的地址空间是二维的,其逻辑地址由段号S和段内偏移量W两部分组成。

FluxBB bbcode 测试

段号的位数决定了每个进程最多可以分为几个段,段内地址的位数决定了每个段的最大长度是多少。
段表
每个进程都有一张逻辑空间与内存空间映射的段表,每个段表项对于进程的一段,段报项记录该段在内存中的起始和长度,段表用于实现从逻辑段到物理内存区的映射。

FluxBB bbcode 测试

地址变换机构
为了实现进程从逻辑地址到物理地址的变换功能,在系统中设置了段表寄存器,用于存放段表始址F和段表长度M。
地址变换过程如下所示:

FluxBB bbcode 测试

    段的共享与保护
    在分段系统中,段的共享是通过两个作业的段表中相应表项指向被共享的段的同一个物理副本来实现的。不能修改的代码称为可重入代码(不属于临界资源),这样的代码和不能修改的数据可以共享,而可修改的代码和数据不能共享。
    分段管理的保护方法主要有两种,一种是存取控制保护,另一种是地址越界保护。地址越界保护将段表寄存器中的段表长度与逻辑地址中的段号比较,若段号大于段表长度,则产生越界中断;再将段表项中的段长与逻辑地址中的段内偏移进行比较,若段内偏移大于段长,也会产生越界中断。分页管理中的越界保护只需要判断页号是否越界,页内偏移是不可能越界的。

分页与分段的对比

    页是信息的物理单位。分页的主要目的是为了实现离散分配,提高内存利用率。分页仅仅是系统管理上的需要,完全是系统行为,对用户是不可见的。
    段是信息的逻辑单位。分段的主要目的是更好地满足用户需求。一个段通常包含着一组属于一个逻辑模块的信息。分段对用户是可见的,用户编程时需要显式地给出段名。
    页的大小固定且由系统决定。段的长度却不固定,决定于用户编写的程序。
    分页的用户进程地址空间是一维的,程序员只需给出一个记忆符即可表示一个地址。
    分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址。
    分段比分页更容易实现信息的共享和保护。不能被修改的代码称为纯代码或可重入代码(不属于临界资源),这样的代码是可以共享的。可修改的代码是不能共享的
    访问一个逻辑地址的访存次数
    分页(单级页表)︰第一次访存――查内存中的页表,第二次访存――访问目标内存单元。总共两次访存
    分段:第一次访存――查内存中的段表,第二次访存――访问目标内存单元。总共两次访存

1.4.3 段页式管理方式

页式管理方式能有效地提高内存利用率,而分段存储管理能反映程序的逻辑结构并有利于段的共享。将这两种存储管理方式结合起来,便形成了段页式存储管理方式。

在段页式系统中,作业的地址空间首先被分成若干逻辑段,每段有自己的段号,然后将每段分成若干大小固定的页。对内存空间的管理仍然和分页存储管理一样,将其分成若干和页面大小相同的存储块,对内存的分配以存储块为单位。

段页式系统中的作业逻辑地址分为三部分:段号、页号和页内偏移量。

FluxBB bbcode 测试

段号的位数决定了每个进程最多可以分几个段
页号位数决定了每个段最大有多少页
页内偏移量决定了页面大小、内存块大小是多少

其中,在一个进程中,段表只有一个,而页表可能有多个。进行一次访问需要查段表、查页表和访问目标单元三次访存。

段页式系统的地址变换机构如下所示:

FluxBB bbcode 测试

2. 虚拟内存管理
2.1 虚拟内存的基本概念
2.1.1 传统存储管理方式的特征

    一次性:作业必须一次性全部装入内存后,才能开始运行。
    驻留性:作业被装入内存后,就一直驻留在内存中,其任何部分都不会被换出,直至作业结束运行。

2.1.2 局部性原理

高速缓存技术利用的是局部性原理,将频繁使用的数据放到更高速的存储器中。

    时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。(因为程序中存在大量的循环)
    空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。(因为很多数据在内存中都是连续存放的)

2.1.3 虚拟存储器的定义和特性

基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。在操作系统的管理下,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存。

虚拟存储器的最大容量由计算机的地址结构决定,实际容量 = min{内存和外存的容量之和,CPU的寻址范围}。并不是简单的内外存容量相加。

虚拟存储器有以下三个特性:

    多次性:无需在作业运行时一次性全部装入内存,而是允许被分成多次调入内存。
    对换性:在作业运行时无需一直常驻内存,而是允许在作业运行过程中,将作业换入、换出。
    虚拟性:从逻辑上扩充了内存的容量,使用户看到的内存容量,远大于实际的容量。

2.1.4 虚拟内存技术的实现

虚拟内存技术的实现需要建立在离散分配的内存管理方式的基础上。

虚拟内存的实现有以下三种方式:

    请求分页存储管理
    请求分段存储管理
    请求段页式存储管理

FluxBB bbcode 测试

不管哪种实现方式,都需要硬件技术的支持。一般需要的支持有以下几个方面:

    一定容量的内存和外出
    页表机制(或段表机制)作为主要的数据结构
    中断机构:当用户程序要访问的部分尚未调入内存时,则产生中断
    地址变换机构:实现逻辑地址到物理地址的变换

2.2 请求分页管理方式

请求分页产生内部碎片,请求分段产生外部碎片

请求分页系统建立在基本分页系统的基础之上,为了支持虚拟存储器功能而增加了请求调页和页面置换功能。在请求分页系统中,只要求将当前需要的一部分页面装入内存,便可以启动程序运行。
2.2.1 页表机制

请求分页系统的页表机制不同于基本分页系统,请求分页系统在一个作业运行之前不要求全部一次性调入内存,因此在作业运行过程中,必然会出现要访问的页面不在内存中的情况,如何发现和处理这种情况是请求分页系统必须解决的两个基本问题。为此,在请求页表项中增加了4个字段,如下图所示。

FluxBB bbcode 测试


    状态位:用于指示该页是否已调入内存,供程序访问时参考。
    访问字段:用于记录本页在一段时间内被访问的次数,或记录本页最近已有多长时间未被访问,供置换算法换出页面时参考。
    修改位:标识该页在调入内存后是否被修改过。
    外存地址:用于指出该页在外存上的地址,通常是物理块号,供调入该页时参考。

2.2.2 缺页中断机构

在请求分页系统中,每当要访问的页面不在内存中时,便产生一个缺页中断,请求操作系统将所缺的页调入内存。此时应将缺页的进程阻塞(调页完成唤醒),若内存中有空闲块,则分配一个块,将要调入的页装入该块,并修改页表中的相应页表项,若此时内存中没有空闲块,则要淘汰某页,若被淘汰的页在内存期间被修改过,则要将其写回外存。

缺页中断与一般中断的不同:

    在指令执行期间而非一条指令执行完后产生和处理中断信号,属于内部中断。
    一条指令在执行期间,可能产生多次缺页中断。

2.2.3 地址变换机构

在进行地址变换时,先检索快表:

    若找到要访问的页,则修改页表项中的访问位,然后利用页表项中给出的物理块号和页内地址形成物理地址。
    若未找到该页的页表项,则应到内存中去查找页表,再对比页表项中的状态位P,看该页是否已调入内存,未调入内存则产生缺页中断,请求从外存把该页调入内存。

FluxBB bbcode 测试

请求分页中的地址变换过程如下所示:

FluxBB bbcode 测试

2.3 页面置换算法

进程运行时,若其访问的页面不在内存中而需将其调入,但内存已无空闲空间时,就需要从内存中调出一页程序或数据,送入磁盘的对换区。
选择调出页面的算法就称为页面置换算法。用页面置换算法决定应该换出哪个页面。页面的换入换出需要有磁盘的I/O,会有较大的开销,因此好的页面置换算法需要追求更少的缺页率。
2.3.1 最佳置换算法(OPT)

最佳页面置换算法选择的被淘汰页面是以后永不使用的页面,或是在最长时间内不再被访问的页面,以便保证获得最低的缺页率。但是,人们无法预知进程在内存下的若干页面中的哪个是未来最长时间内不再被访问的,因为该算法无法实现。

FluxBB bbcode 测试

2.3.2 先进先出页面置换算法(FIFO)

优先淘汰最早进入内存的页面,即再内存中驻留时间最久的页面。
FIFO算法还会产生所分配的物理块数增大而页故障数不减反增的异常现象,这成为Belady异常。只有FIFO算法可能出现Belady异常。

FluxBB bbcode 测试

Belady异常如下图所示:
FluxBB bbcode 测试

物理块数由三个增加到四个,缺页数反而增加。
2.3.3 最近最久未使用置换算法(LRU)

选择最近最长时间未访问过的页面予以淘汰,它认为过去一段时间内未访问过的页面,在最近的将来可能也不会被访问。该算法为每个页面设置一个访问字段,来记录页面自上次被访问以来所经历的时间,淘汰页面时选择现有页面中值最大的予以淘汰。
LRU算法需要寄存器和栈的硬件支持。

FluxBB bbcode 测试


2.3.4 时钟置换算法(CLOCK)

最佳置换算法性能最好,但无法实现;先进先出置换算法实现简单,但算法性能差;最近最久未使用置换算法性能好,是最接近OPT算法性能的,但是实现起来需要专门的硬件支持,算法开销大。
时钟置换算法是一种性能和开销较均衡的算法,又称CLOCk算法,或最近未用算法(NRU,NotRecently Used)

简单的CLOCK算法实现方法
为每个页面设置一个访问位,再将内存中的页面都通过链接指针链接成一个循环队列。当某页被访问时,其访问位置为1。当需要淘汰一个页面时,只需检查页的访问位。如果是o,就选择该页换出;如果是1,则将它置为0,暂不换出,继续检查下一个页面,若第一轮扫描中所有页面都是1,则将这些页面的访问位依次置为o后,再进行第二轮扫描(第二轮扫描中一定会有访问位为0的页面,因此简单的CLOCK算法选择一个淘汰页面最多会经过两轮扫描)

改进型的时钟算法
简单的时钟置换算法仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过,就不需要执行I/O操作写回外存。只有被淘汰的页面被修改过时,才需要写回外存。
因此,除了考虑一个页面最近有没有被访问过之外,操作系统还应考虑页面有没有被修改过。在其他条件都相同时,应优先淘汰没有修改过的页面,避免I/O操作。这就是改进型的时钟置换算法的思想。修改位=0,表示页面没有被修改过;修改位=1,表示页面被修改过。

为方便讨论,用(访问位,修改位)的形式表示各页面状态。如(1,1)表示一个页面近期被访问过,且被修改过。

算法规则:将所有可能被置换的页面排成一个循环队列
第一轮:从当前位置开始扫描到第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
第二轮:若第一轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。本轮将所有扫描过的帧访问位设为o
第三轮:若第二轮扫描失败,则重新扫描,查找第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
第四轮:若第三轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。
由于第二轮已将所有帧的访问位设为0,因此经过第三轮、第四轮扫描一定会有一个帧被选中,因此改进型cLOcK置换算法选择一个淘汰页面最多会进行四轮扫描.
2.4 页面分配策略

驻留集:指请求分页存储管理中给进程分配的物理块的集合。

    固定分配:操作系统为每个进程分配一组固定数目的物理块,在进程运行期间不再改变。即,驻留集大小不变

    可变分配:先为每个进程分配一定数目的物理块,在进程运行期间,可根据情况做适当的增加或减少。即,驻留集大小可变

    局部置换:发生缺页时只能选进程自己的物理块进行置换。

    全局置换:可以将操作系统保留的空闲物理块分配给缺页进程,也可以将别的进程持有的物理块置换到外存,再分配给缺页进程。

页面分配、置换策略

FluxBB bbcode 测试

调入页面的时机

    预调页策略:将预计在不久之后便会被访问的页面预先调入内存。成功率约为50%,因此这种策略主要用于进程的首次调入。
    请求调页策略:进程在运行中需要访问的页面不在内存而提出请求,由系统将所需页面调入内存。缺点是每次只调入一页,调入、调出页面数多时会花费过多的I/O开销。

从何处调入页面
FluxBB bbcode 测试

2.5 抖动

刚刚换出的页面马上又换入主存,刚刚换入的页面马上又换出主存,这种频繁的页面调度行为称为抖动或颠簸。
抖动发生的主要原因是,进程频繁访问的页面数目高于可用的物理页帧数目,即分配给进程的物理块不够。
2.6 工作集

工作集指在某段时间间隔内,进程要访问的页面集合。基于局部性原理,可以用最近访问过的页面来确定工作集。

若工作集窗口大小为4,则各时刻工作集如下所示:

FluxBB bbcode 测试

为了防止抖动现象,一般来说给进程分配的物理块数(即驻留集大小)要大于工作集大小。

#565 入门必备 » c语言基础知识入门 » 2022-08-08 23:53:17

batsom
回复: 0

c语言入门基础知识点有:c语言的命名;变量及赋值;基本数据类型;格式化输出语句;强制类型转换;不可改变的常量;自动类型转换。C语言是一种通用的、面向过程式的计算机程序设计语言。C程序是先由源文件经编译生成目标文件,然后经过连接生成可执行文件。源程序的扩展名为·c,目标程序的扩展名为·obj,可执行程序的扩展名为·exe。

  c语言的命名:编程时给变量或者函数起的名字就是标识符。C语言的标识符是不可以随便起名字的,必须遵守一定的规则。C语言规定,标识符可以是字母(A~Z,a~z)、数字(0~9)、下划线_组成的字符串,并且第一个字符必须是字母或下划线。

  变量及赋值:变量就是可以变化的量,而每个变量都会有一个名字(标识符)。变量占据内存中一定的存储单元。使用变量之前必须先定义变量,要区分变量名和变量值是两个不同的概念。就好比:住在房间里的客人与房间号是两个不同的概念。

  基本数据类型:C语言中的数据也是有类型的,C语言中,数据类型可分为:基本数据类型,构造数据类型,指针类型,空类型四大类。格式化输出语句,也可以说是占位输出,是将各种类型的数据按照格式化后的类型及指定的位置从计算机上显示。自动类型转换:数据类型存在自动转换的情况,自动转换发生在不同数据类型运算时,在编译的时候自动完成。

#566 内核模块 » 操作系统 宏内核和微内核的区别 » 2022-08-08 23:51:13

batsom
回复: 0

宏内核和微内核的区别
宏内核:简单来说,就是把很多东西都集成进内核,例如linux内核,除了最基本的进程、线程管理、内存管理外,文件系统,驱动,网络协议等等都在内核里面。
优点:效率高。
缺点:稳定性差,开发过程中的bug经常会导致整个系统挂掉。
微内核:内核中只有最基本的调度、内存管理。驱动、文件系统等都是用户态的守护进程去实现的。
优点:超级稳定,驱动等的错误只会导致相应进程死掉,不会导致整个系统都崩溃,做驱动开发时,发现错误,只需要kill掉进程,修正后重启进程就行了,比较方便。
缺点:效率低

内核态:CPU可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,CPU也可以将自己从一个程序切换到另一个程序。
用户态:只能受限的访问内存,且不允许访问外围设备,占用CPU的能力被剥夺,CPU资源可以被其他程序获取。

#567 进程模块 » 进程线程(一)——基础知识,什么是进程?什么是线程? » 2022-08-06 18:54:06

batsom
回复: 0

1. 什么是进程和线程

现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统。
1.1 百科解释

进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。

线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
1.2 简单理解

线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

一个进程由几个线程组成(拥有很多相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其他的线程共享进程所拥有的全部资源。

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。

线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
1.3 总结

    进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。

    线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。是程序执行的最小单位。

2. 具体理解

在Linux环境下,每个进程有自己各自独立的 4G 地址空间,大家互不干扰对方,如果两个进程之间通信的话,还需要借助第三方进程间通信工具 IPC 才能完成。不同的进程通过页表映射,映射到物理内存上各自独立的存储空间,在操作系统的调度下,分别轮流占用CPU去运行,互不干扰、互不影响,甚至相互都不知道对方。在每个进程的眼里,CPU就是他的整个世界,虽然不停地被睡眠,但是一旦恢复运行,一觉醒来,仿佛什么都没发生过一样,认为自己拥有整个CPU,一直在占有它。

在一个进程中,可能存在多个线程,每个线程类似于合租的每个租客,除了自己的私有空间外,还跟其它线程共享进程的很多资源,如地址空间、全局数据、代码段、打开的文件等等。在线程中,通过各种加锁解锁的同步机制,一样可以用来防止多个线程访问共享资源产生冲突,比如互斥锁、条件变量、读写锁等。

进程具有的特征:

    动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
    并发性:任何进程都可以同其他进行一起并发执行;
    独立性:进程是系统进行资源分配和调度的一个独立单位;
    结构性:进程由程序,数据和进程控制块三部分组成

对于操作系统来说,它可以同时运行多个任务。你可以一边听歌,一边打游戏,一边还等着QQ开着语音聊着天,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。对于过去的单核CPU,也可以完成这些任务,由于CPU执行代码都是顺序执行的,那么,单核CPU就轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。

对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。

有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。

由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。
3. 为什么使用多线程?

  ①  和进程相比,它是一种非常“节俭”的多任务操作方式。在Linux系统中,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护其代码段、堆栈段和数据段,这种多任务工作方式的代价非常“昂贵”。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且线程间彼此切换所需要时间也远远小于进程间切换所需要的时间。

   ②  线程间方便的通信机制。对不同进程来说它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行。这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其他线程所用,不仅方便,而且快捷。
4. 进程和线程的区别

第一:什么是进程,什么是线程?

    进程是程序一次执行的过程,动态的,进程切换时系统开销大
    线程是轻量级进程,切换效率高

第二:进程和线程的空间分配?

    进程:每个进程都有独立的0-3G的空间,都参与内核调度,互不影响
    线程:同一进程中的线程共享相同的地址空间(共享0-3G)

第三:进程之间和线程之间各自的通信方式

    进程间:(7种)无名管道、有名管道、信号机制、信号灯、共享内存、消息队列、套接字socket
    线程间:全局变量,信号量,互斥锁

#568 进程模块 » 进程调度 » 2022-08-06 18:49:50

batsom
回复: 0

这里简单介绍下,进程的调度原理,调度类型和常用的进程调度算法。

     说道进程调度,我们或许都有个疑问,为什么需要进程调度呢?进程调度的作用是什么?

     需要进程调度的理由很充分,即充分利用计算机系统中的CPU资源,让计算机能够多快好省的完成各种任务。为此,可在内存中存放数目远大于计算机系统内CPU个数的进程,让这些进程在操作系统的进程调度器下,能够让进程高效(高的吞吐量--throughput)、及时(低延迟--latency)、公平(fairness)地使用CPU。为此调度器可设计不同的调度算法来选择进程,这体现了进程调度的策略,同时还需并进一步通过进程的上下文切换(context switch)来完成进程切换,这体现了进程调度的机制。

    总体上说,我们需要何时调度(调度的时机)、是否能够在内核执行的任意位置进行调度(调度的方式)、如果完成进程切换(上下文切换)、如果选择“合适”的进程执行(调度策略/调度算法)、如果评价选择的合理性(进程调度的指标)。了解上述细节,也就可以说是了解了进程调度。
调度的类型(操作系统的三级调度)

   1. 作业调度--高级调度

        用于决定将外存上处于后备队列中的哪些作业调入内存,处于内存的就绪队列,准备执行。

   2. 进程调度--低级调度

        决定就绪队列中那个进程将获得处理机

   3. 交换调度--中级调度

         目的是提高内存的利用率和系统的吞吐量
调度的时机

    什么时候会发生进程调度呢,引起进程调度的因素有哪些,这些也就是进程的调度时机。

   1. 正在执行的进程执行完毕

   2. 执行中的进程因提出I/O请求或发生等事件而暂停执行。

   3. 时间片完成

   4. 在进程通信或同步过程中执行了某种原语操作,如P操作(wait操作)阻塞

   5. 高优先者进入
调度的方式

     这里按照是否剥夺的方式分为两种调度方式。

    1. 非剥夺方式(非抢占方式)

         一旦占用CPU,直至完成或阻塞

        不利用实时任务,不利用短作业;使用于批处理系统   

    2. 剥夺方式(抢占方式)   

        在一定情况下,可剥夺一进程占有的处理机

        抢占的原则有:短作业(进程)优先原则、时间片原则、优先权原则。
调度算法

    先来先服务调度算法(FCFS)

      处于就绪态的进程按先后顺序链入到就绪队列中,而FCFS调度算法按就绪进程进入就绪队列的先后次序选择当前最先进入就绪队列的进程来执行,直到此进程阻塞或结束,才进行下一次的进程选择调度。

     特点:FCFS调度算法利于长作业,不利于短作业。

    短作业优先调度算法(SJF)

      选择就绪队列中确切(或估计)运行时间最短的进程进入执行。它既可采用可抢占调度方式,也可采用不可抢占调度方式。

      特点:有效降低作业的平均等待时间和提高系统的吞吐量。

    时间片轮转调度算法(RR)

     RR调度算法定义了一个的时间单元,称为时间片(或时间量)。一个时间片通常在1~100 ms之间。当正在运行的进程用完了时间片后,即使此进程还要运行,操作系统也不让它继续运行,而是从就绪队列依次选择下一个处于就绪态的进程执行,而被剥夺CPU使用的进程返回到就绪队列的末尾,等待再次被调度。

     特点:简单易行、平均响应时间短。此算法不利于处理紧急作业

     注:时间片的大小可调整,如果时间片大到让一个进程足以完成其全部工作,这种算法就退化为FCFS调度算法;若时间片设置得很小,那么处理机在进程之间的进程上下文切换工作过于频繁,使得真正用于运行用户程序的时间减少。时间片可以静态设置好,也可根据系统当前负载状况和运行情况动态调整,时间片大小的动态调整需要考虑就绪态进程个数、进程上下文切换开销、系统吞吐量、系统响应时间等多方面因素。

    高响应比优先调度算法(HRRF)

     HRRF调度算法是介于先来先服务算法与最短进程优先算法之间的一种折中算法。先来先服务算法只考虑进程的等待时间而忽视了进程的执行时间,而最短进程优先调度算法只考虑用户估计的进程的执行时间而忽视了就绪进程的等待时间。为此需要定义响应比Rp:

    Rp=(等待时间+预计执行时间)/执行时间=响应时间/执行时间

Rp较大的进程执行。

     特点:既考虑进程等待时间,又考虑进程的执行时间。 但HRRF调度算法需要每次计算各各个进程的响应比Rp,这会带来较大的时间开销(特别是在就绪进程个数多的情况下)。

    多级反馈队列调度算法(MLFQ)

    多级反馈队列调度算法是一种CPU处理机调度算法,UNIX操作系统采取的便是这种调度算法。设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二队次之,其余队列优先级依次降低。

   多级反馈队列调度算法描述:
  1、进程在进入待调度的队列等待时,首先进入优先级最高的Q1等待。
  2、首先调度优先级高的队列中的进程。若高优先级中队列中已没有调度的进程,则调度次优先级队列中的进程。例如:Q1,Q2,Q3三个队列,只有在Q1中没有进程等待时才去调度Q2,同理,只有Q1,Q2都为空时才会去调度Q3。
  3、对于同一个队列中的各个进程,按照时间片轮转法调度。比如Q1队列的时间片为N,那么Q1中的作业在经历了N个时间片后若还没有完成,则进入Q2队列等待,若Q2的时间片用完后作业还不能完成,一直进入下一级队列,直至完成。
  4、在低优先级的队列中的进程在运行时,又有新到达的作业,那么在运行完这个时间片后,CPU马上分配给新到达的作业(抢占式)。

    最高优先级优先调度算法

       进程的优先级用于表示进程的重要性及运行的优先性。一个进程的优先级可分为两种:静态优先级和动态优先级。

      静态优先级是在创建进程时确定的。一旦确定后,在整个进程运行期间不再改变。静态优先级一般由用户依据包括进程的类型、进程所使用的资源、进程的估计运行时间等因素来设置。一般而言,若进程需要的资源越多、估计运行的时间越长,则进程的优先级越低;反之,对于I/O bounded的进程可以把优先级设置得高。

      动态优先级是指在进程运行过程中,根据进程执行情况的变化来调整优先级。动态优先级一般根据进程占有CPU时间的长短、进程等待CPU时间的长短等因素确定。占有处理机的时间越长,则优先级越低,等待时间越长,优先级越高。那么进程调度器将根据静态优先级和动态优先级的总和现在优先级最高的就绪进程执行。

#569 文件系统模块 » 文件系统(2)——文件读取过程 » 2022-08-04 23:55:18

batsom
回复: 0

前言

在前面的文章【Linux系统】文件系统(1)——磁盘的基本知识中,主要对存储文件的磁盘的一些基础知识进行了介绍。对于Linux系统来说,一切的数据都起源于磁盘中存储的文件。Linux文件系统的结构及其在磁盘中是如何存储的?操作系统是怎样找到这些文件进行读取的?这一章主要围绕这几个问题进行介绍(以Ext2文件系统(传统的linux文件系统)为例)。
Linux系统目录树

linux文件系统所有文件和目录都是由根目录开始的,以树的形式展开,如下图所示:

根据FHS的基本定义,根目录下面的各个目录(如usr,var)基本上都有其特定的意义,在此不多做介绍。重点说一下根目录"/"的作用和意义:

其他所有目录都是由根目录衍生出来的。

根目录中包含了开机软件、系统内核文件、函数库、文件系统修复程序等。

因此,根目录(/)所在的分区应该越小越好,应用程序所安装的软件最好不要与根目录存放在同一个分区。根目录越小,系统性能会更好,根目录所在的文件系统也较不容易出现问题。
文件(目录)存储方式

linux中,磁盘(硬盘)上的存储划分如下图所示:

MBR: 主引导分区。

自举块(引导分区Boot Sector):分区中文件系统自身引导程序存放的地方。

超级块(Super block): 记录整个文件系统相关的信息的地方,它记录的信息主要有:block与inode的总量、使用量、剩余量,文件系统的挂载时间,最近一次写入数据的时间等。

柱面组(块组) 每个柱面为一个柱面组(组号与柱面号一致),一个分区包含多个柱面。

配置信息:不详。

i节点位图(inode bitmap):每个inode结点对应位图中的一个位(这样一个字节可表示8个inode的使用情况),每个位值为0或1,表示该位所处下标对应的inode有没有被使用。

块位图(block bitmap):每个数据块或目录块都对应着块位图中的一个位,位的下标和块编号一一对应,每个位的值为0或1,表示该块是否已被使用。

i节点表(数组)(inode table):每个文件或者目录都有对应的一个inode,inode放在inode table中,包括inode的编号及其对应的信息。

i节点(inode): 存储文件相关信息(不包括文件名)。

数据块(data block): 存储文件具体内容。

目录块: 特殊一点的数据块,存放inode编号--文件(目录)名。
inode详解

inode的主要记录了文件的属性以及该文件实际数据是放在哪几号数据块(或目录块)中,具体包含以下信息:

文件的访问模式(r/w/x)

文件所有者和组(owner/group)

文件大小

文件创建或状态改变的时间(ctime)

最近一次的读取时间(atime)

最近修改的时间(mtime)

定义文件特性的标志(flag)

文件真正内容指向的数据块(pointer)

另外,inode的特征有:

inode的数量和大小在磁盘格式化的时候就已经固定了,除非再次格式化重新设置,否则不可改变。

每个inode的大小均为128Bytes。

每个文件仅占用一个inode。

文件系统能够创建(存储)的文件数量和inode的数量有关,也和磁盘大小(数据块数量)有关。

系统读取文件时,需要先找到inode,分析inode记录的权限与用户是否符合,若符合才可以开始实际读取数据块中的内容。

为了解决inode数量可能不够用的问题,操作系统将inode记录block号的区域定义为12个直接、1个间接、1个双间接、1个三间接的记录区。

文件IO编程中常说的文件句柄,其实就是inode编号。
文件读取过程

已读取文件/var/log/message为例,讲解读取文件messages时,从磁盘中查找/读取文件内容的过程。

首先系统通过挂载信息(在超级块中,位置固定)找到根目录(/)的inode编号,根目录对应的inode是固定的(通常为2号)。

根据根目录的inode编号(2号),在inode table中找到对应的inode信息,从inode信息中找到存储根目录信息的目录块编号,根据编号找到数据块,如图中标记为‘/’的方格,该目录块存储的信息如图中的dentry所示。

从目录块中存储的信息中,找到文件名(目录名)为var所对应的inode编号(2667711)。

在inode table中找到编号为2667711的inode信息,从该inode信息中,找到var目录存放的数据块。从var数据块存储的信息中,找到log目录对应的inode编号(267850)。

重复上述步骤,直至找到message文件对应的inode结点,根据inode结点中记录的message文件内容对应的数据块,从数据块中读取内容。
扇区、块(簇/数据块)、页

在操作系统数据交互过程中,经常听到扇区、块(簇/数据块)、页这几种单位,他们在数据交互过程中的意义为:

扇区: 磁头从磁盘中读取数据的最小单位,即磁头每次从磁盘中读取数据,都是一个扇区一个扇区读的。

块(簇): 操作系统与磁盘(硬盘)交互的最小数据单元(在linux系统中称为块,在windows系统中称为簇)。操作系统从硬盘中拿一块数据,即完成一次磁盘IO。块(数据块)的大小在硬盘格式化时被指定,一般有1K,2K,4K(最常用)。如果块的大小设置为4K,那么磁盘要读取8个扇区之后,才将数据块传给操作系统。另外,数据块也是DOS下数据存储的最小单元。例如,如果一个文件的大小为1K,而块的大小为4K,那么该文件还是会占用一个块,块中剩下的3K被空闲出来,不能用于存储其他数据。因此,设置块的大小时,需要考虑要存储文件的大小。

页: 操作系统访问内存时的最小单元,一般系统页的大小为4K(或者更大)。操作系统访问内存中的数据时,如果发现内存中没有哪个页可以提供该数据,那么会发生缺页,系统通过页替换(从硬盘中读取数据)的方式,将数据从硬盘读取到内存页中,再返回给调用者。

总的说来,主要就是不同系统、设备间数据交互时,使用了不同的机制和概念。其中磁盘内部(磁盘驱动程序从磁盘)读取数据时,以扇区为单位;操作系统从磁盘读取数据时,以块为单位;操作系统从内存读取数据时,以页为单位。
操作系统对文件存取操作的优化

并非每次读、写文件操作都会真正地从磁盘读出或写入,那样性能难以接受。为此操作系统使用了一系列机制,提升了文件IO的性能。
缓存

不管是硬盘还是操作系统,都会对从磁盘片中读取的数据进行缓存。硬盘中的缓存一般会比较小,如十几M,操作系统中的缓存则可能大很多。系统会将常用的文件数据放到主存储器的缓冲区,以加速文件系统的读写。一般情况下,只要系统的内存够用,系统会尽可能多的将磁盘中常用的文件缓存到内存中,直至内存耗尽(这是正常现象)。比如,如果你发现在电脑上读取文件的速度达到了2G每秒,那肯定不是真的从磁盘读取的,而是从缓存读取的。所以要测试磁盘真正的读数据的速度,需要先清空系统的缓存。
异步处理

当系统加载一个文件到内存后,如果该文件没有被改动过,则在内存区段的文件数据会被标记为clean,如果是被改动了,则会标记为dirty。此时所有的文件操作还是在内存中进行,并没有写入到磁盘中。系统会不定时的将内存中设置为dirty的数据写回到磁盘,以保持磁盘与内存数据的一致性。这个过程是异步的。你也可以sync命令,将内存中的数强制写回到硬盘。

另外,要注意的是,在正常关机的情况下,关机命令会主动调用sync来将内存中的数据写入到磁盘内,但是如果非正常关机(如断电、死机),由于数据没有来得及写入到磁盘,因此重新启动可能会花费很长的时间进行磁盘检验,甚至可能导致文件系统的损毁(非磁盘损坏)。
总结

本文读linux系统文件的读取过程从里到外做了较为详细的说明,已进入了linux操作系统层面,在下一篇文章中,可能会将这些原理与文件IO系统调用函数进行挂钩讲解,从而完成硬件-系统-应用的完整流程。

#570 文件系统模块 » 文件系统(1)——磁盘的基本知识 » 2022-08-04 23:54:38

batsom
回复: 0

前言

文件系统是Linux系统的核心,在Linux系统中,一切皆文件。对于开发者而言,编程过程中通常用到的是文件IO。在open函数打开文件,read函数读取文件的过程底层原理如何?本文件系统系列文章主要尝试针对的是这部分内容进行深入的讲解。

文件最终是存储在磁盘上,文件的存取,最终是读写磁盘。因此,在本文章中,将主要介绍磁盘的一些基础知识。
硬盘的物理结构 硬盘结构概述

其中:

每个盘片有两面,两面都可以存储数据。

每个盘面都有一个磁头,用于存取数据。

所有磁头都是固定的,一起做物理移动。但是每次只有一个磁头执行存取数据的任务,选择使用哪个磁头是由系统控制的。磁头间的切换非常迅速,而磁头摆动寻道则比较慢。

磁头摆动,找到要读取数据所在的磁道;盘片通过转动,将数据“送到”磁头下。
磁头数、磁道、柱面、扇区

磁头数: 每个盘面都有一个磁头,因此磁头数 = 盘面数。

磁道: 上以盘片轴心为圆心,不同半径的同心圆称为磁道,磁道不是真正肉眼可看见的一道一道“坑”,而是被磁盘上被磁化的区域,磁道之间有一定的间隙,以免磁道之间磁介质相互影响。

扇区: 每个磁道被等分为若干个弧段(扇区),每个扇区可以存放512个字节的数据。扇区是磁盘驱动器向磁盘读写数据的最小单元。

柱面: 不同盘片半径相同的磁道组成的一个“柱面”。
CHS编号

所谓硬盘的CHS,即Cylinder(柱面)、Head(磁头)、Sector(扇区)。每个柱面、磁头、扇区都有自己的编号。
磁道编号规则

磁头编号: 从上到下,分别为磁头0号,磁头1号......

柱面编号: 最外圈的柱面编号为0,往里依次递增1,2,3.....
扇区编号规则

每个磁道都可以被分为若干个扇区,假设有18个扇区,编号依次为1,... 18。这里的1...18在一个磁道中是唯一的,但不同磁道之间会重复。因此,要确定一个扇区在硬盘(多个盘片)中的具体位置,则需要结合柱面号/磁头号/扇区号来确定——绝对扇区编号,或者是为整个硬盘的所有扇区分配一个唯一的编号——DOS扇区编号(也称为相对扇区编号或逻辑扇区编号)。

绝对扇区: 由柱面号/磁头号/扇区号唯一确定。

DOS(Disk Operating System)扇区(逻辑扇区): 磁盘操作系统为了管理方便,会将柱面号/磁头号/扇区号确定的扇区编号转换为易于管理和计算的单一数字编号,称为相对扇区编号或逻辑扇区编号。DOS扇区编号是从柱面0/磁头0/扇区1开始的,编号为0,依次递增,即柱面0/磁头0/扇区18的DOS扇区编号为18;柱面0/磁头1/扇区1的DOS扇区编号为19...。每个分区的第一个扇区的逻辑扇区编号为0。
通过CHS计算硬盘容量

硬盘存储容量=磁头数×柱面数×每个磁道扇区数×每个扇区字节数
磁盘存储划分 第一个扇区存储的数据

硬盘的第一个扇区(逻辑编号为0)可以说是整个硬盘最重要扇区了,它主要存储两大信息:

主引导分区(Master Boot Record,MBR): 存放引导加载程序,大小为446字节。

分区表(partition table): 记录整块硬盘分区的状态,占64字节。
硬盘分区

分区的最小单位为柱面(Cylinder),也就是说,分区其实就是指定从第几个柱面到第几个柱面属于哪个区。

假设硬盘有400个柱面,平均分为4个区(C、D、E、F盘),则第四个分区F的柱面范围是第301号到第400号柱面。

分区表中,记录的就是就是每个分区的起始柱面号和结束柱面号。因为分区表只有64字节,因此最多只能容纳4个分区的记录(硬盘默认),要想分更多的去,则需要通过扩展分区来创建逻辑分区。
开机流程

加载BIOS: BIOS(Base Input Output System,基本输入输出系统,读做/"baious/)是硬件厂商写入到主板ROM里的一个程序,电脑开机时,会最先执行BIOS。开机后,进入BIOS界面,如下:

MBR: BIOS在执行的过程中,会根据用户设置(即在BIOS的Boot界面选择的优先启动项,U盘/硬盘/光驱),如果硬盘的优先级最高,则计算机会从硬盘的第一个扇区的MBR中的读取引导加载程序(Boot Loader)。和BIOS一样,主引导分区MBR是硬件本身会支持的东西。

引导加载程序:引导加载程序(Boot Loader)是用于读取操作系统内核文件的一个小软件,不同的操作系统有各自的引导加载程序。每个分区都可以有自己的文件系统,有自己的引导扇区,在启动的过程中,用户可以选择是直接加载引导程序所在分区的操作系统,还是将引导加载功能交给各分区引导扇区中的加载程序。boot loader的功能主要有:

提供菜单:让用户选择不同的开机选项,是多重引导的重要功能。

载入内核文件:直接指向可开机的程序区段,开始操作系统。

转交其他loader: 将引导加载功能转交给其他loader负责。

操作系统内核文件:最后,引导加载程序加载操作系统的内核文件,启动操作系统!
要点

每个分区都有自己的引导扇区(boot sector)。

实际可开机的操作系统内核文件是存放在各个分区内的,如在C盘安装Windows系统,在D盘安装Linux操作系统。

引导加载程序(boot loader)只会认识自己所在分区内的可开机内核文件,以及其他boot loader(相当于一个指针,指向其他loader)。

loader可以直接指向或者间接地讲管理权交给其他loader。

一点经验:如果要安装多系统,最好先安装Windows,后安装Linux。因为Windows会强制覆盖MBS,也就是说,如果你先装Linux再装Windows,MBR中就只有Windows的相关选项(Linux的被覆盖掉了)。而Linux则不会强制覆盖MBR,你可以选择将Linux的引导程序安装在MBR或者其所在分区的引导扇区中;你也可以在Linux的引导程序中设置Windows的开机选项。
磁盘存储数据的形式

磁盘以二进制的形式存储数据。我们平时编程过程中读写文件模式可能有文本形式/二进制形式,文件也有文本文件、音频文件、图片文件...等各种不同类型的文件,但这些文件在磁盘上都是以二进制的方式存储。

可以想像一下,二进制中的0,对应磁盘上的一个“凹”点,1对应磁盘上的一个“凸”点。(只是形象比喻,不是真的凹凸)。
总结

至此,硬盘(机械硬盘)的基本结构和一些核心的概念已了解的差不多了,也了解了开机过程中需要用到硬盘的地方。文件的存取,最终会落在硬盘的读写上。在后面的文章中,将逐渐自底向上介绍架构在磁盘之上的Linux操作系统的文件系统的一些理论性知识。
混合云 云服务器 linux 磁盘文件系统 linux文件系统 磁盘 linux检查磁盘文件系统 linux磁盘与文件系统

#571 引导模块和保护模式 » linux内存与扇区,磁盘的基础知识——扇区、柱面、磁道、族 » 2022-08-04 23:46:22

batsom
回复: 0

磁盘的物理结构

磁盘是由若干块坚硬金属材料制成的并涂以磁性介质的盘片压制而成。每个盘有两面,每面各有一个磁头,都可记录信息。要了解硬盘的物理结构,需要弄懂磁道、扇区、柱面、簇等几个概念.

磁道:磁盘上划分了很多个同心圆,这些同心圆就是磁道。但打开硬盘,用户不能看到这些,实际上磁道是被磁头磁化的同心圆。磁道之间是有间隔的,因为磁化单元太近会产生干扰.

扇区:每条磁道被分成若干等份的区域,每个区域就是一片扇区。扇区是硬盘数据存储的最小单位.

柱面:假如一块磁盘只有3个磁盘片,每块盘片的磁道数是相等的.从外圈开始,这些磁道被分成了0磁道、1磁道、2磁道……。具有相同磁道编号的同心圆组成面就称作柱面。为了便于理解,柱面可以看作没有底的铁桶。柱面数就是磁盘上的磁道数。柱面是硬盘分区的最小单位.

一个硬盘的容量=柱面数(或磁道数)×磁头数×扇区数×每个扇区的大小(通常是512字节)

簇:扇区是硬盘数据存储的最小单位,但操作系统无法对数目众多的扇区进行寻址,所以操作系统就将相邻的扇区组合在一起,形成一个簇,然后再对簇进行管理.每个簇可以包括2、4、8、16、32、64个扇区。族有时也称作磁盘块(block)。它是属于文件系统层面的概念,是文件系统层中数据存储的基本单位。

磁盘的分区

要掌握硬盘的分区,需要掌握MBR、扩展分区、逻辑分区的概念.

硬盘里分为两个区域,一个是放置该硬盘的信息区,称之为主引导扇区(master boot recorder,MBR),一个是实际文件数据放置的地方。其中,MBR是整个磁盘最重要的区域。一旦MBR物理实体损坏时,则该磁盘就差不多报废了.一般来说,MBR有512个字节,且可以分为两个部分.

第一部分有446个字节,用于存放引导代码,即bootloader.

第二部分有64个字节,用于存放磁盘分区表(Disk Partion Table,DPT)。每个分区的信息需要用16个字节来记录,因此,一块磁盘最多可以有4个分区。

注:通常所说的"磁盘分区"就是指修改磁盘分区表。它定义了"第n个磁盘块是从第x个柱面到第y个柱面".因此,当系统要读取第n个磁盘块时,就是去读硬盘上第x个柱面到第y个柱面的信息.

分区有两种,一种是主分区,一种是扩展分区。在一块磁盘中,扩展分区只能有一个,所以这4个分区可以是4个主分区或者3个主分区加1个扩展分区,如下所示:

P + P + P + P

P + P + P + E

其中P表示主分区(Primary),E表示扩展分区(Extend)

重点说明的是,扩展分区不能直接使用,还需要将其划分为逻辑分区.这样就产生了一个问题,既然扩展分区不能直接使用,但为什么还要划分出一定的空间来给扩展分区呢?这是因为,如果用户想要将硬盘划分为5个分区的话,那该如何? 此时,就需要扩展分区来帮忙了.

由于MBR仅能保存4个分区的数据信息,如果超过4个,系统允许在额外的硬盘空间存放另一份磁盘分区信息,这就是扩展分区. 若将硬盘分成3P+E,则E实际上是告诉系统,磁盘分区表在另外的那份分区表,即扩展分区其实是指向正确的额外分区表.本身扩展分区不能直接使用,还需要额外将扩展分区分成逻辑分区才能使用,因此,用户通过扩展分区就可以使用5个以上的分区了.

说明:

不建议用户将硬盘分为4个主分区.这是因为,假如一个20GB的硬盘,若4个主分区占据了15GB的空间,则剩下的5GB空间完全不能使用,因为已经没有多余的分区表可以记录这些空间了.

考虑到磁盘的连续性,一般建议将扩展分区放在最后面的柱面内.

允许一个硬盘只有1个主分区,其它空间都分配给扩展分区。

Linux磁盘分区

Windows操作系统是先将物理地址分开,再在分区上建立目录,因此在Windows操作系统中,所有路径都是从盘符开始,如C://program file。而Linux正好相反。在Linux操作系统中,先有目录,再将物理地址映射到目录中。在Linux操作系统中,所有路径都是从根目录开始.Linux默认可分为3个分区,分别是boot分区、swap分区和根分区.

无论是Windows操作系统,还是Linux操作系统,每个分区均可以有不同的文件系统,如FAT32、NTFS、Yaffs2等.

下面简单介绍一下Linux的3个分区,boot分区、swap分区和根分区。

boot分区

该分区对应于/boot目录,约100MB.该分区存放Linux的Grub(bootloader)和内核源码.用户可通过访问/boot目录来访问该分区.换句话说,用户对/boot目录的操作就是操作该分区.

swap分区

该分区没有对应的目录,故用户无法访问。Linux下的swap分区即为虚拟内存.虚拟内存用于当系统内存空间不足时,先将临时数据存放在swap分区,等待一段时间后,然后再将数据调入到内存中执行.所以说,虚拟内存只是暂时存放数据,在该空间内并没有执行.

关于虚拟内存

虚拟内存是指将硬盘上某个区域模拟为内存.因此虚拟内存的实际物理地址仍然在硬盘上.虚拟内存,或者说swap分区只能由系统访问,其大小为物理内存的2倍.

根分区

在Linux操作系统中,除/boot目录外的其它所有目录都对应于该分区.因此,用户可通过访问除/boot目录外的其它所有目录来访问该分区.

说明:

在Linux操作系统中,用户可根据需要把分区挂载到某个目录下。比如/home目录下有三个目录,假设为test1,test2,test3,可把不同的分区分别挂载到这三个目录下 。

根据上面的讨论,一块磁盘上最多有13个分区,即硬盘分为1个主分区和3个扩展分区,而每个扩展分区又可以分为4个逻辑分区。

硬盘上至少要有1个主分区.

逻辑分区不能再进行分区.

Linux分区目录和"盘符"的关系

假如硬盘安装在IDE1的主盘,并用户想分区成6个可以使用的硬盘分区,则可以采用3个主分区加3个逻辑分区方式,或者是1个主分区加5个逻辑分区。

Linux允许使用fdisk -l命令和df -h命令来查询其硬盘分区.其中,df无法显示出swap分区的大小.

若硬盘的MBR已坏,则该磁盘就不能再作为引导盘,只能作为数据盘.因为MBR位于硬盘的起始处,用户不能通过软件进行修复,也不能跳过起始处.而硬盘中间的某个磁道坏了,用户可以软件修复,也可以跳过该磁道.

#572 引导模块和保护模式 » Linux系统主引导扇区MBR详解 » 2022-08-04 23:43:30

batsom
回复: 0

本篇目标

    介绍引导扇区。
    介绍软盘结构(FAT12)。
    用汇编代码把加载器读取到内存中。
    用汇编代码把内核加载器读取到内存中。

简略流程

计算机启动的简略流程如下:


BIOS对应的中文术语是“基本输入输出系统”。计算机启动时,首先运行的便是BIOS。

BIOS是计算机厂商预置在计算机硬件中的一种软件,它会完成一些操作。我们只需知道,它会从内存地址0x7c00处读取引导扇区,就足够了。引导扇区的作用是从软盘中读取加载器。

我们把”引导扇区“叫做boot,把引导扇区的源码文件命名为boot.asm。boot恰好占用一个扇区,因此,boot所在的扇区被称为“引导扇区”。我们就把“加载器”叫做loader吧,把加载器的源码文件命名为loader.asm。

我们将使用nasm编写boot和loader。
软盘

软盘和硬盘一样,是一种存储介质,但目前已经很少使用。我们将使用bochs创建虚拟软盘。

软盘使用FAT12文件系统。我们写好loader后,会把它存储到软盘中。

    从软盘的第一个字节开始存储还是从0x7c00开始存储?

先介绍一下软盘的数据结构分布图。

我们使用的软盘是1.44M软盘。这种软盘有80个磁道,每个磁道有18个扇区。软盘有两个盘面,因此这种软盘的容量是:

$软盘容量 = 80 * 18 * 512 * 2 / 512 = 2879个扇区≈1.44M$。
int 13h

我们使用BIOS的中断int 13h从软盘中读取数据到内存中。先看下图了解一下中断int 13h的用法。

什么是BIOS中断?我们不必纠缠这个概念,先从我们熟悉的高级语言的角度理解int 13h。

把int 13h理解成一个函数,把这个函数命名为ReadSectorFromFloppy。

这个函数的声明是:void ReadSectorFromFloppy(int ah, int al, int ch, int cl, int dh, int dl, char *dest)。除最后一个参数char *dest外,这个函数的参数对应上图中的同名寄存器。char *dest对应上图中的es:bx。

ReadSectorFromFloppy的作用能简化为:把数据从src复制到dest指定的内存地址处。只不过,src不是通过一个参数传递给函数,而是通过一系列参数传递给函数。再说得明确一些,一系列参数联合起来告诉了函数src是多少。

怎么调用ReadSectorFromFloppy?很简单,按照函数声明传递参数给它就行。对int 13h的使用也是如此,将每个寄存器需要的值填入对应的寄存器,然后,使用int 13h。使用int 13h的伪代码如下:

ReadSector:
        mov ah, 02h
        mov al, 要读的扇区数
        mov ch, 磁道号
        mov cl, 起始扇区号
        mov dh, 磁头号
        mov dl, 驱动器号(0表示A盘)
        mov bx, 目标数据比如loader在内存中的位置
       

        int 13h

我们在后续的开发过程中,还会多次遇到对一些端口的读写操作。和这里的BIOS中断的类似,都能用高级语言中的函数来理解。没什么神秘的,传递一些参数,然后执行某种操作,从某个指定的地址获取数据。仅此而已。

上面说得轻描淡写,大家可能会以为我们只要三四分钟就能从软盘中读取loader了。真这么顺利吗?让我们来试试。

    ah,只需往这个寄存器中填充02h。
    al,每次读取一个扇区,往al中填充01h。
    ch,磁道号是多少?未知。cl、dh、dl、bx中应该填充什么值?全是未知。
    再看一次上面的说明,dl中应该填充0h,bx的值也好确定。

除了四个参数未知,还需要知道调用ReadSector几次才能读完全部loader数据。让我们带着这些疑问去多了解一下软盘。
数据分布


    第0个扇区是引导扇区。
    第1个扇区到第18个扇区是FAT区域。FAT区域中存储两个完全相同的FAT表,分别是FAT1和FAT2。它们互为备份。
    从第19个扇区开始,存储根目录。根目录的占用的扇区数量是多少,取决于在软盘中存储多少个文件。
    根目录区域之后的所有扇区都存储数据区。数据区的初始扇区号、一共占用多少个扇区,都是未知数。

猜猜看,loader存储在软盘数据结构的哪个区域?

显而易见,loader被存储在数据区。

补充说明一点。每个扇区只会存储一个文件的数据,绝对不会存储两个文件的数据。例如,文件A的大小是510字节,从扇区号为N的扇区的最开始那个字节存储文件A。N号扇区存储完A文件后,还剩下2个字节。这个时候,如果往软盘中存储文件B,文件B的大小是2个字节,文件B也不会使用N号扇区的剩下的2个字节来存储,而是会重新使用一个完全没有使用过的扇区来存储文件B。软盘、硬盘都是如此。

继续回到我们的主题。
根目录
查询根目录

根目录的大小由软盘能存储的最大文件数量决定。但是,根目录的大小一定是整数个扇区。

根目录由若干个根目录项组成。根目录项是一段32个bit的存储空间。在根目录项中,最有用的是"文件名"和“文件名对应的文件在数据区中的第一个扇区的扇区号”。

先看一段伪代码。它非常清楚地说明了根目录的作用:根据文件名找到文件对应的根目录项,从目标根目录项中找到文件在数据区的第一个扇区的扇区号。

int get_1st_sector_by_filename(filename){
          start_address;    // 根目录的初始地址
          count;    // 根目录项的数量
          for(i = 0; i < count; i++){
            if(start_address.文件名 == filename){
                return start_address.文件在数据区的第一个扇区的扇区号
          }
            start_address += 32;
      }
          return -1;        // 不存在文件名是filename的文件

}

在后面,我们会使用nasm实现和伪代码思路相同的汇编函数。
根目录项结构

如果用C语言为根目录项建立一个struct,将会是下面这样的。

struct root_directory_entry{
      char[11] DIR_Name;
      char    DIR_Attr;
      char[10] DIR_Reserved;
      short    DIR_WrtTime;
      short    DIR_WrtDate;
      short DIR_FstClus;
      int DIR_FileSize;

};

FAT表
单链表

先说结论:一个文件对应的所有FAT表项构成一个单链表。

什么是FAT表项?它们怎么构成一个单链表?请继续往下看。

从根目录中找到目标文件在数据区的第一个扇区的扇区号,就知道从哪个扇区读取数据了。

可是,根目录只提供了目标文件的第一个扇区的扇区号。如果目标文件需要两个以上扇区存储呢?如何知道第二个扇区、第三个扇区、第N个扇区的扇区号呢?FAT表会提供这些信息。

回忆一下软盘的数据分布图,图中有FAT1和FAT2,它们都是FAT表,我们只需从一个FAT表中获取数据,就选择FAT1吧。

FAT1的大小是9个扇区,512*9个字节。每12个bit构成一个FAT表项,FAT表项的值有两重含义:

    下一个FAT表项的编号。
    文件的下一个扇区的扇区号。

举例说明。软盘中存储着文件名为CG的文件。CG的大小是514个字节。从根目录中查询到CG在数据区的第一个扇区的扇区号是4。

4除了是扇区号,还是FAT表项的编号。注意这句话,非常重要。

在FAT1中找到编号为4的FAT表项,这个FAT表项的值是5,那么,5既是下一个FAT表项的编号,又是下一个扇区号。也就是说,CG对应的FAT表项是第4个FAT表项、第5个FAT表项;在数据区占用的扇区是第4个扇区、第5个扇区。

根据当前FAT表项找到下一个FAT表项,这其实就是一个单链表。和单链表一样,FAT表项构成的单链表也有一个尾结点。识别伪结点的方法是判断FAT表项的值是否大于等于FAT_Entry_Value。FAT_Entry_Value是一个具体的值,等我们写代码时再看看它是多少。
FAT表项

本小节的目的是弄清楚:根据FAT表项的编号计算FAT表项的值。

先看一下FAT表项图。

每个FAT表项占用12个bit,计算机读取数据的最小单位是1个字节8个bit。为了每次都读取到完整的FAT表项,需要一次读取2个字节16个bit。16个bit只能存储一个FAT表项。怎么存储?只有图2-2的两种情况。

每次读取FAT表项,都会读取两个字节,而这两个字节的低12位和高12位都可能是FAT表项。要想获取FAT表项,首先要读取两个字节,然后要判断FAT表项存储在低12位还是高12位。这对我来说,是一个有点费劲的问题,我会写得详细一些。

用具体例子来寻找判断FAT表项存储位置的方法。FAT表存储在初始地址为$512$字节的存储空间中。
编号     读取地址(字节)     占用(bit)     实际读取(bit)     注释     实际读取(字节)
0     1     8~~19     8~~23     20~~23是其他表项数据,低12位是本表项数据     1~~2
1     2     20~~31     16~~31     16~~19是其他表项数据,高12位是本表项数据     2~~3
2     3     32~~43     32~~47     44~~47是其他表项数据,低12位是本表项数据     3~~4

    对上面表格的补充说明:

        单位为bit的列中的数值应该加上基数:$512 * 8$。
        单位为字节的列中的数值应该加上基数:511。
        因版本需要,没有把基数写到表格的列中。

观察表格中的“编号”列和“注释"列,能得到下面的结论:

    当FAT项的编号是奇数时,FAT表项存储在2个字节的高12位。
    当FAT项的编号是偶数时,FAT表项存储在2个字节的低12位。

知道了FAT表项编号N,怎么计算存储FAT表项的存储空间的字节偏移量?计算公式是:$N * 12 / 8$。例如,编号为2的FAT表项的存储空间的初始地址是:$2 * 12/8 = 3 $。

最后,再看一张图。

定位FAT项在软盘中的位置,需要确定两个值:

    FAT项在软盘中的扇区偏移量。
    FAT项在软盘中的某个扇区中的字节偏移量。

结合上面的示意图来解释。扇区偏移量是N,字节偏移量是M。读取N+1号扇区后,从N+1号扇区的第M字节开始读取两个字节,目标FAT项就存储在这两个字节中。
获取FAT项的值
方法一

现在,可以给出查询FAT表项的值的伪代码了。

int get_fat_entry_value(fat_entry_no){
      // fat_entry_no是FAT项的编号。
      remainder = fat_entry_no % 2;
      // sector_number是要读取的扇区数量
      sector_number = 2;
      // sector_offset 是FAT项存储示意图中的N。
      sector_offset = fat_entry_no * 3 / 2 / 512;
      // bit_offset 是FAT项存储示意图中的M。
      bit_offset = fat_entry_no * 12 / 8;
      // 读取偏移量是sector_offset两个扇区
      // sectors是FAT项存储示意图中的N+1号扇区和没有画出来的N+2号扇区。
      sectors = read_sector(sector_offset, sector_number);
    two_byte_value = sector + bit_offset;
    fat_entry_value = remainder == 0 ? (two_byte_value && 0x0FFF) : (two_byte_value && 0xFFF0);
      return fat_entry_value;

}

方法二

在上面的伪代码中,检查FAT项的字节偏移量是不是整数个字节的方法是根据FAT项的编号识别。除了这种方法,还有第二种方法。这个方法如下所述:

    一个FAT项占用12个bit,也就是1.5个字节,所以FAT项的字节偏移量等于FAT项的编号乘以1.5。
    为了避免不同类型的数据之间进行运算,可以采用乘以1.5的等价运算:先乘以3,再除以2。
        商是字节偏移量。
        余数是bit偏移量。根据余数是否为0判断FAT项的字节偏移量是不是整数个字节。

在后面会讲到的GetFATEntry函数中,使用的是第二种方法。理解不了这个函数的代码的时候,记得回头看看这里的讲解。
草稿

    编号为0的FAT表项,读取地址是1,占用(1*8)~~19个bit,实际读取的空间是第823个bit(第1~~2个字节)。~~

    编号为1的FAT表项,读取地址是2,占用20~~31个bit,实际读取的空间是第1631个bit(第2~~3个字节)。~~

    编号为2的FAT表项,读取地址是4,占用32~~43个bit,实际读取的空间是第3247个bit(第4~~5个字节)。~~

        编号为0的FAT表项,读取地址是1,占用(1*8)~~19个bit,实际读取的空间是第1~~2个字节(第8~~23个bit),字节偏移量是$(8/1=1)$个字节。
        编号为1的FAT表项,读取地址是2,占用20~~31个bit,实际读取的空间是第2~~3个字节(第16~~31个bit),字节偏移量是$(20/8=2)$个字节。
        编号为2的FAT表项,读取地址是4,占用32~~43个bit,实际读取的空间是第4~~5个字节(第32~~47个bit),字节偏移量是$(32/8=4)$个字节。

boot

boot要实现的功能是:

    从根目录中找到目标文件在FAT表中的FAT项。
    FAT项包含目标文件的数据存储在哪个扇区。
    使用BIOS中断int 13h读取目标扇区的数据。

直接看代码吧。代码比较长,但是不要被吓到,也不要烦躁,我们一起来看看。
代码解读
泛读

; 计算机启动后,会检查有没有存储设备例如软盘、硬盘等。如果有,会选择一种设备例如软盘,
; 从软盘的引导扇区中读取数据并且复制到内存地址为0x7c00的那段内存空间。
; 也就是说,这个指令的作用是,让BIOS把boot存储到内存地址是0x7c00的内存空间中。
; 然后,BIOS执行结束后,会从0x7c00处开始执行。
; 为什么是0x7c00?这涉及到古老的计算机历史。我以为这种知识不重要,不知道也不影响我们继续开发操作系统。
; 因此,不深究这个问题。
org 0x7c00

    ; 跳转到LABEL_START为开头的那块代码。
    jmp    LABEL_START
    ; 空指令。
    nop

    ; 下面是 FAT12 磁盘的头,叫做"BPB"。
    ; 必须有这段指令,BIOS才会把存储设备中的这个扇区识别为引导扇区。
    ; 也不必深究,我们写操作系统时,照搬这段即可。
  BS_OEMName      DB 'YOUR--OS'   ; OEM String, 必须 8 个字节
  BPB_BytsPerSec  DW 512          ; 每扇区字节数
  BPB_SecPerClus  DB 1            ; 每簇多少扇区
  BPB_RsvdSecCnt  DW 1            ; Boot 记录占用多少扇区
  BPB_NumFATs     DB 2            ; 共有多少 FAT 表
  BPB_RootEntCnt  DW 224          ; 根目录文件数最大值
  BPB_TotSec16    DW 2880         ; 逻辑扇区总数
  BPB_Media       DB 0xF0         ; 媒体描述符
  BPB_FATSz16     DW 9            ; 每FAT扇区数
  BPB_SecPerTrk   DW 18           ; 每磁道扇区数
  BPB_NumHeads    DW 2            ; 磁头数(面数)
  BPB_HiddSec     DD 0            ; 隐藏扇区数
  BPB_TotSec32    DD 0            ; wTotalSectorCount为0时这个值记录扇区数
  BS_DrvNum       DB 0            ; 中断 13 的驱动器号
  BS_Reserved1    DB 0            ; 未使用
  BS_BootSig      DB 29h          ; 扩展引导标记 (29h)
  BS_VolID        DD 0            ; 卷序列号
  BS_VolLab       DB 'YOUR--OS.02'; 卷标, 必须 11 个字节
  BS_FileSysType  DB 'FAT12   '   ; 文件系统类型, 必须 8个字节

LABEL_START:
    ; many code
   

; 引导器最多只有510个字节,如果存储完实现功能的指令后还不够510个字节,就用0填充剩余的存储空间。
times    510 - ($ - $$)    db    0
; 0xAA55是一个魔数。BIOS读取存储设备的第一个扇区后,会检查扇区的最后两个字节是不是`0xAA55`。
; 如果不是,BIOS认为这个扇区不是引导扇区;如果是,BIOS认为这个扇区是引导扇区。

dw    0xAA55

这段代码中的FAT12的磁盘头和扇区的最后两个字节0xAA55一起构成了引导扇区的标志。没有这两个标志,BIOS就认为这个扇区不是引导扇区。

BPB:BIOS参数块(BIOS Parameter Block)。
ReadSector
泛读

先回顾一下前面给出的伪代码。

ReadSector:
        mov ah, 02h
        mov al, 要读的扇区数
        mov ch, 磁道号
        mov cl, 起始扇区号
        mov dh, 磁头号
        mov dl, 驱动器号(0表示A盘)
        mov bx, 目标数据比如loader在内存中的位置
       

        int 13h

下面的代码中:

    ah的值通过mov ah, 02h ; 读软盘设置成02h。

    al的值通过两条语句设置。

mov byte [bp-2], cl

    mov al, [bp-2]

    也就是说,al中的值是cl中的值。cl的值应该在调用ReadSector前设置了值。

ch的值通过下面的语句设置。

mov ch, al

    shr ch, 1    ; ch 是柱面号

    不能一眼看出这两条语句的含义。先搁置。

cl的值通过下面的语句设置。

inc ah

    mov cl, ah

    也不能一眼看出这两条语句的含义。先搁置。

dh的值通过下面的语句设置。

mov dh, al

    and dh, 1    ; dh 是磁头号

    也不能一眼看出这两条语句的含义。先搁置。

dl的值通过mov dl, 0 ; 驱动器号,0表示A盘。

和bx有关系的语句是:

push bx

        pop bx

        bx的值应该是在调用ReadSector前设置的。

难点

经过上面的仔细分析,发现了三个疑问,分别是cl、ch和dh的值。它们分别是:起始扇区号、柱面号和磁头号。先给出这三个值的计算公式:

理解这个计算公式前,复习一次1.44M式软盘的知识。

    软盘有80个磁道。每个磁道有18个扇区。
    软盘有两个盘面,有两个磁头,每个盘面有一个磁头。盘面号和磁头号分别是:0号、1号。
    两个盘面上的对应的一对磁道组成一个柱面。每个柱面包含两个磁道。柱面号的初始值是0。
    每个磁道的扇区的扇区号的初始值是1,不是0。

再理解公式。

    $磁道号 = 扇区号/每磁道扇区数$
    $初始扇区号 = 扇区号sector_no \mod\ 每磁道扇区数 + 1$
        扇区号除以每磁道扇区数的余数是填充若干个磁道后剩余的扇区数量M。
        换句话说,这些扇区是位于第N磁道的前M个扇区,扇区号为sector_no的扇区是第N磁道的第M个扇区。
        第M个扇区在第N磁道的扇区号是多少?
        要回答这个问题,先补充两个知识点:sector_no是扇区在软盘中的扇区号,初始值是0;M是扇区在磁道中的扇区号,初始值是1。
        因此,在磁道中,偏移量是0个扇区的扇区的扇区号是(1 + 0);偏移量是1个扇区的扇区的扇区号是(1 + 1);偏移量是2个扇区的扇区的扇区号是(1 + 2);由此归纳出,偏移量是M个扇区的扇区的扇区号是(1+M)。
        这就是公式中起始扇区号 = R + 1的由来。
    柱面号 = 磁道号 / 2。很容易理解。每个柱面有两个磁道,0号磁道在0号柱面,1号磁道在0号柱面;2号磁道在1号柱面,3号磁道在1号柱面。
    磁头号 = 磁道号 & 1。
        磁道号是奇数,这个磁道的由0号磁头处理;磁道号是偶数,这个磁道由1号磁头处理。
        奇数的最低位bit的值总是1,偶数的最低位bit的值总是0。因此,只需判断磁道号的最低位bit是0还是1就能判断出这个磁道号是奇数还是偶数。

再读代码

我初次看这块知识时花了不少时间,所以,我要再重复写几句。

使用ReadSector读取数据时,直接提供的参数只有从FAT12的根目录和FAT中查询出来的扇区号sector_no。这个扇区号的初始值是0。

在ReadSector函数中把sector_no代入上面的公式计算出来的在磁道中的扇区号的初始值是1。

彻底扫清所有障碍之后,让我们再次直面开始的那个难题吧:起始扇区号、柱面号和磁头号是多少?

    sector_no存储在ax中。
    SectorNumberOfTrack的值是18。在文末的全部代码中将会看到为这个变量赋值的语句。
    div bl ; 商在al中,余数在ah中。
        nasm的div指令,进行除法计算,被除数存储在ax中,除数存储在bl中,商存储在al中,余数存储在ah中。
        根据公式,$柱面号(ch) = 扇区号(ax)/每个磁道包含的扇区的数量(SectorNumberOfTrack)/2$。
        $在磁道中的起始扇区号(cl) = 扇区号(ax) % 每个磁道包含的扇区的数量(SectorNumberOfTrack) + 1$。
        $磁头号(dh) = 扇区号(ax) / 每个磁道包含的扇区的数量(SectorNumberOfTrack) & 1$。

结合上面冗长的分析看下面加了注释的代码,应该不会有太多疑问。

; 读取扇区
ReadSector:
    push ax
    push bp
    push bx
    mov bp, sp
    sub esp, 2
    mov byte [bp-2], cl
   
    ; ax 存储在软盘中的扇区号
    mov bl, SectorNumberOfTrack    ; 一个磁道包含的扇区数
    div bl    ; 商在al中,余数在ah中
    mov ch, al
    shr ch, 1    ; ch 是柱面号
    mov dh, al
    and dh, 1    ; dh 是磁头号
    mov dl, 0    ; 驱动器号,0表示A盘
    inc ah
    mov cl, ah
    mov al, [bp-2]
    add esp, 2
    mov ah, 02h    ; 读软盘
    pop bx
   
    int 13h

    pop bp
    pop ax

    ret

GetFATEntry
汇编指令div和mul

[]()
代码

FATEntryIsInt    equ 0        ; FAT项的字节偏移量是不是整数个字节:0,不是;1,是。
BytesOfSector    equ    512    ; 每个扇区包含的字节数量
; 根据FAT项的编号获取这个FAT项的值
GetFATEntry:
    ; 用FAT项的编号计算出这个FAT项的字节偏移量 start
    ; 复位软驱时会修改ax的值,先把它存储到栈中。
    push ax
    ; 复位软驱
    mov ah, 00h
    mov dl, 0
    int 13h
   
    ; 还原被复位软驱而修改的ax中的值。
    pop ax   
    ; 下面的操作,实现 ax * 3 / 2。
    mov dx, 0
    mov bx, 3
    mul bx
    mov bx, 2
    div bx
    ; 用FAT项的编号计算出这个FAT项的字节偏移量 end
    ; div bx操作会把余数存储在dx中,商存储在ax中。
    ; dx是bit偏移量,ax是字节偏移量。
    mov [FATEntryIsInt], dx
    ; 用字节偏移量计算出扇区偏移量 start
    mov dx, 0
    ; and ax, 0000000011111111b  ; 不知道这句的意图是啥,忘记得太快了!
    ; mov dword ax, al ; 错误用法
    ; mov cx, [BytesOfSector]
    mov cx, 512
    ; div cx操作计算FAT项的扇区偏移量,存储在ax中,dx中存储的是字节偏移量。
    div cx
    ; push dx
    add ax, SectorNumberOfFAT1    ; ax 是在FAT1区域的偏移。要把它转化为在软盘中的扇区号,需加上FAT1对软盘的偏移量。
    ; 用字节偏移量计算出扇区偏移量 end
   
    ; 读两个扇区。
    mov cl, 2
    mov bx, 0
    push es
    ; dx的值可能会在call ReadSector改变,所以先存储到栈中。
    push dx
    push ax
    mov ax, BaseOfFATEntry
    ; ReadSector把两个扇区的数据读取到BaseOfFATEntry:bx处。
    ; bx是什么?bx是0。
    mov es, ax
    pop ax
    ; 用扇区偏移量计算出在某柱面某磁道的扇区偏移量,可以直接调用ReadSector
    call ReadSector
    ; 恢复dx的值,此时,dx中的值是FAT项读取到的两个扇区中的字节偏移量。
    pop dx
    add bx, dx
    ;[es:bx]是FAT项的初始位置,从这个位置开始,复制2个字节到ax中。
    mov ax, [es:bx]
    pop es
    ; 根据FAT项偏移量是否占用整数个字节来计算FAT项的值。
    ; 若偏移量是整数个字节,ax的低12位是FAT项;反之,ax的高12位是FAT项。
    cmp byte [FATEntryIsInt], 0
    jz FATEntry_Is_Int
    ; 获取ax的高12位。
    shr ax, 4   
FATEntry_Is_Int:
    ; 获取ax的低12位。
    and ax, 0x0FFF

    ret

结尾

本篇介绍了:

    计算机启动的极简流程。
    1.44M软盘的结构。
    boot.的代码解释。

需要结合上一篇文章《写操作系统之搭建开发环境》才知道怎么运行boot中的代码。由于本文篇幅有点长,将在下篇《写操作系统之开发引导器》中讲解运行boot中的代码的方法。

祝一切顺利!
boot代码全文

org 0x7c00

    jmp    LABEL_START
    nop

    ; 下面是 FAT12 磁盘的头
  BS_OEMName      DB 'YOUR--OS'   ; OEM String, 必须 8 个字节
  BPB_BytsPerSec  DW 512          ; 每扇区字节数
  BPB_SecPerClus  DB 1            ; 每簇多少扇区
  BPB_RsvdSecCnt  DW 1            ; Boot 记录占用多少扇区
  BPB_NumFATs     DB 2            ; 共有多少 FAT 表
  BPB_RootEntCnt  DW 224          ; 根目录文件数最大值
  BPB_TotSec16    DW 2880         ; 逻辑扇区总数
  BPB_Media       DB 0xF0         ; 媒体描述符
  BPB_FATSz16     DW 9            ; 每FAT扇区数
  BPB_SecPerTrk   DW 18           ; 每磁道扇区数
  BPB_NumHeads    DW 2            ; 磁头数(面数)
  BPB_HiddSec     DD 0            ; 隐藏扇区数
  BPB_TotSec32    DD 0            ; wTotalSectorCount为0时这个值记录扇区数
  BS_DrvNum       DB 0            ; 中断 13 的驱动器号
  BS_Reserved1    DB 0            ; 未使用
  BS_BootSig      DB 29h          ; 扩展引导标记 (29h)
  BS_VolID        DD 0            ; 卷序列号
  BS_VolLab       DB 'YOUR--OS.02'; 卷标, 必须 11 个字节
  BS_FileSysType  DB 'FAT12   '   ; 文件系统类型, 必须 8个字节

LABEL_START:
    ; 0B800h是显存地址,gs存储显存地址。
    mov ax,    0B800h
    mov gs,    ax
    ; 把es设置成BaseOfLoader。
    mov ax, BaseOfLoader
    mov es, ax

    ; 复位软驱
    mov  ah, 00h
    mov  dl, 0
    int 13h
    ; FirstSectorOfRootDirectory的值是19,是根目录在软盘中的扇区号,也是扇区偏移量。
    mov ax,    FirstSectorOfRootDirectory
    mov cl, 1
   
    ; OffSetOfLoader是存储loader的内存空间的初始地址。
    mov bx, OffSetOfLoader
    ; 读取第19号扇区,存储到内存空间的初始地址是OffSetOfLoader的这片内存中。
    call ReadSector
    ; 在根目录中检查3个目录项,这是人为规定,假设根目录中只有3个目录项。
    mov cx, 3
   
    ; 执行这条指令后,[es:di]存储的就是根目录的第一个根目录项。文件名位于根目录项的最开始的11个字节。
    mov di, OffSetOfLoader
; 遍历根目录
SEARCH_FILE_IN_ROOT_DIRECTORY:
    cmp cx, 0
    ; 没有找到目标文件,跳转到FILE_NOT_FOUND开头的那段代码。
    jz FILE_NOT_FOUND
    push cx
    ; LoaderBinFileName是目标文件即loader的文件名的初始地址。
    mov si, LoaderBinFileName
    ; LoaderBinFileNameLength是目标文件的文件名的长度。
    mov cx, LoaderBinFileNameLength
    mov dx, 0
    mov bx, (80 * 18 + 40) * 2
; 开始检查当前目录项中存储的文件名是否和目标文件的文件名相同。方法是:检测每个字符是否相同。
COMPARE_FILENAME:
    ; 从[es:si]中读一个字符复制到al中。
    lodsb
    ;从根目录项的文件名中取一个字符和从LoaderBinFileName中获取的对应位置的字符进行比较。
    ;当二者不相等时,跳转到FILENAME_DIFFIERENT代码块执行。
    cmp al, byte [es:di]
    jnz FILENAME_DIFFIERENT
    ; cx是文件名的长度。
    ; 比较文件名函数结束的条件有两个:一个是对比完了所有字符;一个是发现了不相同的字符。
    dec cx
    ; 将di加1,逐个对比LoaderBinFileName和根目录项中的文件名。
    ; 将dx加1,统计已经比较过的字符的个数。
    inc di
    inc dx

    ; 当已经统计完了所有字符,并且所有字符都相同时,说明当前根目录项就是要目标文件的根目录项,跳转到FILE_FOUND块执行。
    cmp dx, LoaderBinFileNameLength
    jz FILE_FOUND
    ; 继续对比下一个字符。
    jmp COMPARE_FILENAME       
FILENAME_DIFFIERENT:
    mov al, 'D'
  mov ah, 0Ah
  mov [gs:(80 * 24 + 40) *2], ax


    pop cx        ; 在循环中,cx会自动减少吗?
    cmp cx, 0
    dec cx
    jz FILE_NOT_FOUND
    ; 低5位设置为0,其余位数保持原状。回到正在遍历的根目录项的初始位置。
    and di, 0xFFE0   
    add di, 32    ; 增加一个根目录项的大小
    jmp SEARCH_FILE_IN_ROOT_DIRECTORY
FILE_FOUND:
    mov al, 'S'
    mov ah, 0Ah
    mov [gs:(80 * 24 + 35) *2], ax
    ; 修改段地址和偏移量后,获取的第一个簇号错了
    ; 获取文件的第一个簇的簇号
    and di, 0xFFE0  ; 低5位设置为0,其余位数保持原状。回到正在遍历的根目录项的初始位置; 获取文件的第一个簇的簇号
    ; 文件的第一个簇号(可以理解为扇区号)在根目录项中的字节偏移量是0x1A。
    add di, 0x1A
    mov si, di
    mov ax, BaseOfLoader
    push ds
    mov ds, ax
    ; 把[ds:si]处的数据复制到ax中。也就是说,ax中存储着目标文件的第一个扇区的扇区号,同时也是这个文件的第一个FAT项的编号。
    lodsw
    pop ds   
    push ax
    ; 将会把从软盘中读取到的数据复制到[es:bx]开始的内存空间。
    mov bx, OffSetOfLoader
    ; 获取到文件的第一个簇号后,开始读取文件
READ_FILE:
    push bx
   
    ; 簇号就是FAT项的编号,同时也是文件块在数据区的扇区号。
    ; 用簇号计算出目标扇区在软盘中的的扇区号。
    add ax, 19
    add ax, 14
    ; 为什么要减去2?因为0号FAT项、1号FAT项不表示记录任何扇区信息,从2号FAT项开始记录数据区的扇区。
    ; 第2号FAT项记录数据区的第0号扇区。
    sub ax, 2
       
    ; 读取一个扇区的数据 start
    ; add ax, SectorNumberOfFAT1
    mov cl, 1
    pop bx   
    call ReadSector
    ;;xchg bx, bx
    ; 读取一个扇区到[es:bx]后,把下一个扇区读取到[es:bx+512]开始的内存。
  add bx, 512
    ; 读取一个扇区的数据 end
   
    ;jmp READ_FILE_OVER
       
    ; 执行pop后,ax中存储的是目标文件的第一个FAT项的编号。
    ; GetFATEntry能根据这个FAT项的编号获取这个FAT项的值,也就是下一个FAT项的编号。
    pop ax
    push bx
    call GetFATEntry
    pop bx
    push ax
    ;ax >= 0xFF8时,当前扇区是文件的最后一个扇区。
    cmp ax, 0xFF8
    ; 注意了,ax >= 0xFF8 时跳转,使用jc 而不是jz。昨天,一定是在这里弄错了,导致浪费几个小时调试。
    ;jz READ_FILE_OVER   
    ;jc READ_FILE_OVER   
    jnb READ_FILE_OVER   
   
    jmp READ_FILE
   
FILE_NOT_FOUND:
        mov al, 'N'
        mov ah, 0Ah
        mov [gs:(80 * 24 + 36) *2], ax
    jmp OVER

READ_FILE_OVER:
   
    ; 簇号就是FAT项的编号,同时也是文件块在数据区的扇区号。
    ; 用簇号计算出目标扇区在软盘中的的扇区号。
    add ax, 19
    add ax, 14
    sub ax, 2

    ; 读取一个扇区的数据 start
    mov cl, 1

    mov al, 'O'
    mov ah, 0Ah
    mov [gs:(80 * 24 + 33) * 2], ax
   
    ; 跳转到loader执行loader中的指令。
    jmp BaseOfLoader:OffSetOfLoader   
    jmp OVER

OVER:

    jmp $

BootMessage:    db    "Hello,World OS!"
;BootMessageLength:    db    $ - BootMessage
; 长度,需要使用 equ
BootMessageLength    equ    $ - BootMessage

FirstSectorOfRootDirectory    equ    19
SectorNumberOfTrack    equ    18
SectorNumberOfFAT1    equ    1

LoaderBinFileName:    db    "LOADER  BIN"
LoaderBinFileNameLength    equ    $ - LoaderBinFileName    ; 中间两个空格

FATEntryIsInt    equ 0        ; FAT项的字节偏移量是不是整数个字节:0,不是;1,是。
BytesOfSector    equ    512    ; 每个扇区包含的字节数量
; 根据FAT项的编号获取这个FAT项的值
GetFATEntry:
    ; 用FAT项的编号计算出这个FAT项的字节偏移量 start
    ; mov cx, 3
    ; mul cx
    ; mov cx, 2
    ;div cx        ; 商在al中,余数在ah中    ;
    push ax
    MOV ah, 00h
    mov dl, 0
    int 13h
   
    pop ax   
    mov dx, 0
    mov bx, 3
    mul bx
    mov bx, 2
    div bx
    ; 用FAT项的编号计算出这个FAT项的字节偏移量 end
    mov [FATEntryIsInt], dx
    ; 用字节偏移量计算出扇区偏移量 start
    mov dx, 0
    ; and ax, 0000000011111111b  ; 不知道这句的意图是啥,忘记得太快了!
    ; mov dword ax, al ; 错误用法
    ; mov cx, [BytesOfSector]
    mov cx, 512
    div cx
    ; push dx
    add ax, SectorNumberOfFAT1    ; ax 是在FAT1区域的偏移。要把它转化为在软盘中的扇区号,需加上FAT1对软盘的偏移量。
    ; mov ah, 00h

    ; 用字节偏移量计算出扇区偏移量 end
    mov cl, 2
    mov bx, 0
    push es
    push dx
    push ax
    mov ax, BaseOfFATEntry
    mov es, ax
    pop ax
    ; 用扇区偏移量计算出在某柱面某磁道的扇区偏移量,可以直接调用ReadSector
    call ReadSector
    pop dx
    add bx, dx
    mov ax, [es:bx]
    pop es
    ; 根据FAT项偏移量是否占用整数个字节来计算FAT项的值
    cmp byte [FATEntryIsInt], 0
    jz FATEntry_Is_Int
    shr ax, 4   
FATEntry_Is_Int:
    and ax, 0x0FFF
    ret

; 读取扇区
ReadSector:
    push ax
    push bp
    push bx
    mov bp, sp
    sub esp, 2
    mov byte [bp-2], cl
   
    ; ax 存储在软盘中的扇区号
    mov bl, SectorNumberOfTrack    ; 一个磁道包含的扇区数
    div bl    ; 商在al中,余数在ah中
    mov ch, al
    shr ch, 1    ; ch 是柱面号
    mov dh, al
    and dh, 1    ; dh 是磁头号
    mov dl, 0    ; 驱动器号,0表示A盘
    inc ah
    mov cl, ah
    ;add cl, 1    ; cl 是起始扇区号
    ; pop al        ; al 是要读的扇区数量
    mov al, [bp-2]
    add esp, 2
    mov ah, 02h    ; 读软盘
    pop bx
   
    int 13h

    pop bp
    pop ax
    ret   

BaseOfLoader    equ    0x9000
OffSetOfLoader    equ    0x100
BaseOfFATEntry    equ    0x1000


times    510 - ($ - $$)    db    0
dw    0xAA55

#573 入门必备 » 汇编程序 常见错误 » 2022-08-04 23:08:31

batsom
回复: 0

汇编程序在对源程序的汇编过程中,若检查出某语句有语法错误,随时在屏幕上给出出错信息.如操作人员指定的列表文件名(即.LST),汇编程序亦将在列表文件中出错的下面给出出错信息,以便操作人员即时查找错误,给予更正.MASM5.0出错信息格式如下:
源程序文件行:WARNING/ERROR错误信息码:错误描述信息
其中,错误描述信息码由五个字符组成.第一个是字母A,表示汇编语言程序出错;接着有一个数字指明出错类别:'2'为严重错误,'4'为严肃警告,'5'为建议性警告,最后三位为错误编号.
错误编号
错误描述
0
Block nesting error
嵌套出错.嵌套的过程,段,结构,宏指令或重复块等非正常结束.例如在嵌套语句中有外层的结束语句,而无内层的结束语局
1
Extra characters on line
一语句行有多余字符,可能是语句中给出的参数太多
2
Internal error-Register already defined
这是一个内部错误.如出现该错误,请记下发生错误的条件,并使用Product Assistance Request 表与Microsoft公司联系
3
Unkown type specifer
未知的类型说明符.例如类型字符拼错,把BYTE写成BIT,NEAR写成NAER等
4
Redefinition of symbol
符号重定义.同一标识符在两个位置上定义.在汇编第一遍扫描时,在这个标识符的第二个定义位置上给出这个错误
5
Symbol is multidefined
符号多重定义.同一标识符在两个位置上定义.在汇编第二遍扫描时,每当遇到这个标识符都给出这个错误
6
Phase error between passes
两次扫描间的遍错.一个标号在二次扫描时得到不同的地址值,就会给出这种错误.若在启动MASM时使用/D任选项,产生第一遍扫描的列表文件,它可帮助你查找这种错误
7
Already had ELSE clause
已有ELSE语句.在一个条件块里使用多于一个的ELSE语句
8
Must be in conditional block
没有在条件块里.通常是有ENDIF或ELSE语句,而无IF 语句
9
Symbol not defined
符号未定义,在程序中引用了未定义的标识符
10
Syntax error
语法错误.不是汇编程序所能识别的一个语句
11
Type illegal in context
指定非法类型.例如对一个过程指定BYTE类型,而不是NEAR或FAR
12
Group name must be unique
组名应是唯一的.作为组名的符号作为其他符号使用
13
Must be declared during pass 1
必须在第一遍扫描期间定义.在第一遍扫描期间,如一个符号在未定义前就引用,就会出现这种错误.
14
Illegal public declaration
一个标识符被非法的指定为PUBLIC类型
15
Symbol already defferent kind
重新定义一个符号为不同种类符号.例如一个段名重新被当作变量名定义使用
16
Reserved word used as symbol
把汇编语言规定的保留字作标识符使用
17
Forward reference illegal
非法的向前引用.在第一遍扫描期间,引用一个未定义符号.
18
Operand must be register
操作数位置上应是寄存器,但出现了标识符
19
Wrong type of register
使用寄存器出错
20
Operand must be segment or group
应该给出一个段名或组名.例如ASSUME语句中应为某段寄存器和指定一个段名或组名,而不应是别的标号或变量名等
21
Symbol has no segment
不知道标识符的段属性
22
Operand must be type specifier
操作数应给出类型说明,如NEAR,FAR,BYTE等
23
Symbol alread defined locally
以被指定为内部的标识符,企图在EXTRN语句中又定义外部标识
24
Segment paraneters are changed
段参数被改变.如同一标识符定义在不同段内
25
Improper align/combin type
段定义时的定位类型/组合类型使用出错
26
Reference to multidefined symbol
指令引用了多重定义的标识符
27
Operand expected
需要一个操作数,只有操作符
28
Operator expected
需要一个操作符,但只有操作数
29
Divdsion by 0 or overflow
除以0或溢出
30
Negative shift count
运算符SHL或SHR的移位表达式值为负数
31
Operand type must match
操作数类型不匹配.双操作数指令的两个操作数长度不一致,一个是字节,一个是字
32
Illegal use of external
外部符号使用出错
33
Must be record field name
应为记录字段名.在记录字段名位置上出现另外的符号
34
Must be record name or field name
应为记录名或记录字段名.在记录名或记录字段名位置上出现另外的符号
35
Operand must be size
应指明操作数的长度(如BYTE,WORD等).通常使用PTR运算即可改正
36
Must be variable,label,or constant
应该是变量名,标号,或常数的位置上出现了其他信息
37
Must be stucture field name
应该为结构字段名.在结构字段名位置上出现了另外的符号
38
Lefe operand must segment
操作数的左边应该是段的信息.如设DA1,DA2均是变量名,下列语句就是错误的:"MOV AX,DA1:DA2".DA1位置上应使用某段寄存器名
39
One operand must constant
操作数必须是常数.
40
Operand must be in same segment or one constant
"—"运算符用错.例如"MOV AL,—VAR",其中VAR是变量名,应有一常数参加运算.又如两个不同段的变量名相减出错
41
Normal type operand expected
要求给出一个正常的操作数.
42
Constant expected
要求给出一个常数.
43
Operand must have segment
运算符SEG用错.
44
Must be associated with data
在必须与数据段有关的位置上出现了代码段有关的项
45
Must be associated with code
在必须与代码段有关的位置上出现了数据段有关的项
46
Multiple base registers
同时使用了多个基址寄存器.如"MOV AX ,SI"
47
Multiple index registers
同时使用了多个变址寄存器.如"MOV AX ,SI"
48
Must be index or base register
指令仅要求使用基址寄存器或变址寄存器,而不能使用其他寄存器.
49
Illegal use of register
非法使用寄存器出错
50
Value is out of range
数值太大,超过允许值.例如:"MOV AL ,100H"
51
Operand not in current CS ASSUME segment
操作数不在当前代码段内.通常指转移指令的目标地址不在当前CS段内
52
Improper operand type
操作数类型使用不当.例如:"MOV VAR1,VAR2".两个操作数均为存储器操作数,不能汇编出目标代码
53
Jump out of range by %ld byte
条件转移指令跳转范围超过-128~ 127个字节.出错厂,信息同时给出超过的字节数
54
Index displacement must be constant
变址寻址的位移量必须是常数
55
Illegal register value
非法的寄存器值.目标代码中表达寄存器的值超过7
56
Immediate mode illegal
不允许使用立即数寻址.例如"MOV DS,CODE"其中CODE是段名,不能把段名作为立即数传送给段寄存器DS
57
Illegal size for operand
使用操作数大小(字节数)出错.例如:使用双字的存储器操作数
58
Byte register illegal
要求用字寄存器的指令使用了字节寄存器.如PUSH,POP指令的操作数寄存器必须是字寄存器
59
Illegal uer of CS register
指令中错误使用了段寄存器CS.如:"MOV CS,AX"CS不能做目的操作数
60
Must be accumulator register
要求用AX或AL的位置上使用可其他寄存器.如IN,OUT指令必须使用累加器AX或AL
61
Improper uer of segment register
不允许使用段寄存器的位置上使用了段寄存器.如"SHL DS,1"
62
Missing or unreachable CS
试图跳转去执行一个CS达不到的标号.通常是指缺少ASSUME语句中CS与代码段相关联
63
Operand combination illegal
双操作数指令中两个操作数组合出错
64
Near JMP/CALL to different CS
试图用NEAR属性的转移指令跳转到不在当前段的一个地址
65
Label cannot have segment override
段前缀使用出错
66
Must have instuction agter prefix
在重复前缀REP,REPE,REPNE后面必须有指令
67
Cannot override ES for destination
串操作指令中目的操作数不能用其他段寄存器替代ES
68
Cannot address with srgment register
指令中寻找一个操作数,但ASSUME语句中未指明哪个段寄存器与该操作数所在段有关联
69
Must be in segment block
指令语句没有在段内
70
Cannot use EVEN or ALIGN with byte alignment
在段定义伪指令的定位类型中选用BYTE,这时不能使用EVEN或ALIGN伪指令
71
Forward needs override or FAR
转移指令的目标没有在源程序中说明为FAR属性,可用PTR指定
72
Illegal value for DUP count
操作符DUP前的重复次数是非法的或未定义
73
Symbol id already external
在模块内试图定义的符号,它已在外部符号伪指令中说明
74
DUP nesting too deep
操作数DUP的嵌套太深
75
Illegak use of undefinde operand( )
不定操作符" "使用不当.例如"DB 10H DUP( 2)"
76
Too many valer for struc or record initialization
在定义结构变量或记录变量时,初始值太多
77
Angle brackets requored around initialized list
定义结构体变量时,初始值未用尖括号()括起来
78
Directive illegal structure
在结构体定义中的伪指令使用不当.结构定义中的伪指令语句仅二种:分号(;)开始的注释语句和用DB,DW等数据定义伪指令语句
79
Override with DUP illegal
在结构变量初始值表中使用DUP操作符出错
80
Field cannot be overridden
在定义结构变量语句中试图对一个不允许修改的字段设置初值
81
Override id of wrong type
在定义结构变量语句中设置初值时类型出错
82
Circular chain of EQU aliases
用等值语句定义的符号名,最后又返回指向它自己.如:
A EQU B
B EQU A
83
Cannot emulate cooprocessor opcode
仿真器不能支持的8087协处理器操作码
84
End of file,not END directive
源程序文件无END文件
85
Data emitted with no segment
语句数据没有在段内

#574 站务处理 » 管理团队招募 » 2022-08-03 23:46:23

batsom
回复: 4

管理团队招募!

1:linux技术爱好者
2:愿意分享技术和学习心得

硬性条件:在校大学生
想参与的朋友请留言!欢迎大家踊跃报名,水平高低无所谓,技术都是慢慢积累的!

#575 内核与硬件 » Bus options » 2022-08-03 23:39:47

batsom
回复: 0

Bus options (PCI etc.)
总线选项

PCI support
CONFIG_PCI
    PCI是最重要的内部总线,不但PCI与PCI Express设备依赖于它,
    而且USB/IDE/SATA/SCSI/火线(IEEE 1394)/PCMCIA/CardBus等各种内部和外部总线也都依赖于它.
    所以必须选"Y",除非你知道自己在干什么.

    Support mmconfig PCI config space access
      CONFIG_PCI_MMCONFIG
        允许通过mmconfig方式访问PCI config space,这种访问方式比传统的IO方式速度更快.
        建议开启.MMCONFIG的意思是"Memory-Mapped config",它是PCI Express引入的新总线枚举方式.
        背景知识:PCI设备都有一组叫做'Configuration Space'的寄存器,
        PCI-E设备在PCI的基础上又增加了一组叫做'Extended Configuration Space'的寄存器.
        这些寄存器都被映射到了内存中(Memory-Mapped),
        操作系统理应提供相应的API供设备驱动和诊断程序访问这些'Configuration Space'.
        但如果操作系统没有提供Memory-Mapped方式的API的话,
        这些驱动程序和诊断程序就必须自己根据操作系统的底层规则(IO方式)去访问,
        这显然就增加了开发难度.这个选项的目的就是提供Memory-Mapped方式的API.
       
    Read CNB20LE Host Bridge Windows
    CONFIG_PCI_CNB20LE_QUIRK
        CNB20LE芯片组PCI热插拔支持.除非你非常明确的知道你需要它,否则请关闭此项.
       
    PCI Express support
    CONFIG_PCIEPORTBUS
        PCI Express是PCI的升级版并在软件层与PCI兼容,其目标是统一电脑内部总线.
        基本上只要不是古董机,都早已支持PCI-E了.选"Y".
        PCI Express Hotplug driver
        CONFIG_HOTPLUG_PCI_PCIE
            如果你的主板和设备都支持PCI Express热插拔就可以选上.
        Root Port Advanced Error Reporting support
        CONFIG_PCIEAER
            PCI Express Root Port Advanced Error Reporting (AER) 驱动支持.
            这样,发送到 Root Port 的 Error reporting messages 就会由
            PCI Express AER 处理.建议开启.背景知识:PCI Express 定义了两种错误报告范例:(1)baseline,
            所有PCI-E组件都必须要支持,功能也比较基础.(2)AER(Advanced Error Reporting),功能比较高级,
            也更可靠,但并不要求所有组件都支持.

            PCI Express ECRC settings control
            CONFIG_PCIE_ECRC
                允许覆写firmware/bios设置的 PCI Express ECRC(端对端循环冗余校验).
                建议关闭,除非你确实知道为什么要开启.
            PCIe AER error injector support
            CONFIG_PCIEAER_INJECT
                允许 PCI-E AER 注入,仅用于测试目的.

        PCI Express ASPM control
        CONFIG_PCIEASPM
            PCI Express ASPM(Active State Power Management) 和 Clock Power Management 支持.
            这是PCI-E规范制定的一种电源管理方案,可以在设备空闲时采用节电模式.
            建议开启.ASPM可以在运行时通过 /sys/module/pcie_aspm/parameters/policy 进行开启或关闭.

            Debug PCI Express ASPM
            CONFIG_PCIEASPM_DEBUG
                仅供调试.
               
            Default ASPM policy
                默认的ASPM电源管理策略.下面的三个选项:"BIOS default"表示使用BIOS中的设置作为默认.
                "Powersave"表示在可能的情况下,默认使用"L0s"和"L1",以尽可能节约电力.
                "Performance"表示禁止使用"L0s"和"L1"(即使BIOS开启也同样禁止),以保证最高性能.
        PCIe Downstream Port Containment support
            PCIe下游端口遏制支持
        PCIe Precision Time Measurement support
            PCIe精确时间测量支持

Message Signaled Interrupts (MSI and MSI-X)
CONFIG_PCI_MSI
    PCI/PCI-E支持三类中断:(1)INTx使用传统的IRQ中断,可以与现行的驱动程序和操作系统兼容.
    (2)MSI是PCI2.2规范中新增的,通过写入特殊的内存地址来触发和发送中断,
    该种方式脱离了中断引脚带来的数目限制,并且延迟小/效率高.
    不过MSI方式将中断全部落在单个CPU上,对多核CPU利用不佳.(3)MSI-X是在PCI3.0规范中新增的,
    在MSI的基础上,支持更多的消息数量以及独立的消息地址,可以自动在多个CPU上分担中断,
    更适合多CPU系统.建议开启.开启后,也可以使用"pci=nomsi"内核引导参数关闭MSI特性.
   
PCI Debugging
CONFIG_PCI_DEBUG
    将PCI调试信息输出到系统日志里.如果你想诊断PCI设备的故障,可以开启,否则应该关闭.
   
Enable PCI resource re-allocation detection
CONFIG_PCI_REALLOC_ENABLE_AUTO
    让内核自动检测"是否需要重新分配PCI资源".即使此项已开启,你依然可以用"pci=realloc=[on|off]"来覆盖它.
    此项仅在已开启CONFIG_PCI_IOV的情况下才有意义.
    此时,如果BIOS没有为SR-IOV(Single-Root I/O Virtualization) BAR(基地址寄存器)分配资源,
    那么内核将会自动对PCI资源进行重新分配.建议与CONFIG_PCI_IOV同开关.
   
  PCI Stub driver
  CONFIG_PCI_STUB
      PCI Stub driver 的作用是将PCI设备跟目前绑定的驱动分离,
      暂时由其接管,最后再交给虚拟机自己去驱动这个PCI设备.
     
  Xen PCI Frontend
  CONFIG_XEN_PCIDEV_FRONTEND
      如果你使用XEN的半虚拟化技术,并且你的硬件支持IOMMU,那么可以开启此项,否则应该关闭.
     
  Interrupts on hypertransport devices
  CONFIG_HT_IRQ
      允许本地的HyperTransport设备使用中断.这个一般用于AMD平台,Intel平台不支持这个.
     
PCI IOV support
CONFIG_PCI_IOV
    PCI I/O Virtualization支持.这需要硬件支持IOMMU技术(AMD-Vi,Intel VT-d).
   
PCI PRI support
CONFIG_PCI_PRI
    PCI Page Request Interface 支持.它允许IOMMU之后的设备能够从页错误中恢复过来.
    这需要硬件支持IOMMU技术(AMD-Vi,Intel VT-d).
   
PCI PASID support
CONFIG_PCI_PASID
    PASID(Process Address Space Identifiers)可以被PCI设备用来同时访问多个IO地址空间.
    这需要硬件支持IOMMU技术(AMD-Vi,Intel VT-d).

Support for PCI Hotplug
   支持PCI Hotplug
DesignWare PCI Core Support
   DesignWare PCI核心支持
PCI host controller drivers
   PCI主控制器驱动程序
PCI Endpoint
   PCI端点
PCI switch controller drivers
   PCI开关控制器驱动器
   
PCI IO-APIC hotplug support
CONFIG_PCI_IOAPIC
    PCI IO-APIC 热插拔支持.
   
ISA-style bus support on modern systems
    现代系统上的ISA式总线支持
   
ISA-style DMA support
CONFIG_ISA_DMA_API
    ISA-style DMA控制器支持.目前基本只有LPC总线设备需要使用,最常见的是串口,并口,
    PS/2键盘,Super I/O芯片(可以使用Superiotool和sensors-detect工具检测).
    不确定的选"Y".[说明]这是一个历史遗留问题,对于ISA架构,DMA操作是由一个专用的"DMA控制器"
    (最常见的是Intel 8237)来执行的,但是到了PCI架构,
    由于每一个PCI设备都可以控制PCI总线(成为"bus master")并直接读写系统内存,所以"DMA控制器"又消失了.
    此选项只是为那些需要"ISA-DMA控制器"的设备提供了兼容性接口(API)而已.
   
PCCard (PCMCIA/CardBus) support
CONFIG_PCCARD
    PCCard(PCMCIA/CardBus/ExpressCard)接口通常出现在笔记本电脑上,
    这些接口卡通常大小与信用卡差不多,厚度大约3-5毫米.
    注意:必须要配合pcmciautils工具才能正常使用PCMCIA设备.
    16-bit PCMCIA support
    CONFIG_PCMCIA
        老旧的 16-bit PCMCIA 卡支持
        Load CIS updates from userspace
        CONFIG_PCMCIA_LOAD_CIS
            有些PCMCIA卡需要从用户空间更新CIS(Card Information Structure)之后才能正常工作.
            开启此项后,内核将可以使用内置的固件加载器和热插拔子系统自动加载CIS,而不再需要用户空间工具的辅助.
            建议选"Yes".
    32-bit CardBus support
    CONFIG_CARDBUS
        常见的PCMCIA卡基本上都是32位的CardBus与ExpressCard设备.如果你有这样的卡,就选"Yes".
        由于绝大多数的卡都是"yenta-compatible"的,所以一般你还需要选中CONFIG_YENTA项.
    CardBus yenta-compatible bridge support
    CONFIG_YENTA
        使用PCMCIA卡的基本上都需要选择这一项,子项是一些拥有自己特定扩展的硬件,请按实际情况选择.
    {省略的部分请按照自己实际使用的PCMCIA卡选择}
    Special initialization for O2Micro bridges
        O2Micro网桥的特殊初始化
    Special initialization for Ricoh bridges
        理光桥的特殊初始化
    Special initialization for TI and EnE bridges
        TI和EnE网桥的特殊初始化
       Auto-tune EnE bridges for CB cards
           自动调整CB卡的EnE网桥
    Special initialization for Toshiba ToPIC bridges
        Toshiba ToPIC网桥的特殊初始化
  Cirrus PD6729 compatible bridge support
    Cirrus PD6729兼容桥接支持
    i82092 compatible bridge support
       i82092兼容网桥支持

Support for PCI Hotplug
CONFIG_HOTPLUG_PCI
    PCI热插拔不仅仅针对PCI和PCI-E设备,也包括CardBus与ExpressCard设备.请按需选择.
{省略的部分请按照自己实际使用PCI控制器进行选择}

RapidIO support
CONFIG_RAPIDIO
    RapidIO总线支持.这种总线主要用于嵌入式系统.
    Discovery timeout duration (seconds)
    CONFIG_RAPIDIO_DISC_TIMEOUT
        等待主机完成枚举(也就是初始化)的超时秒数.
    Enable RapidIO Input/Output Ports
    CONFIG_RAPIDIO_ENABLE_RX_TX_PORTS
        开启所有 RapidIO Input/Output 端口.
    DMA Engine support for RapidIO
    CONFIG_RAPIDIO_DMA_ENGINE
        使用DMA引擎(CONFIG_DMADEVICES)进行RapidIO数据传输
    RapidIO subsystem debug messages
    CONFIG_RAPIDIO_DEBUG
        将RapidIO调试信息输出到系统日志里.如果你想诊断RapidIO设备的故障,可以开启,否则应该关闭.
    {省略的部分请按照自己实际使用的控制器进行选择}
   
Mark VGA/VBE/EFI FB as generic system framebuffer
    将VGA / VBE / EFI FB标记为通用系统帧缓冲区

页脚

Powered by FluxBB

本站由XREA提供空间支持