荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: jjk (你看了帮助没?), 信区: Linux
标  题: [转载] [转寄] 基于Linux的学习方法
发信站: 荔园晨风BBS站 (Wed Dec  5 12:07:10 2001), 转信

【 以下文字转载自 jjk 的信箱 】
【 原文由 jjk.bbs@bbs.nju.edu.cn 所发表 】
发信人: CRX (老邪), 信区: LinuxUnix
标  题: 基于Linux的学习方法
发信站: 南京大学小百合站 (Tue Dec  4 20:02:41 2001), 站内信件

基于Linux的学习方法
关于我:
  我是一名普通的计算机软件人员,接受过普通的计算机科学教育,有6年工作经验,
由于工作原因接触过的系统包括DOS、Windows、Windows NT、Digital UNIX、VMS、Lin
ux、IRIX,在一个比较封闭但是比较安静的环境里自学使用Linux两年多的时间,使用过
程中逐步系统了自己的计算机观点,清楚了许多在多年以前老师没有讲清楚或是自己没
有想清楚的东西,同样也试着把自己的一点经验和同事们进行探讨,具备一定的Linux支
持的经验,自我感觉学习Linux的过程给了我一笔非常宝贵的财富,是一个非常明智的决
定。
目的:
  一直有一种想法,就是把自己的学习经验和别人进行探讨,让别人不要重复自己的
错误,在学习和使用Linux的过程中少走弯路,以一种比较轻松的态度来对待Linux系统
,并且我也希望能够为新手提供指路,希望有一天我们的大学生中也能够出现几个象Lin
us一样的程序员,实际上,中国有足够的人是应该能够做到这点的,对于国家有这样的程
序设计人员或是振臂一呼应者如云的工程管理人员是一件幸事。
  GNU/Linux的学习实际上是一个不断实践的过程,根据自己的需要不断地选择系统中
 的软件根据其相关文档在系统中付诸实施的过程,制定一个自己满意的目标可能效果更
好。参考的目标可能以学习为主更好,倘若你有一定的基础制定一个基于GNU/Linux系统

解决方案也不错;参考的应用范围开始以作为服务器运行更好,而不是作为和Windows 9
5一样的作桌面系统,比较理想的环境就是有一台Windows95的机器作为客户端而把Lin u
x作为服务器,我接触Linux很偶然完全是想看一看免费的UNIX的样子,学习Linux是的
目标开始很简单,就是实现类似于Windows NT中的IIS的功能。
  我把GNU/Linux的学习分为以下三个部分,并根据自己的经验提出注意的事项。
  1、系统安装
  对于熟悉Windows环境的人来说GNU/Linux的安装是一个非常痛苦的过程,对于安装
过程中遇到的问题足以让许多闻风而动的人放弃了GNU/Linux的使用。安装过程中比较典
 型的问题是硬件不支持,如声卡和显示卡,其中显示卡的问题一般是希望安装X-WINDOW

环境而引起的,并且常常引起系统重启或安装失败。所以,我给大家的第一个提示就是可
以考虑不安装X-WINDOW软件包; 其次如果你不是使用的Sound Blast系列的声卡,可 以
考虑不安装声卡;如果你的网络卡不识别可以考虑更换一块廉价的NE2000兼容网络卡
,保证网络的正常工作对于调试客户/服务器方式的应用程序是必须的,网络服务启动成
功但不能正常访问多是由于缺省的路由(default route)没有指定为网卡的地址引起。
 由于现在的硬件制造商还没有普遍提供Linux下的驱动程序,所以希望提供象Windows环
 境一样的硬件兼容的确有些困难。另外,建议做好可能要数次格式化硬盘的准备,实际

上当你使用Linux系统一段时间后,就会发现以前频繁格式化硬盘实际上是错误的。当然
 ,由一个具有Linux使用经验的人进行指导安装是最好的选择。同样,有一本关于Linux
 系统指南的手册是入门时不错的参考,不过不用太厚的著作因为许多的著作涉及的软件

介绍实际多是对软件文档的复制,最好的文档实际就在你的/usr/doc目录下,当然其文档
主要是英文不过都不是太深奥,毕竟很多软件的作者并不是用英文作为母语,并且开始是
可以从/usr/doc/HOWTO开始,如可读性极强的《DOS-TO-Linux HOWTO》《NET-3-HOWTO》

等文档。在不熟悉UNIX命令的情况下,可以使用一个类似pctools的软件mc,通过他你可以
完成许多的文件操作,如文件的浏览、编辑、删除等,当然熟悉UNIX的基本命令可能是开
始时的主要工作,UNIX的命令有一个比较特殊的地方就是有太多的选择项,其实开始只要
会它的基本功能就行。
系统的安装和配置是一个不断积累经验的过程,急于求成可能效果不好,和Windows入门
容易深造难刚好相反Linux是入门困难而深造容易。
  2、系统管理
  由于Linux是一个多用户多任务的操作系统,系统管理对于在Windows 95环境下的用
户可能是一个陌生的事情,在熟悉了基本的文件操作命令之后就可以考虑进行系统管理,
系统管理的工作主要分为用户管理、资源管理、软件安装、服务配置和网络。当你使用l
inuxconf进行系统管理时,你会发现他许多真正优秀的地方。系统管理是相通的,只要是
多用户的系统都存在这?
奈侍猓迪值姆椒ㄒ彩谴笸∫欤ü齦inuxconf进行管理实际是调用了许多的UNIX命
令,就象mc一样。Linux的通常使用的命令一般放在/usr/sbin、/usr/bin、/sbin、/bin
目录下,可以抽点时间看看到底他们都包含了些什么东西 ,并且使用man
看一下他该怎么使用。资源管理的一个特色是文件系统的限额功能,这是UNIX系统普遍具
有的功能而在微软的Windows NT 4没有实现的功能。软件安装实际就
是使用RPM软件或是直接使用tar,其中RPM包格式的软件更容易安装和使用。我对服务的
理解是系统的驻留程序,要配置Linux的各种服务需要了解涉及的程序和多半在/etc目录
下配置文件的作用,/etc目录和Windows的注册表文件类似,包含了系统启动和相关软件
的配置信息,在集中管理?
矫嬗Ω盟礧indows做得更好,不过/etc的文件多半是文本文件,你可以使用编辑器打开他
们,其中很多文件里包含了详细的配置帮助,配置服务的过程有时可能就是去掉它的注释
符号,实际上Linux下的服务和商用UNIX提供的服务很相似
。和在Windows环境相比,最好的是一般修改一个服务后不需要重新启动系统,而只是重
新启动该服务。网络是Linux给我最多收获的部分,由于Linux是一个互联网上诞生的系统
,支持完整的TCP/IP网络协议族,其网络应用软件非常丰富,并且很多网络应用软件都系
出名门且广泛使用,如B
IND、apache、wu-ftp、sendmail、telnetd、NFS、NIS等, 并且有非常完备的文档和例
子支持,通过配置其网络服务你可以深入了解TCP/IP协议,
构造一个非常不错的内部网环境,相关的文章比较多,在此便不再赘述。同样,支持Lin
ux的大型关系数据库也不少,目前各大数据库厂商(Oracle、Sybase、Informix)为了对
抗微软的操作系统垄断,均将产品向Linux移植,主要的目的是为大家提供使用和学习的
机会,他们对一般对产品?
南拗剖遣荒苡τ糜谏桃盗煊颍敝С諰inux的免费数据库系统也不少如PostgreSQL、M
ySQL等,由于SQL数据库涉及数据的并发控制、安全管理、 备份等问题,掌握它能够让你
在管理员的角度思考你的数据共享的问题,并且SQL作为一
个标准其通用性较强,所以把Linux作为一个数据库服务器平台是很理想的。同时,将数
 据库和Web服务器结合起来,利用PHP3或是其他的CGI工具便能够实现一个基于Web的数据
 库环境,并且是能够被你自由控制的环境。
  3、程序设计
  目前GNU/Linux系统是一个为程序员提供的操作系统和编程环境,因为其操作系统核
 心源代码和应用软件的源代码都是公开的,并且你可以根据你的爱好选择编程语言,你
 可以通过大量的应用软件源代码理解进程、线程、文件、设备、网络、RPC、IPC等原来

比较抽象的概念。和Windows环境下的可视开发环境相比,Linux下的编程可能会困难一
些,但是如果只是完成特定的服务端应用,就比较简单和高效,你开了集成环境可能需
要的是多开几个虚拟控制台,方便对库函数的参考。由于应用软件主要是C语言程序,有

C语言基础是比较好的,并且应用软件的注释都比较好,可以比较方便地修改。当然,用
JAVA也比较好,可以充分享受其跨平台的优势,还有就是可以使用以前只是听过而没有用
过的Ada、Lisp等语言。基于GNU/Linux的程序设计可以让你充分感受选择的自由,没有必
要大家都去研究核心程?
颍暇辜扑慊枪ぞ撸芄晃咛逵τ梅癫攀羌扑慊?
件人员的职责,仅有系统核心也是不够的。不过,在Linux下编写驱动程序不是象想象的
那么困难,原因主要是我们的设计者为我们提供了详细的指南,感受最深刻的是在/usr/
src/linux/drivers/net/skeleton.c程序,它描述了网卡驱动程序的框架,并且相关的文
档非常多如KHG等,相比W
indows就更为开放和容易。我不是程序设计的高手,通过GNU/Linux的使用知道一些有用
的方法,学会了欣赏别人的程序,如果让我做一个程序我的第一步可能就是找到相关的程
序并查看他们的编程方法。同样,如果能够真正使用互联网,你可以发现更多的程序设计
的参考。
  两年的实践给了我一种驾驭系统的轻松,因为我知道很多东西没有想象的那么难以接
受,最重要的是"try
again"。实际上,你能够真切地感受到各种系统是相通的,如同水是相通的一样;学习不
应该是一种负担,而是根据自己的实际情况去解决实际的问题,兴趣是一种不可忽视的因
素。我想象不出如果能够根据大学计算机专业的课程设置,
同步提供Linux的实践,那么我们的大学生在毕业时会有怎样的技术水平?同样,我们的
高校可能会有更多一些的可以为普通人接受的软件,而不是只是培养专家的科研成果,
或是寥寥千套的获奖操作系统。没有理论的实践是盲目的,没有实践的理论是空洞的,
而计算机软件是需要理论和实践紧密结合的。
使用PHP的错误处理
使用PHP的错误处理 在web 上所有常见的错误之一就是无效的链接。一旦从其它站点上出
现了无效的链接,你会重新安排你的站点。人们会将他们喜欢的站点存成书签,如果在三
个月后再次访问时,仅仅发现的是'404 Not
Found'时,将不会给他们任务帮助,告诉他们如何从你的站点去查找原始的信息。让我们
解决这个问题,或者至少给你的用户一个友好的帮助,一旦他们遇到'a 404' 错误时,能
够得到一些痕迹。你可以创建普通的页面来 报告在处理你的页面时所遇到的所有的错误

  PHP 与Apache一起可以很自由地让你创建自已的出错页面,但是需要重新进行配置,
并且要编少量的代码。先让我们学习配置部分。
  Apache的ErrorDocument指示用来指出在出现错误时Apache应重定向到哪一个文档(
或URI)。它允许你为每一个你的用户可能遇到的错误代码指定一个资源。通过在你的服
务器配置中增加一个ErrorDocument
404/error.php指示。这个将在用户访问一个不存在的页面时,重定向到'error.php'中,
下面我们就会写出'error.php'页面。不要忘了重新启动Apache以使改动生效。
  接着,我们写出一个简单的error.php:

你所请求的文件 (<?=$REDIRECT_URL?>) 在这个服务器上不存在。请查找你想要的文件从
<A HREF="/">前页</A>。
  现在试着读取一个在你服务器上不存在的页面,怎么样,你可以看到error.php了,
它有着一个良好和好的消息,并且还有一个到前页的链接。
  让我们把它扩展一下。正如你所见,我在error.php中使用了REDIRECT_URL变量。这
个变量是Apache在执行了一个ErrorDocument指示时所设置的,并且给出了一种可能来找
到原始的资源。在这种情况下,Apache还设置了一些别的变量,所有的变量可以在这里找
到。使用这些变量可能创
建一个很好的出错页面,用于给用户一个不错与友好的出错页面,而代替Apache给出的缺
省页面。

从PHP页面中输出错误
  从一个PHP页面输出错误与模拟Apache对ErrorDocument指示所做的很象,你只要简单
地将用户重定向,通过使用query-string变量,而Apache则通常是设置在环境变量里面。
这样就可以使用同一个出错页面来处理各种错误。下面是一个例子:

-----------------------------------------------------------------------------
---
<?php

function throw_error($message) {
$error_page ="/err/error.php";

$error_url = $error_page;
$error_url .="?REDIRECT_ERROR_NOTES=$message";
$error_url .="&REDIRECT_URL=". $GLOBALS["PHP_SELF"];
$error_url .="&REDIRECT_REQUEST_METHOD=$REQUEST_METHOD";
$error_url .="&REDIRECT_STATUS=501";
Header("Status: 501");
Header("Location: $error_url");
exit;
}

ob_start();
// 使用输出缓冲以便在这页中的任何地方输出错误

if(!condition) {
throw_error("the condition failed");
}

ob_end_flush();
// 页面处理完毕,刷新输出缓冲

?>---------------------------------------------------------------------------
-----
  使用PHP4的输出缓冲特性对生成一般的出错报告功能也会有帮助。但是在你确认整个
出错页面处理完毕时,不要忘记刷新缓冲区,你可以在你的代码中的任可地方通过Heade
r调用来进行重定向。
  读者可以自行设计,实现自已的出错页面来适应他/她的站点。不要忘了,你可以包
含一个带email的提交表单在出错页面中,这样用户可以反馈给你一些意见。
Linux 内核编程风格

本文在http://gem.ncic.ac.cn/~xhg/documents/CodingStyle.html

谢华刚(xhg@gem.ncic.ac.cn)翻译 ,欢迎任何问题和建议.

这篇短小的文章是对Linux内核编程风格的建议. 编程风格非常的个性化,而且,我并不想
将我的观点强加给任何人,但是为了变于维护,我不得不提出这个观点.详情如下:

在最开始,我应该写出GNU 编程风格的标准而不用理会它. 不要理会他们,它只是一个符号
表情而已.

好,让我们开始吧!

第一章: 缩进格式

Tab 是 8 个字符,于是缩进也是8个字符.有很多怪异的风格,他们将缩进格式定义为4个字
符(设置为2个字符!)的深度,这就象试图将PI定义为3一样让人难以接受.

理由是: 缩进的大小是为了清楚的定义一个块的开始和结束.特别是当你已经在计算机前
面呆了20多个小时了以后,你会发现一个大的缩进格式使得你对程序的理解更容易.

现在,有一些人说,使用8个字符的缩进使得代码离右边很近,在80个字符宽度的终端屏幕上
看程序很难受.回答是,但你的程序有3个以上的缩进的时候,你就应该修改你的程序.

总之,8个字符的缩进使得程序易读,还有一个附加的好处,就是它能在你将程序变得嵌套层
数太多的时候给你警告.这个时候,你应该修改你的程序.

第二章:大符号的位置

另外一个C 程序编程风格的问题是对 大括号的处理. 同缩进大小不同,几乎没有什么理由
去选择一种而不选择另外一种风格,但有一种推荐的风格,它是 Kernighan 和 Ritchie的
经典的那本书带来的,它将开始的大括号放在一行的最后,而将结束大括号放在一行的第一
位,如下所示:

if (x is true) {

we do y

}

然而,还有一种特殊的情况:命名函数: 开始的括号是放在下一行的第一位,如下:

int function(int x)

{

body of function

}

所有非正统的人会非难这种不一致性,但是,所有思维正常的人明白: (第一) K&R是___对
___的,(第二)如果K&R不对,请参见第一条. (:-))......另外,函数也是特殊的,不一定非
得一致.



需要注意的是结束的括号在它所占的那一行是空的,__除了__它跟随着同一条语句的继续
符号.如"while"在 do-while循环中,或者"else"在if语句中.如下:

do {

body of do-loop

} while (condition);

以及

if (x == y) {

..

} else if (x>y) {

...

} else {

....

}

理由: K&R.

另外,注意到这种大括号的放置方法减小了空行的数量,但却没有减少可读性.于是,在屏幕
大小受到限制的时候,你就可以有更多的空行来写些注释了.

第三章 : 命名系统

C是一种简洁的语言,那么,命名也应该是简洁的. 同MODULE-2以及PASCAL语言不同的是,C
程序员不使用诸如 ThisVariableIsATemporaryCounter 之类的命名方式. 一个C语言的程
序员会将之命名为"tmp",这很容易书写,且并不是那么难以去理解.

然而,当混合类型的名字不得不出现的时候,描述性名字对全局变量来说是必要的了. 调用
一个名为"foo"全局的函数是很让人恼火的.

全局变量( 只有你必须使用的时候才使用它) ,就象全局函数一样,需要有描述性的命名方
式.假如你有一个函数用来计算活动用户的数量,你应该这样命名--"count_active_users
()"--或另外的相近的形式,你不应命名为"cntusr()".

有一种称为 Hungarian命名方式,它将函数的类型编码写如变量明中,这种方式是脑子有毛
病的一种表现 --- 编译器知道这个类型而且会去检查它,而这样只会迷惑程序员. --知道
为什么Micro$oft为什么会生产这么多"臭虫"程序了把!!.

局部变量的命名应该短小精悍. 假如你有一个随机的整数循环计数器,它有可能有"i",如
果没有任何可能使得它能被误解的话,将其写作"loop_counter"是效率低下的.同样的,""
tmp"可以是任何临时数值的函数变量.

如果你害怕混淆你的局部变量的名字,还有另外一个问题,就是称 function-growth-horm
one-imbalance syndrome.

第四章: 函数

函数应该短小而迷人,而且它只作一件事情. 它应只覆盖一到两个屏幕(80*24一屏),并且
只作一件事情,而且将它做好.( 这不就是UNIX 的风格吗,译者注).

一个函数的最大长度和函数的复杂程度以及缩进大小成反比. 于是, 如果你已经写了简单
但长度较长的的函数, 而且你已经对不同的情况做了很多很小的事情,写一个更长一点的
函数也是无所谓的.

然而, 假如你要写一个很复杂的函数, 而且你已经估计到假如一般人读这个函数,他可能
都不知道这个函数在说些什么,这个时候,使用具有描述性名字的有帮助的函数.

另外一个需要考虑的是局部变量的数量. 他们不应该超过5-10个, 否则你有可能会出错.
 重新考虑这个函数 , 将他们分割成更小的函数. 人的大脑通常可以很容易的记住7件不
同的事情,超过这个数量会引起混乱. 你知道你很聪明,但是你可能仍想去明白2周以前的
做的事情.

第5章 : 注释

注释是一件很好的事情, 但是过多的注释也是危险的,不要试图区解释你的代码是注释如
何如何的好: 你应该将代码写得更好,而不是花费大量的时间去解释那些糟糕的代码.

通常情况下,你的注释是说明你的代码做些什么,而不是怎么做的. 而且,要试图避免将注
释插在一个函数体里: 假如这个函数确实很复杂,你需要在其中有部分的注释,你应该回到
第四章看看. 你可以写些简短的注释来注明或警告那些你认为特别聪明(或及其丑陋)的部
分,
但是你必须要避免过多. 取而代之的是, 将注释写在函数前,告诉别人它做些什么事情,和
可能为什么要这样做.

第六章 : 你已经深陷其中了.

不要着急. 你有可能已经被告之"GUN emacs"会自动的帮你处理C 的源代码格式, 而且你
已经看到它确实如此, 但是, 缺省的情况下, 它的作用还是不尽如人意(实际上,他们比随
便敲出来的东西还要难看 - a infinite number of monkeys typing into GNU emacs w
ould never make a
good program)

于是, 你可以要么 不要使用 GUN emacs, 要么让它使用saner valules. 使用后者,你需
要将如下的语句输入到你的 .emacs文件中.

(defun linux-c-mode ()

"C mode with adjusted defaults for use with the Linux kernel."

(interactive)

(c-mode)

(c-set-style"K&R")

(setq c-basic-offset 8))

这会定义一个 M-x Linux-c-mode 的命令. 当你hacking 一个模块的时候, 如何你将-*-
 linux-c -*- 输入在最开始的两行, 这个模式会自动起作用. 而且, 你也许想加入如下


(setq auto-mode-alist (cons '("/usr/src/linux.*/.*\\.[ch]$". linux-c-mode)

auto-mode-alist))

到你的.emacs文件中, 这样的话, 当你在/usr/src/linux下编辑文件的时候,它会自动切
换到linux-c-mode .

