就在昨天,我面臨了研究所最後的關卡,
論文口試,心中雖然不安,卻也不是很煩惱,
理所當然的口試,理所當然的過關,
一切應該都在預料之中,
跟想像中差不多,我講的有點結巴,但是還是全場講完了
雖然不是完美,但也表現的不錯了,對我而言
最後,在口試完的那一剎那,還真是爽阿...............
接下來,就是人生的另一道關卡了,希望我在邁入社會後能有不同的人生體驗阿

eager 發表在 痞客邦 留言(0) 人氣()

這是OS入門的好文章...
強烈推薦
我在大陸抓的,作者不明...不過寫出這種好東西的好人應該不會介意我分享他的文章
要是有人知道原作者,麻煩通知我一下,我會標注
謝謝
============================
自由软件社区是一个充满自由和梦想的地方,在10余年的时间里它创造了一个又一个奇
迹。然而,这些奇迹的创造者不只是Stallman,也不只是Linus Torvalds,而是活跃在
世界各地的不计其数的开发人员。
在使用各种功能强大的自由软件时,我总会对其开发者充满崇敬之情,期盼有朝一日自
己也能成为他们中的一员。很多对自由社区充满向往之情的人,虽然也想努力融身于其
中,但又不知该怎么做。那么,就请与我们一起从编写一个简单的操作系统开始吧!
我们要做的事情
有人可能担心自己既没有学过计算机原理,也没有学过操作系统原理,更不懂汇编语言
,对C语言也一知半解,能写操作系统吗?答案是没问题。我将带大家一步一步完成自己
的操作系统。当然如果学一学上述内容再好不过。
首先要明确处理器(也就是CPU)控制着计算机。对PC而言,启动的时候,CPU都处在实模
式状态,相当于只是一个Intel 8086处理器。也就是说,即使你现在拥有一个奔腾处理
器,它的功能也只能是8086级别。从这一点上来讲,可以使用一些软件把处理器转换到
著名的保护模式。只有这样,我们才可以充分利用处理器的强大功能。
编写操作系统开始是对BIOS控制,取出存储在ROM里的程序。BIOS是用来执行POST(Powe
r On Self Test,自检)的。自检是检查计算机的完整性(比如外设是否工作正常、键盘
是否连接等)。这一切完成以后,你就会听到PC喇叭发出一声清脆的响声。如果一切正常
,BIOS就会选择一个启动设备,并且读取该设备的第一扇区(即启动扇区),然后控制过
程就会转移到指定位置。启动设备可能是一个软盘、光盘、硬盘,或者其它所选择的设
备。在此我们把软盘作为启动设备。如果我们已经在软盘的启动扇区里写了一些代码,
这时它就被执行。因此,我们的目的很明确,就是往软盘的启动扇区写一些程序。
首先使用8086汇编来写一个小程序,然后将其拷贝至软盘的启动扇区。为了实现拷贝,
要写一个C程序。最后,使用软盘启动计算机。
需要的工具
● as86:这是一个汇编程序,它负责把写的代码转换成目标文件。
● ld86:这是一个连接器,as86产生的目标代码由它来转换成真正的机器语言。机器语
言是8086能够解读的形式。
● GCC:著名的C编程器。因为我们需要写一个C程序将自己的OS转移到软盘中。
● 一张空软盘:它用于存储编写的操作系统,也是启动设备。
● 一台装有Linux的计算机:这台机器可以很旧,386、486都可以。
在大部分标准Linux发行版中都会带有as86和ld86。在我使用的Red Hat 7.3中就包含有
这两个工具,并且在默认的情况下,它已经安装在机器里。如果使用的Linux没有这两个
工具,可以从网上下载(http://www.cix.co.uk/~mayday/),这两个工具都包含在一个名
为bin86的软件包中。此外,有关的文档也可以在网上获得(www.linux.org/docs/ldp/h
owto/Assembly-HOWTO/as86.html)。
开始工作
使用一个你喜欢的编辑器输入以下内容:
entry start
start:
mov ax,#0xb800
mov es,ax
seg es
mov [0],#0x41
seg es
mov [1],#0x1f
loop1: jmp loop1
这是as86可以读懂的一段汇编程序。第一个句子指明了程序的入口点,声明整个过程从
start处开始。第二行指明了start的位置,说明整个程序要从start处开始执行。0xb80
0是显存的开始地址。#表明其后是一个立即数。执行语句:
mov ax,#oxb800
ax寄存器的值就变为0xb800,这就是显存的地址。下面再将这个值移至es寄存器,es是
附加段寄存器。请记住8086有一个分段的体系结构。它的各段寄存器为代码段、数据段
、堆栈段和附加段,对应的寄存器名称分别为cs、ds、ss和es。事实上,我们把显存地
址送入了附加段,因此,任何送入附加段的东西都会被送到显存中。
要在屏幕上显示字符,就需要向显存中写两个字节。前一个是所要显示字符的ASCⅡ值,
第二个字节表示该字符的属性。属性包括字符的前景色、背景色及是否闪烁等等。seg
es指明下一个将要执行的指令是指向es段的。所以,我们把值0x41(在ASCⅡ中表示的字
符是A)送到显存的第一个字节中。接下来要把字符的属性送到下一个字节当中。在此输
入的是0x1f,该属性指的是在蓝色背景下显示白色的字符。因此,如果执行这个程序,
就可以在屏幕上得到显示在蓝底上的一个白色的A。接着是一个循环。因为在执行完显示
字符的任务后,要么让程序结束,要么使用一个循环使其永远运行下去。把该文件命名
为boot.s,然后存盘。
此处显存的概念说得不是很清楚,有必要进一步解释一下。假设屏幕由80列×25行组成
,那么第一行就需要160字节,其中一个字节用于表示字符,另外一个字节用于表示字符
的属性。如果要在第三行显示某一字符的话,就要跳过显存的第0和1字节(它们是用于显
示第1列的),第2和3字节(它们是用于显示第2列的),然后把需要显示字符的ASCⅡ码值
入第4字节,把字符的属性写入第5字节。
把程序写至启动扇区
下面写一个C程序,把我的操作系统写入软盘第一扇区。程序内容如下:
#include <sys/types.h> /* unistd.h 需要这个文件 */
#include <unistd.h> /* 包含有read和write函数 */
#include <fcntl.h>
int main()
{
char boot_buf[512];
int floppy_desc, file_desc;
file_desc = open("./boot", O_RDONLY);
read(file_desc, boot_buf, 510);
close(file_desc);
boot_buf[510] = 0x55;
boot_buf[511] = 0xaa;
floppy_desc = open("/dev/fd0", O_RDWR);
lseek(floppy_desc, 0, SEEK_CUR);
write(floppy_desc, boot_buf, 512);
close(floppy_desc);
}
首先,以只读模式打开boot文件,然后在打开文件时把文件描述符复制到file_desc变量
中。从文件中读取510个字符,或者读取直到文件结束。在本例中由于文件很小,所以是
读取至文件结束。然后关闭文件。
最后4行代码打开软盘驱动设备(一般来说是/dev/fd0)。使用lseek找到文件开始处,然
后从缓冲中向软盘写512个字节。
在read、write、open和lseek的帮助页中,可以看到与函数所有有关的参数及其使用方
法。程序中有两行比较难懂:
boot_buf[510] = 0x55;
boot_buf[511] = 0xaa;
该信息是用于BIOS的,如果它识别出该设备是一个可启动的设备,那么在第510和511的
位置,该值就应该是0x55和0xaa。程序会把文件boot读至名为boot_buf的缓冲中。它要
求改变第510和第511字节,然后把boot_buf写至软盘之上。如果执行代码,软盘上的前
512字节就包含了启动代码。最后,把文件存为write.c。
编译运行
使用下面的命令把文件变为可执行文件:
as86 boot.s -o boot.o
ld86 -d boot.o -o boot
cc write.c -o write
首先将boot.s文件编译成目标文件boot.o,然后将该文件连接成最终的boot文件。最后
C程序编译成可执行的write文件。
插入一个空白软盘,运行以下程序:
/write
重新启动电脑,进行BIOS的界面设置,并且把软盘设为第一个启动的设备。然后插入软
盘,电脑从软盘上启动。
启动完成后,在屏幕上可以看到一个字母A(蓝底白字),启动速度很快,几乎是在瞬间完
成。这就意味着系统已经从我们制作的软盘上启动了,并且执行了刚才写入启动扇区的
程序。现在,它正处在一个无限循环的状态。所以,如果想进入Linux,必需拿掉软盘,
并且重启机器。
至此,这个操作系统就算完成了,虽然它没有实现什么功能,但是它已经可以启动机器
了。
下一期我将在这个启动扇区程序里加入一些代码,使它可以做一些比较复杂的事情(比如
使用BIOS中断、保护模式切换等等)。
=================================
上一期,我讲述了如何在软盘的启动扇区写一些代码,然后再从软盘启动的过程。制作
好一个启动扇区,在切换到保护模式之前,我们还应该知道如何使用BIOS中断。BIOS中
断是一些由BIOS提供的、为了使操作系统的创建更容易的低级程序。在本文中,我们将
学习处理BIOS的中断。
为什么要用BIOS
BIOS会把启动扇区拷贝至RAM中,并且执行这些代码。除此之外,BIOS还要做很多其它的
事情。当一个操作系统刚开始启动时,系统中并没有显卡驱动、软盘驱动等任何驱动程
序。因此,启动扇区中不可能包含任何一个驱动程序,我们要采取其它的途径。这个时
候,BIOS就可以帮助我们了。BIOS中包含有各种可以使用的程序,包括检测安装的设备
、控制打印机、计算内存大小等用于各种目的的程序。这些程序就是所说的BIOS中断。
如何调用BIOS中断
在一般的程序设计语言中,函数的调用是一件非常容易的事情。比如在C语言中,如果有
一个名为display的程序,它带有两个参数,其中参数noofchar表示显示的字符数,参数
attr表示显示字符的属性。那么要调用它,只需给出程序的名称即可。对于中断的调用
,我们使用的是汇编语言中的int指令。
比如,在C语言中要显示一些东西时,使用的指令如下所示:
display(nofchar,attr);
而使用BIOS时,要实现相同功能使用的指令如下:
int 0x10
如何传递参数
在调用BIOS中断之前,我们需要先往寄存器中送一些特定的值。假设要使用BIOS的中断
13h,该中断的功能是把数据从软盘传送至内存之中。在调用该中断之前,要先指定拷贝
数据的段地址,指定驱动器号、磁道号、扇区号,以及要传送的扇区数等等。然后,就
要往相应的寄存器送入相应的值。在进行下面的步骤前,读者有必要对这一点有比较明
确地认识。
此外,一个比较重要的事实是同一个中断往往可以实现各种不同的功能。中断所实现的
确切功能取决于所选择的功能号,功能号一般都存在ah寄存器之中。比如中断13h可以用
于读磁盘、写磁盘等功能,如果把3送入ah寄存器中,那么中断选择的功能就是写磁盘;
如果把2送入ah寄存器中,选择的功能则是读磁盘等。
我们要做的事情
这次我们的源代码由两个汇编语言程序和一个C程序组成。第一个汇编文件是引导扇区的
代码。在引导扇区中,我们写的代码是要把软盘中第二扇区拷贝至内存段的0x500处(地
址是0x5000,即偏移地址为0)。这时我们需要使用BIOS的中断13h。这时启动扇区的代码
就会把控制权转移至0x500处。在第二个汇编文件中,代码会使用BIOS中断10h在屏幕上
显示一个信息。C程序实现的功能则是把可执行的文件1拷贝至启动扇区,把可执行的文
件2拷贝至软盘的第二扇区。
启动扇区代码
使用中断13h,启动扇区把软盘第二扇区里的内容加载至内存的0x5000处(段地址为0x50
0)。下面的代码是用于实现这一目的的代码,将其保存至文件sbect.s中。
LOC1=0x500
entry start
start:
mov ax,#LOC1
mov es,ax
mov bx,#0
mov dl,#0
mov dh,#0
mov ch,#0
mov cl,#2
mov al,#1
mov ah,#2
int 0x13
jmpi 0,#LOC1
上面代码第一行类似于一个宏。接下去的两行则是把值0x500加载至es寄存器中,这是软
盘上第二扇区代码将拷贝到的地方(第一扇区是启动扇区)。这时,把段内的偏移设为0。
接下来把驱动器号送入dl寄存器中,其中磁头号送入dl寄存器中,磁道号送入ch寄存器
中,扇区号送入cl寄存器中,扇区数送入al寄存器之中。我们想要实现的功能是把扇区
2、磁道号为0、驱动器号为0的内容送至段地址0x500处。所有这些参数都和1.44MB的软
盘相对应。
把2送入ah寄存器中,是选择了由中断13h提供的相应功能,即实现从软驱转移数据的功
能。
最后调用中断13h,并且转至偏移为0的段地址0x500处。
第二个扇区的代码
第二个扇区中的代码如下所示(把这些代码保存至文件sbect2.s之中):
entry start
start:
mov ah,#0x03
xor bh,bh
int 0x10
mov cx,#26
mov bx,#0x0007
mov bp,#mymsg
mov ax,#0x1301
int 0x10
loop1: jmp loop1
mymsg:
byte 13,10
ascii “Operating System is Loading......”
上面代码将被加载至段地址为0x500处,并且被执行。在这段代码中,使用了中断10h来
获取目前的光标位置,然后显示信息。
从第3行到第5行用于得到目前光标的位置,在此中断10h选用的是功能3。然后,清除了
bh寄存器的内容,并把字符串送至ch寄存器中。在bx中,我们送入了页码及显示的属性
。此处,我们想要在黑背景上显示白色的字符。然后,把要显示字符的地址送到bp之中
,信息由两个字节组成,其值分别为13的10,它们分别对应回车和LF(换行)的ASCⅡ值。
接下来是一个由29个字符组成的串;在下面实现的功能是输出字符串然后移动光标;最
后是调用中断,然后进入循环。
C程序代码
C程序的源代码如下所示,将其存储为write.c文件。
#include <sys/types.h> /* unistd.h needs this */
#include <unistd.h> /* contains read/write */
#include <fcntl.h>
int main()
{
char boot_buf[512];
int floppy_desc, file_desc;
file_desc = open(“./bsect”, O_RDONLY);
read(file_desc, boot_buf, 510);
close(file_desc);
boot_buf[510] = 0x55;
boot_buf[511] = 0xaa;
floppy_desc = open(“/dev/fd0”, O_RDWR);
lseek(floppy_desc, 0, SEEK_SET);
write(floppy_desc, boot_buf, 512);
file_desc = open(“./sect2”, O_RDONLY);
read(file_desc, boot_buf, 512);
close(file_desc);
lseek(floppy_desc, 512, SEEK_SET);
write(floppy_desc, boot_buf, 512);
close(floppy_desc);
}
在上一期中,我曾经介绍过如何操作能启动的软盘。现在这一个过程稍微有点不同,首
先把由bsect.s编译出来的可执行文件bsect拷贝至软盘的启动扇区。然后再把由sect2.
s产生的可执行文件sect2拷贝至软盘的第二个扇区。
把上述文件置于同一目录之下,然后分别对其进行编译,方法如下所示:
as86 bsect.s -o bsect.o
ld86 -d bsect.o -o bsect
对sect2.s文件重复以上的操作,得出可执行文件sect2。编译write.c,插入软盘后执行
write文件,命令如下所示:
cc write.c -o write
/write
下一步我们要做的事情
从软盘启动以后,可以看到显示出来的字符串。这是使用了BIOS中断来完成的。下一期
要做的事情是在这个操作系统中实现实模式向保护模式的转换。
=================================================
在上两期中(自己动手写操作系统1,2),我向大家讲述了如何使用Linux提供的开发
工具在软盘的启动扇区写一些代码,以及如何调用BIOS的问题。现在,这个操作系统已
经越来越接近当年Linus Torvalds的那个具有“历史意义”的Linux内核了。因此,要马
上把这个系统切换到保护模式之下。
什么是保护模式
自从1969年推出第一个微处理器以来,Intel处理器就在不断地更新换代,从8086、808
8、80286,到80386、80486、奔腾、奔腾Ⅱ、奔腾4等,其体系结构也在不断变化。803
86以后,提供了一些新的功能,弥补了8086的一些缺陷。这其中包括内存保护、多任务
及使用640KB以上的内存等,并仍然保持和8086家族的兼容性。也就是说80386仍然具备
了8086和80286的所有功能,但是在功能上有了很大的增强。早期的处理器是工作在实模
式之下的,80286以后引入了保护模式,而在80386以后保护模式又进行了很大的改进。
在80386中,保护模式为程序员提供了更好的保护,提供了更多的内存。事实上,保护模
式的目的不是为了保护程序,而是要保护程序以外的所有程序(包括操作系统)。
简言之,保护模式是处理器的一种最自然的模式。在这种模式下,处理器的所有指令及
体系结构的所有特色都是可用的,并且能够达到最高的性能。
保护模式和实模式
从表面上看,保护模式和实模式并没有太大的区别,二者都使用了内存段、中断和设备
驱动来处理硬件,但二者有很多不同之处。我们知道,在实模式中内存被划分成段,每
个段的大小为64KB,而这样的段地址可以用16位来表示。内存段的处理是通过和段寄存
器相关联的内部机制来处理的,这些段寄存器(CS、DS、SS和ES)的内容形成了物理地
址的一部分。具体来说,最终的物理地址是由16位的段地址和16位的段内偏移地址组成
的。用公式表示为:
物理地址=左移4位的段地址+偏移地址。
在保护模式下,段是通过一系列被称之为“描述符表”的表所定义的。段寄存器存储的
是指向这些表的指针。用于定义内存段的表有两种:全局描述符表(GDT)和局部描述符表
(LDT)。GDT是一个段描述符数组,其中包含所有应用程序都可以使用的基本描述符。在
实模式中,段长是固定的(为64KB),而在保护模式中,段长是可变的,其最大可达4GB。
LDT也是段描述符的一个数组。与GDT不同,LDT是一个段,其中存放的是局部的、不需要
全局共享的段描述符。每一个操作系统都必须定义一个GDT,而每一个正在运行的任务都
会有一个相应的LDT。每一个描述符的长度是8个字节,格式如图3所示。当段寄存器被加
载的时候,段基地址就会从相应的表入口获得。描述符的内容会被存储在一个程序员不
可见的影像寄存器(shadow register)之中,以便下一次同一个段可以使用该信息而不用
每次都到表中提取。物理地址由16位或者32位的偏移加上影像寄存器中的基址组成。实
模式和保护模式的不同可以从图1和图2中很清楚地看出来。
图1 实模式的寻址
图2 保护模式下的寻址
图3 段描述俯的格式
此外,还有一个中断描述符表(IDT)。这些中断描述符会告诉处理器到那里可以找到中断
处理程序。和实模式一样,每一个中断都有一个入口,但是这些入口的格式却完全不同
。因为在切换到保护模式的过程中没有使用到IDT,所以在此就不多做介绍了。
进入保护模式
80386有4个32位控制寄存器,名字分别为CR0、CR1、CR2和CR3。CR1是保留在未来处理器
中使用的,在80386中没有定义。CR0包含系统的控制标志,用于控制处理器的操作模式
和状态。CR2和CR3是用于控制分页机制的。在此,我们关注的是CR0寄存器的PE位控制,
它负责实模式和保护模式之间的切换。当PE=1时,说明处理器运行于保护模式之下,其
采用的段机制和前面所述的相应内容对应。如果PE=0,那么处理器就工作在实模式之下

切换到保护模式,实际就是把PE位置为1。为了把系统切换到保护模式,还要做一些其它
的事情。程序必须要对系统的段寄存器和控制寄存器进行初始化。把PE位置1后,还要执
行跳转指令。过程简述如下:
1.创建GDT表;
2.通过置PE位为1进入保护模式;
3.执行跳转以清除在实模式下读取的任何指令。
下面使用代码来实现这个切换过程。
需要的东西
◆ 一张空白软盘
◆ NASM编译器
下面是整个程序的源代码:
org 0x07c00; 起始地址是0000:7c00
jmp short begin_boot ; 跳过其它的数据,跳转到引导程序的开始处
bootmesg db "Our OS boot sector loading ......"
pm_mesg db "Switching to protected mode ...."
dw 512 ; 每一扇区的字节数
db 1 ; 每一簇的扇区数
dw 1 ; 保留的扇区号
db 2
dw 0x00e0
dw 0x0b40
db 0x0f0
dw 9
dw 18
dw 2 ; 读写扇区号
dw 0 ; 隐藏扇区号
print_mesg :
mov ah,0x13 ; 使用中断10h的功能13,在屏幕上写一个字符串
mov al,0x00 ; 决定调用函数后光标所处的位置
mov bx,0x0007 ; 设置显示属性
mov cx,0x20 ; 在此字符串长度为32
mov dx,0x0000 ; 光标的起始行和列
int 0x10 ; 调用BIOS的中断10h
ret ; 返回调用程序
get_key :
mov ah,0x00
int 0x16 ; Get_key使用中断16h的功能0,读取下一个字符
ret
clrscr :
mov ax,0x0600 ; 使用中断10h的功能6,实现卷屏,如果al=0则清屏
mov cx,0x0000 ; 清屏
mov dx,0x174f ; 卷屏至23,79
mov bh,0 ; 使用颜色0来填充
int 0x10 ; 调用10h中断
ret
begin_boot :
call clrscr ; 先清屏
mov bp,bootmesg ; 提供串地址
call print_mesg ; 输出信息
call get_key ; 等待用户按下任一键
bits 16
call clrscr ; 清屏
mov ax,0xb800 ; 使gs指向显示内存
mov gs,ax ; 在实模式下显示一个棕色的A
mov word [gs:0],0x641 ; 显示
call get_key ; 调用Get_key等待用户按下任一键
mov bp,pm_mesg ; 设置串指针
call print_mesg ; 调用print_mesg子程序
call get_key ; 等待按键
call clrscr ; 清屏
cli ; 关中断
lgdt[gdtr] ; 加载GDT
mov eax,cr0
or al,0x01 ; 设置保护模式位
mov cr0,eax ; 将更改后的字送至控制寄存器中
jmp codesel:go_pm
bits 32
go_pm :
mov ax,datasel
mov ds,ax ; 初始化ds和es,使其指向数据段
mov es,ax
mov ax,videosel ; 初始化gs,使其指向显示内存
mov gs,ax
mov word [gs:0],0x741 ; 在保护模式下显示一个白色的字符A
spin : jmp spin ; 循环
bits 16
gdtr :
dw gdt_end-gdt-1 ; gdt的长度
dd gdt ; gdt的物理地址
gdt
nullsel equ $-gdt ; $指向当前位置,所以nullsel = 0h
gdt0 ; 空描述符
dd 0
dd 0 ; 所有的段描述符都是64位的
codesel equ $-gdt ; 这是8h也就是gdt的第二个描述符
code_gdt
dw 0x0ffff ; 段描述符的界限是4Gb
dw 0x0000
db 0x00
db 0x09a
db 0x0cf
db 0x00
datasel equ $-gdt
data_gdt
dw 0x0ffff
dw 0x0000
db 0x00
db 0x092
db 0x0cf
db 0x00
videosel equ $-gdt
dw 3999
dw 0x8000 ; 基址是0xb8000
db 0x0b
db 0x92
db 0x00
db 0x00
gdt_end
times 510-($-$$) db 0
dw 0x0aa55
把上面的代码存在一个名为abc.asm的文件之中,使用命令nasm abc.asm,将得出一个名
为abc的文件。然后插入软盘,输入命令:dd if=abc of=/dev/fd0。该命令将把文件ab
c写入到软盘的第一扇区之中。然后重新启动系统,就会看到如下的信息:
*Our os booting................
* A (棕色)
* Switching to protected mode....
* A (白色)
对代码的解释
上面给出了所有的代码,下面我对上述代码做一些解释。
◆ 使用的函数
下面是代码中一些函数的说明:
print_mesg 该子程序使用了BIOS中断10h的功能13h,即向屏幕写一字符串。属性控制是
通过向一些寄存器中送入不同的值来实现的。中断10h是用于各种字符串操作,我们把子
功能号13h送到ah中,用于指明要打印一个字符串。al寄存器中的0说明了光标返回的起
始位置,0表示调用函数后光标返回到下一行的行首。如果al为1则表示光标位于最后一
个字符处。
显存被分成了几页,在同一时刻只能显示其中的一页。bh指明的是页号;bl则指明要显
示字符的颜色;cx指明要显示字符串的长度;dx指明光标的位置(即起始的行和列)。所
有相关寄存器初始化完成以后,就可以调用BIOS中断10h了。
get_key 使用中断16h的子功能00h,从屏幕得到下一个字符。
clrscr 该函数使用了中断10h的另外一个子功能06h,用于输出开始前清屏。初始化时给
al中送入0。寄存器cx和dx指明要清屏的屏幕范围,在本例中是整个屏幕。寄存器bh指明
屏幕填充的颜色,在本例中是黑色。
◆ 其它内容
程序一开始是一条短跳转指令,跳到begin_boot处。在实模式下,在此打印一个棕色的
“A”,并且设置一个GDT。切换到保护模式,并且打印一个白色的“A”。这两种模式使
用的都是自己的寻址方法。
在实模式下,使用段寄存器gs指示显存位置,我们使用的是CGA显卡(默认基址是0xb800
0)。在代码中是不是漏了一个0呢?没有,因为实模式下会提供一个附加的0。这种方式
也被80386继承下来了。A的ASCⅡ是0x41,0x06指明了需要一个棕色的字符。该显示会一
直持续直至按下任意键。下面要在屏幕上显示一句话,告诉使用者下面马上要进入保护
模式了。
启动到保护模式,在进行切换时不希望此时有中断的影响,故要关闭所有的中断(使用c
li来实现)。然后对GDT初始化。在整个切换过程中,对4个描述符进行了初始化。这些描
述符对代码段(code_gdt)、数据和堆栈段(data_gdt),以及为了访问显存而对显示段进
行初始化。此外,还会对一个空描述符进行初始化。
GDT的基址要加载至GDTR系统寄存器之中。gdtr段的第一个字加载的是GDT的大小,在下
一个双字中则加载的是基址。然后,lgdt指令把把gdt段加载至GDTR寄存器中。现在已经
做好了切换到保护模式前的所有准备。最后一件事情就是把CR0寄存器的PE位置1。不过
,即使这样还没有处于保护模式状态之下。
设置了PE位以后,还需要通过执行JMP指令来清除处理器指令预取队列。在80386中,使
用指令前总是先将其从内存中取出,并且进行解码和寻址。然而,当进入保护模式以后
,预取指令信息(它还处于实地址模式)就无效了。使用JMP指令的目的就是强迫处理器放
弃无效的信息。
现在,已经在保护模式下了。那么,如何检测是在保护模式状态之下呢?让我们来看一
看屏幕上这个白色的字母A。在这里,使用了数据段选择符(datase1)对数据段和附加段
进行了初始化,使用显示段选择符(videose1)对gs进行了初始化。告示的字符“A”其A
SCⅡ值和属性位于[gs:0000]处,也就是b8000:0000处。循环语句使得该字符一直在屏幕
上显示,直至重新启动系统。
下一步要做的事
现在,这个操作系统已经工作在保护模式下了,但是实际上它并不实现什么具体的功能
。你可以在这个基础上为它增加各种操作系统所具有的功能。我们自己动手写操作系统
到此也就告一段落。

eager 發表在 痞客邦 留言(0) 人氣()

這是OS入門的好文章...
強烈推薦
我在大陸抓的,作者不明...不過寫出這種好東西的好人應該不會介意我分享他的文章
要是有人知道原作者,麻煩通知我一下,我會標注
謝謝
============================
自由软件社区是一个充满自由和梦想的地方,在10余年的时间里它创造了一个又一个奇
迹。然而,这些奇迹的创造者不只是Stallman,也不只是Linus Torvalds,而是活跃在
世界各地的不计其数的开发人员。
在使用各种功能强大的自由软件时,我总会对其开发者充满崇敬之情,期盼有朝一日自
己也能成为他们中的一员。很多对自由社区充满向往之情的人,虽然也想努力融身于其
中,但又不知该怎么做。那么,就请与我们一起从编写一个简单的操作系统开始吧!
我们要做的事情
有人可能担心自己既没有学过计算机原理,也没有学过操作系统原理,更不懂汇编语言
,对C语言也一知半解,能写操作系统吗?答案是没问题。我将带大家一步一步完成自己
的操作系统。当然如果学一学上述内容再好不过。
首先要明确处理器(也就是CPU)控制着计算机。对PC而言,启动的时候,CPU都处在实模
式状态,相当于只是一个Intel 8086处理器。也就是说,即使你现在拥有一个奔腾处理
器,它的功能也只能是8086级别。从这一点上来讲,可以使用一些软件把处理器转换到
著名的保护模式。只有这样,我们才可以充分利用处理器的强大功能。
编写操作系统开始是对BIOS控制,取出存储在ROM里的程序。BIOS是用来执行POST(Powe
r On Self Test,自检)的。自检是检查计算机的完整性(比如外设是否工作正常、键盘
是否连接等)。这一切完成以后,你就会听到PC喇叭发出一声清脆的响声。如果一切正常
,BIOS就会选择一个启动设备,并且读取该设备的第一扇区(即启动扇区),然后控制过
程就会转移到指定位置。启动设备可能是一个软盘、光盘、硬盘,或者其它所选择的设
备。在此我们把软盘作为启动设备。如果我们已经在软盘的启动扇区里写了一些代码,
这时它就被执行。因此,我们的目的很明确,就是往软盘的启动扇区写一些程序。
首先使用8086汇编来写一个小程序,然后将其拷贝至软盘的启动扇区。为了实现拷贝,
要写一个C程序。最后,使用软盘启动计算机。
需要的工具
● as86:这是一个汇编程序,它负责把写的代码转换成目标文件。
● ld86:这是一个连接器,as86产生的目标代码由它来转换成真正的机器语言。机器语
言是8086能够解读的形式。
● GCC:著名的C编程器。因为我们需要写一个C程序将自己的OS转移到软盘中。
● 一张空软盘:它用于存储编写的操作系统,也是启动设备。
● 一台装有Linux的计算机:这台机器可以很旧,386、486都可以。
在大部分标准Linux发行版中都会带有as86和ld86。在我使用的Red Hat 7.3中就包含有
这两个工具,并且在默认的情况下,它已经安装在机器里。如果使用的Linux没有这两个
工具,可以从网上下载(http://www.cix.co.uk/~mayday/),这两个工具都包含在一个名
为bin86的软件包中。此外,有关的文档也可以在网上获得(www.linux.org/docs/ldp/h
owto/Assembly-HOWTO/as86.html)。
开始工作
使用一个你喜欢的编辑器输入以下内容:
entry start
start:
mov ax,#0xb800
mov es,ax
seg es
mov [0],#0x41
seg es
mov [1],#0x1f
loop1: jmp loop1
这是as86可以读懂的一段汇编程序。第一个句子指明了程序的入口点,声明整个过程从
start处开始。第二行指明了start的位置,说明整个程序要从start处开始执行。0xb80
0是显存的开始地址。#表明其后是一个立即数。执行语句:
mov ax,#oxb800
ax寄存器的值就变为0xb800,这就是显存的地址。下面再将这个值移至es寄存器,es是
附加段寄存器。请记住8086有一个分段的体系结构。它的各段寄存器为代码段、数据段
、堆栈段和附加段,对应的寄存器名称分别为cs、ds、ss和es。事实上,我们把显存地
址送入了附加段,因此,任何送入附加段的东西都会被送到显存中。
要在屏幕上显示字符,就需要向显存中写两个字节。前一个是所要显示字符的ASCⅡ值,
第二个字节表示该字符的属性。属性包括字符的前景色、背景色及是否闪烁等等。seg
es指明下一个将要执行的指令是指向es段的。所以,我们把值0x41(在ASCⅡ中表示的字
符是A)送到显存的第一个字节中。接下来要把字符的属性送到下一个字节当中。在此输
入的是0x1f,该属性指的是在蓝色背景下显示白色的字符。因此,如果执行这个程序,
就可以在屏幕上得到显示在蓝底上的一个白色的A。接着是一个循环。因为在执行完显示
字符的任务后,要么让程序结束,要么使用一个循环使其永远运行下去。把该文件命名
为boot.s,然后存盘。
此处显存的概念说得不是很清楚,有必要进一步解释一下。假设屏幕由80列×25行组成
,那么第一行就需要160字节,其中一个字节用于表示字符,另外一个字节用于表示字符
的属性。如果要在第三行显示某一字符的话,就要跳过显存的第0和1字节(它们是用于显
示第1列的),第2和3字节(它们是用于显示第2列的),然后把需要显示字符的ASCⅡ码值
入第4字节,把字符的属性写入第5字节。
把程序写至启动扇区
下面写一个C程序,把我的操作系统写入软盘第一扇区。程序内容如下:
#include /* unistd.h 需要这个文件 */
#include /* 包含有read和write函数 */
#include
int main()
{
char boot_buf[512];
int floppy_desc, file_desc;
file_desc = open("./boot", O_RDONLY);
read(file_desc, boot_buf, 510);
close(file_desc);
boot_buf[510] = 0x55;
boot_buf[511] = 0xaa;
floppy_desc = open("/dev/fd0", O_RDWR);
lseek(floppy_desc, 0, SEEK_CUR);
write(floppy_desc, boot_buf, 512);
close(floppy_desc);
}
首先,以只读模式打开boot文件,然后在打开文件时把文件描述符复制到file_desc变量
中。从文件中读取510个字符,或者读取直到文件结束。在本例中由于文件很小,所以是
读取至文件结束。然后关闭文件。
最后4行代码打开软盘驱动设备(一般来说是/dev/fd0)。使用lseek找到文件开始处,然
后从缓冲中向软盘写512个字节。
在read、write、open和lseek的帮助页中,可以看到与函数所有有关的参数及其使用方
法。程序中有两行比较难懂:
boot_buf[510] = 0x55;
boot_buf[511] = 0xaa;
该信息是用于BIOS的,如果它识别出该设备是一个可启动的设备,那么在第510和511的
位置,该值就应该是0x55和0xaa。程序会把文件boot读至名为boot_buf的缓冲中。它要
求改变第510和第511字节,然后把boot_buf写至软盘之上。如果执行代码,软盘上的前
512字节就包含了启动代码。最后,把文件存为write.c。
编译运行
使用下面的命令把文件变为可执行文件:
as86 boot.s -o boot.o
ld86 -d boot.o -o boot
cc write.c -o write
首先将boot.s文件编译成目标文件boot.o,然后将该文件连接成最终的boot文件。最后
C程序编译成可执行的write文件。
插入一个空白软盘,运行以下程序:
/write
重新启动电脑,进行BIOS的界面设置,并且把软盘设为第一个启动的设备。然后插入软
盘,电脑从软盘上启动。
启动完成后,在屏幕上可以看到一个字母A(蓝底白字),启动速度很快,几乎是在瞬间完
成。这就意味着系统已经从我们制作的软盘上启动了,并且执行了刚才写入启动扇区的
程序。现在,它正处在一个无限循环的状态。所以,如果想进入Linux,必需拿掉软盘,
并且重启机器。
至此,这个操作系统就算完成了,虽然它没有实现什么功能,但是它已经可以启动机器
了。
下一期我将在这个启动扇区程序里加入一些代码,使它可以做一些比较复杂的事情(比如
使用BIOS中断、保护模式切换等等)。
=================================
上一期,我讲述了如何在软盘的启动扇区写一些代码,然后再从软盘启动的过程。制作
好一个启动扇区,在切换到保护模式之前,我们还应该知道如何使用BIOS中断。BIOS中
断是一些由BIOS提供的、为了使操作系统的创建更容易的低级程序。在本文中,我们将
学习处理BIOS的中断。
为什么要用BIOS
BIOS会把启动扇区拷贝至RAM中,并且执行这些代码。除此之外,BIOS还要做很多其它的
事情。当一个操作系统刚开始启动时,系统中并没有显卡驱动、软盘驱动等任何驱动程
序。因此,启动扇区中不可能包含任何一个驱动程序,我们要采取其它的途径。这个时
候,BIOS就可以帮助我们了。BIOS中包含有各种可以使用的程序,包括检测安装的设备
、控制打印机、计算内存大小等用于各种目的的程序。这些程序就是所说的BIOS中断。
如何调用BIOS中断
在一般的程序设计语言中,函数的调用是一件非常容易的事情。比如在C语言中,如果有
一个名为display的程序,它带有两个参数,其中参数noofchar表示显示的字符数,参数
attr表示显示字符的属性。那么要调用它,只需给出程序的名称即可。对于中断的调用
,我们使用的是汇编语言中的int指令。
比如,在C语言中要显示一些东西时,使用的指令如下所示:
display(nofchar,attr);
而使用BIOS时,要实现相同功能使用的指令如下:
int 0x10
如何传递参数
在调用BIOS中断之前,我们需要先往寄存器中送一些特定的值。假设要使用BIOS的中断
13h,该中断的功能是把数据从软盘传送至内存之中。在调用该中断之前,要先指定拷贝
数据的段地址,指定驱动器号、磁道号、扇区号,以及要传送的扇区数等等。然后,就
要往相应的寄存器送入相应的值。在进行下面的步骤前,读者有必要对这一点有比较明
确地认识。
此外,一个比较重要的事实是同一个中断往往可以实现各种不同的功能。中断所实现的
确切功能取决于所选择的功能号,功能号一般都存在ah寄存器之中。比如中断13h可以用
于读磁盘、写磁盘等功能,如果把3送入ah寄存器中,那么中断选择的功能就是写磁盘;
如果把2送入ah寄存器中,选择的功能则是读磁盘等。
我们要做的事情
这次我们的源代码由两个汇编语言程序和一个C程序组成。第一个汇编文件是引导扇区的
代码。在引导扇区中,我们写的代码是要把软盘中第二扇区拷贝至内存段的0x500处(地
址是0x5000,即偏移地址为0)。这时我们需要使用BIOS的中断13h。这时启动扇区的代码
就会把控制权转移至0x500处。在第二个汇编文件中,代码会使用BIOS中断10h在屏幕上
显示一个信息。C程序实现的功能则是把可执行的文件1拷贝至启动扇区,把可执行的文
件2拷贝至软盘的第二扇区。
启动扇区代码
使用中断13h,启动扇区把软盘第二扇区里的内容加载至内存的0x5000处(段地址为0x50
0)。下面的代码是用于实现这一目的的代码,将其保存至文件sbect.s中。
LOC1=0x500
entry start
start:
mov ax,#LOC1
mov es,ax
mov bx,#0
mov dl,#0
mov dh,#0
mov ch,#0
mov cl,#2
mov al,#1
mov ah,#2
int 0x13
jmpi 0,#LOC1
上面代码第一行类似于一个宏。接下去的两行则是把值0x500加载至es寄存器中,这是软
盘上第二扇区代码将拷贝到的地方(第一扇区是启动扇区)。这时,把段内的偏移设为0。
接下来把驱动器号送入dl寄存器中,其中磁头号送入dl寄存器中,磁道号送入ch寄存器
中,扇区号送入cl寄存器中,扇区数送入al寄存器之中。我们想要实现的功能是把扇区
2、磁道号为0、驱动器号为0的内容送至段地址0x500处。所有这些参数都和1.44MB的软
盘相对应。
把2送入ah寄存器中,是选择了由中断13h提供的相应功能,即实现从软驱转移数据的功
能。
最后调用中断13h,并且转至偏移为0的段地址0x500处。
第二个扇区的代码
第二个扇区中的代码如下所示(把这些代码保存至文件sbect2.s之中):
entry start
start:
mov ah,#0x03
xor bh,bh
int 0x10
mov cx,#26
mov bx,#0x0007
mov bp,#mymsg
mov ax,#0x1301
int 0x10
loop1: jmp loop1
mymsg:
byte 13,10
ascii “Operating System is Loading......”
上面代码将被加载至段地址为0x500处,并且被执行。在这段代码中,使用了中断10h来
获取目前的光标位置,然后显示信息。
从第3行到第5行用于得到目前光标的位置,在此中断10h选用的是功能3。然后,清除了
bh寄存器的内容,并把字符串送至ch寄存器中。在bx中,我们送入了页码及显示的属性
。此处,我们想要在黑背景上显示白色的字符。然后,把要显示字符的地址送到bp之中
,信息由两个字节组成,其值分别为13的10,它们分别对应回车和LF(换行)的ASCⅡ值。
接下来是一个由29个字符组成的串;在下面实现的功能是输出字符串然后移动光标;最
后是调用中断,然后进入循环。
C程序代码
C程序的源代码如下所示,将其存储为write.c文件。
#include /* unistd.h needs this */
#include /* contains read/write */
#include
int main()
{
char boot_buf[512];
int floppy_desc, file_desc;
file_desc = open(“./bsect”, O_RDONLY);
read(file_desc, boot_buf, 510);
close(file_desc);
boot_buf[510] = 0x55;
boot_buf[511] = 0xaa;
floppy_desc = open(“/dev/fd0”, O_RDWR);
lseek(floppy_desc, 0, SEEK_SET);
write(floppy_desc, boot_buf, 512);
file_desc = open(“./sect2”, O_RDONLY);
read(file_desc, boot_buf, 512);
close(file_desc);
lseek(floppy_desc, 512, SEEK_SET);
write(floppy_desc, boot_buf, 512);
close(floppy_desc);
}
在上一期中,我曾经介绍过如何操作能启动的软盘。现在这一个过程稍微有点不同,首
先把由bsect.s编译出来的可执行文件bsect拷贝至软盘的启动扇区。然后再把由sect2.
s产生的可执行文件sect2拷贝至软盘的第二个扇区。
把上述文件置于同一目录之下,然后分别对其进行编译,方法如下所示:
as86 bsect.s -o bsect.o
ld86 -d bsect.o -o bsect
对sect2.s文件重复以上的操作,得出可执行文件sect2。编译write.c,插入软盘后执行
write文件,命令如下所示:
cc write.c -o write
/write
下一步我们要做的事情
从软盘启动以后,可以看到显示出来的字符串。这是使用了BIOS中断来完成的。下一期
要做的事情是在这个操作系统中实现实模式向保护模式的转换。
=================================================
在上两期中(自己动手写操作系统1,2),我向大家讲述了如何使用Linux提供的开发
工具在软盘的启动扇区写一些代码,以及如何调用BIOS的问题。现在,这个操作系统已
经越来越接近当年Linus Torvalds的那个具有“历史意义”的Linux内核了。因此,要马
上把这个系统切换到保护模式之下。
什么是保护模式
自从1969年推出第一个微处理器以来,Intel处理器就在不断地更新换代,从8086、808
8、80286,到80386、80486、奔腾、奔腾Ⅱ、奔腾4等,其体系结构也在不断变化。803
86以后,提供了一些新的功能,弥补了8086的一些缺陷。这其中包括内存保护、多任务
及使用640KB以上的内存等,并仍然保持和8086家族的兼容性。也就是说80386仍然具备
了8086和80286的所有功能,但是在功能上有了很大的增强。早期的处理器是工作在实模
式之下的,80286以后引入了保护模式,而在80386以后保护模式又进行了很大的改进。
在80386中,保护模式为程序员提供了更好的保护,提供了更多的内存。事实上,保护模
式的目的不是为了保护程序,而是要保护程序以外的所有程序(包括操作系统)。
简言之,保护模式是处理器的一种最自然的模式。在这种模式下,处理器的所有指令及
体系结构的所有特色都是可用的,并且能够达到最高的性能。
保护模式和实模式
从表面上看,保护模式和实模式并没有太大的区别,二者都使用了内存段、中断和设备
驱动来处理硬件,但二者有很多不同之处。我们知道,在实模式中内存被划分成段,每
个段的大小为64KB,而这样的段地址可以用16位来表示。内存段的处理是通过和段寄存
器相关联的内部机制来处理的,这些段寄存器(CS、DS、SS和ES)的内容形成了物理地
址的一部分。具体来说,最终的物理地址是由16位的段地址和16位的段内偏移地址组成
的。用公式表示为:
物理地址=左移4位的段地址+偏移地址。
在保护模式下,段是通过一系列被称之为“描述符表”的表所定义的。段寄存器存储的
是指向这些表的指针。用于定义内存段的表有两种:全局描述符表(GDT)和局部描述符表
(LDT)。GDT是一个段描述符数组,其中包含所有应用程序都可以使用的基本描述符。在
实模式中,段长是固定的(为64KB),而在保护模式中,段长是可变的,其最大可达4GB。
LDT也是段描述符的一个数组。与GDT不同,LDT是一个段,其中存放的是局部的、不需要
全局共享的段描述符。每一个操作系统都必须定义一个GDT,而每一个正在运行的任务都
会有一个相应的LDT。每一个描述符的长度是8个字节,格式如图3所示。当段寄存器被加
载的时候,段基地址就会从相应的表入口获得。描述符的内容会被存储在一个程序员不
可见的影像寄存器(shadow register)之中,以便下一次同一个段可以使用该信息而不用
每次都到表中提取。物理地址由16位或者32位的偏移加上影像寄存器中的基址组成。实
模式和保护模式的不同可以从图1和图2中很清楚地看出来。
图1 实模式的寻址
图2 保护模式下的寻址
图3 段描述俯的格式
此外,还有一个中断描述符表(IDT)。这些中断描述符会告诉处理器到那里可以找到中断
处理程序。和实模式一样,每一个中断都有一个入口,但是这些入口的格式却完全不同
。因为在切换到保护模式的过程中没有使用到IDT,所以在此就不多做介绍了。
进入保护模式
80386有4个32位控制寄存器,名字分别为CR0、CR1、CR2和CR3。CR1是保留在未来处理器
中使用的,在80386中没有定义。CR0包含系统的控制标志,用于控制处理器的操作模式
和状态。CR2和CR3是用于控制分页机制的。在此,我们关注的是CR0寄存器的PE位控制,
它负责实模式和保护模式之间的切换。当PE=1时,说明处理器运行于保护模式之下,其
采用的段机制和前面所述的相应内容对应。如果PE=0,那么处理器就工作在实模式之下

切换到保护模式,实际就是把PE位置为1。为了把系统切换到保护模式,还要做一些其它
的事情。程序必须要对系统的段寄存器和控制寄存器进行初始化。把PE位置1后,还要执
行跳转指令。过程简述如下:
1.创建GDT表;
2.通过置PE位为1进入保护模式;
3.执行跳转以清除在实模式下读取的任何指令。
下面使用代码来实现这个切换过程。
需要的东西
◆ 一张空白软盘
◆ NASM编译器
下面是整个程序的源代码:
org 0x07c00; 起始地址是0000:7c00
jmp short begin_boot ; 跳过其它的数据,跳转到引导程序的开始处
bootmesg db "Our OS boot sector loading ......"
pm_mesg db "Switching to protected mode ...."
dw 512 ; 每一扇区的字节数
db 1 ; 每一簇的扇区数
dw 1 ; 保留的扇区号
db 2
dw 0x00e0
dw 0x0b40
db 0x0f0
dw 9
dw 18
dw 2 ; 读写扇区号
dw 0 ; 隐藏扇区号
print_mesg :
mov ah,0x13 ; 使用中断10h的功能13,在屏幕上写一个字符串
mov al,0x00 ; 决定调用函数后光标所处的位置
mov bx,0x0007 ; 设置显示属性
mov cx,0x20 ; 在此字符串长度为32
mov dx,0x0000 ; 光标的起始行和列
int 0x10 ; 调用BIOS的中断10h
ret ; 返回调用程序
get_key :
mov ah,0x00
int 0x16 ; Get_key使用中断16h的功能0,读取下一个字符
ret
clrscr :
mov ax,0x0600 ; 使用中断10h的功能6,实现卷屏,如果al=0则清屏
mov cx,0x0000 ; 清屏
mov dx,0x174f ; 卷屏至23,79
mov bh,0 ; 使用颜色0来填充
int 0x10 ; 调用10h中断
ret
begin_boot :
call clrscr ; 先清屏
mov bp,bootmesg ; 提供串地址
call print_mesg ; 输出信息
call get_key ; 等待用户按下任一键
bits 16
call clrscr ; 清屏
mov ax,0xb800 ; 使gs指向显示内存
mov gs,ax ; 在实模式下显示一个棕色的A
mov word [gs:0],0x641 ; 显示
call get_key ; 调用Get_key等待用户按下任一键
mov bp,pm_mesg ; 设置串指针
call print_mesg ; 调用print_mesg子程序
call get_key ; 等待按键
call clrscr ; 清屏
cli ; 关中断
lgdt[gdtr] ; 加载GDT
mov eax,cr0
or al,0x01 ; 设置保护模式位
mov cr0,eax ; 将更改后的字送至控制寄存器中
jmp codesel:go_pm
bits 32
go_pm :
mov ax,datasel
mov ds,ax ; 初始化ds和es,使其指向数据段
mov es,ax
mov ax,videosel ; 初始化gs,使其指向显示内存
mov gs,ax
mov word [gs:0],0x741 ; 在保护模式下显示一个白色的字符A
spin : jmp spin ; 循环
bits 16
gdtr :
dw gdt_end-gdt-1 ; gdt的长度
dd gdt ; gdt的物理地址
gdt
nullsel equ $-gdt ; $指向当前位置,所以nullsel = 0h
gdt0 ; 空描述符
dd 0
dd 0 ; 所有的段描述符都是64位的
codesel equ $-gdt ; 这是8h也就是gdt的第二个描述符
code_gdt
dw 0x0ffff ; 段描述符的界限是4Gb
dw 0x0000
db 0x00
db 0x09a
db 0x0cf
db 0x00
datasel equ $-gdt
data_gdt
dw 0x0ffff
dw 0x0000
db 0x00
db 0x092
db 0x0cf
db 0x00
videosel equ $-gdt
dw 3999
dw 0x8000 ; 基址是0xb8000
db 0x0b
db 0x92
db 0x00
db 0x00
gdt_end
times 510-($-$$) db 0
dw 0x0aa55
把上面的代码存在一个名为abc.asm的文件之中,使用命令nasm abc.asm,将得出一个名
为abc的文件。然后插入软盘,输入命令:dd if=abc of=/dev/fd0。该命令将把文件ab
c写入到软盘的第一扇区之中。然后重新启动系统,就会看到如下的信息:
*Our os booting................
* A (棕色)
* Switching to protected mode....
* A (白色)
对代码的解释
上面给出了所有的代码,下面我对上述代码做一些解释。
◆ 使用的函数
下面是代码中一些函数的说明:
print_mesg 该子程序使用了BIOS中断10h的功能13h,即向屏幕写一字符串。属性控制是
通过向一些寄存器中送入不同的值来实现的。中断10h是用于各种字符串操作,我们把子
功能号13h送到ah中,用于指明要打印一个字符串。al寄存器中的0说明了光标返回的起
始位置,0表示调用函数后光标返回到下一行的行首。如果al为1则表示光标位于最后一
个字符处。
显存被分成了几页,在同一时刻只能显示其中的一页。bh指明的是页号;bl则指明要显
示字符的颜色;cx指明要显示字符串的长度;dx指明光标的位置(即起始的行和列)。所
有相关寄存器初始化完成以后,就可以调用BIOS中断10h了。
get_key 使用中断16h的子功能00h,从屏幕得到下一个字符。
clrscr 该函数使用了中断10h的另外一个子功能06h,用于输出开始前清屏。初始化时给
al中送入0。寄存器cx和dx指明要清屏的屏幕范围,在本例中是整个屏幕。寄存器bh指明
屏幕填充的颜色,在本例中是黑色。
◆ 其它内容
程序一开始是一条短跳转指令,跳到begin_boot处。在实模式下,在此打印一个棕色的
“A”,并且设置一个GDT。切换到保护模式,并且打印一个白色的“A”。这两种模式使
用的都是自己的寻址方法。
在实模式下,使用段寄存器gs指示显存位置,我们使用的是CGA显卡(默认基址是0xb800
0)。在代码中是不是漏了一个0呢?没有,因为实模式下会提供一个附加的0。这种方式
也被80386继承下来了。A的ASCⅡ是0x41,0x06指明了需要一个棕色的字符。该显示会一
直持续直至按下任意键。下面要在屏幕上显示一句话,告诉使用者下面马上要进入保护
模式了。
启动到保护模式,在进行切换时不希望此时有中断的影响,故要关闭所有的中断(使用c
li来实现)。然后对GDT初始化。在整个切换过程中,对4个描述符进行了初始化。这些描
述符对代码段(code_gdt)、数据和堆栈段(data_gdt),以及为了访问显存而对显示段进
行初始化。此外,还会对一个空描述符进行初始化。
GDT的基址要加载至GDTR系统寄存器之中。gdtr段的第一个字加载的是GDT的大小,在下
一个双字中则加载的是基址。然后,lgdt指令把把gdt段加载至GDTR寄存器中。现在已经
做好了切换到保护模式前的所有准备。最后一件事情就是把CR0寄存器的PE位置1。不过
,即使这样还没有处于保护模式状态之下。
设置了PE位以后,还需要通过执行JMP指令来清除处理器指令预取队列。在80386中,使
用指令前总是先将其从内存中取出,并且进行解码和寻址。然而,当进入保护模式以后
,预取指令信息(它还处于实地址模式)就无效了。使用JMP指令的目的就是强迫处理器放
弃无效的信息。
现在,已经在保护模式下了。那么,如何检测是在保护模式状态之下呢?让我们来看一
看屏幕上这个白色的字母A。在这里,使用了数据段选择符(datase1)对数据段和附加段
进行了初始化,使用显示段选择符(videose1)对gs进行了初始化。告示的字符“A”其A
SCⅡ值和属性位于[gs:0000]处,也就是b8000:0000处。循环语句使得该字符一直在屏幕
上显示,直至重新启动系统。
下一步要做的事
现在,这个操作系统已经工作在保护模式下了,但是实际上它并不实现什么具体的功能
。你可以在这个基础上为它增加各种操作系统所具有的功能。我们自己动手写操作系统
到此也就告一段落。

eager 發表在 痞客邦 留言(0) 人氣()

其實很早以前就有過這種經驗了,剛開始覺得某某東西很難
擱了一陣在去看,居然發現它變簡單了,可以理解了
這是不可思議的,我把他歸咎於經驗,由於我先前累積了其他相關經驗,
所以在閱讀這些東西時我可以先由其他相關經驗來來輔助組織書中的內容並學習,
但是,最近突然又有一些發現,那就是...其實有另外的教材是不用相關經驗來來輔助學習的
也就是說,如果你要學linux核心,你可能需要C,asm等語法基礎
相關作業系統基礎,最好還要有相當的程式經驗,
這樣,你才能在很複雜的專業書中獲得一點知識,
但是就是有一些獨特的教材能夠讓使用者能夠以最少的準備獲得最大的收益,
它可以不用程式碼的方式解說,或是以圖示文字來解說一段程式碼流程來加強學習者理解
針對某些部分來解說,盡量隔離不相關的部分,以免增加複雜度影響學習者理解
甚至用簡化的例子來讓學習者了解程序工作原理
這樣就算學習者對於程式不是很熟練,也可以很快理解流程,了解系統架構,快速入門
雖然對於想要進入專業領域的人來講,對於程式碼的閱讀了解是早晚要培養的能力
但是為了要減低剛入門的恐懼,這樣的教材還是適宜的,入門有入門的教材
進階有進階的教材....
我的學習之路其實遇到很多類是這樣的事,也就是無法找到好的學習方法
學習英文時沒有好的教材,讀書時不先挑中文書看,這其實都影響了很多學習的效果
讀原文書好阿,可以順便增加英文閱讀能力,但是對於英文不佳的人來講
這卻又會影響到他其他能力的學習,現有大學上應該不少人是死在英文教科書上,
教材阿教材,挑個好書確實能影響學習效果阿....
有些人確實不是笨阿,只是他不會找好教材罷了.....

eager 發表在 痞客邦 留言(0) 人氣()

其實很早以前就有過這種經驗了,剛開始覺得某某東西很難
擱了一陣在去看,居然發現它變簡單了,可以理解了
這是不可思議的,我把他歸咎於經驗,由於我先前累積了其他相關經驗,
所以在閱讀這些東西時我可以先由其他相關經驗來來輔助組織書中的內容並學習,
但是,最近突然又有一些發現,那就是...其實有另外的教材是不用相關經驗來來輔助學習的
也就是說,如果你要學linux核心,你可能需要C,asm等語法基礎
相關作業系統基礎,最好還要有相當的程式經驗,
這樣,你才能在很複雜的專業書中獲得一點知識,
但是就是有一些獨特的教材能夠讓使用者能夠以最少的準備獲得最大的收益,
它可以不用程式碼的方式解說,或是以圖示文字來解說一段程式碼流程來加強學習者理解
針對某些部分來解說,盡量隔離不相關的部分,以免增加複雜度影響學習者理解
甚至用簡化的例子來讓學習者了解程序工作原理
這樣就算學習者對於程式不是很熟練,也可以很快理解流程,了解系統架構,快速入門
雖然對於想要進入專業領域的人來講,對於程式碼的閱讀了解是早晚要培養的能力
但是為了要減低剛入門的恐懼,這樣的教材還是適宜的,入門有入門的教材
進階有進階的教材....
我的學習之路其實遇到很多類是這樣的事,也就是無法找到好的學習方法
學習英文時沒有好的教材,讀書時不先挑中文書看,這其實都影響了很多學習的效果
讀原文書好阿,可以順便增加英文閱讀能力,但是對於英文不佳的人來講
這卻又會影響到他其他能力的學習,現有大學上應該不少人是死在英文教科書上,
教材阿教材,挑個好書確實能影響學習效果阿....
有些人確實不是笨阿,只是他不會找好教材罷了.....

eager 發表在 痞客邦 留言(0) 人氣()

以前,在台中住久了,對於颱風總時沒甚感覺,
因為雖然電視台上撥著台北下大雨,刮大風,
但是,我家附近還是沒下雨,或是下一點小雨
也因此,我對於颱風沒甚感覺
但是...現在變了,這次在花蓮,我嚐到了有史以來最強烈的體驗,
早在入夜後,我就從窗戶感受到雨以往不同的感覺,被固定的窗戶規律性的震動
被狂風推擠,窗戶些微變形,在在都顯示外面風雨的強大
就在昨天pm11:30左右,整個圖資大樓都停了電,
為了安全??(那時腦袋扒帶了),我和兩個學弟決定要衝回家,
一出門,覺得還好,風勢在可忍受範圍,但是
慢慢的,我們就知道我們錯了,大錯特錯,因為門口附近有大型遮蔽物
所以風勢較弱,接著當我們離開遮蔽建築後,迎面而來的就是狂風,暴雨
以及地上斷裂的檳榔樹,沒錯,斷裂的檳榔樹,
但是,衣服都濕了,這讓我們沒有退路,
在勇敢的突進後終於到了較空曠的停車場邊的道路,
但是,強大的風,讓我們舉步維艱,而到達停車場時,風勢更是把我打趴了
天阿,這樣根本不能騎車阿,在商議後,我們退守到較近的理工學院去
由於停電,整個理工學院像是惡靈古堡內的場景,地上都是積水,而樓梯上流下一道強烈的水柱
就像瀑布一般,由於我們實驗室有分部在這裡,我們就去分部看了一下,果然也積水了
離開時順便拿了緊急照明燈(乎,原本要拿衣服的,因為衣服都濕了),
在由捷徑回到實驗室後,已經12:20左右了,期間真是驚險萬分,
ps1,電力在一點多就回復了,台電修復員工真是令人佩服,在那種風勢下還趕去搶修電力
ps2,風勢在兩點多後就變輕了,回去時我一值在罵自己怎會這麼愚蠢...在風勢最大時走出去 a  圖一 有遮蔽建築的步道 b 圖二 靠近停車場的道路

eager 發表在 痞客邦 留言(0) 人氣()


以前,在台中住久了,對於颱風總時沒甚感覺,
因為雖然電視台上撥著台北下大雨,刮大風,
但是,我家附近還是沒下雨,或是下一點小雨
也因此,我對於颱風沒甚感覺
但是...現在變了,這次在花蓮,我嚐到了有史以來最強烈的體驗,
早在入夜後,我就從窗戶感受到雨以往不同的感覺,被固定的窗戶規律性的震動
被狂風推擠,窗戶些微變形,在在都顯示外面風雨的強大
就在昨天pm11:30左右,整個圖資大樓都停了電,
為了安全??(那時腦袋扒帶了),我和兩個學弟決定要衝回家,
一出門,覺得還好,風勢在可忍受範圍,但是
慢慢的,我們就知道我們錯了,大錯特錯,因為門口附近有大型遮蔽物
所以風勢較弱,接著當我們離開遮蔽建築後,迎面而來的就是狂風,暴雨
以及地上斷裂的檳榔樹,沒錯,斷裂的檳榔樹,
但是,衣服都濕了,這讓我們沒有退路,
在勇敢的突進後終於到了較空曠的停車場邊的道路,
但是,強大的風,讓我們舉步維艱,而到達停車場時,風勢更是把我打趴了
天阿,這樣根本不能騎車阿,在商議後,我們退守到較近的理工學院去
由於停電,整個理工學院像是惡靈古堡內的場景,地上都是積水,而樓梯上流下一道強烈的水柱
就像瀑布一般,由於我們實驗室有分部在這裡,我們就去分部看了一下,果然也積水了
離開時順便拿了緊急照明燈(乎,原本要拿衣服的,因為衣服都濕了),
在由捷徑回到實驗室後,已經12:20左右了,期間真是驚險萬分,
ps1,電力在一點多就回復了,台電修復員工真是令人佩服,在那種風勢下還趕去搶修電力
ps2,風勢在兩點多後就變輕了,回去時我一值在罵自己怎會這麼愚蠢...在風勢最大時走出去
圖一 有遮蔽建築的步道
圖二 靠近停車場的道路

eager 發表在 痞客邦 留言(0) 人氣()


很久以前就想要研究linux 核心了,但是看了很多書還是不得其門而入
恩,我承認我沒毅力....
但是 該做的還是要做,該學的還是要學
希望過沒多就能夠入手阿,這一篇是不錯的文章
最近又突然奮發了...so .....
=================================
http://www.linuxforum.net 喻鋒榮 (2001-04-21 18:11:02)
針對好多Linux 愛好者對內核很有興趣卻無從下口,本文旨在介紹一種解讀linux內核源碼的入門方法,
而不是解說linux復雜的內核機制;
一.核心源程序的文件組織: 
1.Linux核心源程序通常都安裝在/usr/src/linux下,而且它有一個非常簡單的編號約定:任何偶數的
核心(例如2.0.30)都是一個穩定地發行的核心,而任何奇數的核心(例如2.1.42)都是一個開發中的核心。
本文基穩定的2.2.5源代碼,第二部分的實現平台為 Redhat Linux 6.0。
2.核心源程序的文件按樹形結構進行組織,在源程序樹的最上層你會看到這樣一些目錄:
●Arch :arch子目錄包括了所有和體系結構相關的核心代碼。它的每一個子目錄都代表一種支持的體系
  結構,例如i386就是關intel cpu及與之相兼容體系結構的子目錄。PC機一般都基此目錄;
●Include: include子目錄包括編譯核心所需要的大部分頭文件。與平台無關的頭文件在 include/linux
  子目錄下,與 intel cpu相關的頭文件在include/asm-i386子目錄下,而include/scsi目錄則是有關
  scsi設備的頭文件目錄;
●Init: 這個目錄包含核心的初始化代碼(注:不是系統的引導代碼),包含兩個文件main.c和Version.c,
  這是研究核心如何工作的一個非常好的起點。
●Mm :這個目錄包括所有獨立 cpu 體系結構的內存管理代碼,如頁式存儲管理內存的分配和釋放等;而和
  體系結構相關的內存管理代碼則位arch/*/mm/,例如arch/i386/mm/Fault.c
●Kernel:主要的核心代碼,此目錄下的文件實現了大多數linux系統的內核函數,其中最重要的文件當屬
  sched.c;同樣,和體系結構相關的代碼在arch/*/kernel中;
●Drivers: 放置系統所有的設備驅動程序;每種驅動程序又各佔用一個子目錄:如,/block 下為塊設備
  驅動程序,比如ide(ide.c)。如果你希望查看所有可能包含文件系統的設備是如何初始化的,你可以看
  drivers/block/genhd.c中的device_setup()。它不僅初始化硬盤,也初始化網絡,因為安裝nfs文件
  系統的時候需要網絡
其他:  如, Lib放置核心的庫代碼; Net,核心與網絡相關的代碼; Ipc,這個目錄包含核心的進程間通訊的代碼; 
  Fs ,所有的文件系統代碼和各種類型的文件操作代碼,它的每一個子目錄支持一個文件系統,例如fat和ext2;
  Scripts, 此目錄包含用配置核心的腳本文件等。
   一般,在每個目錄下,都有一個 .depend 文件和一個 Makefile 文件,這兩個文件都是編譯時
  使用的輔助文件,仔細閱讀這兩個文件對弄清各個文件這間的聯系和依托關系很有幫助;而且,在有的目錄下
  還有Readme 文件,它是對該目錄下的文件的一些說明,同樣有利我們對內核源碼的理解;
  
二.解讀實戰:為你的內核增加一個系統調用
雖然,Linux 的內核源碼用樹形結構組織得非常合理、科學,把功能相關聯的文件都放在同一個子目錄下,這樣使
得程序更具可讀性。然而,Linux 的內核源碼實在是太大而且非常復雜,即便採用了很合理的文件組織方法,在
不同目錄下的文件之間還是有很多的關聯,分析核心的一部分代碼通常會要查看其它的幾個相關的文件,而且可能
這些文件還不在同一個子目錄下。
體系的龐大復雜和文件之間關聯的錯綜復雜,可能就是很多人對其望而生畏的主要原因。當然,這種令人生畏的勞動
所帶來的回報也是非常令人著迷的:你不僅可以從中學到很多的計算機的底層的知識(如下面將講到的系統的引導),
體會到整個操作系統體系結構的精妙和在解決某個具體細節問題時,算法的巧妙;而且更重要的是:在源碼的分析過
程中,你就會被一點一點地、潛移默化地專業化;甚至,只要分析十分之一的代碼,你就會深刻地體會到,什樣
的代碼才是一個專業的程序員寫的,什樣的代碼是一個業余愛好者寫的。
為了使讀者能更好的體會到這一特點,下面舉了一個具體的內核分析實例,希望能通過這個實例,使讀者對 Linux 
的內核的組織有些具體的認識,從中讀者也可以學到一些對內核的分析方法。
以下即為分析實例:
【一】操作平台:
硬件:cpu                    intel Pentium II ;
軟件:Redhat Linux 6.0;      內核版本2.2.5
【二】相關內核源代碼分析:
1.系統的引導和初始化:Linux 系統的引導有好幾種方式:常見的有 Lilo, Loadin引導和Linux的自舉引導
  (bootsect-loader),而者所對應源程序為arch/i386/boot/bootsect.S,它為實模式的匯編程序,限
   篇幅在此不做分析;無論是哪種引導方式,最都要跳轉到  arch/i386/Kernel/setup.S, setup.S主要是
   進行時模式下的初始化,為系統進入保護模式做準備;此,系統執行 arch/i386/kernel/head.S (對經壓縮
   存放的內核要先執行 arch/i386/boot/compressed/head.S); head.S 中定義的一段匯編程序setup_idt ,
   它負責建立一張256項的 idt 表(Interrupt Descriptor Table),此表保存著所有自陷和中斷的入口地址;其中
   包括系統調用總控程序 system_call 的入口地址;當然,除此之外,head.S還要做一些其他的初始化工作;
2.系統初始化運行的第一個內核程序asmlinkage void __init start_kernel(void) 定義在
   /usr/src/linux/init/main.c中,它通過調用usr/src/linux/arch/i386/kernel/traps.c  中的一個函數 
   void __init trap_init(void) 把各自陷和中斷服務程序的入口地址設置到 idt 表中,其中系統調用總控程序
    system_cal就是中斷服務程序之一;void __init trap_init(void)   函數則通過調用一個宏 
    set_system_gate(SYSCALL_VECTOR,&system_call); 把系統調用總控程序的入口掛在中斷0x80上;
其中SYSCALL_VECTOR是定義在 /usr/src/linux/arch/i386/kernel/irq.h中的一個常量0x80;  而 system_call 
 即為中斷總控程序的入口地址;中斷總控程序用匯編語言定義在/usr/src/linux/arch/i386/kernel/entry.S中;
3.中斷總控程序主要負責保存處理機執行系統調用前的狀態,檢驗當前調用是否合法, 並根據系統調用向量,使處理機
  跳轉到保存在 sys_call_table 表中的相應系統服務例程的入口; 從系統服務例程返回恢復處理機狀態退回用戶程序;
  而系統調用向量則定義在/usr/src/linux/include/asm-386/unistd.h  中;sys_call_table 表定義在
  /usr/src/linux/arch/i386/kernel/entry.S 中;  同時在 /usr/src/linux/include/asm-386/unistd.h 
  中也定義了系統調用的用戶編程接口;
4.由此可見 ,  linux 的系統調用也象 dos 系統的  int 21h 中斷服務,  它把0x80 中斷作為總的入口, 然
  轉到保存在 sys_call_table 表中的各種中斷服務例程的入口地址 , 形成各種不同的中斷服務;
  由以上源代碼分析可知,  要增加一個系統調用就必須在 sys_call_table  表中增加一項 ,  並在其中保存好自己
  的系統服務例程的入口地址,然重新編譯內核,當然,系統服務例程是必不可少的。
由此可知在此版linux內核源程序<2。2。5>中,與系統調用相關的源程序文件就包括以下這些:
1.arch/i386/boot/bootsect.S
2.arch/i386/Kernel/setup.S
3.arch/i386/boot/compressed/head.S
4.arch/i386/kernel/head.S
5.init/main.c
6.arch/i386/kernel/traps.c
7.arch/i386/kernel/entry.S
8.arch/i386/kernel/irq.h
9.include/asm-386/unistd.h
 當然,這只是涉及到的幾個主要文件。而事實上,增加系統調用真正要修改文件只有include/asm-386/unistd.h
 和arch/i386/kernel/entry.S兩個;
【三】 對內核源碼的修改:
1.在kernel/sys.c中增加系統服務例程如下:
       asmlinkage int sys_addtotal(int numdata)
{
int i=0,enddata=0;
while(i<=numdata)
enddata+=i++;
return enddata;
}
  該函數有一個 int 型入口參數 numdata , 並返回從 0 到 numdata 的累加值;  當然也可以把系統服務例程放
  在一個自己定義的文件或其他文件中,只是要在相應文件中作必要的說明;
2.把 asmlinkage int sys_addtotal( int) 的入口地址加到sys_call_table表中:
  arch/i386/kernel/entry.S 中的最幾行源代碼修改前為: 
...   ...
.long SYMBOL_NAME(sys_sendfile)
.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */
.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */
.long SYMBOL_NAME(sys_vfork)            /* 190 */
.rept NR_syscalls-190
.long SYMBOL_NAME(sys_ni_syscall)
.endr
修改為:  ...   ...
.long SYMBOL_NAME(sys_sendfile)
.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */
.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */
.long SYMBOL_NAME(sys_vfork)            /* 190 */
/* add by I */
.long SYMBOL_NAME(sys_addtotal)
.rept NR_syscalls-191
.long SYMBOL_NAME(sys_ni_syscall)
.endr
3. 把增加的 sys_call_table 表項所對應的向量,在include/asm-386/unistd.h 中進行必要申明,以供
   用戶進程和其他系統進程查詢或調用:
增加的部分 /usr/src/linux/include/asm-386/unistd.h 文件如下:
...   ...
#define __NR_sendfile 187
#define __NR_getpmsg  188
#define __NR_putpmsg   189     
#define __NR_vfork 190
/* add by I */
#define __NR_addtotal 191
4.測試程序(test.c)如下:
#include
#include
_syscall1(int,addtotal,int, num)
main()
{
int i,j;
do 
   printf("Please input a number
");
  while(scanf("%d",&i)==EOF); 
if((j=addtotal(i))==-1)
   printf("Error occurred in syscall-addtotal();
"); 
printf("Total from 0 to %d is %d 
",i,j);
}
對修改的新的內核進行編譯,並引導它作為新的操作系統,運行幾個程序可以發現一切正常;在新的系統下
對測試程序進行編譯(*注:由原內核並未提供此系統調用,所以只有在編譯的新內核下,此測試程序才能
可能被編譯通過),運行情況如下:
$gcc -o test test.c
$./test
Please input a number
36
Total from 0 to 36 is 666
可見,修改成功;
而且,對相關源碼的進一步分析可知,在此版本的內核中,從/usr/src/linux/arch/i386/kernel/entry.S 
文件中對 sys_call_table 表的設置可以看出,有好幾個系統調用的服務例程都是定義在
  /usr/src/linux/kernel/sys.c 中的同一個函數:
asmlinkage int sys_ni_syscall(void)
{
return -ENOSYS;
}
例如第188項和第189項就是如此:
...   ...
.long SYMBOL_NAME(sys_sendfile)
.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */
.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */
.long SYMBOL_NAME(sys_vfork)            /* 190 */
... ...
而這兩項在文件 /usr/src/linux/include/asm-386/unistd.h 中卻申明如下:
...   ...
#define __NR_sendfile 187
#define __NR_getpmsg 188 /* some people actually want streams */
#define __NR_putpmsg 189 /* some people actually want streams */
#define __NR_vfork 190
由此可見,在此版本的內核源代碼中,由asmlinkage int sys_ni_syscall(void) 函數並不進行任何操作,
所以包括 getpmsg, putpmsg  在內的好幾個系統調用都是不進行任何操作的,即有待擴充的空調用; 但它們
卻仍然佔用著sys_call_table表項,估計這是設計者們為了方便擴充系統調用而安排的; 所以只需增加相應
服務例程(如增加服務例程getmsg或putpmsg),就可以達到增加系統調用的作用。
結語:當然對龐大復雜的 linux 內核而言,一篇文章遠遠不夠,而且與系統調用相關的代碼也只是內核中極其
微小的一部分;但重要的是方法、掌握好的分析方法;所以上的分析只是起個引導的作用,而正真的分析還有待
讀者自己的努力。

eager 發表在 痞客邦 留言(0) 人氣()

老實說,
我活到這麼大了,都還沒看過獨腳仙,真是慚愧阿
又不是從小就在都市過活...我是純種的農村小孩阿...真是...
總之,來到花蓮後,在距離畢業的前一個月,終於給我看到了
因為端午節很熱,學校冷氣又關了,我晚上就打開窗戶通風一下
沒想到這隻笨蛋,就飛進來了,我剛開始還嚇了一跳
真是興奮.....耶...還滿大隻的. 1087969063

eager 發表在 痞客邦 留言(0) 人氣()

在網路上看到的好文章
推一下...
================================================================
美國史坦福大學醫學研究所Phillip Marter先生,半年前在網路討論群組發表了一篇文字,
引起了許多網友的共鳴, 紛紛張貼轉載,歷久不衰,特別介紹給讀者。
如果我們把全球人口壓縮成只有100人的部落,而且維持人類的各種比率,那麼我們會得到
1. 57個亞洲人、21個歐洲人、14個美洲人、8個非洲人
2. 52個男人、48個女人
3. 30個白種人、70個非白種人
4. 30個基督徒、70個非基督徒
5. 89個異性戀者、11個同性戀者
6. 6個人將擁有全部財富的59%,而且這6個人全部來自美國
7. 80個人的居家生活不甚理想
8. 70個文盲
9. 50個人營養不良
10. 1個人即將死亡、1個人即將生產
11. 1個人 (Yes, only ONE!) 擁有大專學歷
12. 1個人擁有電腦
當我們從這樣壓縮的角度來看這個世界時,我們會更清楚這個世界需要更多的接納、諒解和
教育。
還有一些值得我們深思的:
如果您今天早上醒來時還算健康,恭喜您,因為有一百萬人將活不過一星期。
如果您不曾經歷戰爭的危險、被監禁的寂寞、被凌虐的痛苦、或是飢寒交迫,恭喜您,您比5億人還好命。
如果您可以參加宗教活動而不必擔心被騷擾、逮捕、凌虐、或死亡,恭喜您,您比30億人還自由。
如果您的冰箱裡有食物、有衣服穿、還有地方住,恭喜您,您比全世界75%的人還富有。
如果您在銀行有存款、錢包裡有鈔票、還有一些零錢,恭喜您,您是全世界前8%的有錢人。
如果您的雙親都還健在而且沒有離婚,您算是幸運兒。
您可以讀這篇文章,那是雙重幸運:因為有人想到您這個朋友,而且有20億人根本不識字。
工作,可以不必要汲汲營營;用心去愛,即使您曾經受傷;盡情跳舞,引喉高歌,不管別人
怎麼想;讓自己活在人間天堂。
雖然您是這些幸運的人之一,但對生活一定還會有不滿!
如果您願意,有個方法可以改善您生活的品質,讓您有多一點的金錢和時間陪伴您的家人
如果您願意瞭解...................

eager 發表在 痞客邦 留言(0) 人氣()

在網路上看到的好文章
推一下...
================================================================
美國史坦福大學醫學研究所Phillip Marter先生,半年前在網路討論群組發表了一篇文字,
引起了許多網友的共鳴, 紛紛張貼轉載,歷久不衰,特別介紹給讀者。
如果我們把全球人口壓縮成只有100人的部落,而且維持人類的各種比率,那麼我們會得到
1. 57個亞洲人、21個歐洲人、14個美洲人、8個非洲人
2. 52個男人、48個女人
3. 30個白種人、70個非白種人
4. 30個基督徒、70個非基督徒
5. 89個異性戀者、11個同性戀者
6. 6個人將擁有全部財富的59%,而且這6個人全部來自美國
7. 80個人的居家生活不甚理想
8. 70個文盲
9. 50個人營養不良
10. 1個人即將死亡、1個人即將生產
11. 1個人 (Yes, only ONE!) 擁有大專學歷
12. 1個人擁有電腦
當我們從這樣壓縮的角度來看這個世界時,我們會更清楚這個世界需要更多的接納、諒解和
教育。
還有一些值得我們深思的:
如果您今天早上醒來時還算健康,恭喜您,因為有一百萬人將活不過一星期。
如果您不曾經歷戰爭的危險、被監禁的寂寞、被凌虐的痛苦、或是飢寒交迫,恭喜您,您比5億人還好命。
如果您可以參加宗教活動而不必擔心被騷擾、逮捕、凌虐、或死亡,恭喜您,您比30億人還自由。
如果您的冰箱裡有食物、有衣服穿、還有地方住,恭喜您,您比全世界75%的人還富有。
如果您在銀行有存款、錢包裡有鈔票、還有一些零錢,恭喜您,您是全世界前8%的有錢人。
如果您的雙親都還健在而且沒有離婚,您算是幸運兒。
您可以讀這篇文章,那是雙重幸運:因為有人想到您這個朋友,而且有20億人根本不識字。
工作,可以不必要汲汲營營;用心去愛,即使您曾經受傷;盡情跳舞,引喉高歌,不管別人
怎麼想;讓自己活在人間天堂。
雖然您是這些幸運的人之一,但對生活一定還會有不滿!
如果您願意,有個方法可以改善您生活的品質,讓您有多一點的金錢和時間陪伴您的家人
如果您願意瞭解...................

eager 發表在 痞客邦 留言(0) 人氣()


老實說,
我活到這麼大了,都還沒看過獨腳仙,真是慚愧阿
又不是從小就在都市過活...我是純種的農村小孩阿...真是...
總之,來到花蓮後,在距離畢業的前一個月,終於給我看到了
因為端午節很熱,學校冷氣又關了,我晚上就打開窗戶通風一下
沒想到這隻笨蛋,就飛進來了,我剛開始還嚇了一跳
真是興奮.....耶...還滿大隻的.

eager 發表在 痞客邦 留言(0) 人氣()

Blog Stats
⚠️

成人內容提醒

本部落格內容僅限年滿十八歲者瀏覽。
若您未滿十八歲,請立即離開。

已滿十八歲者,亦請勿將內容提供給未成年人士。