但是, 假如你还不能让emaces去自动处理文件的格式, 不要紧张, 你还有一样东西 :"缩
进".

GNU 的缩进格式也很死板, 这就是你为什么需要加上几行命令选项. 然而, 这还不算太坏
,因为GNU 缩进格式的创造者也记得 K&R 的权威, ( GNU 没有罪, 他们仅仅是在这件事情
上错误的引导了人们) , 你要做的就只有输入选项"-kr -i8"(表示"K&R, 缩进8个字符).


"缩进"有很多功能, 特别是当它建议你重新格式你你的代码的时候,你应该看看帮助. 但
要记住 :"缩进"不是风格很差的程序的万灵丹.

Linux编译内核详解

Linux编译内核详解

内核简介

内核,是一个操作系统的核心。它负责管理系统的进程、内存、设备驱动程序、文件和

网络系统,决定着系统的性能和稳定性。

Linux的一个重要的特点就是其源代码的公开性,所有的内核源程序都可以在/usr/src/

linux下找到,大部分应用软件也都是遵循GPL而设计的,你都可以获取相应的源程序代

码。全世界任何一个软件工程师都可以将自己认为优秀的代码加入到其中,由此引发的

一个明显的好处就是Linux修补漏洞的快速以及对最新软件技术的利用。而Linux的内核

则是这些特点的最直接的代表。

想象一下,拥有了内核的源程序对你来说意味着什么?首先,我们可以了解系统是如何

工作的。通过通读源代码,我们就可以了解系统的工作原理,这在Windows下简直是天方

夜谭。其次,我们可以针对自己的情况,量体裁衣,定制适合自己的系统,这样就需要

重新编译内核。在Windows下是什么情况呢?相信很多人都被越来越庞大的Windows整得

莫名其妙过。再次,我们可以对内核进行修改,以符合自己的需要。这意味着什么?没

错,相当于自己开发了一个操作系统,但是大部分的工作已经做好了,你所要做的就是

要增加并实现自己需要的功能。在Windows下,除非你是微软的核心技术人员,否则就不

用痴心妄想了。

内核版本号

由于Linux的源程序是完全公开的,任何人只要遵循GPL,就可以对内核加以修改并发布

给他人使用。Linux的开发采用的是集市模型(bazaar,与cathedral--教堂模型--对应

),为了确保这些无序的开发过程能够有序地进行,Linux采用了双树系统。一个树是稳

定树(stable tree),另一个树是非稳定树(unstable tree)或者开发树(developm

ent tree)。一些新特性、实验性改进等都将首先在开发树中进行。如果在开发树中所

做的改进也可以应用于稳定树,那么在开发树中经过测试以后,在稳定树中将进行相同

的改进。一旦开发树经过了足够的发展,开发树就会成为新的稳定树。开发数就体现在

源程序的版本号中;源程序版本号的形式为x.y.z:对于稳定树来说,y是偶数;对于开

发树来说,y比相应的稳定树大一(因此,是奇数)。到目前为止,稳定树的最高版本是

2.2.16,最新发布的Redhat7.0所采用的就是2.2.16的内核;开发树的最新版本是2.3.9

9。也许你已经发现和多网站上都有2.4.0-test9-pre7之类的内核,但是这并不是正式版

本。内核版本的更新可以访问http://www.kernel.org。

为什么重新编译内核

Linux作为一个自由软件,在广大爱好者的支持下,内核版本不断更新。新的内核修订了

旧内核的bug,并增加了许多新的特性。如果用户想要使用这些新特性,或想根据自己的

系统度身定制一个更高效,更稳定的内核,就需要重新编译内核。

通常,更新的内核会支持更多的硬件,具备更好的进程管理能力,运行速度更快、 更稳

定,并且一般会修复老版本中发现的许多漏洞等,经常性地选择升级更新的系统内核是

Linux使用者的必要操作内容。

为了正确的合理地设置内核编译配置选项,从而只编译系统需要的功能的代码,一般主

要有下面四个考虑:

l 自己定制编译的内核运行更快(具有更少的代码)

l 系统将拥有更多的内存(内核部分将不会被交换到虚拟内存中)

l 不需要的功能编译进入内核可能会增加被系统攻击者利用的漏洞

l 将某种功能编译为模块方式会比编译到内核内的方式速度要慢一些

内核编译模式

要增加对某部分功能的支持,比如网络之类,可以把相应部分编译到内核中(build-in

),也可以把该部分编译成模块(module),动态调用。如果编译到内核中,在内核启

动时就可以自动支持相应部分的功能,这样的优点是方便、速度快,机器一启动,你就

可以使用这部分功能了;缺点是会使内核变得庞大起来,不管你是否需要这部分功能,

它都会存在,这就是Windows惯用的招数,建议经常使用的部分直接编译到内核中,比如

网卡。如果编译成模块,就会生成对应的.o文件,在使用的时候可以动态加载,优点是

不会使内核过分庞大,缺点是你得自己来调用这些模块。

内核编译详解

新版本内核的获取和更新

Linux内核版本发布的官方网站是http://www.kernel.org,国内各大ftp上一般都可以找

到某些版本的内核。新版本的内核的发布有两种形式,一种是完整的内核版本,另外一

种是patch文件,即补丁。完整的内核版本比较大,比如linux-2.4.0-test8.tar.bz2就

有18M之多,网速快的用户可以下载使用。完整内核版本一般是.tar.gz(.tgz)文件或

者是.bz2文件,二者分别是使用gzip或者bzip2进行压缩的文件,使用时需要解压缩。p
atch文件则比较小,一般只有几十K到几百K,极少的会超过1M,网速慢的用户可以使用
patch文件来升级内核。但是patch文件是针对于特定的版本的,你需要找到自己对应的
版本才能使用。
编译内核需要root权限,以下操作都假定你是root用户。请把你需要升级的内 拷贝到
/usr/src/下(下文中以2.4.0test8的内核的linux-2.4.0test8.tar.gz为例),命令为
#cp linux-2.4.0test8.tar.gz /usr/src

让我们先来查看一下当前/usr/src的内容,注意到有一个linux的符号链接,它指向一个

类似于linux-2.2.14(对应于你现在使用的内核版本号)的目录。首先删除这个链接:
#cd /usr/src
#rm -f linux
现在解压我们下载的源程序文件。如果所下载的是.tar.gz(.tgz)文件,请使用下面的
命令:
#tar -xzvf linux-2.4.0test8.tar.gz
如果你所下载的是.bz2文件,例如linux-2.4.0test8.tar.bz2,请使用下面的命令
#bzip2 -d linux-2.4.0test8.tar.bz2
#tar -xvf linux.2.4.0.test8.tar
现在让我们再来看一下/usr/src下的内容,你会发现现在有了一个名为linux的目录,里
面就是我们需要升级到的版本的内核的源程序。还记得那个名为linux的链接么?之所以
使用那个链接就是防止在升级内核的时候会不慎把原来版本内核的源程序给覆盖掉了。
我们也需要同样处理:
#mv linux linux-2.4.0test8
#ln -s linux-2.4.0test8 linux
这样我们也有了一个名为linux的符号链接,就不用担心以后会把它覆盖掉了(也许你会
觉得重新建立linux的符号链接没有必要,但实际上这是必不可少的,下文中会有介绍)
。如果你还下载了patch文件,比如patch-2.4.0test8,你就可以进行patch操作(下面
假设patch-2.4.0test8已经位于/usr/src目录下了,否则你需要先把该文件拷贝到/usr
/src下):
#patch -p0<patch-2.4.0test8
现在,我们已经把内核源程序升级到最新版本了,下面就让我们开始内核编译的旅程吧
准备工作

通常要运行的第一个命令是:
#cd /usr/src/linux;make mrproper
该命令确保源代码目录下没有不正确的.o文件以及文件的互相依赖。由于我们使用刚下
载的完整的源程序包进行编译,所以本步可以省略。而如果你多次使用了这些源程序编
译内核,那么最好要先运行一下这个命令。
确保/usr/include/目录下的asm、linux和scsi等链接是指向要升级的内核源代码的。它
们分别链向源代码目录下的真正的、该计算机体系结构(对于PC机来说,使用的体系结
构是i386)所需要的真正的include子目录。如:asm指向/usr/src/linux/include/asm
-i386等。若没有这些链接,就需要手工创建,按照下面的步骤进行:
# cd /usr/include/
# rm -r asm linux scsi
# ln -s /usr/src/linux/include/asm-i386 asm
# ln -s /usr/src/linux/include/linux linux
# ln -s /usr/src/linux/include/scsi scsi
这是配置非常重要的一部分。删除掉/usr/include下的asm、linux和scsi链接后,再创
建新的链接指向新内核源代码目录下的同名的目录。这些头文件目录包含着保证内核在
系统上正确编译所需要的重要的头文件。现在你应该明白为什么我们上面又在/usr/src
下"多余"地创建了个名为linux的链接了吧?
配置
接下来的内核配置过程比较烦琐,但是配置的适当与否与日后Linux的运行直接相关,有
必要了解一下一些主要的且经常用到的选项的设置。
配置内核可以根据需要与爱好使用下面命令中的一个:
#make config(基于文本的最为传统的配置界面,不推荐使用)
#make menuconfig(基于文本选单的配置界面,字符终端下推荐使用)
#make xconfig(基于图形窗口模式的配置界面,Xwindow下推荐使用)
#make oldconfig(如果只想在原来内核配置的基础上修改一些小地方,会省去不少麻烦

这三个命令中,make xconfig的界面最为友好,如果你可以使用Xwindow,那么就推荐你
使用这个命令,界面如下:
图xconfig_main.jpg
如果你不能使用Xwindow,那么就使用make menuconfig好了。界面虽然比上面一个差点
,总比make config的要好多了,下图为make menuconfig的界面:
图menuconfig_main.jpg
选择相应的配置时,有三种选择,它们分别代表的含义如下:
  Y--将该功能编译进内核
  N--不将该功能编译进内核
  M--将该功能编译成可以在需要时动态插入到内核中的模块
如果使用的是make xconfig,使用鼠标就可以选择对应的选项。如果使用的是make men
uconfig,则需要使用空格键进行选取。你会发现在每一个选项前都有个括号, 但有的是
中括号有的是尖括号,还有一种圆括号。 用空格键选择时可以发现,中括号里要么是空
,要么是"*",而尖括号里可以是空,"*"和"M"这表示前者对应的项要么不要,要么编译
到内核里;后者则多一样选择,可以编译成模块。而圆括号的内容是要你在所提供的几
个选项中选择一项。
在编译内核的过程中,最烦杂的事情就是这步配置工作了,很多新手都不清楚到底该如
何选取这些选项。实际上在配置时,大部分选项可以使用其缺省值,只有小部分需要根
据用户不同的需要选择。选择的原则是将与内核其它部分关系较远且不经常使用的部分
功能代码编译成为可加载模块,有利于减小内核的长度,减小内核消耗的内存,简化该
功能相应的环境改变时对内核的影响;不需要的功能就不要选;与内核关心紧密而且经
常使用的部分功能代码直接编译到内核中。下面就让我们对常用的选项分别加以介绍。
1. Code maturity level options
代码成熟等级。此处只有一项:prompt for development and/or incomplete code/dr
ivers,如果你要试验现在仍处于实验阶段的功能,比如khttpd、IPv6等,就必须把该项
选择为Y了;否则可以把它选择为N。
2. Loadable module support
对模块的支持。这里面有三项:
l Enable loadable module support:除非你准备把所有需要的内容都编译到内核里面
,否则该项应该是必选的。
l Set version information on all module symbols:可以不选它。
l Kernel module loader:让内核在启动时有自己装入必需模块的能力,建议选上。
3. Processor type and features
CPU类型。内容蛮多的,不一一介绍了,有关的几个如下:
l Processor family:根据你自己的情况选择CPU类型。
l High Memory Support:大容量内存的支持。可以支持到4G、64G,一般可以不选。
l Math emulation:协处理器仿真。协处理器是在386时代的宠儿,现在早已不用了。
l MTTR support:MTTR支持。可不选。
l Symmetric multi-processing support:对称多处理支持。除非你富到有多个CPU,否
则就不用选了。
4. General setup
这里是对最普通的一些属性进行设置。这部分内容非常多,一般使用缺省设置就可以了
。下面介绍一下经常使用的一些选项:
l Networking support:网络支持。必须,没有网卡也建议你选上。
l PCI support:PCI支持。如果使用了PCI的卡,当然必选。
l PCI access mode:PCI存取模式。可供选择的有BIOS、Direct和Any,选Any吧。
l Support for hot-pluggabel devices:热插拔设备支持。支持的不是太好,可不选。
l PCMCIA/CardBus support:PCMCIA/CardBus支持。有PCMCIA就必选了。
l System V IPC
l BSD Process Accounting
l Sysctl support:以上三项是有关进程处理/IPC调用的,主要就是System V和BSD两种
风格。如果你不是使用BSD,就按照缺省吧。
l Power Management support:电源管理支持。
l Advanced Power Management BIOS support:高级电源管理BIOD支持。
5. Memory Technology Device(MTD)
MTD设备支持。可不选。
6. Parallel port support
串口支持。如果不打算使用串口,就别选了。
7. Plug and Play configuration
即插即用支持。虽然Linux对即插即用目前支持的不如Windows好,但是还是选上吧,这
样你可以拔下鼠标之类的体验一下Linux下即插即用的感觉。
8. Block devices
块设备支持。这个就得针对自己的情况来选了,简单说明一下吧:
l Normal PC floppy disk support:普通PC软盘支持。这个应该必选。
l XT hard disk support:
l Compaq SMART2 support:
l Mulex DAC960/DAC1100 PCI RAID Controller support:RAID镜像用的。
l Loopback device support:
l Network block device support:网络块设备支持。如果想访问网上邻居的东西,就
选上。
l Logical volume manager(LVM)support:逻辑卷管理支持。
l Multiple devices driver support:多设备驱动支持。
l RAM disk support:RAM盘支持。
9. Networking options
网络选项。这里配置的是网络协议。内容太多了,不一一介绍了,自己看吧,如果你对
网络协议有所了解的话,应该可以看懂的。如果懒得看,使用缺省选项(肯定要选中TC
P/IP networking哦)就可以了。让我们看看,TCP/IP、ATM、IPX、DECnet、Appletalk
……支持的协议好多哦,IPv6也支持了,Qos and/or fair queueing(服务质量公平调
度)也支持了,还有kHTTPd,不过这些都还在实验阶段。
10. Telephony Support
电话支持。这个是什么东东?让我查查帮助,原来是Linux下可以支持电话卡,这样你就
可以在IP上使用普通的电话提供语音服务了。记住,电话卡可和modem没有任何关系哦。
11. ATA/IDE/MFM/RLL support
这个是有关各种接口的硬盘/光驱/磁带/软盘支持的,内容太多了,使用缺省的选项吧,
如果你使用了比较特殊的设备,比如PCMCIA等,就到里面自己找相应的选项吧。
12. SCSI support
SCSI设备的支持。我没有SCSI的设备,所以根本就不用选,如果你用了SCSI的硬盘/光驱
/磁带等设备,自己找好了。
13. IEEE 1394(FireWire)support
这个是什么?低版本的没有见过,看看帮助再说。原来是要Fireware硬件来提高串行总
线的性能,我没有,不选了。
14. I2O device support
这个也不清楚,帮助里说是这个需要I2O接口适配器才能支持的,在智能Input/Output(
I2O)体系接口中使用,又是要硬件,不选了。
15. Network device support
网络设备支持。上面选好协议了,现在该选设备了,可想而知,内容肯定多得很。还好
还好,里面大概分类了,有ARCnet设备、Ethernet(10 or 100 Mbit)、Ethernet(10
00Mbit)、Wireless LAN(non-hamradio)、Token Ring device、Wan interfaces、P
CMCIA network device support几大类。我用的是10/100M的以太网,看来只需要选则这
个了。还是10/100M的以太网设备熟悉,内容虽然多,一眼就可以看到我所用的RealTec
k RTL-8139 PCI Fast Ethernet Adapter support,为了免得麻烦,编译到内核里面好
了,不选M了,选Y。耐心点,一般说来你都能找到自己用的网卡。如果没有,你只好自
己到厂商那里去要驱动了。
16. Amateur Radio support
又一个不懂的,应该是配置业余无线广播的吧,没有,不要了。
17. IrDA(infrared)support
这个要红外支持,免了。
18. ISDN subsystem
如果你使用ISDN上网,这个就必不可少了。自己看着办好了。
19. Old CD-ROM drivers(not SCSI、not IDE)
做的可真周到,原来那些非SCSI/IDE口的光驱谁还在用啊,自己选吧,反正我是用的ID
E的CD-ROM,不选这个。
20. Character devices
字符设备。这个内容又太多了,先使用缺省设置,需要的话自己就修改。把大类介绍一
下吧:
l I2C support:I2C是Philips极力推动的微控制应用中使用的低速串行总线协议。如果
你要选择下面的Video For Linux,该项必选。
l Mice:鼠标。现在可以支持总线、串口、PS/2、C&T 82C710 mouse port、PC110 dig
itizer pad,自己根据需要选择。
l Joysticks:手柄。即使在Linux下把手柄驱动起来意义也不是太大,游戏太少了。
l Watchdog Cards:虽然称为Cards,这个可以用纯软件来实现,当然也有硬件的。如果
你把这个选中,那么就会在你的/dev下创建一个名为watchdog的文件,它可以记录你的
系统的运行情况,一直到系统重新启动的1分钟左右。有了这个文件,你就可以恢复系统
到重启前的状态了。
l Video For Linux:支持有关的音频/视频卡。
l Ftape, the floppy tape device driver:
l PCMCIA character device support:
21. File systems
文件系统。内容又太多了,老法子,在缺省选项的基础上进行修改。介绍以下几项:
l Quota support:Quota可以限制每个用户可以使用的硬盘空间的上限,在多用户共同
使用一台主机的情况中十分有效。
l DOS FAT fs support:DOS FAT文件格式的支持,可以支持FAT16、FAT32。
l ISO 9660 CD-ROM file system support:光盘使用的就是ISO 9660的文件格式。
l NTFS file system support:ntfs是NT使用的文件格式。
l /proc file system support:/proc文件系统是Linux提供给用户和系统进行交互的通
道,建议选上,否则有些功能没法正确执行。
还有另外三个大类都规到这儿了:Network File Systems(网络文件系统)、Partitio
n Types(分区类型)、Native Language Support(本地语言支持)。值得一提的是Ne
twork File Systems里面的两种:NFS和SMB分别是Linux和Windows相互以网络邻居的形
式访问对方所使用的文件系统,根据需要加以选择。
22. Console drivers
控制台驱动。一般使用VGA text console就可以了,标准的80*25的文本控制台。
23. Sound
声卡驱动。如果你能在列表中找到声卡驱动那自然最好,否则就试试OSS了。
24. USB supprot
USB支持。很多USB设备,比如鼠标、调制解调器、打印机、扫描仪等,在Linux都可以得
到支持,根据需要自行选择。
25. Kernel hacking
配置了这个,即使在系统崩溃时,你也可以进行一定的工作了。普通用户是用不着这个
功能的。
总算配置完了,现在存盘退出,当然你也可以把现在的配置文件保存起来,这样下次再
配置的时候就省力气了。
编译
在繁杂的配置工作完成以后,下面你就可以自己到杯茶耐心等候了。与编译有关的命令
有如下几个:
#make dep
#make clean
#make zImage
#make bzImage
#make modules
#make modules_install
#depmod -a
第一个命令make dep实际上读取配置过程生成的配置文件,来创建对应于配置的依赖关
系树,从而决定哪些需要编译而那些不需要;第二命令make clean完成删除前面步骤留
下的文件,以避免出现一些错误;第三个命令make zImage和第四个命令make bzImage实
现完全编译内核,二者生成的内核都是使用gzip压缩的,只要使用一个就够了,它们的
区别在于使用make bzImage可以生成大一点的内核,比如在编译2.4.0版本的内核时如果
使用make zImage命令,那么就会出现system too big的错误提示。建议大家使用make
bzImage命令。
后面三个命令只有在你进行配置的过程中,在回答Enable loadable module support (
CONFIG_MODULES)时选了"Yes"才是必要的,make modules和make modules_install分别
生成相应的模块和把模块拷贝到需要的目录中。
严格说来,第七个命令和编译过程并没有关系,它是生成模块间的依赖关系,这样你启
动新内核之后,使用modprobe命令加载模块时就能正确地定位模块。
更新
经过以上的步骤,我们终于得到了新版本的内核。为了能够使用新版本的内核,我们还
需要做一些改动:
#cp /usr/src/linux/System.map /boot/System.map-2.4.0test8
#cp /usr/src/linux/arch/i386/bzImage /boot/vmlinuz-2.4.0test8
以上这两个文件是我们刚才编译时新生成的。下面修改/boot下的两个链接System.map和
vmlinuz,使其指向新内核的文件:
#cd /boot;rm -f System.map vmlinuz
#ln -s vmlinuz-2.4.0test8 vmlinuz
#ln -s System.map-2.4.0test8 System.map
然后修改/etc/lilo.conf:
#vi /etc/lilo.conf
增加如下一段:
image=/boot/vmlinuz-2.4.0test8
label=linux240
read-only
root=/dev/hda2
其中root=/dev/hda2一行要根据需要自行加以修改。
运行:
#/sbin/lilo -v
确认对/etc/lilo.conf的编辑无误,现在重新启动系统:
#shutdown -r now
在机器重启后出现LILO时按TAB键,输入linux240,我们的新内核发挥作用了,好好享受
Linux 引导解析(使用bootsect.s+setup.s方式)
Linux 引导解析(使用bootsect.s+setup.s方式)
  我详细解析了bootsect.s,同时阅读了setup.s。其中bootsect.s存放于磁盘的主引
导扇区,bios-startup程序加载该程序(bootsect.s)到内存0x700处,并由此执行boot
sect.s来引导Linux
kernel。在bootsect.s中加载setup.s至内存中,并在执行完它自身后,jump跳至刚刚已
读入的setup部份,继续执行。
  Linux Kernel Image 生成过程:
  一.
引导扇区汇编代码bootsect.s被预处理成bbootsect.s或bootsect.s(无论有无D_BIG_KE
RNEL),当然这取决于编译目标是bzImage还是bImage。bbootsect.s被汇编,然后被转化
成合法的二进制文件,通过调用bbootsect.s。(或者是bootsect.s被汇编,然后被转化
成合法的二进制文件,通
过调用bootsect.s。)
  二. 启动代码setup.s(include video.s)被预处理成bsetup.s(就bzImage而言)
,或者是setup.s(就zImage而言)。和bootsect.s一样,不同的是bzImage使用-D_BIG_
KERNEL来标志。结果同样也将被转化成合法的二进制文件,通过调用bsetup.s或setup.s

  三. 进入子目录 arch/i386/boot/compressed 把/usr/src/linux/vmlinux 转变成
$tmppiggy.gz(临时文件名,合法的二进制格式)。删除.note 和.comment 文件。
  四. gzip -9<$tmppiggy>$tmppiggy.gz。(gunzip()在main.c中被实现。用于解压
缩部份Linux内核,负责解压缩Linux内核的函数还有decompressed())
  五. 联接(Link)$temppiggy.gz ,使其成为 ELF relocate(ld -r)文件 piggy.0。
  六. 编译压缩程序 head.s 和 misc.c(仍然存在于子目录 arch/i386/boot/compre
ssed中。)成ELF的目标文件head.o和misc.o。
  七. 联接(Link)所有的目标文件head.o、misc.o、piggy.o成bvmlinux(或者vmli
nux,就zImage而言,注意:不要错误地把它认为是 /usr/src/linux/vmlinux !)。一定要
注意到这两者之间的不同:-Ttext 0x1000(调用的是经过压缩的内核镜像vmlinux),-Tt
ext
0x100000(调用的是未经过压缩的大内核镜像bvmlinux,还有bzImage compression load
er 将被high-loaded,调到内存高地址处)。
  八. 把bvmlinux转化成合法的二进制文件 bvmlinux.out ,删除 .note 和.commen
t部份。
  九. 最后返回 arch/i386/boot 子目录,使用编程工具把bbootsect+bsetup+compr
essed/bvmlinux.out 编译成bzImage。(也可以使编译成zImage)。

  bootsect.s完成如下功能:
  一. bootsect.s将它"自己"从被ROM BIOS载入的绝对地址0x7C00处搬到0x9000处,
然后利用一个jmpi(jump indirectly)的指令,跳到新位置的jmpi的下一行(go:)去执行

  二. 接着,将其他segment registers包括DS,ES,SS都指向0x9000这个位置,与C
S看齐。另外将SP及DX指向一任意位移地址( offset ),这个地址等一下会用来存放磁盘
参数表(disk para- meter table )。设置堆栈段,且开始处为0x4000-12。
  三. 读入磁盘参数,并建立参数表。
  四. 接着利用BIOS中断服务int 13h的第0号功能,重置磁盘控制器,使得刚才的设
定发挥功能。
  五. 完成重置磁盘控制器之后,bootsect就从磁盘上读入紧邻着bootsect的setup程
序,也就是setup.S,此读入动作是利用BIOS中断服务int 13h的第2号功能。setup的ima
ge将会读入至程序所指定的内存绝对地址0x90200处,也就是在内存中紧邻着bootsect
所在的位置。待setup的image读入内存后,利用BIOS中断服务int 13h的第8号功能读取目
前磁盘的参数。
  六. 再来,就要读入真正linux的kernel了,也就是你可以在linux的根目录下看到
的"vmlinuz"。在读入前,将会先呼叫BIOS中断服务int 10h 的第3号功能,读取游标位置
,之后再呼叫BIOS 中断服务int 10h的第13h号功能,在萤幕上输出字串"Loading",这个
字串在boot
linux时都会首先被看到。
  七. 接下来做的事是检查root device,之后就仿照一开始的方法,利用indirectj
ump 跳至刚刚已读入的setup.s部份。
  源代码解析如下:
!
! bootsect.s Copyright (C) 1991, 1992 Linus Torvalds
! modified by Drew Eckhardt
! modified by Bruce Evans (bde)
!
! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves
! itself out of the way to address 0x90000, and jumps there.
!
! bde - should not jump blindly, there may be systems with only 512K low
! memory. Use int 0x12 to get the top of memory, etc.
!
! It then loads 'setup' directly after itself (0x90200), and the system
! at 0x10000, using BIOS interrupts.
!
! NOTE! currently system is at most (8*65536-4096) bytes long. This should
! be no problem, even in the future. I want to keep it simple. This 508 kB
! kernel size should be enough, especially as this doesn't contain the
! buffer cache as in minix (and especially now that the kernel is
! compressed :-)
!
! The loader has been made as simple as possible, and continuous
! read errors will result in a unbreakable loop. Reboot by hand. It
! loads pretty fast by getting whole tracks at a time whenever possible.
#include<linux/config.h>/* for CONFIG_ROOT_RDONLY */
#include<asm/boot.h>
.text
!The values of DEF_INITSEG, DEF_SETUPSEG, DEF_SYSSEG, DEF_SYSSIZE are taken
!from include/asm/boot.h
! /*Don't touch these ,unless you really know what you're doing*/
! #define DEF_INITSEG 0x9000
! #define DEF_SETUPSEG 0x9020
! #define DEF_SYSSIZE 0x7F00
! #define DEF_SYSSEG 0x1000
SETUPSECS = 4 ! default nr of setup-sectors
BOOTSEG = 0x07C0 ! original address of boot-sector
INITSEG = DEF_INITSEG ! we move boot here - out of the way
SETUPSEG = DEF_SETUPSEG ! setup starts here
SYSSEG = DEF_SYSSEG ! system loaded at 0x10000 (65536).
SYSSIZE = DEF_SYSSIZE ! system size: number of 16-byte clicks
! to be loaded
! ROOT_DEV&SWAP_DEV are now written by"build".
ROOT_DEV = 0
SWAP_DEV = 0
#ifndef SVGA_MODE
#define SVGA_MODE ASK_VGA
#endif
#ifndef RAMDISK
#define RAMDISK 0
#endif
#ifndef CONFIG_ROOT_RDONLY
#define CONFIG_ROOT_RDONLY 1
#endif
! ld86 requires an entry symbol. This may as well be the usual one.
.globl _main
_main:
#if 0 /* hook for debugger, harmless unless BIOS is fussy (old HP) */
int 3
#endif
mov ax,#BOOTSEG
mov ds,ax
mov ax,#INITSEG
mov es,ax
mov cx,#256
sub si,si
sub di,di
cld
rep
movsw
jmpi go,INITSEG
!The lines 66--76 move the bootsector code from address 0x7C00 to 0x90000.
!This is achieved by:
! 1. set ds:si to $BOOTSEG:0 (0x07C0:0 = 0x07C00)
! 2. set es:di to $INITSEG:0 (0x9000:0 = 0x90000)
! 3. set the number of 16bit words in %cx (256 words = 512 bytes = 1 sector)
! 4. clear DF (direction) flag in EFLAGS to auto-increment address (cld)
! 5. go ahead and copy 512 bytes (rep movsw)
! ax and es already contain INITSEG
go: mov di,#0x4000-12 ! 0x4000 is arbitrary value>= length of
! bootsect + length of setup + room for stack
! 12 is disk parm size
! bde - changed 0xff00 to 0x4000 to use debugger at 0x6400 up (bde). We
! wouldn't have to worry about this if we checked the top of memory. Also
! my BIOS can be configured to put the wini drive tables in high memory
! instead of in the vector table. The old stack might have clobbered the
! drive table.
mov ds,ax ! The value of ax is INITSEG
mov ss,ax ! put stack at INITSEG:0x4000-12.
mov sp,di
/*
* Many BIOS's default disk parameter tables will not
* recognize multi-sector reads beyond the maximum sector number
* specified in the default diskette parameter tables - this may
* mean 7 sectors in some cases.
*
* Since single sector reads are slow and out of the question,
* we must take care of this by creating new parameter tables
* (for the first disk) in RAM. We will set the maximum sector
* count to 36 - the most we will encounter on an ED 2.88.
*
* High doesn't hurt. Low does.
*
* Segments are as follows: ds=es=ss=cs - INITSEG,
* fs = 0, gs is unused.
*/
! cx contains 0 from rep movsw above
mov fs,cx
mov bx,#0x78 ! fs:bx is parameter table address
push ds
seg fs
lds si,(bx) ! ds:si is source;
! The line equal: lds si,fs:[bx]
mov cl,#6 ! copy 12 bytes
cld
push di
rep
movsw ! movs可以把由(SI)指向的数据段中的一个字
! 传送到由(DI)指向的附加段中的一个字中去.
!The lines 120--132 possibily copy the parameter table to ram 0x4000-12
pop di
pop ds
movb 4(di),*36 ! patch sector count
seg fs
mov (bx),di
seg fs
mov 2(bx),es
! load the setup-sectors directly after the bootblock.
! Note that 'es' is already set up.
! Also cx is 0 from rep movsw above.
load_setup:
xor ah,ah ! reset FDC
xor dl,dl
int 0x13
! 完成重置磁盘控制器之后,bootsect就从磁盘上读入紧邻着bootsect的setup
!程序,也就是setup.S,此读入动作是利用BIOS中断服务int 13h的第2号功能。
!程序第167-173行。
!setup的image将会读入至程序所指定的内存绝对地址0x90200处,也就是在内存
!中紧邻着bootsect 所在的位置。待setup的image读入内存后,利用BIOS中断服
!务int 13h的第8号功能读取目前磁盘的参数。(194-196)
xor dx, dx ! drive 0, head 0
mov cl,#0x02 ! sector 2, track 0
mov bx,#0x0200 ! address = 512, in INITSEG
mov ah,#0x02 ! service 2, nr of sectors
mov al,setup_sects ! (assume all on head 0, track 0)
! SETUPSECS = 4 which is defined at line 39
! default nr of setup-sectors
int 0x13 ! read it
jnc ok_load_setup ! ok - continue 进位为0则转移
push ax ! dump error code
call print_nl
mov bp, sp
call print_hex
pop ax
jmp load_setup
ok_load_setup:
! Get disk drive parameters, specifically nr of sectors/track
#if 0
! bde - the Phoenix BIOS manual says function 0x08 only works for fixed
! disks. It doesn't work for one of my BIOS's (1987 Award). It was
! fatal not to check the error code.
xor dl,dl
mov ah,#0x08 ! AH=8 is get drive parameters
int 0x13
xor ch,ch
#else
! It seems that there is no BIOS call to get the number of sectors. Guess
! 36 sectors if sector 36 can be read, 18 sectors if sector 18 can be read,
! 15 if sector 15 can be read. Otherwise guess 9.
mov si,#disksizes ! table of sizes to try
probe_loop:
lodsb ! 该指令把由(SI)指定的数据段中某单元的内容送
! 到AL或AX中,并根据方向标志及数据类型修改SI的内容.
cbw ! extend to word
mov sectors, ax
cmp si,#disksizes+4
jae got_sectors ! if all else fails, try 9
! jae:above or equal 0
xchg ax, cx ! cx = track and sector
xor dx, dx ! drive 0, head 0
xor bl, bl
mov bh,setup_sects
inc bh
shl bh,#1 ! address after setup (es = cs)
mov ax,#0x0201 ! service 2, 1 sector
int 0x13
jc probe_loop ! try next value
#endif
got_sectors:
! Restore es
mov ax,#INITSEG
mov es,ax
! Print some inane message
mov ah,#0x03 ! read cursor pos
xor bh,bh
int 0x10
mov cx,#9
mov bx,#0x0007 ! page 0, attribute 7 (normal)
mov bp,#msg1
mov ax,#0x1301 ! write string, move cursor
int 0x10
! ok, we've written the message, now
! we want to load the system (at 0x10000)
mov ax,#SYSSEG
mov es,ax ! segment of 0x010000
call read_it
call kill_motor
call print_nl
! After that we check which root-device to use. If the device is
! defined (!= 0), nothing is done and the given device is used.
! Otherwise, one of /dev/fd0H2880 (2,32) or /dev/PS0 (2,28) or /dev/at0 (2,8),
! depending on the number of sectors we pretend to know we have.
seg cs
mov ax,root_dev
or ax,ax
jne root_defined
seg cs
mov bx,sectors
mov ax,#0x0208 ! /dev/ps0 - 1.2Mb
cmp bx,#15
je root_defined
mov al,#0x1c ! /dev/PS0 - 1.44Mb
cmp bx,#18
je root_defined
mov al,#0x20 ! /dev/fd0H2880 - 2.88Mb
cmp bx,#36
je root_defined
mov al,#0 ! /dev/fd0 - autodetect
root_defined:
seg cs
mov root_dev,ax
! after that (everything loaded), we jump to
! the setup-routine loaded directly after
! the bootblock:
jmpi 0,SETUPSEG ! 开始执行setup.s
! This routine loads the system at address 0x10000, making sure
! no 64kB boundaries are crossed. We try to load it as fast as
! possible, loading whole tracks whenever we can.
!
! in: es - starting address segment (normally 0x1000)
!
sread: .word 0 ! sectors read of current track
head: .word 0 ! current head
track: .word 0 ! current track
read_it:
mov al,setup_sects
inc al
mov sread,al
mov ax,es
test ax,#0x0fff
die: jne die ! es must be at 64kB boundary
xor bx,bx ! bx is starting address within segment
rp_read:
#ifdef __BIG_KERNEL__
#define CALL_HIGHLOAD_KLUDGE .word 0x1eff,0x220 ! call far * bootsect_kludge
! NOTE: as86 can't assemble this
CALL_HIGHLOAD_KLUDGE ! this is within setup.S
#else
mov ax,es
sub ax,#SYSSEG
#endif
cmp ax,syssize ! have we loaded all yet?
jbe ok1_read
ret
ok1_read:
mov ax,sectors
sub ax,sread
mov cx,ax
shl cx,#9
add cx,bx
jnc ok2_read
je ok2_read
xor ax,ax
sub ax,bx
shr ax,#9
ok2_read:
call read_track
mov cx,ax
add ax,sread
cmp ax,sectors
jne ok3_read
mov ax,#1
sub ax,head
jne ok4_read
inc track
ok4_read:
mov head,ax
xor ax,ax
ok3_read:
mov sread,ax
shl cx,#9
add bx,cx
jnc rp_read !rp_read is in 303.
mov ax,es
add ah,#0x10
mov es,ax
xor bx,bx
jmp rp_read
read_track:
pusha
pusha
mov ax, #0xe2e ! loading... message 2e = .
mov bx, #7 ! BL=前景色 AL=字符
int 0x10
popa
mov dx,track ! track : current track
mov cx,sread ! sectors read of current track
inc cx
mov ch,dl
mov dx,head ! current head
mov dh,dl
and dx,#0x0100
mov ah,#2
push dx ! save for error dump
push cx
push bx
push ax
int 0x13
jc bad_rt
add sp, #8
popa
ret
bad_rt: push ax ! save error code
call print_all ! ah = error, al = read
xor ah,ah ! 软盘系统复位
xor dl,dl
int 0x13
add sp, #10 ! popa 会影响sp的值
popa
jmp read_track
/*
* print_all is for debugging purposes.
* It will print out all of the registers. The assumption is that this is
* called from a routine, with a stack frame like
* dx
* cx
* bx
* ax
* error
* ret<- sp
*
*/
print_all:
mov cx, #5 ! error code + 4 registers
mov bp, sp
print_loop:
push cx ! save count left
call print_nl ! nl for readability
cmp cl, #5
jae no_reg ! see if register name is needed
mov ax, #0xe05 + 'A - 1
sub al, cl
int 0x10
mov al, #'X
int 0x10
mov al, #':
int 0x10
no_reg:
add bp, #2 ! next register
call print_hex ! print it
pop cx
loop print_loop
ret
print_nl:
mov ax, #0xe0d ! CR
int 0x10
mov al, #0xa ! LF
int 0x10
ret
/*
* print_hex is for debugging purposes, and prints the word
* pointed to by ss:bp in hexadecimal.
*/
print_hex:
mov cx, #4 ! 4 hex digits
mov dx, (bp) ! load word into dx
print_digit:
rol dx, #4 ! rotate so that lowest 4 bits are used
mov ax, #0xe0f ! ah = request, al = mask for nybble
and al, dl
add al, #0x90 ! convert al to ASCII hex (four instructions)
daa
adc al, #0x40
daa
int 0x10
loop print_digit
ret
/*
* This procedure turns off the floppy drive motor, so
* that we enter the kernel in a known state, and
* don't have to worry about it later.
*/
kill_motor:
push dx
mov dx,#0x3f2 ! outb 指令使用短指令
xor al, al
outb
pop dx
ret
sectors:
.word 0
disksizes:
.byte 36,18,15,9
msg1:
.byte 13,10
.ascii"Loading"
.org 497
setup_sects:
.byte SETUPSECS
root_flags:
.word CONFIG_ROOT_RDONLY
syssize:
.word SYSSIZE
swap_dev:
.word SWAP_DEV
ram_size:
.word RAMDISK
vid_mode:
.word SVGA_MODE
root_dev:
.word ROOT_DEV
boot_flag:
.word 0xAA55
  在程序bootsect.s执行完以后(setup.s的前4个扇区的内容已被读入),setup.s紧
接着开始执行。它负责从BIOS中获取系统信息,并且将它们存放到内存中适当的地方。它
完成如下一些主要功能:
  一. 检测setup.s的代码是否已完全被读入,如果没有的话,则查找其余部分代码。
并将它自己的其余部分移动到内存中紧邻着先前被读入的4个扇区的内容后面。
  二. 检测键盘、显示适配器、PS/2设备、Micro Channel(mca) bus,获取内存长度
(以kb计算)。
  三. 检查系统是否已被移动到了正确的地方,假如代码没有被准确移动到0x90000这
个地方,我们则需要把代码移动到那个地方。当然在这以前,首先得检验我们调用的是否
是bing-kernel。
程序大部分代码解析如下:
!
! setup.S Copyright (C) 1991, 1992 Linus Torvalds
!
! setup.s is responsible for getting the system data from the BIOS,
! and putting them into the appropriate places in system memory.
! both setup.s and system has been loaded by the bootblock.
!
! This code asks the bios for memory/disk/other parameters, and
! puts them in a"safe"place: 0x90000-0x901FF, ie where the
! boot-block used to be. It is then up to the protected mode
! system to read them from there before the area is overwritten
! for buffer-blocks.
!
! Move PS/2 aux init code to psaux.c
! (troyer@saifr00.cfsat.Honeywell.COM) 03Oct92
!
! some changes and additional features by Christoph Niemann,
! March 1993/June 1994 (Christoph.Niemann@linux.org)
!
! add APM BIOS checking by Stephen Rothwell, May 1994
! (Stephen.Rothwell@canb.auug.org.au)
!
! High load stuff, initrd support and position independency
! by Hans Lermen&Werner Almesberger, February 1996
!<lermen@elserv.ffm.fgan.de>,<almesber@lrc.epfl.ch>
!
! Video handling moved to video.S by Martin Mares, March 1996
!<mj@k332.feld.cvut.cz>
!
! Extended memory detection scheme retwiddled by orc@pell.chi.il.us (david
! parsons) to avoid loadlin confusion, July 1997
!
#define __ASSEMBLY__
#include<linux/config.h>
#include<asm/segment.h>
#include<linux/version.h>
#include<linux/compile.h>
#include<asm/boot.h>
! Signature words to ensure LILO loaded us right
#define SIG1 0xAA55
#define SIG2 0x5A5A
INITSEG = DEF_INITSEG ! 0x9000, we move boot here - out of the way
SYSSEG = DEF_SYSSEG ! 0x1000, system loaded at 0x10000 (65536).
SETUPSEG = DEF_SETUPSEG ! 0x9020, this is the current segment
! ... and the former contents of CS
DELTA_INITSEG = SETUPSEG - INITSEG ! 0x0020
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text
entry start
start:
jmp start_of_setup
! ------------------------ start of header --------------------------------
!
! SETUP-header, must start at CS:2 (old 0x9020:2)
!
.ascii"HdrS"! Signature for SETUP-header
.word 0x0201 ! Version number of header format
! (must be>= 0x0105
! else old loadlin-1.5 will fail)
realmode_swtch: .word 0,0 ! default_switch,SETUPSEG
start_sys_seg: .word SYSSEG
.word kernel_version ! pointing to kernel version string
! note: above part of header is compatible with loadlin-1.5 (header v1.5),
! must not change it
type_of_loader: .byte 0 ! = 0, old one (LILO, Loadlin,
! Bootlin, SYSLX, bootsect...)
! else it is set by the loader:
! 0xTV: T=0 for LILO
! T=1 for Loadlin
! T=2 for bootsect-loader
! T=3 for SYSLX
! T=4 for ETHERBOOT
! V = version
loadflags: ! flags, unused bits must be zero (RFU)
LOADED_HIGH = 1 ! bit within loadflags,
! if set, then the kernel is loaded high
CAN_USE_HEAP = 0x80 ! if set, the loader also has set heap_end_ptr
! to tell how much space behind setup.S
| can be used for heap purposes.
! Only the loader knows what is free!
#ifndef __BIG_KERNEL__
.byte 0x00
#else
.byte LOADED_HIGH
#endif
setup_move_size: .word 0x8000 ! size to move, when we (setup) are not
! loaded at 0x90000. We will move ourselves
! to 0x90000 then just before jumping into
! the kernel. However, only the loader
! know how much of data behind us also needs
! to be loaded.
code32_start: ! here loaders can put a different
! start address for 32-bit code.
#ifndef __BIG_KERNEL__
.long 0x1000 ! 0x1000 = default for zImage
#else
.long 0x100000 ! 0x100000 = default for big kernel
#endif
ramdisk_image: .long 0 ! address of loaded ramdisk image
! Here the loader (or kernel generator) puts
! the 32-bit address were it loaded the image.
! This only will be interpreted by the kernel.
ramdisk_size: .long 0 ! its size in bytes
bootsect_kludge:
.word bootsect_helper,SETUPSEG
heap_end_ptr: .word modelist+1024 ! space from here (exclusive) down to
! end of setup code can be used by setup
! for local heap purposes.
! ------------------------ end of header ----------------------------------
start_of_setup:
! Bootlin depends on this being done early
mov ax,#0x01500
mov dl,#0x81
int 0x13
#ifdef SAFE_RESET_DISK_CONTROLLER
! Reset the disk controller.
mov ax,#0x0000
mov dl,#0x80
int 0x13
#endif
! set DS=CS, we know that SETUPSEG == CS at this point
mov ax,cs ! aka #SETUPSEG
mov ds,ax
!0xAA55和0x5A5A是用来确保LILO正确引导setup.s的两个标志
!检察setup代码结尾处的标志是否为AA55或5A5A,
!假如是,则跳到good_sig1处;否则,跳到bad_sig处,查找setup.s的剩余部分代码,
!并且将其移动到内存中0x1000处。
cmp setup_sig1,#SIG1
jne bad_sig
cmp setup_sig2,#SIG2
jne bad_sig
jmp good_sig1
! Routine to print ASCIIz string at DS:SI
prtstr: lodsb !lodsb是从串取指令(AL<--(SI)),(SI)<--(SI)+/-1
and al,al
jz fin !显示完所有需要显示的内容后,则返回调用处。
call prtchr
jmp prtstr
fin: ret
! Space printing
prtsp2: call prtspc ! Print double space
prtspc: mov al,#0x20 ! Print single space (fall-thru!)
! Part of above routine, this one just prints ASCII al
! 利用int 0x10(AH=0x0E)在屏幕上以BL中得值为背景色显示存储在AL中的字符。
prtchr: push ax
push cx
xor bh,bh
mov cx,#0x01
mov ah,#0x0e
int 0x10
pop cx
pop ax
ret
beep: mov al,#0x07
jmp prtchr
no_sig_mess: .ascii"No setup signature found ..."
db 0x00
good_sig1:
jmp good_sig
!我们现在必须得找到setup代码、数据的剩余部分。
bad_sig:
mov ax,cs ! aka #SETUPSEG(cs的值为#SETUPSEG=0x9020)
sub ax,#DELTA_INITSEG ! aka #INITSEG(#INITSEG=0x9000)
mov ds,ax ! 设置ds=0x9000,即bootsect.s被载入的地方
xor bh,bh
mov bl,[497] ! get setup sects from boot sector
sub bx,#4 ! LILO loads 4 sectors of setup
!注意:此时bx中存放的是存储还未读取的setup代码的扇区数。
shl bx,#8 ! convert to words
mov cx,bx !cx通常用作计数器。
shr bx,#3 ! convert to segment
add bx,#SYSSEG !计算出存放setup.s剩余部分代码、数据的地址。
seg cs
mov start_sys_seg,bx
!把setup.s剩余部分的代码、数据移动到这个地方。
mov di,#2048 ! four sectors loaded by LILO
sub si,si
mov ax,cs ! aka #SETUPSEG=0x9020
mov es,ax
mov ax,#SYSSEG
mov ds,ax !ax=0x1000
rep
movsw !movs(movsw、movsb)可以把由(SI)指向的数据段中的一个字(或字节)传送到
由(DI)
!指向的附加段中的一个字(字节)中去,同时根据方向标志及数据格式(字或字节)对
SI和
!DI进行修改。但与REP联用时,则可将数据段中的整个字符串传送到附加段中去。
mov ax,cs ! aka #SETUPSEG
mov ds,ax
cmp setup_sig1,#SIG1
jne no_sig ! 同142--149
cmp setup_sig2,#SIG2
jne no_sig
jmp good_sig
no_sig:
lea si,no_sig_mess
call prtstr
no_sig_loop:
jmp no_sig_loop
good_sig:
mov ax,cs ! aka #SETUPSEG
sub ax,#DELTA_INITSEG ! aka #INITSEG
mov ds,ax !ds=0x9000
! check if an old loader tries to load a big-kernel
seg cs
test byte ptr loadflags,#LOADED_HIGH ! Have we a big kernel?
jz loader_ok ! NO, no danger even for old loaders
! YES, we have a big-kernel
seg cs
cmp byte ptr type_of_loader,#0 ! Have we one of the new loaders?
! 参见77--85,#0:采用LILO引导,
!在Romed Linux中,我们使用bootsect.s引导,
!则应改为cmp byte ptr type_of_loader,#2
!或者,不要更改下面内容,直接跳到loader_ok处。
jnz loader_ok ! YES, OK
! NO, we have an old loader, must give up
push cs
pop ds
lea si,loader_panic_mess
call prtstr
jmp no_sig_loop
loader_panic_mess:
.ascii"Wrong loader: giving up."
db 0
loader_ok:
! Get memory size (extended mem, kB)
#ifndef STANDARD_MEMORY_BIOS_CALL
push ebx
xor ebx,ebx ! preload new memory slot with 0k
mov [0x1e0], ebx
mov ax,#0xe801
int 0x15
jc oldstylemem
! Memory size is in 1 k chunksizes, to avoid confusing loadlin.
! We store the 0xe801 memory size in a completely different place,
! because it will most likely be longer than 16 bits.
! (use 1e0 because that's what Larry Augustine uses in his
! alternative new memory detection scheme, and it's sensible
! to write everything into the same place.)
and ebx, #0xffff ! clear sign extend
shl ebx, 6 ! and go from 64k to 1k chunks
mov [0x1e0],ebx ! store extended memory size
and eax, #0xffff ! clear sign extend
add [0x1e0],eax ! and add lower memory into total size.
! and fall into the old memory detection code to populate the
! compatibility slot.
oldstylemem:
pop ebx
#else
mov dword ptr [0x1e0], #0
#endif
mov ah,#0x88
int 0x15
mov [2],ax
! Set the keyboard repeat rate to the max
mov ax,#0x0305
xor bx,bx ! clear bx
int 0x16
! Check for video adapter and its parameters and allow the
! user to browse video modes.
call video ! NOTE: we need DS pointing to boot sector
!The function video is achieved in video.s
! Get hd0 data
xor ax,ax ! clear ax
mov ds,ax
lds si,[4*0x41]
mov ax,cs ! aka #SETUPSEG
sub ax,#DELTA_INITSEG ! aka #INITSEG
push ax
mov es,ax
mov di,#0x0080
mov cx,#0x10
push cx
cld
rep
movsb
! Get hd1 data
xor ax,ax ! clear ax
mov ds,ax
lds si,[4*0x46]
pop cx
pop es
mov di,#0x0090
rep
movsb
! Check that there IS a hd1 :-)
mov ax,#0x01500
mov dl,#0x81
int 0x13
jc no_disk1
cmp ah,#3
je is_disk1
no_disk1:
mov ax,cs ! aka #SETUPSEG
sub ax,#DELTA_INITSEG ! aka #INITSEG
mov es,ax
mov di,#0x0090
mov cx,#0x10
xor ax,ax ! clear ax
cld
rep
stosb
is_disk1:
! check for Micro Channel (MCA) bus
mov ax,cs ! aka #SETUPSEG
sub ax,#DELTA_INITSEG ! aka #INITSEG
mov ds,ax
mov ds,ax
xor ax,ax
mov [0xa0], ax ! set table length to 0
mov ah, #0xc0
stc
int 0x15 ! puts feature table at es:bx
jc no_mca
push ds
mov ax,es
mov ds,ax
mov ax,cs ! aka #SETUPSEG
sub ax, #DELTA_INITSEG ! aka #INITSEG
mov es,ax
mov si,bx
mov di,#0xa0
mov cx,(si)
add cx,#2 ! table length is a short
cmp cx,#0x10
jc sysdesc_ok
mov cx,#0x10 ! we keep only first 16 bytes
sysdesc_ok:
rep
movsb
pop ds
no_mca:
! Check for PS/2 pointing device
mov ax,cs ! aka #SETUPSEG
sub ax,#DELTA_INITSEG ! aka #INITSEG
mov ds,ax
mov [0x1ff],#0 ! default is no pointing device
int 0x11 ! int 0x11: equipment determination
test al,#0x04 ! check if pointing device installed
jz no_psmouse
mov [0x1ff],#0xaa ! device present
no_psmouse:
#ifdef CONFIG_APM
! check for APM BIOS
! NOTE: DS is pointing to the boot sector
!
mov [64],#0 ! version == 0 means no APM BIOS
mov ax,#0x05300 ! APM BIOS installation check
xor bx,bx
int 0x15
jc done_apm_bios ! error ->no APM BIOS
cmp bx,#0x0504d ! check for"PM"signature
jne done_apm_bios ! no signature ->no APM BIOS
and cx,#0x02 ! Is 32 bit supported?
je done_apm_bios ! no ...
mov ax,#0x05304 ! Disconnect first just in case
xor bx,bx
int 0x15 ! ignore return code
mov ax,#0x05303 ! 32 bit connect
xor bx,bx
int 0x15
jc no_32_apm_bios ! error
mov [66],ax ! BIOS code segment
mov [68],ebx ! BIOS entry point offset
mov [72],cx ! BIOS 16 bit code segment
mov [74],dx ! BIOS data segment
mov [78],esi ! BIOS code segment length
mov [82],di ! BIOS data segment length
!
! Redo the installation check as the 32 bit connect
! modifies the flags returned on some BIOSs
!
mov ax,#0x05300 ! APM BIOS installation check
xor bx,bx
int 0x15
jc apm_disconnect ! error ->should not happen, tidy up
cmp bx,#0x0504d ! check for"PM"signature
jne apm_disconnect ! no signature ->should not happen, tidy up

mov [64],ax ! record the APM BIOS version
mov [76],cx ! and flags
jmp done_apm_bios
apm_disconnect:
mov ax,#0x05304 ! Disconnect
xor bx,bx
int 0x15 ! ignore return code
jmp done_apm_bios
no_32_apm_bios:
and [76], #0xfffd ! remove 32 bit support bit
done_apm_bios:
#endif
! Now we want to move to protected mode ...
seg cs
cmp realmode_swtch,#0
jz rmodeswtch_normal
seg cs
callf far * realmode_swtch
jmp rmodeswtch_end
rmodeswtch_normal:
push cs
call default_switch
rmodeswtch_end:
! we get the code32 start address and modify the below 'jmpi'
! (loader may have changed it)
seg cs
mov eax,code32_start
seg cs
mov code32,eax
!一旦这个数据不再被需要,它将被覆盖(overwrite),通过把整个内核景象从0x10000
移动到
!0x1000(当然是物理地址)。这一功能将由setup.s来实现,setup.s把环境设置成保护
模式(641--661),
!同时跳转到0x1000处,那里是整个压缩内核的开端(head.s、misc.c)。设立堆栈,并
且calls
!decompress_kernel(),负责把内核解压缩到0x100000处,然后跳转到该地址。
! Now we move the system to its rightful place
! ...but we check, if we have a big-kernel.
! in this case we *must* not move it ...
seg cs
test byte ptr loadflags,#LOADED_HIGH
jz do_move0 ! we have a normal low loaded zImage
! we have a high loaded big kernel
jmp end_move ! ... and we skip moving
do_move0:
mov ax,#0x100 ! start of destination segment
mov bp,cs ! aka #SETUPSEG
sub bp,#DELTA_INITSEG ! aka #INITSEG
seg cs
mov bx,start_sys_seg ! start of source segment
! start_sys_seg:0x1000,there the system will be loaded.
cld ! 'direction'=0, movs moves forward
do_move:
mov es,ax ! destination segment
inc ah ! instead of add ax,#0x100
mov ds,bx ! source segment
add bx,#0x100
sub di,di
sub si,si
mov cx,#0x800
rep
movsw ! 共移动了34k数据。
cmp bx,bp ! we assume start_sys_seg>0x200,
! so we will perhaps read one page more then
! needed, but never overwrite INITSEG because
! destination is minimum one page below source
jb do_move
! then we load the segment descriptors
end_move:
mov ax,cs ! aka #SETUPSEG ! right, forgot this at first. didn't work :-)
mov ds,ax !cs=ds,cs、ds不一定等于0x9020。
! If we have our code not at 0x90000, we need to move it there now.
! We also then need to move the parameters behind it (command line)
! Because we would overwrite the code on the current IP, we move
! it in two steps, jumping high after the first one.
mov ax,cs
cmp ax,#SETUPSEG
je end_move_self
cli ! make sure we really have interrupts disabled !
! because after this the stack should not be used
sub ax,#DELTA_INITSEG ! aka #INITSEG
mov dx,ss
cmp dx,ax
jb move_self_1
add dx,#INITSEG
sub dx,ax ! this will be SS after the move
move_self_1:
mov ds,ax
mov ax,#INITSEG ! real INITSEG
mov es,ax
seg cs
mov cx,setup_move_size
std ! we have to move up, so we use direction down
! because the areas may overlap
mov di,cx
dec di
mov si,di
sub cx,#move_self_here+0x200
rep
movsb
jmpi move_self_here,SETUPSEG ! jump to our final place
move_self_here:
mov cx,#move_self_here+0x200 ! What's meaning?????
rep
movsb
mov ax,#SETUPSEG
mov ds,ax
mov ss,dx
! now we are at the right place
end_move_self:
lidt idt_48 ! load idt with 0,0
lgdt gdt_48 ! load gdt with whatever appropriate
! that was painless, now we enable A20
call empty_8042
mov al,#0xD1 ! command write
out #0x64,al
call empty_8042
mov al,#0xDF ! A20 on
out #0x60,al
call empty_8042
! wait until a20 really *is* enabled;it can take a fair amount of
! time on certain systems;Toshiba Tecras are known to have this
! problem. The memory location used here is the int 0x1f vector,
! which should be safe to use;any *unused* memory location<0xfff0
! should work here.
#define TEST_ADDR 0x7c
push ds
xor ax,ax ! segment 0x0000
mov ds,ax
dec ax ! segment 0xffff (HMA)
mov gs,ax
mov bx,[TEST_ADDR] ! we want to restore the value later
a20_wait:
inc ax
mov [TEST_ADDR],ax
seg gs
cmp ax,[TEST_ADDR+0x10]
je a20_wait ! loop until no longer aliased
mov [TEST_ADDR],bx ! restore original value
pop ds
! make sure any possible coprocessor is properly reset..
xor ax,ax
out #0xf0,al
call delay
out #0xf1,al
call delay
! well, that went ok, I hope. Now we have to reprogram the interrupts :-(
! we put them right after the intel-reserved hardware interrupts, at
! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
! messed this up with the original PC, and they haven't been able to
! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
! which is used for the internal hardware interrupts as well. We just
! have to reprogram the 8259's, and it isn't fun.
mov al,#0x11 ! initialization sequence
out #0x20,al ! send it to 8259A-1
call delay
out #0xA0,al ! and to 8259A-2
call delay
mov al,#0x20 ! start of hardware int's (0x20)
out #0x21,al
call delay
mov al,#0x28 ! start of hardware int's 2 (0x28)
out #0xA1,al
call delay
mov al,#0x04 ! 8259-1 is master
out #0x21,al
call delay
mov al,#0x02 ! 8259-2 is slave
out #0xA1,al
call delay
mov al,#0x01 ! 8086 mode for both
out #0x21,al
call delay
out #0xA1,al
call delay
mov al,#0xFF ! mask off all interrupts for now
out #0xA1,al
call delay
mov al,#0xFB ! mask all irq's but irq2 which
out #0x21,al ! is cascaded
! Well, that certainly wasn't fun :-(. Hopefully it works, and we don't
! need no steenking BIOS anyway (except for the initial loading :-).
! The BIOS routine wants lots of unnecessary data, and it's less
!"interesting"anyway. This is how REAL programmers do it.
!
! Well, now's the time to actually move into protected mode. To make
! things as simple as possible, we do no register set-up or anything,
! we let the GNU-compiled 32-bit programs do that. We just jump to
! absolute address 0x1000 (or the loader supplied one),
! in 32-bit protected mode.
!
! Note that the short jump isn't strictly needed, although there are
! reasons why it might be a good idea. It won't hurt in any case.
!
mov ax,#1 ! protected mode (PE) bit
lmsw ax ! This is it!
jmp flush_instr
flush_instr:
xor bx,bx ! Flag to indicate a boot
! NOTE: For high loaded big kernels we need a
! jmpi 0x100000,__KERNEL_CS
!
! but we yet haven't reloaded the CS register, so the default size
! of the target offset still is 16 bit.
! However, using an operant prefix (0x66), the CPU will properly
! take our 48 bit far pointer. (INTeL 80386 Programmer's Reference
! Manual, Mixing 16-bit and 32-bit code, page 16-6)
db 0x66,0xea ! prefix + jmpi-opcode
code32: dd 0x1000 ! will be set to 0x100000 for big kernels
dw __KERNEL_CS
kernel_version: .ascii UTS_RELEASE
.ascii"("
.ascii LINUX_COMPILE_BY
.ascii"@"
.ascii LINUX_COMPILE_HOST
.ascii")"
.ascii UTS_VERSION
db 0

! This is the default real mode switch routine.
! to be called just before protected mode transition
default_switch:
cli ! no interrupts allowed !
mov al,#0x80 ! disable NMI for the bootup sequence
out #0x70,al
retf
! This routine only gets called, if we get loaded by the simple
! bootsect loader _and_ have a bzImage to load.
! Because there is no place left in the 512 bytes of the boot sector,
! we must emigrate to code space here.
!
!由以上的注释可知,下面代码对于Romed Linux是很重要的,因为我采用了LINUX的sim
ple
! bootsect loader。
bootsect_helper:
seg cs
cmp word ptr bootsect_es,#0
jnz bootsect_second
seg cs
mov byte ptr type_of_loader,#0x20
mov ax,es
shr ax,#4
seg cs
mov byte ptr bootsect_src_base+2,ah
mov ax,es
seg cs
mov bootsect_es,ax
sub ax,#SYSSEG
retf ! nothing else to do for now
bootsect_second:
push cx
push si
push bx
test bx,bx ! 64K full ?
!BX=0x0,es=0x9000
jne bootsect_ex
mov cx,#0x8000 ! full 64K move, INT15 moves words
push cs
pop es !es=0x9000 INITSEG
mov si,#bootsect_gdt
mov ax,#0x8700
int 0x15 !es:bx=数据传输区地址
jc bootsect_panic ! this, if INT15 fails
seg cs
mov es,bootsect_es ! we reset es to always point to 0x10000
seg cs
inc byte ptr bootsect_dst_base+2
bootsect_ex:
seg cs
mov ah, byte ptr bootsect_dst_base+2
shl ah,4 ! we now have the number of moved frames in ax
xor al,al
pop bx
pop si
pop cx
retf
bootsect_gdt:
.word 0,0,0,0
.word 0,0,0,0
bootsect_src:
.word 0xffff
bootsect_src_base:
.byte 0,0,1 ! base = 0x010000
.byte 0x93 ! typbyte
.word 0 ! limit16,base24 =0
bootsect_dst:
.word 0xffff
bootsect_dst_base:
.byte 0,0,0x10 ! base = 0x100000
.byte 0x93 ! typbyte
.word 0 ! limit16,base24 =0
.word 0,0,0,0 ! BIOS CS
.word 0,0,0,0 ! BIOS DS
bootsect_es:
.word 0
bootsect_panic:
push cs
pop ds
cld
lea si,bootsect_panic_mess
call prtstr
bootsect_panic_loop:
jmp bootsect_panic_loop
bootsect_panic_mess:
.ascii"INT15 refuses to access high memory. Giving up."
db 0
! This routine checks that the keyboard command queue is empty
! (after emptying the output buffers)
!
! Some machines have delusions that the keyboard buffer is always full
! with no keyboard attached...
empty_8042:
push ecx
mov ecx,#0xFFFFFF
empty_8042_loop:
dec ecx
jz empty_8042_end_loop
call delay
in al,#0x64 ! 8042 status port
test al,#1 ! output buffer?
jz no_output
call delay
in al,#0x60 ! read it
jmp empty_8042_loop
no_output:
test al,#2 ! is input buffer full?
jnz empty_8042_loop ! yes - loop
empty_8042_end_loop:
pop ecx
ret
!
! Read the CMOS clock. Return the seconds in al
!
gettime:
push cx
mov ah,#0x02
int 0x1a
mov al,dh ! dh contains the seconds
and al,#0x0f
mov ah,dh
mov cl,#0x04
shr ah,cl
aad
pop cx
ret
!
! Delay is needed after doing I/O
!
delay:
.word 0x00eb ! jmp $+2
ret
!
! Descriptor tables
!
gdt:
.word 0,0,0,0 ! dummy
.word 0,0,0,0 ! unused
.word 0xFFFF ! 4Gb - (0x100000*0x1000 = 4Gb)
.word 0x0000 ! base address=0
.word 0x9A00 ! code read/exec
.word 0x00CF ! granularity=4096, 386 (+5th nibble of limit)
.word 0xFFFF ! 4Gb - (0x100000*0x1000 = 4Gb)
.word 0x0000 ! base address=0
.word 0x9200 ! data read/write
.word 0x00CF ! granularity=4096, 386 (+5th nibble of limit)
idt_48:
.word 0 ! idt limit=0
.word 0,0 ! idt base=0L
gdt_48:
.word 0x800 ! gdt limit=2048, 256 GDT entries
.word 512+gdt,0x9 ! gdt base = 0X9xxxx
!
! Include video setup&detection code
!
#include"video.S"
!
! Setup signature -- must be last
!
setup_sig1: .word SIG1
setup_sig2: .word SIG2
!
! After this point, there is some free space which is used by the video mode
! handling code to store the temporary mode table (not used by the kernel).
!
modelist:
.text
endtext:
.data
enddata:
.bss
endbss:
Linux下的多进程编程
纲要
    Linux下开发多进程程序同其它UNIX没有什么区别,所以下面所讲述的方法实际上
也可以应用于其它UNIX上。
目录
Linux下的多进程编程 ( 1999-07-02 )
(一) 理解Linux下进程的结构
  Linux下一个进程在内存里有三部份的数据,就是"数据段","堆栈段"和"代码段",
其实学过汇编语言的人一定知道,一般的CPU象I386,都有上述三种段寄存器,以方便操
作系统的运行。"代码段",顾名思义,就是存放了程序代码的数据,假如机器中有数个进
程运行相同的一个程序,
那么它们就可以使用同一个代码段。
  堆栈段存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段
则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取
得的空间)。这其中有许多细节问题,这里限于篇幅就不多介绍了。系统如果同时运行数
个相同的程序,它们之
间就不能使用同一个堆栈段和数据段。
(二) 如何使用fork
  在Linux下产生新的进程的系统调用就是fork函数,这个函数名是英文中"分叉"的意
思。为什么取这个名字呢?因为一个进程在运行中,如果使用了fork,就产生了另一个进
程,于是进程就"分叉"了,所以这个名字取得很形象。下面就看看如何具体使用fork,这
段程序演示了使用fork?
幕究蚣埽?
void main(){
int i;
if ( fork() == 0 ) {
/* 子进程程序 */
for ( i = 1;i<1000;i ++ )
printf("This is child process\n");
}
else {
/* 父进程程序*/
for ( i = 1;i<1000;i ++ )
printf("This is process process\n");
}
}
  程序运行后,你就能看到屏幕上交替出现子进程与父进程各打印出的一千条信息了。
如果程序还在运行中,你用ps命令就能看到系统中有两个它在运行了。
  那么调用这个fork函数时发生了什么呢?一个程序一调用fork函数,系统就为一个新
的进程准备了前述三个段,首先,系统让新的进程与旧的进程使用同一个代码段,因为它
们的程序还是相同的,对于数据段和堆栈段,系统则复制一份给新的进程,这样,父进程
的所有数据都可以留给
子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却
已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。而如果两
个进程要共享什么数据的话,就要使用另一套函数(shmget,shmat,shmdt等)来操作。
现在,已经是两个进程
了,对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零,
这样,对于程序,只要判断fork函数的返回值,就知道自己是处于父进程还是子进程中。

  读者也许会问,如果一个大程序在运行中,它的数据段和堆栈都很大,一次fork就要
复制一次,那么fork的系统开销不是很大吗?其实UNIX自有其解决的办法,大家知道,一
般CPU都是以"页"为单位分配空间的,象INTEL的CPU,其一页在通常情况下是4K字节大小
,而无论是数据段还是?
颜欢味际怯尚矶?"页"构成的,fork函数复制这两个段,只是"逻辑"上的,并非"物理"上
的,也就是说,实际执行fork时,物理空间上两个进程的数据段和堆栈段都还是共享着的
,当有一个进程写了某个数据时,这时两个进程之间的数据才有了区别,系统就将有区别
的"页"从物理上也分开?
O低吃诳占渖系目涂梢源锏阶钚 ?
  一个小幽默:下面演示一个足以"搞死"Linux的小程序,其源代码非常简单:
void main()
{
for(;;) fork();
}
  这个程序什么也不做,就是死循环地fork,其结果是程序不断产生进程,而这些进程
又不断产生新的进程,很快,系统的进程就满了,系统就被这么多不断产生的进程"撑死
了"。用不着是root,任何人运行上述程序都足以让系统死掉。哈哈,但这不是Linux不安
全的理由,因为只要系?
彻芾碓弊愎淮厦鳎ɑ蛩┚涂梢栽は雀扛鲇没柚每稍诵械淖畲蠼淌庋
灰皇莚oot,任何能运行的进程数也许不足系统总的能运行和进程数的十分之一,这样
,系统管理员就能对付上述恶意的程序了。
(三) 如何启动另一程序的执行
  下面我们来看看一个进程如何来启动另一个程序的执行。在Linux中要使用exec类的
函数,exec类的函数不止一个,但大致相同,在Linux中,它们分别是:execl,execlp,
execle,execv,execve和execvp,下面我只以execlp为例,其它函数究竟与execlp有何
区别,请通过manexec命?
罾戳私馑堑木咛迩榭觥?
  一个进程一旦调用exec类函数,它本身就"死亡"了,系统把代码段替换成新的程序的
代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,
就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。(不
过exec类函数中有的还
允许继承环境变量之类的信息。)
  那么如果我的程序想启动另一程序的执行但自己仍想继续运行的话,怎么办呢?那就
是结合fork与exec的使用。下面一段代码显示如何启动运行其它程序:
char command[256];
void main()
{
int rtn;/*子进程的返回数值*/
while(1) {
/* 从终端读取要执行的命令 */
printf(">");
fgets( command, 256, stdin );
command[strlen(command)-1] = 0;
if ( fork() == 0 ) {
/* 子进程执行此命令 */
execlp( command, command );
/* 如果exec函数返回,表明没有正常执行命令,打印错误信息*/
perror( command );
exit( errorno );
}
else {
/* 父进程, 等待子进程结束,并打印子进程的返回值 */
wait (&rtn );
printf("child process return %d\n",. rtn );
}
}
}

  此程序从终端读入命令并执行之,执行完成后,父进程继续等待从终端读入命令。熟
悉DOS和WINDOWS系统调用的朋友一定知道DOS/WINDOWS也有exec类函数,其使用方法是类
似的,但DOS/WINDOWS还有spawn类函数,因为DOS是单任务的系统,它只能将"父进程"驻
留在机器内再执行"子进?
?",这就是spawn类的函数。WIN32已经是多任务的系统了,但还保留了spawn类函数,WI
N32中实现spawn函数的方法同前述UNIX中的方法差不多,开设子进程后父进程等待子进程
结束后才继续运行。UNIX在其一开始就是多任务的系统,所以从核心角度上讲不需要spa
wn类函数。
  另外,有一个更简单的执行其它程序的函数system,它是一个较高层的函数,实际上
相当于在SHELL环境下执行一条命令,而exec类函数则是低层的系统调用。
(四) Linux的进程与Win32的进程/线程有何区别
  熟悉WIN32编程的人一定知道,WIN32的进程管理方式与UNIX上有着很大区别,在UNI
X里,只有进程的概念,但在WIN32里却还有一个"线程"的概念,那么UNIX和WIN32在这里
究竟有着什么区别呢?
  UNIX里的fork是七十年代UNIX早期的开发者经过长期在理论和实践上的艰苦探索后取
得的成果,一方面,它使操作系统在进程管理上付出了最小的代价,另一方面,又为程序
员提供了一个简洁明了的多进程方法。
  WIN32里的进程/线程是继承自OS/2的。在WIN32里,"进程"是指一个程序,而"线程"
是一个"进程"里的一个执行"线索"。从核心上讲,WIN32的多进程与UNIX并无多大的区别
,在WIN32里的线程才相当于UNIX的进程,是一个实际正在执行的代码。但是,WIN32里同
一个进程里各个线程之间
是共享数据段的。这才是与UNIX的进程最大的不同。
  下面这段程序显示了WIN32下一个进程如何启动一个线程:(请注意,这是个终端方
式程序,没有图形界面)
int g;
DWORD WINAPI ChildProcess( LPVOID lpParameter ){
int i;
for ( i = 1;i<1000;i ++) {
g ++;
printf("This is Child Thread: %d\n", g );
}
ExitThread( 0 );
};
void main()
{
int threadID;
int i;
g = 0;
CreateThread( NULL, 0, ChildProcess, NULL, 0,&threadID );
for ( i = 1;i<1000;i ++) {
g ++;
printf("This is Parent Thread: %d\n", g );
}
}
  在WIN32下,使用CreateThread函数创建线程,与UNIX不同,线程不是从创建处开始
运行的,而是由CreateThread指定一个函数,线程就从那个函数处开始运行。此程序同前
面的UNIX程序一样,由两个线程各打印1000条信息。threadID是子线程的线程号,另外,
全局变量g是子线程与父
线程共享的,这就是与UNIX最大的不同之处。大家可以看出,WIN32的进程/线程要比UNI
X复杂,在UNIX里要实现类似WIN32的线程并不难,只要fork以后,让子进程调用ThreadP
roc函数,并且为全局变量开设共享数据区就行了,但在WIN32下就无法实现类似fork的功
能了。所以现在WIN32下?
腃语言编译器所提供的库函数虽然已经能兼容大多数UNIX的库函数,但却仍无法实现for
k。
  对于多任务系统,共享数据区是必要的,但也是一个容易引起混乱的问题,在WIN32
下,一个程序员很容易忘记线程之间的数据是共享的这一情况,一个线程修改过一个变量
后,另一个线程却又修改了它,结果引起程序出问题。但在UNIX下,由于变量本来并不共
享,而由程序员来显式?
刂付ㄒ蚕淼氖荩钩绦虮涞酶逦氚踩?
  Linux还有自己的一个函数叫clone,这个函数是其它UNIX所没有的,而且通常的Lin
ux也并不提供此函数(要使用此函数需自己重新编译内核,并设置CLONE_ACTUALLY_WORK
S_OK选项),clone函数提供了更多的创建新进程的功能,包括象完全共享数据段这样的
功能。
  至于WIN32的"进程"概念,其含义则是"应用程序",也就是相当于UNIX下的exec
Linux标准C库:字符处理
The last article was on Input and Output. This article is on character handli
ng.
上一篇文章是关于的输入输出部分。这篇文章介绍的字符处理。
Character handling allows us to classify characters as alpha, digit, hexdigit
, whitespace, printable, lowercase, uppercase, punctuation and to map to and
from the upper and lowercase alphabets. Most importantly implements these fun
ctions in a
non-system dependent way.
字符操作可以让我们把字符分成字母,数字,十六进制数,空格,制表符,小写,答谢,
标点符号,和大小写间的转换。最重要的是使用系统无关的方法实现了这些功能。
If you write your program assuming that every computer is an ASCII computer y
ou will have trouble porting your program to non ASCII machines. If you write
 your character handling functions in terms of these functions your program w
ill be much more
portable to other platforms.
如果你写程序的时候假设每台计算机都是ASCII计算机的话,那当这个程序移植到其他非
ASCII计算机的时候将会出现问题。而如果你使用这些函数来写程序,它对其他平台的移
植性就会增强很多。
I am assuming a knowledge of c programming on the part of the reader. There i
s no guarantee of accuracy in any of this information nor suitability for any
 purpose.
我假设这一章的读者有c编程的知识。这篇文档的正确性我也不做保证。
The program example that I will do this month will go thru the entire 8bit AS
CII range and tell us to which classes any one chacter belongs. The example i
s rogers_example04.c. The output the program generates will be an html docume
nt and the run from
my system is rogers_example04.html .
在这个程序例子中,我将把整个8位ASCII表遍历一遍,并且分出来每个字符都属于那一类
。这是程序:rogers_example04.c。程序将输出一个html文档,在这里rogers_example0
4.html。
This program can be used as a cgi-bin script and is a demonstration of the fl
exibility of the c language.
这个程序可以作为一个cgi-bin脚本运行,这一点可以证明c语言的灵活性。
As always, if you see an error in my documentation please tell me and I will
correct myself in a later document. See corrections at end of the document to
 review corrections to the previous articles.
如果你在这个文档里发现了错误请告诉我,我会在以后的文档里纠正错误的。看本文最后
的勘误,那里有前几期的文章订正。
字符处理
#include
int isalpha(int c);
int isalnum(int c);
int isdigit(int c);
int isxdigit(int c);
int iscntrl(int c);
int isspace(int c);
int ispunct(int c);
int isgraph(int c);
int isprint(int c);
int islower(int c);
int isupper(int c);
int tolower(int c);
int toupper(int c);
isalpha 如果字符的范围在A-Z 或 a-z之间返回true
isalnum 如果字符的范围在A-Z 或 a-z 或 0-9之间返回true
isdigit 如果字符的范围在0-9 之间返回true
isxdigit 如果字符的范围在0-9 或 a-f或A-F之间返回true
iscntrl 如果字符在这个集合里返回true:(FF, NL, CR, HT, VT, BEL or BS)
isspace 如果字符在这个集合里返回truet:(space, FF, NL, CR, HT or VT).
ispunct returns 如果字符是非数字、非空格和非控制字符,则返回true
isgraph 如果isalnum或ispunct返回真,这个函数就返回真
isprint 如果字符是isspace或isgraph,则返回true
islower 如果字符的范围在A-Z 之间返回true
tolower 如果isupper为真,则本函数返回它的小写字母,否则返回字符本身
toupper 如果islower为真,则本函数返回它的大写字母,否则返回字符本身
参考数目:
The ANSI C Programming Language, Second Edition, Brian W. Kernighan, Dennis M
. Ritchie, Printice Hall Software Series, 1988
The Standard C Library, P. J. Plauger, Printice Hall P T R, 1992
The Standard C Library, Parts 1, 2, and 3, Chuck Allison, C/C++ Users Journal
, January, February, March 1995
CTYPE(3), BSD MANPAGE, Linux Programmer's Manual, 29 November 1993
Previous"The Standard C Library for Linux"Articles
The Standard C Library for Linux, Part One, James M. Rogers, January 1998
The Standard C Library for Linux, Part Two, James M. Rogers, July 1998
The Standard C Library for Linux, Part Three, James M. Rogers, August 1998
Linux核心资源
本章主要描叙寻找某个特殊核心函数时用到的Linux核心资源。
本书并不要求读者具有C编程语言的能力或者拥有Linux核心源代码来理解Linux核心工
作原理。但是如果
对核心源代码进行阅读将加深对Linux操作系统的理解。本章提供了一个核心源代码的
综述。
从哪里得到Linux核心源码
所有主要Linux分发版本(如Craftworks,Debian,Slackware,Redhat)都包含了源码
在内。通常安装在
你的Linux系统核心就是从这些源码中构造出来的。由于一些显然的因素,这些源码都
或多或少有点过期。
你可以在www-appendix一章中的那些WEB站点中得到最新的版本。这些站点包括
ftp://ftp.cs.helsinki.fi
以及所有其他镜象站点中。helsinki的这个WEB站点上的Linux源码显然是最新的但是
MIT和Sunsite中的也
不会差太远。
?别斓拸杨湿恀涴虳WEB桴萸ㄛ衄勍嗣CD ROM扣妀眕准?狨穔躁蛝鯄廜忿劼瑱坪EB桴萸
的镜象光盘。
有些 厂商还提供每季度甚至每个月更新的订购服务。另外你所在的本地Linux用户组也
是一个很好的资源。
Linux核心代码的版本编号很简单。任何偶数编号的核心(如2.0.30)都是稳定的发行
版而记数编号的核心
(如2.1.42)都是正在开发的核心。本书基于稳定的2.0.30版本。开发版的核心具有所
有最新的特征并支持
最新的设备。尽管它们不是你所希望的那样稳定,但是对于Linux用户团体来说试用新
核心是非常重要的。
因为他们将完成这些评测工作。当试用非发行版本核心时备份系统总是有好处的。
核心的修改以patch文件来分发。而patch实用程序被用来对一些核心源码进行编辑
例如如果现在你已经 有了2.0.39的核心代码但是你想升级到2.0.30,
那么你在取得2.0.30补丁文件后可以实用以下命令来修改 现存核心:
$ cd /usr/src/linux
$ patch -p1<patch-2.0.30
一个收集核心补丁的站点是http://www.linuxhq.com。
核心源码的组织
核心源码的顶层是/usr/src/linux目录,在此目录下你可以看到大量子目录:
arch
这个子目录包含了所有体系结构相关的核心代码。它还包含每种支持的体系结构的子目
录,如i386。
include
这个目录包括了用来重构核心的大多数include文件。对于每种支持的体系结构分别有
一个子目录。
此目录中的asm子目录中是对应某种处理器的符号连接,如include/asm-i386。
要修改处理器结构 则只需编辑核心的makefile并重新运行Linux核心配置程序。
init
此目录包含核心启动代码。
mm
此目录包含了所有的内存管理代码。与具体体系结构相关的内存管理代码位于
arch/*/mm目录下,
如arch/i386/mm/fault.c 。
drivers
系统中所有的设备驱动都位于此目录中。它又进一步划分成几类设备驱动,如block。
ipc
此目录包含了核心的进程间通讯代码。
modules
此目录仅仅包含已建好的模块。
fs
所有的文件系统代码。它也被划分成对应不同文件系统的子目录,如vfat和ext2。
kernel
主要核心代码。同时与处理器结构相关代码都放在arch/*/kernel目录下。
net
核心的网络部分代码。
lib
此目录包含了核心的库代码。与处理器结构相关库代码被放在arch/*/lib/目录下。
scripts
此目录包含用于配置核心的脚本文件(如awk和tk脚本)。

从哪里入手
阅读象Linux核心代码这样的复杂程序令人望而生畏。它象一个越滚越大的雪球。
阅读核心某个部分经常要 用到好几个其他的相关文件,不久你将会忘记你原来在干什
么。
本小节将给出一些提示。
系统启动与初始化
在基于intel的系统上,Linux可以通过loadlin.exe或者LILO将核心载入内存并将控制
传递给它。
这部分程序 位于arch/i386/kerneld/head.S。
此文件完成一些处理器相关操作并跳转到init/main.c中的main()例程。
内存管理
这部分代码主要位于mm目录中但其处理器结构相关部分被放在arch/*/mm中。
页面出错处理代码位于mm下的 memory.c文件中而内存映射与页面cache代码位于
filemap.c中。
buffer cache则在mm/buffer.c中实现, swap cache位于mm/swap_state.c和
mm/swapfile.c中。

核心
大多数通用代码位于kernel目录下而处理器相关代码被放在arch/*/kernel中。
调度器位于kernel/sched.c 而fork代码位于kernel/fork.c中。
底层部分处理代码位于include/linux/interrupt.h中。
task_struct的 描叙则在/linux/sched.h中可以找到。
PCI
PCI伪设备驱动位于drivers/pci/pci.c且其系统通用定义放在include/linux/pci.h
中。
每个处理器结构 具有特殊的PCI BIOS代码,Alpha AXP的位于
arch/alpha/kernel/bios32.c中。
进程间通讯
所有这些代码都在ipc目录中。系统V IPC对象都包含一个ipc_perm结构,它在
include/linux/ipc.h中描叙。
中断处理
核心的中断处理代码总是与微处理器结构相关。Intel系统的中断处理代码位于
arch/i386/kernel/irq.c中,
其定义位于include/asm-i386/irq.h中。
设备驱动
Linux核心源码的大多数都是设备驱动。所有Linux的设备驱动源码都放在drivers目录
中并分成以下几类:
/block
块设备驱动包括IDE(在ide.c中)驱动。如果你想寻找这些可包含文件系统的设备的初
始化过程
则应该在drivers/block/genhd.c中的device_setup()。当安装一个nfs文件系统时不
但要初始化
硬盘还需初始化网络。块设备包括IDE与SCSI设备。
/char
此目录包含字符设备的驱动,如ttys,串行口以及鼠标。
/cdrom
包含所有Linux CDROM代码。在这里可以找到某些特殊的CDROM设备(如Soundblaster
CDROM)。
IDE接口的CD驱动位于drivers/block/ide-cd.c中而SCSI CD驱动位于
drivers/scsi/scsi.c中。
/pci
它包含了PCI伪设备驱动源码。这里可以找到关于PCI子系统映射与初始化的代码。
另外位于 arch/alpha/kernel/bios32.c中的Alpha AXP PCI补丁代码也值得一读。
/scsi
这里可以找到所有的SCSI代码以及Linux支持的SCSI 设备的设备驱动。
/net
包含网络驱动源码,如tulip.c中的DECChip 21040 PCI以太网驱动。
/sound
所有的声卡驱动源码。
文件系统
EXT2文件系统的源码位于fs/ext2中,其数据结构定义位于include/linux/ext2_fs.h,
ext2_fs_i.h 以及 ext2_fs_sb.h中。虚拟文件系统数据结构在include/linux/fs.h中
描叙且其代码在fs/*中。buffer cache 和update核心后台进程在fs/buffer.c中实现。
网络
网络代码位于net目录而大多数包含文件位于include/net中。BSD套接口代码位于
net/socket.c中。
IPV4的 INET套接口代码位于net/ipv4/af_inet.c中。通用协议支撑代码(包括sk_buff
处理过程)
位于net/core中, 同时TCP/IP网络代码位于net/ipv4中。网络设备驱动位于
drivers/net中。
模块
核心模块代码部分位于核心中部分位于modules包中。核心代码位于kernel/modules.c
且其数据结构与核心
后台进程kerneld消息位于include/linux/module.h和include/linux/kerneld.h目录
中。
同时必要时需查阅 include/linux/elf.h中的ELF文件格式。
File translated from TEX by TTH, version 1.0.
Top of Chapter, Table of Contents, Show Frames, No Frames
?1996-1999 David A Rusling copyright notice.
Shell编程
作者:Norkai Laboratory 网址:http://www.eastlinux.net/
在DOS 中,你可能会从事一些例行的重覆性工作,此时你会将这些重覆性的命令写成批次
档,只要执行这个批次档就等於执行这些命令。大家会问在UNIX中是否有批次处理这个东
东,答案是有的。在UNIX中不只有如DOS 的批次处理,它的功能比起DOS
更强大,相对地也较复杂,已经和一般的高阶语言不相上下。在UNIX中大家都不叫做批次
档,而叫做Shell Script。
一般而言,Shell Script的地位和其它的可执行档(或命令)是完全相同的,只不过She
ll Script是以文字档的方式储存,而非二进位档。而执行Shell Script时,必须有一个
程式将其内容转成一道道的命令执行,而这个程式其实就是Shell ,这也就是为什麽我们
叫做Shell
Script的原因(往後我们称为Script)。不同Shell 的Script基本上会有一些差异,所以
我们不能将写给A shell 的Script用B shell 执行。而在UNIX中大家最常使用Bourne Sh
ell以及C Shell ,所以这堂课就介绍这两种Script的写法。
将文字档设为可执行的Shell Script
如果我们已经写好Script,如何将其设成可执行档呢?因为Script其实是一个可执行档,
所以必须将其存取权设定成可执行。我们可以使用下列命令更改存取权:
chmod u+x filename 只有自己可以执行,其它人不能执行
chmod ug+x filename 只有自己以及同一群可以执行,其它人不能执行
chmod +x filename 所有人都可以执行
而我们如何指定使用那一个Shell 来解释所写的Script呢?几种基本的指定方式如下所述

1. 如果Script的第一个非空白字元不是"#",则它会使用Bourne Shell。
2. 如果Script的第一个非空白字元是"#"时,但不以"#!"开头时,则它会使用C Shell。
3. 如果Script以"#!"开头,则"#!"後面所写的就是所使用的Shell,而且要将整个路径名
称指出来。
这里建议使用第三种方式指定Shell ,以确保所执行的就是所要的。Bourne Shell的路径
名称为/bin/sh ,而C Shell 则为/bin/csh。
1. 使用Bourne Shell
庚""""""""""庖 庚""""""""""庖
岫echo enter filename 岫 岫#!/bin/sh 岫
岫 . 岫 or 岫 . 岫
岫 . 岫 岫 . 岫
岫 . 岫 岫 . 岫
弩""""""""""彼 弩""""""""""彼
2. 使用C Shell
庚""""""""""庖 庚""""""""""庖
岫# C Shell Script 岫 岫#!/bin/csh 岫
岫 . 岫 岫 . 岫
岫 . 岫 岫 . 岫
岫 . 岫 岫 . 岫
弩""""""""""彼 弩""""""""""彼
3. 使用/etc/perl
庚""""""""""庖
岫#! /etc/perl 岫
岫 . 岫
岫 . 岫
岫 . 岫
弩""""""""""彼
除了在Script内指定所使用的Shell 外,你也可以在命令列中强制指定。比如你要用C S
hell 执行某个Script,你可以下这个命令:
csh filename
此时的Script的存取权就不一定要为可执行档,其内部所指定的Shell 也会无效,详细的
情形後面会讨论。
□Script的基本结构及观念
Script是以行为单位,我们所写的Script会被分解成一行一行来执行。而每一行可以是命
令、注解、或是流程控制指令等。如果某一行尚未完成,可以在行末加上"\",这个时候
下一行的内容就会接到这一行的後面,成为同一行,如下
庚"""""""""""庖
岫echo The message is \ 岫
岫too long so we have \ 岫
岫to split it into \ 岫
岫several lines 岫
弩"""""""""""彼
当Script中出现"#"时,再它後面的同一行文字即为注解,Shell 不会对其翻译。
在Script中要执行一个命令的方法和在命令列中一样,你可以前景或背景执行,执行命令
时也会需要设定一些环境变数。
Script的流程控制和一般高阶语言的流程控制没有什麽两样,也和高阶语言一样有副程式
。这些使得Script的功能更加强大。
为了达到与高阶语言相同的效果,我们也可以在Script中设定变数,如此使Script 成为
一个名付其实的高阶语言。
□Bourne Shell
一、变数
Bourne Shell的变数型态只有字串变数,所以要使用数值运算则必须靠外部命令达 成目
的。而其变数种类有下列几种:
1. 使用者变数
这是最常使用的变数,我们可以任何不包含空白字元的字串来当做变数名称。 设定变数
值时则用下列方式:
var=string
取用变数时则在变数名称前加上一"$"号。
庚"""""""庖
岫name=Tom 岫
岫echo name 岫
岫echo $name 岫
弩"""""""彼
结果如下:
name
Tom
2. 系统变数(环境变数)
和使用者变数相似,只不过此种变数会将其值传给其所执行的命令。要将一使 用者变数
设定为系统变数,只要加上:
export var
庚"""""""庖
岫name=Tom 岫
岫export name 岫
弩"""""""彼
以下是使用者一进入系统之後就已设定好的系统变数:
$HOME 使用者自己的目录
$PATH 执行命令时所搜寻的目录
$TZ 时区
$MAILCHECK 每隔多少秒检查是否有新的信件
$PS1 在命令列时的提示号
$PS2 当命令尚未打完时,Shell 要求再输入时的提示号
$MANPATH man 指令的搜寻路径
3. 唯读的使用者变数
和使用者变数相似,只不过这些变数不能被改变。要将使用者变数设成唯读的 ,只要加
上:
readonly var
而若只打readonly则会列出所有唯读的变数。还有一点,系统变数不可以设定 成唯读的。
庚"""""""庖
岫name=Tom 岫
岫readonly name 岫
岫echo $name 岫
岫name=John 岫
岫readonly 岫
弩"""""""彼
结果如下:
Tom
name: is read only
readonly name
readonly ......
4. 特殊变数
有些变数是一开始执行Script时就会设定,并且不以加以修改,但我们不叫它 唯读的系
统变数,而叫它特殊变数(有些书会叫它唯读的系统变数),因为这 些变数是一执行程
式时就有了,况且使用者无法将一般的系统变数设定成唯读 的。以下是一些等殊变数:

$0 这个程式的执行名字
$n 这个程式的第n个参数值,n=1..9
$* 这个程式的所有参数
$# 这个程式的参数个数
$$ 这个程式的PID
$! 执行上一个背景指令的PID
$? 执行上一个指令的返回值
当你执行这个程式时的参数数目超过9 个时,我们可以使用shift 命令将参数 往前移一
格,如此即可使用第10个以後的参数。除此之外,吾人可以用set 命 令改变$n及$*,方
法如下:
set string
如此$*的值即为string,而分解後则会放入$n。如果set 命令後面没有参数, 则会列出
所有已经设定的变数以及其值。
档名:ex1 参数:this is a test
庚"""""""""""庖
岫echo Filename: $0 岫
岫echo Arguments: $* 岫
岫echo No. of args.: $# 岫
岫echo 2nd arg.: $2 岫
岫shift 岫
岫echo No. of args.: $# 岫
岫echo 2nd arg.: $2 岫
岫set hello, everyone 岫
岫echo Arguments: $* 岫
岫echo 2nd arg.: $2 岫
弩"""""""""""彼
结果如下:
Filename: ex1
Arguments: this is a test
No. of args.: 4
2nd arg.: is
No. of args.: 3
2nd arg.: a
Arguments: hello, everyone
2nd arg.: everyone
值得一提的是,当你想从键盘输入一变数值时,你可以使用下面的命令:
read var1 var2.....
这时read会将一个字分给一个变数。如果输入的字比变数还多,最後一个变数会将剩下的
字当成其值。如果输入的字比变数还少,则後面的变数会设成空字串。 如果需要处理数
值运算,我们可以使用expr命令。其参数及输出列於附录A。
二、执行命令
在Bourne Shell中有五种方法执行一个命令,而这五种方式所产生的果有些许的不 同。
1. 直接下命令
这个方式和在命令列中直接下命令的效果一样。
2. 使用sh命令
sh command
这个档案必须是Bourne Shell的Script,但这个档案并不一定要设成可执行。 除此之外
和直接下命令的方式一样。
3. 使用"."命令
. command
这时和使用sh命令相似,只不过它不像sh一般会产生新的process ,相反地, 它会在原
有的process 下完成工作。
4. 使用exec命令
exec command
此时这个Script将会被所执行的命令所取代。当这个命令执行完毕之後,这个 Script也
会随之结束。
5. 使用命令替换
这是一个相当有用的方法。如果想要使某个命令的输出成为另一个命令的参数 时,就一
定要使用这个方法。我们将命令列於两个"`"号之间,而Shell 会以 这个命令执行後的输
出结果代替这个命令以及两个"`"符号。
str='Current directory is '`pwd`
echo $str
结果如下:
Current directory is /users/cc/mgtsai
这个意思是pwd 这个命令输出"/users/cc/mgtsai",而後整个字串代替原 来的`pwd` 设
定str 变数,所以str 变数的内容则会有pwd 命令的输出。
number=`expr $number + 1`
这就是先前所提要作数值运算的方法,基本上expr命令只将运算式解,而 後输出到标准
输出上。如果要将某变数设定成其值,非得靠命令替换的方 式不可。这个例子是将numb
er变数的值加1 後再存回number变数。
三、流程控制
在介绍流程控制之前,我们先来看看test命令。test命令的参数是条件判断式,当 条件
为真时则传回非零值,而条件为伪时则传回零。在所有的流程控制都必须用到 test命令
来判断真伪。而test命令的使用方法则列於附录B。
test $# = 0
如果执行这个程式没有参数时,会传回非零值代表"$# = 0"这个条件成立。反 之则会传
回零。
以下介绍各种流程控制:
1. if then语法以及流程图如下
岫 FALSE
if (condition) ˉconditionˇ"庖
then 岫TRUE 岫
then-commands then-commands 岫
fi 念""""彼

condition 是一个test命令。往後所介绍的各种流程中的condition 都是test 命令。
档名:chkarg
庚"""""""""""庖
岫if (test $# != 0) 岫
岫 then 岫
岫 echo Arg1: $1 岫
岫fi 岫
弩"""""""""""彼
$ chkarg Hello
Arg1: Hello
$ chkarg
$
2. if then else语法以及流程图如下
岫 FALSE
if (condition) ˉconditionˇ"""""庖
then 岫TRUE 岫
then-commands then-commands else-commands
else 念""""""""彼
else-commands 岫
fi
3. if then elif语法以及流程图如下
岫 FALSE
if (condition1) ˉcondition1ˇ"庖
then 岫TRUE 岫 FALSE
commands1 commands1 ˉcondition2ˇ"庖
elif (condition2) 岫 岫 TRUE 岫
then 岫 commands2 commands3
commands2 念"""""拂""""彼
else 岫
commands3
commands3
fi
echo 'word 1: \c'
read word1
echo 'word 2: \c'
read word2
echo 'word 3: \c'
read word3
if (test"$word1"="$word2"-a"$word2"="$word3")
then
echo 'Match: words 1, 2,&3'
elif (test"$word1"="$word2")
then
echo 'Match: words 1&2'
elif (test"$word1"="$word3")
then
echo 'Match: words 1&3'
elif (test"$word2"="$word3")
then
echo 'Match: words 2&3'
else
echo 'No match'
fi
4. for in语法以及流程图如下
岫 FALSE
for var in arg-list 庚"ˉarg-list逊衄陲昹镉\ˇ"庖
do 岫 岫TRUE 岫
commands 岫 植arg-list?腕珨砐 岫
done 岫 温善曹杅var 岫
岫 岫 岫
岫 commands 岫
弩""""""彼 岫
庚"""""""""""庖 庚"""""彼
岫for a in xx yy zz 岫 岫
岫 do 岫
岫 echo $a 岫
岫done 岫
弩"""""""""""彼
结果如下:
xx
yy
yy
zz
5. for语法以及流程图如下
岫 FALSE
for var 庚"ˉ统杅笢逊衄陲昹镉\ˇ"庖
do 岫 岫TRUE 岫
commands 岫 植统杅笢?腕珨砐 岫
done 岫 温善曹杅var 岫
岫 岫 岫
岫 commands 岫
弩"""""彼 岫
紫靡ㄩlstarg 庚"""""彼
庚"""""""""""庖 岫
岫for a 岫
岫 do 岫
岫 echo $a 岫
岫done 岫
弩"""""""""""彼
$lstarg xx yy zz
xx
yy
yy
zz
6. while 语法以及流程图如下
岫 FALSE
while (condition) 庚"ˉconditionˇ"庖
do 岫 岫TRUE 岫
commands 岫 commands 岫
done 弩""""彼 岫
庚""""彼

庚"""""""""""""""庖
岫number=0 岫
岫while (test $number -lt 10) 岫
岫 do 岫
岫 echo"$number\c" 岫
岫 number=`expr $number + 1` 岫
岫done 岫
岫echo 岫
弩"""""""""""""""彼
结果如下:
0123456789
7. until语法以及流程图如下
岫 TRUE
until (condition) 庚"ˉconditionˇ"庖
do 岫 岫FALSE 岫
commands 岫 commands 岫
done 弩""""彼 岫
庚""""彼

它和while 的不同只在於while 是在条件为真时执行回圈,而until 是在条件 为假时执
行回圈。
8. break及continue
这两者是用於for, while, until 等回圈控制下。break 会跳至done後方执行 ,而cont
inue会跳至done执行,继续执行回圈。
9. case语法以及流程图如下
岫 TRUE
case str in ˉstr=pat1ˇ""""commands1"庖
pat1) commands1;; 岫FALSE TRUE 岫
pat2) commands2;; ˉstr=pat2ˇ""""commands2"怕
pat3) commands3;; 岫FALSE TRUE 岫
esac ˉstr=pat3ˇ""""commands3"怕
岫FALSE 岫
念""""""""""""彼

而pat 除了可以指定一些确定的字串,也可以指定字串的集合,如下
* 任意字串
? 任意字元
[abc] a, b, 或c三字元其中之一
[a-n] 从a到n的任一字元
| 多重选择
庚"""""""""""""""庖
岫echo 'Enter A, B, or C: \c' 岫
岫read letter 岫
岫case $letter in 岫
岫 A|a) echo 'You entered A.';;岫
岫 B|b) echo 'You entered B.';;岫
岫 C|c) echo 'You entered C.';;岫
岫 *) echo 'Not A, B, or C';; 岫
岫esac 岫
弩"""""""""""""""彼
10. 函数格式如下
function-name()
{
commands
}
而要呼叫此函数,就像在命令列下直接下命令一般。
用 GDB 调试程序
用 gdb 调试 GCC 程序
Linux 包含了一个叫 gdb 的 GNU 调试程序. gdb 是一个用来调试 C 和 C++ 程序的强力
调试器. 它使你能在程序运行时观察程序的内部结构和内存的使用情况.
以下是 gdb 所提供的一些功能:
它使你能监视你程序中变量的值.
它使你能设置断点以使程序在指定的代码行上停止执行.
它使你能一行行的执行你的代码.
在命令行上键入 gdb 并按回车键就可以运行 gdb 了, 如果一切正常的话, gdb 将被启动
并且你将在屏幕上看到类似的内容:
GDB is free software and you are welcome to distribute copies of it
under certain conditions;type"show copying"to see the conditions.
There is absolutely no warranty for GDB;type"show warranty"for details.
GDB 4.14 (i486-slakware-linux), Copyright 1995 Free Software Foundation, Inc.
(gdb)
当你启动 gdb 后, 你能在命令行上指定很多的选项. 你也可以以下面的方式来运行 gdb :
gdb<fname>
当你用这种方式运行 gdb , 你能直接指定想要调试的程序. 这将告诉gdb 装入名为 fna
me 的可执行文件. 你也可以用 gdb 去检查一个因程序异常终止而产生的 core 文件, 或
者与一个正在运行的程序相连. 你可以参考 gdb 指南页或在命令行上键入 gdb -h
得到一个有关这些选项的说明的简单列表.
为调试编译代码(Compiling Code for Debugging)
为了使 gdb 正常工作, 你必须使你的程序在编译时包含调试信息. 调试信息包含你程序
里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号. gdb 利用这些信息
使源代码和机器码相关联.
在编译时用 -g 选项打开调试选项.
gdb 基本命令
gdb 支持很多的命令使你能实现不同的功能. 这些命令从简单的文件装入到允许你检查所
调用的堆栈内容的复杂命令, 表27.1列出了你在用 gdb 调试时会用到的一些命令. 想了
解 gdb 的详细使用请参考 gdb 的指南页.
表 27.1. 基本 gdb 命令.
命 令
描 述
file
装入想要调试的可执行文件.
kill
终止正在调试的程序.
list
列出产生执行文件的源代码的一部分.
next
执行一行源代码但不进入函数内部.
step
执行一行源代码而且进入函数内部.
run
执行当前被调试的程序
quit
终止 gdb
watch
使你能监视一个变量的值而不管它何时被改变.
break
在代码里设置断点, 这将使程序执行到这里时被挂起.
make
使你能不退出 gdb 就可以重新产生可执行文件.
shell
使你能不离开 gdb 就执行 UNIX shell 命令.
gdb 支持很多与 UNIX shell 程序一样的命令编辑特征. 你能象在 bash 或 tcsh里那样
按 Tab 键让 gdb 帮你补齐一个唯一的命令, 如果不唯一的话 gdb 会列出所有匹配的命
令. 你也能用光标键上下翻动历史命令.
gdb 应用举例
本节用一个实例教你一步步的用 gdb 调试程序. 被调试的程序相当的简单, 但它展示了
 gdb 的典型应用.
下面列出了将被调试的程序. 这个程序被称为 greeting , 它显示一个简单的问候, 再用
反序将它列出.
#include <stdio.h>
main ()
{
char my_string[] ="hello there";
my_print (my_string);
my_print2 (my_string);
}
void my_print (char *string)
{
printf ("The string is %s\n", string);
}
void my_print2 (char *string)
{
char *string2;
int size, i;
size = strlen (string);
string2 = (char *) malloc (size + 1);
for (i = 0;i<size;i++)
string2[size - i] = string[i];
string2[size+1] = `\0';
printf ("The string printed backward is %s\n", string2);
}
用下面的命令编译它:
gcc -o test test.c
这个程序执行时显示如下结果:
The string is hello there
The string printed backward is
输出的第一行是正确的, 但第二行打印出的东西并不是我们所期望的. 我们所设想的输出
应该是:
The string printed backward is ereht olleh
由于某些原因, my_print2 函数没有正常工作. 让我们用 gdb 看看问题究竟出在哪儿,
先键入如下命令:
gdb greeting
-----------------------------------------------------------------------------
---
注意: 记得在编译 greeting 程序时把调试选项打开.
-----------------------------------------------------------------------------
---
如果你在输入命令时忘了把要调试的程序作为参数传给 gdb , 你可以在 gdb 提示符下用
 file 命令来载入它:
(gdb) file greeting
这个命令将载入 greeting 可执行文件就象你在 gdb 命令行里装入它一样.
这时你能用 gdb 的 run 命令来运行 greeting 了. 当它在 gdb 里被运行后结果大约会
象这样:
(gdb) run
Starting program: /root/greeting
The string is hello there
The string printed backward is
Program exited with code 041
这个输出和在 gdb 外面运行的结果一样. 问题是, 为什么反序打印没有工作? 为了找出
症结所在, 我们可以在 my_print2 函数的 for 语句后设一个断点, 具体的做法是在 gd
b 提示符下键入 list 命令三次, 列出源代码:
(gdb) list
(gdb) list
(gdb) list
-----------------------------------------------------------------------------
---
技巧: 在 gdb 提示符下按回车健将重复上一个命令.
-----------------------------------------------------------------------------
---
第一次键入 list 命令的输出如下:
1 #include <stdio.h>
2
3 main ()
4 {
5 char my_string[] ="hello there";
6
7 my_print (my_string);
8 my_print2 (my_string);
9 }
10
如果按下回车, gdb 将再执行一次 list 命令, 给出下列输出:
11 my_print (char *string)
12 {
13 printf ("The string is %s\n", string);
14 }
15
16 my_print2 (char *string)
17 {
18 char *string2;
19 int size, i;
20
再按一次回车将列出 greeting 程序的剩余部分:
21 size = strlen (string);
22 string2 = (char *) malloc (size + 1);
23 for (i = 0;i<size;i++)
24 string2[size - i] = string[i];
25 string2[size+1] = `\0';
26 printf ("The string printed backward is %s\n", string2);
27 }
根据列出的源程序, 你能看到要设断点的地方在第24行, 在 gdb 命令行提示符下键入如
下命令设置断点:
(gdb) break 24
gdb 将作出如下的响应:
Breakpoint 1 at 0x139: file greeting.c, line 24
(gdb)
现在再键入 run 命令, 将产生如下的输出:
Starting program: /root/greeting
The string is hello there
Breakpoint 1, my_print2 (string = 0xbfffdc4"hello there") at greeting.c :24
24 string2[size-i]=string[i]
你能通过设置一个观察 string2[size - i] 变量的值的观察点来看出错误是怎样产生的
, 做法是键入:
(gdb) watch string2[size - i]
gdb 将作出如下回应:
Watchpoint 2: string2[size - i]
现在可以用 next 命令来一步步的执行 for 循环了:
(gdb) next
经过第一次循环后, gdb 告诉我们 string2[size - i] 的值是 `h`. gdb 用如下的显示
来告诉你这个信息:
Watchpoint 2, string2[size - i]
Old value = 0 `\000'
New value = 104 `h'
my_print2(string = 0xbfffdc4"hello there") at greeting.c:23
23 for (i=0;i<size;i++)
这个值正是期望的. 后来的数次循环的结果都是正确的. 当 i=10 时, 表达式 string2[
size - i] 的值等于 `e`, size - i 的值等于 1, 最后一个字符已经拷到新串里了.
如果你再把循环执行下去, 你会看到已经没有值分配给 string2[0] 了, 而它是新串的第
一个字符, 因为 malloc 函数在分配内存时把它们初始化为空(null)字符. 所以 string
2 的第一个字符是空字符. 这解释了为什么在打印 string2 时没有任何输出了.
现在找出了问题出在哪里, 修正这个错误是很容易的. 你得把代码里写入 string2 的第
一个字符的的偏移量改为 size - 1 而不是 size. 这是因为 string2 的大小为 12, 但
起始偏移量是 0, 串内的字符从偏移量 0 到 偏移量 10, 偏移量 11 为空字符保留.
为了使代码正常工作有很多种修改办法. 一种是另设一个比串的实际大小小 1 的变量.
这是这种解决办法的代码:
#include <stdio.h>
main ()
{
char my_string[] ="hello there";
my_print (my_string);
my_print2 (my_string);
}
my_print (char *string)
{
printf ("The string is %s\n", string);
}
my_print2 (char *string)
{
char *string2;
int size, size2, i;
size = strlen (string);
size2 = size -1;
string2 = (char *) malloc (size + 1);
for (i = 0;i<size;i++)
string2[size2 - i] = string[i];
string2[size] = `\0';
printf ("The string printed backward is %s\n", string2);
}
Xwindow编程
A maina of X&Unix
X 视窗的资料共享:性质
关键字:
X 视窗系统 X window system
X 伺服器 X server
机制 mechanism
性质 property
原子 atom
视窗识别码 window id
导言
在 unix 系统中,不同的应用程式要共享或交换资料,可以透过像pipe,share memory
等内部程序沟通(InterProcess Communication) 的机制来达成;而在 X 视窗系统中,也
提供了类似 unix 内部程序沟 通的机制,使得不同 X 视窗的应用程式可以共享或交换资
料,这机制就是 X
视窗系统中的性质(property)。打个比方来说,我们可以把
X视窗的性质看做是一个可以装填资料的容器,这个容器标明了一个名字与其内含值的资
料型态,并且将这个容器放在相关应用程式都知道的地方,於是这些相关的应用程式就可
透过这个容器,来达成资料共享或资料交换的目的。本文即就 X 视窗的性质机制作一番
研讨,并设计两支X
视窗的应用程式,来验证透过性质的机制可使不同的应用程式共享资料。
性质与原子
在导言中提到过,性质必须有名字(name)及内含值的资料型态(data type),这两者都是
以可变长度的字元串来定义的,例如定义一个性质的名字(name)为「bdc」,其资料型态
(data
type)为「bdc_type」。应用程式可以自行定义性质的名字与内含值的资料型态,然後再
分别将它们转换为原子,也可以使用 X 视窗预先为我们定义好的性质名字的原子与资料
型态的原子,<X11/Xatom.h>标头档中就包含了这些定义好的原子,其起始字元串皆为「
XA_」,如
XA_STRING,XA_INTEGER 等。而什麽是原子呢?我们已经知道性质的名字与内含值的资料
型态是以可变长度的字元串来定义的,但是在 X 伺服器与应用程式之间,藉由传送整个
字元串来指定性质的名字与内含值的资料型态是没有效率且浪费网路频宽的;因此,X
视窗系统以另一个三十二位元的识别码(32-bit id)来表示这个字元串,这三十二位元的
识别码就是「原子」(atom)。在同一个 X 伺服器上,每一个原子都是唯一的(unique),
不会有两个原子是相同的。
为性质命名
应用程式可以使用 X 视窗预先定义的性质名字的原子,如果应用程式不打算使用预先定
义的性质名字的原子,可以自行定义性质的名字,然後将它转换为原子。当应用程式为一
个性质取定一个名字後,接著便是将这个名字转换为 X 伺服器看得懂的原子,转换的工
作可藉由呼叫
XInternAtom 来达成,其函式的型式如下:
Display *display;
char property_name[];
Bool only_if_exists;
Atom atom;
atom = XInternAtom(display, property_name, only_if_exists);
display 为应用程式和 X 伺服器的连线(connection),通常 X 视窗的应用程式一开始就
会建立这条连线。property_name 为性质的名字,当应用程式要产生一个新的性质时,指
定 False 给 only_if_exists;当性质已经存在且已悬挂在某视窗上, 则指定 True 给
only_if_exists,此时 XInternAtom 会传回该性质名字相对应的原子;但是如果此性质
已经不存在,而应用程式又指明 only_if_exists之值为 True,此时XInternAtom 的传回
值为
None。在为性质命名时,必须注意名字的大小写是有分别的,如「thing」和「Thing」,
分别代表两个不同性质的名字。
如果 XInternAtom 在执行中发生错误,则其传回值为 BadAlloc 或 BadValue。一个应用
程式产生一个新的性质,并将它转换为相对应的原子後,即使当初产生该性质的应用程式
结束,其性质相对应的原子的定义依然有效,除非 X伺服器整个结束掉,这个原子的定义
才会被释放。所以 X
视窗的应用程式应该尽量避免使用过量的原子,以节省 X 视窗的系统资源。
指明性质内含值的资料型态
性质内含值的资料型态(data type)是以可变长度来定义的,应用程式可以使用 X 视窗预
先定义的资料型态的原子,也可以自行定义一个字元串来表示一个资料型态,然後将它转
换为原子,这项转换工亦是透过呼叫 XInternAtom
来达成。举例来说,如果应用程式使用预先定义的资料型态原子,如 XA_STRING,则应用
程式便可免掉呼叫 XInternAtom的工作,直接使用预先定义的资料型态原子;但是如果应
用程式自行定义了一个性质的资料型态为「bdc_type」,则应用程式必须呼叫
XInternAtom函式将「bdc_type」这个字元串转换为原子,以便和 X 伺服器沟通,其程式
码如下:
Display *display;
Atom prop_type;
prop_type = XInternAtom(display,"bdc_type", False);
性质的资料格式
每一个的性质都有一个资料格式,其格式的参数值为 8、16 或 32 ,分别表示性质内的
资料是以八、十六或三十二位元来储存。
将性质挂在视窗上并存入资料
在一般的情况下,不同的应用程要共享、交换资料,最方便的方式是将性质挂在根视窗(
root window)下,因为每一个 X 伺服器都有一个根视窗,这个根视窗是 X 视窗应用程式
「都知道的地方」,透过DefaultRootWindow
这个巨集呼叫,应用程式可以取得根视窗的视窗识别码。另一方式是把性质挂在某个非根
视窗的视窗上,而要共享、交换资料的相关的应用程式「都要知道这个视窗」,如此才能
把资料存到这个视窗上的性质中,或从这个视窗上的性质中读取资料。藉由呼叫 XChang
eProperty
这个函式,可以将性质挂在视窗上,并将资料存入性质中, 其函式的型式如下:
Display *display;
Window window;
Atom prop_name;
Atom prop_type;
int prop_format;
int prop_mode;
unsigned char *data;
int nbytes;
XChangeProperty(display, window, prop_name, prop_type,
prop_format, prop_mode, data, nbytes);
display 为应用程式和 X 伺服器的连线(connection),通常 X 视窗的应用程式一开始就
会建立这条连线。
window 为性质欲悬挂的视窗的视窗识别码。
prop_name 为性质名字的原子。
prop_type 为性质资料型态的原子。
prop_format 指明资料将以何种格式存放於性质内,其允许值为 8、16 或32,如果是 1
6 或 32,则 XChangeProperty 中的第七个参数 data 必须做(char *) 的型态转换(typ
e casting)。prop_format 内的资讯使得 X 伺服器在必要的时候可以做 byte swap ope
ration。
prop_mode 指明资料的储存方式,其值定义於<X11/X.h>中,分述如下:
一、 PropModeReplace 表示性质中的原有资料完全被新的资料所覆盖。
二、 PropModePrepend 表示新的资料是附加於性质中的原有资料的前面。
三、 PropModeAppend 表示新的资料是附加於性质中的原有资料的後面。data 即为应用
程式所要存放於性质中的资料,为一字元指标。nbytes 指明 data 中有多少个 byte。X
ChangeProperty 用来将资料存入悬挂在视窗上的一个性质中,如果指定的性质不存在,
 则 XChangeProperty
会自动地产生这个性质。 此外,呼叫
XChangeProperty 会产生一个 PropertyNotify 事件给悬挂该性质的视窗。性质一旦被应
用程式所定义,即使当初定义该性质的应用程式结束,该性质的定义依然有效,除非有下
列三种情况之一发生,该性质的定义才会被释放,这三种情况是:
一、当初定义该性质的应用程式,呼叫 XDeleteProperty 函式删除该性质的定义。
二、悬挂该性质的视窗已被关掉(已不存在)。
三、和 X 伺服器的连线(connection)已被结束掉。
性质内能存放资料的大小是和伺服器相关的(server-dependent),如果伺服器上 的记忆
体空间够大的话,可以调整性质内能存放资料的空间大小。
由视窗上的性质中读取资料
当应用程式知道某个视窗上有某个性质时,可以透过呼叫 XGetWindowProperty这个函式
去读取该视窗上该性质内的资料,其函式的型式如下:
Display *display;
Window window;
Atom prop_name;
long offset;
long length;
Bool delete;
Atom prop_type;
Atom ret_prop_type;
int ret_format;
unsigned long ret_length;
unsigned long bytes_left;
unsigned char *ret_prop;
XGetWindowProperty(display, window, prop_name,
offset, length, delete, prop_type,
&ret_prop_type,&ret_format,
&ret_length,&bytes_left,
&ret_prop);
display 为应用程式和 X 伺服器的连线(connection),通常 X 视窗的 应用程式一开始
就会建立这条连线。
window 为悬挂该性质的视窗的视窗识别码。
prop_name 为该视窗上该性质名字相对应的原子。
offset 指明应用程式要从那个起始位置读取资料。
length 指明应用程式一次要读取多少笔资料。。
delete 表示应用程式在读取性质内的资料後,要不要删除该性质,其值可为True 或 Fa
lse,当 delete 之值为 False 时,表示应用程式在读取完性质内的资料後,不会去删除
该性质;当 delete 之值为 True 时,表示应用程式在读取完该性质内的资料後,如果
bytes_left
的值为零(表示性质内已无尚未读取的资料),则删除该视窗上的性质,并产生一个 Prop
ertyNotify 事件给该视窗。
prop_type 表示应用程式所期望的性质是哪一种资料型态,以原子来表示。
ret_prop_type 表示该视窗上该性质之真正的资料型态,以原子来表示,此参数是结果回
传值。
ret_format 表示该视窗上该性质内的资料格式,可能的值为 8、16 或 32,此 参数是结
果回传值。
ret_length 表示该性质内的资料,如果以 ret_format 的单位来计算,总共有多
少笔,此参数是结果回传值。
bytes_left 表示应用程式在执行一次读取该性质内的资料後,性质内还有多少 尚未被读
取的资料;如果性质内还有尚未被读取的资料,应用程式可以再呼叫
XGetWindowProperty 函式去撷取尚未被读取的资料,此参数是结果回传值。
ret_prop 为指向该性质内资料的一个字元指标,此参数是结果回传值。如果应用程式指
明的性质不在该视窗上,则该函式呼叫完後, ret_prop_type 之值为 None, ret_form
at 之值为零, bytes_left
之值为零,ret_length之值为零。如果应用程式指明的性质的确悬挂在该视窗上,但是应
用程式所期望的性质资料型态和该性质的真正资料型态不一致时,则该函式呼叫完後,r
et_prop_type 表明性质真正的资料型态, ret_format 表明性质内的资料格式(绝不会为
零), bytes_left
表明该性质内有多少个位元组(byte)的资料(即使ret_format 的值为 16 或 32), ret_
length 之值为零。如果应用程式指明的性质的确悬挂在该视窗上且应用程式所期望的性
质资料型态和该性质的真正资料型态一致,或该性质的真正资料型态为 AnyPropertyTyp
e
时,则在该函式呼叫完後,ret_prop_type 表明性质真正的资料型态, ret_format 表明
性质内的资料格式(绝不会为零),而 ret_length 及 bytes_left 的计算公式如下:
N = 表示性质内的资料,以位元组来计算的话有多少笔。
I = 4 * offset (将单位由 long 转换为 byte)
L = min( (N-I) , 4 * length ),如果 L 小於零,则为 BadValue,如果 L
大於或等於零,则将 L 除以 ret_format 後的值指定给 ret_length。
bytes_left = N - (I+L)
综合上述,可以归纳出:
一、如果 ret_format 的值为零的话,表示该视窗上没有应用程式所指明的性质。
二、如果 ret_format 的值不为零,但是 ret_length 的值为零,表示应用程式
所期望的性质资料型态与悬挂在该视窗上性质的真正资料型态不一致。
三、如果 bytes_left 的值不为零,表示该性质中还有尚未被取的资料,应用程式
应该分段地把性质内的资料全部读取出来。
当 XGetWindowProperty 执行成功时,传回值为 Success(其值为零),失败时
传回值为 1 。
删除性质的定义
如果应用程式打算删除性质的定义,可呼叫 XDeleteProperty 来完成这项工作,其函式
的型式如下:
Display *display;
Widnow window;
Atom prop_name;
XDeleteProperty(display, window, prop_name);
display 为应用程式和 X 伺服器的连线(connection),通常 X 视窗的
应用程式一开始就会建立这条连线。
window 为悬挂该性质的视窗的视窗识别码。
prop_name 为应用程式要删除的性质名字的原子。
此函式执行成功後,会送出 PropertyNotify 事件给原来悬挂该性质的
视窗。
什麽时候会产生 PropertyNotify 事件
在 X 视窗函式呼叫中,共有四个函式呼叫会产生 PropertyNotify 事件,
这四个函式为:
一、 XChangeProperty
二、 XDeleteProperty
三、 XGetWindowProperty
四、 XRotateWindowProperty
因此,当应用程式叫用其中一个系统呼叫时,需记得其已产生了一个PropertyNotify 事
件,而选择此事件的事件遮罩(event mask)为PropertyChangeMask,应用程式可藉由呼叫
 XSelectInput 函式来选择
PropertyNotify 事件。
对 PropertyNotify 事件执行相对应的运作程序
在 X 视窗系统中,所有的运作都是根据事件发生的与否来决定要不要执行相对应的运作
程序(operation procedure),即所有的运作都是由事件来导动的(event-driven)。在 X

视窗的系统呼叫中,部份的系统呼叫会产生特定的事件而後送给特定的视窗,应用程式必
须自己决定是否对这些事件做出必要的反应。举例来说,在前面所介绍的 X 系统呼叫中
, XChangeProperty 就会产生一个 PropertyNotify
事件,送给悬挂该性质的视窗,此举在通知相关的应用程式,悬挂在该视窗上的性质内的
资料已被更改或性质已被删除,接下来的就是这些相关的应用程式必须决定当 Property
Notify
事件发生时,要不要执行相对应的运作程序,而这相对应的运作程序是由应用程式定义的
。因此,如果应用程式想知道某视窗上的性质内的资料是否被更动,必须在该视窗的事件
回圈中选择 (select or solicit) PropertyNotify 事件,以便当 PropertyNotify
事件发生时,能执行其所定义的运作程序。
PropertyNotify 事件的结构
PropertyNotify 事件的结构定义如下:
typedef struct {
int type;
unsigned long serial;
Bool send_event;
Display *display;
Widnow window;
Atom atom;
Time time;
int state;
} XPropertyEvent;
typedef union _XEvent {
...
XPropertyEvent xproperty;
...
} XEvent;
XPropertyEvent 的各栏位意义如下:
type 为发生的事件型态。
serial 为上一个被服务的请求的序列编号(serial number),此乃因 X 视窗系统将
应用程式送给 X 伺服器的所有请求做了序列化处理(serialization),以
避免在存取系统资源时发生竞争现象。
display 为指向 X 伺服器的连线(connection)。
window 为悬挂该性质视窗的视窗识别码。
atom 为该性质名字的原子。
time 表示性质内的资料被更动时,X 视窗的系统时间。
state 表示性质的变化状态,其值有二,分别是
一、PropertyNewValue 表示性质内的资料是一个新值。
二、PropertyDeleted 表示性质的定义已被删除。
程式说明
程式的发展平台为 Solaris 2.4, Solaris Software Developer Kit, X11R5, Motif 1.
2.3 run time environment。 □例程式有两支,分述如下:
【 put_to_prop.c 】
此程式主要大纲为:产生一个新的性质,将这性质悬挂在根视窗(root window)下,由te
xt widget 中读取资料後,将这资料存放到悬挂在根视窗的性质中。程式一开始执行 X
视窗应用程式的初始化工作,以产生一个 toplevel widget,接著分别呼叫XtDisplay 及
 DefaultRootWindow
以取得应用程式和 X 伺服器的连线及根视窗的视窗识别码,这两个都是呼叫性质操作函
式时要用到的参数;再来则是抓取命令列的第二个参数作为性质的名字,指定 False 给
 only_if_exists 後,呼叫 XInternAtom以产生一个新的性质,其转换出来的原子叫做
myproperty。toplevel
widget 有两个孩子及两个孙子,分别是 rc 、 quit_btn 、 data_field 及 put_data,
其中data_field 及 put_data 都是 rc 的孩子。 put_data widget 有一个事件处理程序
 (event handler) 叫做 PutData,其主要工作是当 put_data widget 被压下时,呼叫X
mTextGetString
函式将 text widget 中的资料存放到一个缓冲区後,再呼叫XChangeProperty 函式将缓
冲区内的资料存放到根视窗的性质中。 quit_btn widget有一个回叫程序 (callback pr
ocedure) 叫做 delete_prop,其主要工作是呼XDeleteProperty函式删除该性质的定义,
之後呼叫
XtCloseDispaly 函式把应用程式和 X 伺服器的连线,结束掉。
【 get_from_prop.c 】
此程式的主要大纲为:在事件回圈(event loop)中侦收 PropertyNotify 事件,当Prope
rtyNotify 事件发生时,判断其发生事件的视窗是否为根视窗及性质名字的原子是否为应
用程式所感兴趣的原子;如果是,则呼叫 GetData 程序将性质内的资料读取出来,显示
在 text widget
上;如果其中一个条件不符,则呼叫XtDispatchEvent函式将事件丢往它该去的视窗上。
程式一开始执行 X 视窗应用程式的初始化工作,以产生一个 toplevel widget,接著分
别呼叫 XtDisplay 及 DefaultRootWindow以取得应用程式和 X
伺服器的连线及根视窗的视窗识别码,这两个都是呼叫性质操作函式时会用到的参数;再
来则是抓取命令列的第二个参数作为性质的名字,指定 True 给only_if_exists 後,呼
叫 XInternAtom 找出其相对应的原子,如果此性质不存在,则传回 None。 toplevel w
idget
有两个孩子及一个孙子,分别是 rc 、 quit_bnt 及data_field,其中 data_field 为
rc 的孩子。因为性质是悬挂在根视窗下,而根视窗并不是一个 widget,所以无法使用
XtMainLoop 及 XtAddEventHandler 的函式呼叫来对 PropertyNotify 事件做出反应,因
此应用程式改采 Xlib
的风格来选择事件及设计事件回圈。GetData 程序中呼叫了 XGetWindowProperty 函式,
从根视窗的性质中去读取资料,其参数 offset 及 length 分别为 0 及 8192,这是一般
性质所能存放资料的最大空间;参数 delete 之值为
False,表示应用程式在读取完性质内的资料後,不去删除该性质的定义;应用程式所期
望的性质资料型态为 XA_STRING ,这是 X
视窗预先定义(predefined)的性质资料型态的原子,而此函式後面的五个参数皆为结果回
传值,执行完此函式後,应用程式判断其是否执行成功及性质内的真正资料型态是否和应
用程式所期望资料型态的一致,如果两项条件皆符合,接著便呼叫 XmTextSetString
函式将回传回来的资料显示於 text widget 中,最後呼叫 XFree 函式把 X 视窗系统为
应用程式配置的缓冲空间(ret_property)释放掉。
程式执行画面说明
图一:在背景下分别执行 put_to_prop 及 get_from_prop 两支程式,性质的名字为beh
avior」(put_to_prop behavior&;sleep 5;get_from_prop behavior&),待视窗出现後在
 put_to_prop 的 text widget 中键入资料,此时尚未压下PutData widget,所以 put_
to_prop 的 text
widget 中的资料还未存放到根视窗的 behavior 性质中。
图二:压下在 put_to_prop 中的 PutData widget,此时 put_to_prop 的 text widget
中的资料被存放到悬挂在根视窗的 behavior 性质内,同此时刻 get_from_prop侦收到
ProertyNotify 事件,接著就到根视窗的 behavior 性质中去读取资料,并将资料显示於
 get_from_prop 的
text widget 中。
/*
* Program: put_to_prop.c
*
* Purpose: Retrieve data from text widget, then put it into
* a specified property which is hanged on root window.
*
* Author : Chung-Chia Chen
*
* Date : Dec. 14, 1994
*/
#include<X11/StringDefs.h>
#include<X11/Intrinsic.h>
#include<X11/Xatom.h>
#include<Xm/Xm.h>
#include<Xm/RowColumn.h>
#include<Xm/PushB.h>
#include<Xm/Text.h>
#include<stdio.h>
#include<stdlib.h>
#define ROWS 10
#define COLS 40
static void PutData(Widget, Widget*, XEvent*);
static void DeleteProp(Widget, caddr_t, XmAnyCallbackStruct*);
static Display *mydisplay;
static Window root_window;
static Atom myproperty;
void main(int argc, char *argv[])
{
Widget toplevel, rc, put_data, data_field, quit_btn;
if( argv[1] == NULL) {
printf("Usage: program_name property_name\n");
exit(0);
}
toplevel = XtInitialize(argv[0],"PutDemo", NULL, 0,
&argc, argv);
mydisplay = XtDisplay(toplevel);
if( (root_window = DefaultRootWindow(mydisplay)) == NULL ) {
printf("root_window id is null\n");
exit(-1);
}
/***************************************************
* Get the display and root window id.
***************************************************/
myproperty = XInternAtom(mydisplay, argv[1], False);
if( myproperty == None ) {
printf("Trying to create argv[1] property failed.", argv[1]);
exit(-1);
}
/**********************************************************
* Create a new property, convert the property's name
* into an atom called myproperty.
* Application takes predefined atom"XA_STRING"as the
* data type of the property, so the job that converts
* the property's data type into an atom can be exempted.
**********************************************************/
rc = XtVaCreateManagedWidget("Panel",
xmRowColumnWidgetClass, toplevel,
NULL);
data_field = XtVaCreateManagedWidget("DataField",
xmTextWidgetClass, rc,
XmNeditMode, XmMULTI_LINE_EDIT,
XmNrows, ROWS,
XmNcolumns, COLS,
NULL);
put_data = XtVaCreateManagedWidget("PutData",
xmPushButtonWidgetClass, rc,
NULL);
XtAddEventHandler(put_data, ButtonPressMask, FALSE ,
(XtEventHandler) PutData,&data_field);
/********************************************************
* Create a push button widget(put_data), then register
* an event handler named PutData which solicits button
* press event.
* data_field is taken as a client data which is gonna
* pass to the PutData event handler.
********************************************************/
quit_btn = XtVaCreateManagedWidget("Quit",
xmPushButtonWidgetClass, rc,
NULL);
XtAddCallback(quit_btn, XmNactivateCallback,
(XtCallbackProc) DeleteProp, NULL);
XtRealizeWidget(toplevel);
XtMainLoop();
}
static void PutData(Widget w,
Widget *client_data,
XEvent *ev)
{
char *buff;
buff = XmTextGetString(*client_data);
if(buff == NULL) {
printf("XmTextGetString returns NULL\n");
return;
}
XChangeProperty(mydisplay, root_window,
myproperty, XA_STRING,
32, PropModeReplace,
(unsigned char*) buff, strlen(buff));
/***************************************
* Store the data pointed by buff into
* the property named myproperty.
***************************************/
free(buff);
}
static void DeleteProp(Widget w,
caddr_t client_data,
XmAnyCallbackStruct *call_data)
{
XDeleteProperty(mydisplay, root_window, myproperty);
XFlush(mydisplay);
XtCloseDisplay(mydisplay);
exit(0);
}
/*
* Program: get_from_prop.c
*
* Purpose: Get the data from a property which is hanged on
* root window.
*
* Author : Chung-Chia Chen
*
* Date : Dec. 12, 1994
*/

#include<X11/StringDefs.h>
#include<X11/Intrinsic.h>
#include<X11/Xatom.h>
#include<Xm/Xm.h>
#include<Xm/RowColumn.h>
#include<Xm/PushB.h>
#include<Xm/Text.h>
#include<stdio.h>
#include<stdlib.h>
#define ROWS 10
#define COLS 40
static void GetData(Widget*);
static void CloseApp(Widget, XtPointer*, XmAnyCallbackStruct*);
static Display *mydisplay;
static XEvent myevent;
static Window root_window;
static Atom myproperty;
void main(int argc, char *argv[])
{
Widget toplevel, rc, data_field, quit_btn;
if( argv[1] == NULL ) {
printf("Usage: program_name property_name_that_already_exists\n");
exit(0);
}
toplevel = XtInitialize(argv[0],"GetDemo", NULL, 0,
&argc, argv);
mydisplay = XtDisplay(toplevel);
if( (root_window = DefaultRootWindow(mydisplay)) == NULL ) {
printf("root_window is null, error\n");
exit(0);
}
/***************************************************
* Get the display and root window id.
***************************************************/
myproperty = XInternAtom(mydisplay, argv[1], True);
if( myproperty == None ) {
printf("The property named %s does not exist.\n", argv[1]);
exit(0);
}
/*************************************************
* Check that the property exists or not.
*************************************************/
rc = XtVaCreateManagedWidget("Panel",
xmRowColumnWidgetClass, toplevel,
NULL);
data_field = XtVaCreateManagedWidget("DataField",
xmTextWidgetClass, rc,
XmNeditMode, XmMULTI_LINE_EDIT,
XmNrows, ROWS,
XmNcolumns, COLS,
NULL);
quit_btn = XtVaCreateManagedWidget("Quit",
xmPushButtonWidgetClass, rc,
NULL);
XtAddCallback(quit_btn, XmNactivateCallback, (XtCallbackProc) CloseApp, NULL);
XtRealizeWidget(toplevel);
XSelectInput(XtDisplay(toplevel), root_window, PropertyChangeMask);
/*****************************************************
* The root window solicits PropertyChange event.
*****************************************************/
while(TRUE) {
XtNextEvent(&myevent);
switch (myevent.type){
case PropertyNotify:
printf("PropertyNotify event occured on root window\n");
if(myevent.xproperty.window = root_window&&
myevent.xproperty.atom == myproperty)
GetData(&data_field);
else
XtDispatchEvent(&myevent);
break;
/************************************************
* It's important to check the xproperty.window
* and xproperty.atom, otherwise some bad access
* event will occur.
************************************************/
default:
XtDispatchEvent(&myevent);
break;
}/* end of switch(myevent.type) */
}/* end of while(TRUE) */
}
static void GetData(Widget *data_w)
{
Atom ret_type;
int ret_format;
unsigned long ret_len, ret_after;
unsigned char *ret_property;
if( (XGetWindowProperty(mydisplay, root_window, myproperty,
0, 8192, False, XA_STRING,
&ret_type,&ret_format,&ret_len,&ret_after,
&ret_property) == Success)&&(ret_type == XA_STRING)) {
XmTextSetString(*data_w, (char *) ret_property);
XFree(ret_property);
}
else
printf("XGetWindowProperty failed\n");
}
static void CloseApp(Widget w,
XtPointer *client_data,
XmAnyCallbackStruct *call_data)
{
XtCloseDisplay(XtDisplay(w));
exit(0);
}
Linux 内核编程风格
  这篇短小的文章是对Linux内核编程风格的建议.编程风格非常的个性化,而且,我并不
想将我的观点强加给任何人,但是为了变于维护,
我不得不提出这个观点.详情如下:
  在最开始,我应该写出GNU编程风格的标准而不用理会它.不要理会他们,它只是一个符
号表情而已.
  好,让我们开始吧!
  第一章:缩进格式
  Tab是8个字符,于是缩进也是8个字符.有很多怪异的风格,他们将缩进格式定义为4个
字符(设置为2个字符!)的深度,这就象试图将PI定义为3一样让人难以接受.
  理由是:缩进的大小是为了清楚的定义一个块的开始和结束.特别是当你已经在计算机
前面呆了20多个小时了以后,你会发现一个大的缩进格式使得你对程序的理解更容易.
  现在,有一些人说,使用8个字符的缩进使得代码离右边很近,在80个字符宽度的终端屏
幕上看程序很难受.回答是,但你的程序有3个以上的缩进的时候,你就应该修改你的程序.

  总之,8个字符的缩进使得程序易读,还有一个附加的好处,就是它能在你将程序变得嵌
套层数太多的时候给你警告.这个时候,你应该修改你的程序.
  第二章:大符号的位置
  另外一个C程序编程风格的问题是对大括号的处理.同缩进大小不同,几乎没有什么理
由去选择一种而不选择另外一种风格,但有一种推荐的风格,它是Kernighan和Ritchie的经
典的那本书带来的,它将开始的大括号放在一行的最后,而将结束大括号放在一行的第一位
,如下所示:
  if (x is true) { we do y }
  然而,还有一种特殊的情况:命名函数:开始的括号是放在下一行的第一位,如下: int
 function(int x) { body of function }
  所有非正统的人会非难这种不一致性,但是,所有思维正常的人明白: (第一) K&R是_
__对___的,(第二)如果K&R不对,请参见第一条. (:-))......另外,函数也是特殊的,不一
定非得一致.
  需要注意的是结束的括号在它所占的那一行是空的,__除了__它跟随着同一条语句的
继续符号.如"while"在do-while循环中,或者"else"在if语句中.如下:
  do { body of do-loop } while (condition);
  以及
  if (x == y) { .. } else if (x>y) { ... } else { .... }
  理由: K&R.
  另外,注意到这种大括号的放置方法减小了空行的数量,但却没有减少可读性.于是,在
屏幕大小受到限制的时候,你就可以有更多的空行来写些注释了.
  第三章:命名系统
  C是一种简洁的语言,那么,命名也应该是简洁的.同MODULE-2以及PASCAL语言不同的是
,C程序员不使用诸如ThisVariableIsATemporaryCounter之类的命名方式.一个C语言的程
序员会将之命名为"tmp",这很容易书写,且并不是那么难以去理解.
  然而,当混合类型的名字不得不出现的时候,描述性名字对全局变量来说是必要的了.
调用一个名为"foo"全局的函数是很让人恼火的.
  全局变量(只有你必须使用的时候才使用它) ,就象全局函数一样,需要有描述性的命
名方式.假如你有一个函数用来计算活动用户的数量,你应该这样命名--"count_active_u
sers()"--或另外的相近的形式,你不应命名为"cntusr()".
  有一种称为Hungarian命名方式,它将函数的类型编码写入变量名中,这种方式是脑子
有毛病的一种表现---编译器知道这个类型而且会去检查它,而这样只会迷惑程序员. --知
道为什么Micro$oft为什么会生产这么多"臭虫"程序了把!!.
  局部变量的命名应该短小精悍.假如你有一个随机的整数循环计数器,它有可能有"i"
,如果没有任何可能使得它能被误解的话,将其写作"loop_counter"是效率低下的.同样的
,""tmp"可以是任何临时数值的函数变量.
  如果你害怕混淆你的局部变量的名字,还有另外一个问题,就是称function-growth-h
ormone-imbalancesyndrome.
  第四章:函数
  函数应该短小而迷人,而且它只作一件事情.它应只覆盖一到两个屏幕(80*24一屏),并
且只作一件事情,而且将它做好.(这不就是UNIX
的风格吗,译者注).
  一个函数的最大长度和函数的复杂程度以及缩进大小成反比.于是,如果你已经写了简
单但长度较长的的函数,而且你已经对不同的情况做了很多很小的事情,写一个更长一点的
函数也是无所谓的.
  然而,假如你要写一个很复杂的函数,而且你已经估计到假如一般人读这个函数,他可
能都不知道这个函数在说些什么,这个时候,使用具有描述性名字的有帮助的函数.
  另外一个需要考虑的是局部变量的数量.他们不应该超过5-10个,否则你有可能会出错
.重新考虑这个函数,将他们分割成更小的函数.人的大脑通常可以很容易的记住7件不同的
事情,超过这个数量会引起混乱.你知道你很聪明,但是你可能仍想去明白2周以前的做的事
情.
  第5章:注释
  注释是一件很好的事情,但是过多的注释也是危险的,不要试图区解释你的代码是注释
如何如何的好:你应该将代码写得更好,而不是花费大量的时间去解释那些糟糕的代码.
  通常情况下,你的注释是说明你的代码做些什么,而不是怎么做的.而且,要试图避免将
注释插在一个函数体里:假如这个函数确实很复杂,你需要在其中有部分的注释,你应该回
到第四章看看.你可以写些简短的注释来注明或警告那些你认为特别聪明(或极其丑陋)的
部分,但是你必须要避免?
?.取而代之的是,将注释写在函数前,告诉别人它做些什么事情,和可能为什么要这样做.
  第六章:你已经深陷其中了.
  不要着急.你有可能已经被告之"GUN emacs"会自动的帮你处理C的源代码格式,而且你
已经看到它确实如此,但是,缺省的情况下,它的作用还是不尽如人意(实际上,他们比随便
敲出来的东西还要难看- ainfinite number of monkeys typing into GNU emacs would
 nevermake a good
program)
  于是,你可以要么不要使用GUN emacs,要么让它使用sanervalules.使用后者,你需要
将如下的语句输入到你的.emacs文件中.(defun linux-c-mode()"C mode with adjusted
 defaults for usewith the Linux kernel."(interactive) (c-mode) (c-set-style"K
&R") (setq
c-basic-offset8))
  这会定义一个M-x Linux-c-mode的命令.当你hacking一个模块的时候,如何你将-*-
linux-c -*-输入在最开始的两行,这个模式会自动起作用.而且,你也许想加入如下
  (setq auto-mode-alist (cons `("/usr/src/linux.*/.*\\.〖ch〗$". linux-c-mo
de) auto-mode-alist))
  到你的.emacs文件中,这样的话,当你在/usr/src/linux下编辑文件的时候,它会自动
切换到linux-c-mode .
  但是,假如你还不能让emaces去自动处理文件的格式,不要紧张,你还有一样东西:"缩
进".
  GNU的缩进格式也很死板,这就是你为什么需要加上几行命令选项.然而,这还不算太坏
,因为GNU缩进格式的创造者也记得K&R的权威, (GNU没有罪,他们仅仅是在这件事情上错误
的引导了人们) ,你要做的就只有输入选项"-kr -i8"(表示"K&R,缩进8个字符).
  "缩进"有很多功能,特别是当它建议你重新格式你的代码的时候,你应该看看帮助.但
要记住:"缩进"不是风格很差的程序的万灵丹.


--
※ 来源:.南京大学小百合站 bbs.nju.edu.cn.[FROM: 202.38.80.251]
--
※ 转寄:.南京大学小百合站 bbs.nju.edu.cn.[FROM: 深圳大学BBS]
--
※ 转载:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.0.146]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店