荔园在线

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

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


发信人: bstone (等我!), 信区: Hacker
标  题: [转载] unix环境高级编程--第2章 UNIX标准化?**{F*(L?*(p***
发信站: BBS 荔园晨风站 (Fri Mar 24 12:25:21 2000), 转信

发信人: scircle (yuanyuan), 信区: Security
标  题: unix环境高级编程--第2章 UNIX标准化及实现
发信站: BBS 水木清华站 (Thu Mar 23 16:42:56 2000)

第二章〓Unix标准化及实现
21〓引言
在使各种风格的Unix和C程序设计语言标准化方面已经做了很多工作。虽然Unix应
用程序在
不同的Unix版本之间进行移植是相当容易的,但是八十年代中Unix版本的剧增以及
它们之间
差别的扩大导致很多大工作(例如美国政府)要求对其进行标准化。
本章将介绍正在进行的各种标准化工作,然后讨论这些标准对本书所说明的实际U
nix实现的
影响。所有标准化工作的一个重要部分是对每种实现必须定义的各种限制的说明,
所以我们
将说明这些限制以及确定它们值的多种方法。
22〓Unix标准化
221〓ANSI C
在1989年后期,C程序设计语言的ANSI标准X3159-1989得到批准〔ANSI 1989〕。
此标准已
被采用为国际标准ISO/IEC 9899:1990。ANSI是美国国家标准学会,它是由制造商
和用户组
成的非赢利性组织。在美国,它是全面性的无偿标准交换站,在国际标准化组织(
成的非赢利性组织。在美国,它是全面性的无偿标准交换站,在国际标准化组织(
ISO)中是
代表美国的成员。
ANSI C标准的意图是提供C程序的可移植性,使其能适合于大量不同的操作系统,
而不只是U
nix。此标准不仅定义C程序设计语言的语法和语义,也定义其标准库〔ANSI 1989
第四章;P
lauger 1992;Kernighan及Ritchie 1988中的附录B〕。因为很多新的Unix系统(例
如在本书
中说明的几个Unix系统)都提供C标准中说明的库函数,所以此库对我们来讲是很重
要的。
按照该标准定义的各个头文件,可将该库分成15区。图21中列出了C标准定义的
头文件,
以及下面几节中说明的另外两个标准(POSIX1和XPG3)定义的头文件。在其中也列
举了哪些
头文件是SVR4和43+BSD所支持的。我们也将在本章中对这两种Unix实现进行说明
。
222〓IEEE POSIX
POSIX是一个由IEEE(电气和电子工程师学会)制订的标准族。POSIX的意思是计算机
环境的可
移植操作系统界面(Portable Operating System Interface for Computer Envir
onment)。
它原来指的只是IEEE标准10031-1988(操作系统界面),但是,IEEE现正在制订P
它原来指的只是IEEE标准10031-1988(操作系统界面),但是,IEEE现正在制订P
OSIX族中
的其它有关标准。例如,10032将是针对shell和公用程序的标准,10037将是
系统管理
方面的标准。在1003工作组中有15个以上的子委员会。
与本书特别有关的是10031操作系统界面标准,该标准定义了"POSIX依从的"操作
系统必
须提供的服务。虽然10031标准是以Unix操作系统为基础的,但是它又不限定于
Unix和类
似于Unix的系统。确实,有些供应专有操作系统的制造商也声称这些系统将依从P
OSIX(同时
还保有它们的所有专有功能)。
由于10031标准说明了一个界面而不是一种实现,所以并不区分系统调用和库函
数。所有
在标准中的例程都被称为函数。
标准是不断演变的,10031标准也不例外。该标准的1988版,IEEE 10031-198
8经修改
后递交国际标准化组织(ISO),没有增加新的界面或功能,但修改了文本。最后的
文档作为I
EEE Std10031-1990正式出版〔IEEE 1990〕,这也就是国际标准ISO/IEC 9945-
1:1990
。该标准通常被称之为POSIX1,我们将在本书中使用此标准。
IEEE 10031工作组此后对其又作了更多更改,它们应在1993被批准。这些更改(
IEEE 10031工作组此后对其又作了更多更改,它们应在1993被批准。这些更改(
现在称之
为10031a)应由IEEE作为IEEE标准10031-1990的附件出版,这些更改也对本书
有所影响
,主要是因为贝克莱风格的符号链接很可能将被加到标准中作为一种所要求的功能
。这些更
改也很可能成为ISO/IEC 9945-1:1990的一个附录。在本书中,我们用注释的方法
来说明P
OSIX1的10031a版本,指出哪些功能很可能会加到10031a中。
POSIX1没有包括"超级用户"这样的概念。代之以规定某些操作要求"适当的优先
权",
POSIX1将此术语的定义留由具体实现进行解释。某些符合国防部安全性指导原则
要求的Un
ix系统具有很多不同的安全级。在本书中,我们仍使用传统的Unix术语,并指明要
求超级用
户特权的操作。

图21〓由各种标准和实现定义的头文件
223〓X/Open XPG3
X/OPEN是一个国际计算机制造商组织。提出了一个七卷布可移植性指南,移为X/O
pen可移植
性指南,第三版〔X/Open 1989〕,我们将称之为XPG3。XPG3的第二卷(XSI系统
界面和头
界面和头
文件)对类似Unix的系统定义了一个界面,该界面定义是在IEEE Std10031-19
88界面的
基础上制订的。XPG3包含了一些POSIX1没有的功能。
例如,一个POSIX1没有但XPG3却有的功能是X/Open的消息设施。该设施可由应用
程序使用
以在不同的语言中显示文件消息。
XPG3界面使用了ANSI C草案而不是最后的正式标准,所以在XPG3界面规格说明中包
含的某些
功能是不再使用的。这些问题很可能会在将来的XPG规格说明的新版本中解决。(有
关XPG4的
工作正在进行,很可能会在1993年完成)。
224〓FIPS
FIPS的含意是联邦信息处理标准(Federal Information Processing Standard),
这些标准
是由美国政府出版的,并由美国政府用于计算机系统的操购。FIPS151-1(19894
)是基于I
EEE Std 10031-1988及ANSI C标准草案的。FIPS 151-要求某些POSIX1规定为
可选的
功能。这种FIPS有时称为POSIX1 FIPS。其255节列出了FIPS所要求的POSIX
1的可选
项。
POSIX1 FIPS的影响是:它要求任一希望向美国政府销售POSIX1依从的计算机
POSIX1 FIPS的影响是:它要求任一希望向美国政府销售POSIX1依从的计算机
系统的厂
商应支持POSIX1的某些可选功能。我们将不把POSIX1 FIPS视作为另一个标准
,因为实
际上它只是一个更加严格的POSIX1标准。
23〓Unix实现
上面一节说明了三个由各自独立的组织所制定的标准:ANSI C、IEEE POSIX以及X
/Open XPG
3。但是,标准只是界面的规格说明。这些标准是如何与现实世界相关连的呢?这些
标准由制
造商采用,然后转变成具体实施。本书中我们感兴趣的是这些标准和它们的具体实
施两者。

在Leffler等著作〔1989〕的11节中给出了Unix族树的详细历史和关系图。
Unix的各
种版本和变体都起源于在PDP-11系统上运行的Unix分时系统第6版(1976)和第7版(
1979)(通
常称为Version 6和Version 7)。这两个版本是在贝尔实验室以外首先得到广泛应
用的Unix
系统。从这棵树上发展出三个分支:(a)AT&T分支,从此导出了系统Ⅲ和系统Ⅴ(被
称之为Un
ix的商用版本),(b)加州大学贝克莱分校分支,从此导出4XBSD实现,(c)由AT&
T贝尔实验
T贝尔实验
室的计算科学研究中心不断开发的Unix研究版本,从此导出第8、第9和第10版。

231〓系统Ⅴ第4版
系统Ⅴ第4版(SVR4)是AT&T Unix系统实验室的产品,它汇集了下列系统的功能:A
T&T Unix
系统Ⅴ第32版(SVR32),Sun Microsystem的Sunos系统,加州大学贝克莱分校
的43 BS
D以及Microsoft的Xenix系统。(Xenix是在Verson7基础上开发的,后来又采用了很
多系统Ⅴ
的功能。)其源代码于1989年后期分布,在1990年则开始向最终用户提供。SVR4符
合于POSIX
 10031标准和X/OPEN XPG3标准。
AT&T也制版了系统Ⅴ界面定义(SVID)〔AT&T 1989〕。SVID第三版说明了Unix系统
要达到SVR
4质量要求所应提供的功能。如同POSIX1一样,SVID说明了一个界面,而不是一
种实现。
对于一个具体实现的4应查看其参考手册,以了解其不同之处。〔AT&T 1990e〕。

SV包含了BSD的兼容库〔AT&T 1990c〕,它提供了功能与43BSD对应部分相同的函
数和命令
。但是其中某些函数与POSIX的对应部分有所不同,本书中的所有SVR4实例都没有
此兼容库
T贝尔实验
室的计算科学研究中心不断开发的Unix研究版本,从此导出第8、第9和第10版。

231〓系统Ⅴ第4版
系统Ⅴ第4版(SVR4)是AT&T Unix系统实验室的产品,它汇集了下列系统的功能:A
T&T Unix
系统Ⅴ第32版(SVR32),Sun Microsystem的Sunos系统,加州大学贝克莱分校
的43 BS
D以及Microsoft的Xenix系统。(Xenix是在Verson7基础上开发的,后来又采用了很
多系统Ⅴ
的功能。)其源代码于1989年后期分布,在1990年则开始向最终用户提供。SVR4符
合于POSIX
 10031标准和X/OPEN XPG3标准。
AT&T也制版了系统Ⅴ界面定义(SVID)〔AT&T 1989〕。SVID第三版说明了Unix系统
要达到SVR
4质量要求所应提供的功能。如同POSIX1一样,SVID说明了一个界面,而不是一
种实现。
对于一个具体实现的4应查看其参考手册,以了解其不同之处。〔AT&T 1990e〕。

SV包含了BSD的兼容库〔AT&T 1990c〕,它提供了功能与43BSD对应部分相同的函
数和命令
。但是其中某些函数与POSIX的对应部分有所不同,本书中的所有SVR4实例都没有
此兼容库
此兼容库
。只有在你有一些早期的应用程序,又不想改变它们时才使用此兼容库,新的应用
程序不应
使用它。
232〓43+BSD
BSD是由加州大学贝克莱分校的计算机系统研究组研究开发和分发的。42BSD在1
983年问世
,43BSD则在1986年。这两个版本都在VAX小型机上运行。它们的下一个版本4
3BSD Taho
e在1988年发布,在一台称为Tahoe的小型机上运行〔Leffler等的著作〔1989
〕说明了4
3BSD Taboe版。〕其后又有1990年的43BSD Reno版,它支持很多POSIX1的功
能。下一
个主要版本44BSD应在1992年发布。
原来的BSD系统包含了AT&T专有的源代码,它们需要AT&T许可证。为了获得BSD系统
的源代码
,首先需要持有AT&T源代码正被代换成非AT&T源代码,很多加到BSD系统上的新功
能也来自
非AT&T方面。
在1989年,贝克莱将43BSD Taboe中很多非AT&T源代码包装成BSD网络软件,1
0版,并使
其成为公众可用的软件。其后则有BSD网络软件的20版,它是从43BSD Reno版
导出的,
导出的,
其目的是使大部分(如果不是全部的话)44 BSD系统不再受AT&T许可证的限制,于
是其全部
源代码都可为公众使用。
正如我们在前言中所说明的,在全书中,我们用术语43+BSD来指本书所说明的B
SD系统,
该系统位于BSD网络软件20版和将出现的44BSD之间。
在贝克莱所进行的Unix开发工作是从PDP-11开始的,然后转移到VAX小型机上,然
后又转移
到工作站上。在九十年代早期,贝克莱得到支持在广泛得到应用的80386个人计算
机上开发B
SD版本,结果产生了386BSD。这一工作是由Bill Jolitg完成的。其相关文档是发
表在1991
年的DrDobb′s Journal上的系列文章(每月一篇)。其中很多代码出现在BSD网络
软件,2
0版中。
24〓标准和实现的关系
我们已提及的标准定义了任一实际系统的子集。虽然IEEE POSIX正致力于在其它所
需方面(
例如,网络界面,进程间的通信,系统管理)制订出标准,但在编著本书时,这些
标准还并
不存在。
本书的注意力集中于说明两个实际的Unix系统:SVR4和43+BSD。因为这两个系统
本书的注意力集中于说明两个实际的Unix系统:SVR4和43+BSD。因为这两个系统
都宣称是
依从POSIX的,所以我们一方面集中于说明POSIX1标准所要求的功能,同时POSI
X和这两个
系统具体实现之间的差别,为此,SVR4或43+BSD特有的功能和例程都被清楚地标
记出来。
因为XPG3是POSIX1的超集,所以我们叙述了属于XPG3,但不属于POSIX1的功能
。
应当了解,SVR4和43+BSD都提供了对它们早期版本功能的兼容性(例如SVR32和
43BSD)
。例如,SVR4和POSIX规格说明中的非阻塞I/O(O[CD#*2]NONBLOCK)以及传统的系统
Ⅴ方法(O
[CD#*2]NDELAY)都提供了支持。在本书中,我们将只使用POSIX1的功能,但是也
会提及它
所代换的是哪一种非标准功能。与此相类似,SVR32和43BSD以某种方法提供了
可靠信号
机制,这种方法也有别于POSIX1标准。在第十章中,我们只说明POSIX1的信号
机制。
25〓限制
有很多由实现定义的幻数和常数。其中有很多已被编写到程序中,或由特定的技术
所确定。
由于大量标准化工作的努力,已经提供了若干种可移植的方法以确定这些幻数和实
现定义的
限制可能
是255个字符。
为了解决这些问题,提供了三种限制:
1编辑时的可选项及限制。
2不与文件或目录相关联的运行时限制。
3与文件或目录相关联的运行时限制。
使事情变得更加复杂的是,如果一个特定的运行时限制在一个给定的系统上并不改
变,则可
将其静态地定义在一个头文件中,但是,如果没有将其定义在头文件中,则应用程
序就必须
调用三个conf函数中的一个(我们很快就会对它们进行说明)。以确定其在运行时的
值。
251〓ANSI C限制
所有由ANSI C定义的限制都是编辑时的限制。图22中列示了在文件<limitsh>
中定义的C
标准限制。这些常数总是定义在该头文件中,而且在一个给定系统中并不会改变。
在第三列
中列出了ANSI C标准可接受的最小值。这用于整型长度为16位的系统它使用1的补
码表示。
在第四列中列出了整型长度为32位的当前系统的值,用的是2的补码表示法。注意
,对不带
符号的数据类型都没有列出其最小值,它们都应为0。
我们将会遇到的一个区别是系统是否提供带符号(signed)或不带符号的
我们将会遇到的一个区别是系统是否提供带符号(signed)或不带符号的
(unsigned)的字符值,从图22中的第四列可见,该特定系统使用带符号字符。从
表中可以
看到CHAR[CD#*2]MIN等于SCHAR[CD#*2]MIN,CHAR[CD#*2]MAX等于SCHAR[CD#*2]MA
X。如果系
统使用不带符号字符,则CHAR[CD#*2]MIN等于0,CHAR[CD#*2]MAR等于UCHAR[CD#*
2]MAX。
在头文件<floath>中,对浮点数据类型也有类似的一组定义。
我们会遇到的另一个ANSI C常数是FOPEN[CD#*2]MAX,这是实现保证的可同时打开
的标准I/O
流的最小数,该值在头文件<stdioh>中,其最小值是8。POSIX1中的值STREAM
[CD#*2]MA
X(若定义的话),则应具与FOPEN[CD#*2]MAX相同的值。
ANSI C在<stdioh>中也定义了常数TMP[CD#*2]MAX,这是由tmpnam函数产生的唯
一文件名
的最大数。关于此常数我们将在513节中进行更多说明。

图22〓<limitsh>中的整型值大小
252〓POSIX限制
POSIX定义了很多涉及操作系统实现限制的常数,不幸,这是POSIX1中最使人迷
惑部分中
的一个。
有33个限制和常数,它们被分成下列八类:
我们将会遇到的一个区别是系统是否提供带符号(signed)或不带符号的
(unsigned)的字符值,从图22中的第四列可见,该特定系统使用带符号字符。从
表中可以
看到CHAR[CD#*2]MIN等于SCHAR[CD#*2]MIN,CHAR[CD#*2]MAX等于SCHAR[CD#*2]MA
X。如果系
统使用不带符号字符,则CHAR[CD#*2]MIN等于0,CHAR[CD#*2]MAR等于UCHAR[CD#*
2]MAX。
在头文件<floath>中,对浮点数据类型也有类似的一组定义。
我们会遇到的另一个ANSI C常数是FOPEN[CD#*2]MAX,这是实现保证的可同时打开
的标准I/O
流的最小数,该值在头文件<stdioh>中,其最小值是8。POSIX1中的值STREAM
[CD#*2]MA
X(若定义的话),则应具与FOPEN[CD#*2]MAX相同的值。
ANSI C在<stdioh>中也定义了常数TMP[CD#*2]MAX,这是由tmpnam函数产生的唯
一文件名
的最大数。关于此常数我们将在513节中进行更多说明。

图22〓<limitsh>中的整型值大小
252〓POSIX限制
POSIX定义了很多涉及操作系统实现限制的常数,不幸,这是POSIX1中最使人迷
惑部分中
的一个。
有33个限制和常数,它们被分成下列八类:
在254中在说明sysconf,pathconf和fpatheonf函数时说明可定义可不定义的限
制和常数
(第4-8条)。在图27中我们摘录了所有限制和常数。13个不变最小值则示于图2
3中。

图23〓<limitsh>中的POSIX1不变最小值
这些值是不变的〖CD2〗它们并不附系统而改变。它们指定了这些特征方面的最严
格的值。
一个符合POSIX1的实现应当提供至少这样大的值。这就是为什么将它们称为最小
的原因,
虽然它们的名字都包含了MAX。另外,一个可移植的应用程序不应要求更大的值。
我们将在
本书的适当部分说明这些这些常数中每一个的含意。
不幸的是,这些不变最小值中的某一些在实际应用中是太小了。例如,现时的Uni
x系统所提
供的每个进程可同时打开文件数远超过16,即使是1978年的Version 7也向每个进
程提供了2
0个打开文件。另外,[CD#*2]POSIX[CD#*2]PATH[CD#*2]MAX的最小值限制255也是
太小了,
路径名可能会超过这一限制。这意味着我们在编辑时不能使用这两个常数[CD#*2]
POSIX[CD#
*2]OPEN[CD#*2]MAX和[CD#*2]POSIX[CD#*2]PATH[CD#*2]MAX作为数组长度。
图23中的13个不变最小值的每一个都有一个相关的实现值,其名字是将图23中
图23中的13个不变最小值的每一个都有一个相关的实现值,其名字是将图23中
的名字删
除前缀[CD#*2]POSIX[CD#*2]后构成的。(这13个实现值是我们在本节开始部分所列
出的2-5
项:不变值、运行时不能增加的值、运行时不变的值、以及路径名可变值。)问题
是并不保
证所有这13个实现值定义在<limith>头文件中。一个特定值可能不定义在此头文
件中的理
由是:例如对一个给定进程的实际值可能依赖于系统的存储器总量。如果没有在头
文件中定
义它们,则我们就不能在编辑时使用它们作为数组边界。所以,POSIX1决定提供
三个运行
时函数供我们调用,它们是:syseonf,pathconf以及fpathconf,用它们可以在运
行时得到
实际的实现值。但是,还有一个问题,因为其中某些值是由POSIX1定义为"可能
不确定的
"(逻辑上无限的),这就意味着该值没有实际上限。例如,SVR4的每个进程打开文
件数限制
在假想上是无限的,所以在SVR4中OPEN[CD#*2]MAX被认为是不确定的。在257
中我们还
将讨论运行时不确定限制的问题。
253〓XPG3限制
XPG3定义了七个常数,它们总是包含在<limitsh>头文件中。POSIX1则会把它
XPG3定义了七个常数,它们总是包含在<limitsh>头文件中。POSIX1则会把它
们称之为
不变最小值。它们列于图24中。这些值的大多数都涉及消息。
图24〓XPG3不变最小值(在<limitsh>中)
XPG3也定义了值PASS[CD#*2]MAX,作为口令字中的最大有效字符数(不包括终止字
符null),
它可能包含在<limitsh>中。POSIX1则把它称之为运行时不变的值(可能不确定
),其最

可接受的值是8。PASS[CD#*2]MAX值也可在运行时用sysconf函数取得,该函数将在
254
中说明。

254〓sysconf、pathconf以及fpathconf函数
我们已列出了一个实现必须支持的各种最小值,但是怎样才能找到一个特定系统实
际支持的
限制值呢?正如我们在前面提到的,某些限制值在编辑时是可用的,而另外一些则
必须在运
行时确定。我们也曾提及在一个给定的系统中某些限制值是不会更改的,而其它则
与文件和
目录相关联。运行时限制是由调用下面三个函数中的一个而取得的。
#include <unistdh>
long sysconf(int [WTBX]name[WTBZ]);
long sysconf(int [WTBX]name[WTBZ]);
log pathconf(const char *[WTBX]pathname[WTBZ],int [WTBX]name[WTBZ]);
log fpathconf(int [WTBX]filedes[WTBZ],int [WTBX]name[WTBZ]);
All three return:corresponding value if OK,-1 on error (see later)
最后两个函数之间的差别是一个用路径名作为其参数,另一个则取文件描述符作为
参数。
图25中列出了这三个函数所使用的name参数。以[CD#*2]SC[CD#*2]开始的常数用
作为sysc
onf的参数,而以[CD#*2]PC[CD#*2]开始的常数则作为pathconf或fpathconf的参数
。
对于pathconf的参数pathname,fpathconf的参数filedes有很多限制。如果不满足
其中任何
一个限制,则结果是未定义的。
1[CD#*2]PC[CD#*2]MAX[CD#*2]CANON,[CD#*2]PC[CD#*2]MAX[CD#*2]INPUT以及
[CD#*2]PC
[CD#*2]VDISABLE所涉及的文件必须是终端文件。
2[CD#*2]PC[CD#*2]LINK[CD#*2]MAX所涉及的文件可以是文件或目录。如果这是
一个目录
,则返回值用于目录本身(不用于目录内的文件名项)。
3[CD#*2]PC[CD#*2]NAME[CD#*2]MAX和[CD#*2]PC[CD#*2]NO[CD#*2]TRUNC所涉及
的文件必
须是目录,返回值用于该目录中的文件名。
4[CD#*2]PC[CD#*2]PATH[CD#*2]MAX涉及的必须是目录。当所指定的目录是工作
4[CD#*2]PC[CD#*2]PATH[CD#*2]MAX涉及的必须是目录。当所指定的目录是工作
目录时,
返回值是相对路径名的最大长度。(不幸的是,这不是我们想要知道的一个绝对路
径名的实
际最大长度,我们将在257中再回到这一问题上来)
5[CD#*2]PC[CD#*2]PIPE[CD#*2]BUF所涉及的文件必须是管道,FIFO或目录。在
管道或FIF
O情况下,返回值是对所涉及的管道或FIFO的限制值。对于目录,则返回值是对在
该目录中
创建的任一FIFO的限制值。
6[CD#*2]PC[CD#*2]CHOWN[CD#*2]RESTRICTED必须是文件或目录。如果它是目录
,则返回
值指明此可选项是否适用于该目录中的文件。
图25〓对sysconf、pathconf和fpathconf的限制及name参数
我们需要更详细地说明这三个函数的不同返回值。
1如果name不是图25第3列中的一个合适常数,则所有这三个函数都返回-1,并
将error
设置为EINVAL。
2包含MAX的12个名字以及名字[CD#*2]PC[CD#*2]PIPE[CD#*2]BUF可能或者返回该
变量的值
(返回值ZO),或者返回-1,这表示该值是不确定的,此时并不更改errno的值。

3对[CD#*2]SC[CD#*2]CLK[CD#*2]TCK的返回值是每秒的时钟滴答数,以用于tim
3对[CD#*2]SC[CD#*2]CLK[CD#*2]TCK的返回值是每秒的时钟滴答数,以用于tim
es函数的
返回值(815节)。
4对[CD#*2]SC[CD#*2]VERSION的返回值以4位数,2位数分别表示以标准的年、月
。这可能
或者是198808L,或199009L,或此标准某个以后版本的值。
5对[CD#*2]SC[CD#*2]XOPEN[CD#*2]VERSION的返回值表示此系统所遵从的XPG版
本,其当
前值是3。
6[CD#*2]SC[CD#*2]JOB[CD#*2]CONTROL和[CD#*2]SC[CD#*2]SAVED[CD#*2]IDS是
两个可选
功能。若sysconf返回-1(没有更改errno)则不支持相应的功能。这两个功能也可在
编辑时从
<unistdh>头文件中决定。
7对[CD#*2]PC[CD#*2]CHOWN[CD#*2]RESTRICTED和[CD#*2]PC[CD#*2]NO[CD#*2]T
RUNC的返
回值若为-1(不改变errno),则表示对所指定的pathname或filedes不支持此功能。

8对[CD#*2]PC[CD#*2]VDISABLE的返回值若为-1(不改变errno),则表示对所指定
的pathna
me或filedes不支持此功能。若支持此功能,则返回值是被用于禁止特定终端输入
字符的字
符值(图16)。
符值(图16)。
实例
程序21打印所有这些限制,并处理一个限制未被定义的情况。
程序21〓打印所有可能的sysconf和pathconf值
我们条件地包括了两个常数,它们已被加至POSIX1,但不是IEEEStd 10031-1
988版本
的一部分。图26显示了在几个不同的系统上,程序21的样本输出。表中的"no
 def"表
示该常数未定义。我们在414中可以了解到,SVR4 S5文件系统是可以回逆到Ver
sion 7的
传统系统Ⅴ文件系统。UFS是贝克莱快速文件系统的SVR4实现。
图26〓配置限制的实例
255〓FIPS 151-1要求
FIPS 151-1标准(我们已在224节中提及)由于要求下列功能,所以它比POSIX
1标准更
严:
·要求下列POSIX1可选功能:
[CD#*2]POSIX[CD#*2]JOB[CD#*2]CONTROL,[CD#*2]POSIX[CD#*2]SAVED[CD#*2]IDS
,[CD#*2]P
OSIX[CD#*2]NO[CD#*2]TRONC,[CD#*2]POSIX[CD#*2]CHOWN[CD#*2]RESTRICTED和[C
D#*2]POSI
X[CD#*2]VDISABLE。
·NGROUPS[CD#*2]MAX的最小值是8。
·NGROUPS[CD#*2]MAX的最小值是8。
·新创建的文件或目录的组ID应设置为它所在目录的组ID(在46节中说明此功能
)
·在已传输了一些数据后,若read或write被一个捕捉到的信号所中断,则这些函
数应返回
已被传输的字节数(在105节中讨论被中断的系统调用)。
·登录shell应定义环境变量HOME和LOGNAME。
因为美国政府购买很多计算机系统,所以大多数POSIX的制造商都将支持这些增加
的FIPS要
求。
图27〓编辑时和运行时限制的摘要
256〓限制摘要
我们已说明了很多限制和幻常数,其中某些总被包含在一头文件中,某些可选地包
含在头文
件中,其它则可在运行时决定。图27以字母序摘要列出了所有这些常数以及得到
它们值的
各种方法。以[CD#*2]SC[CD#*2]开始的运行时名字是sysconf函数的参数,以[CD#
*2]PC[CD#
*2]开始的名字是pathconf和fpathconf函数的参数,如果它有最小值,则也将其列
于其中。

注意,图23中的13个POSIX1不变最小值示于图27中的最右一列。
257〓未确定的运行时限制
257〓未确定的运行时限制
我们已提及图27中的某些值可能是未确定的,这些值是第三列标记为可选的(op
tional),
其名字中或包含MAX,或是PIPE[CD#*2]BUF。我们遇到的问题是如果这些值没有在
头文件<li
mitsh>中定义,那么在编辑时间我们也就不能使用它们。但是,如果它们的值是
未确定的
,那么在运行时间,它们可能也是未定义的。让我们观察两个特殊的例子〖CD2〗
为一个路
径名分配存储器,以及决定文件描述符数
路径名
很多程序需要为路径名分配存储器,典型地,在编辑时就为其分配了存储器,而且
使用了各
种幻数(其中很少值是正确的)作为数组长度:256,512,1024或标准I/O常数BUFS
IZ在头文
件<sys/paramh>中的43BSD常数MAXPATHLEN是正确值,但是很多43BSD应用程
序并未用
综。
POSIX1试图用PATH[CD#*2]MAX值来帮助我们,但是如果此值是不确定的,那么仍
是毫无帮
助的。程序22是一个我们在全书中都将使用的为路径名动态地分配存储器的函数
。
如若在<limitsh>中定义了常数PATH[CD#*2]MAX,那么就没有任何问题,如果没有
257〓未确定的运行时限制
我们已提及图27中的某些值可能是未确定的,这些值是第三列标记为可选的(op
tional),
其名字中或包含MAX,或是PIPE[CD#*2]BUF。我们遇到的问题是如果这些值没有在
头文件<li
mitsh>中定义,那么在编辑时间我们也就不能使用它们。但是,如果它们的值是
未确定的
,那么在运行时间,它们可能也是未定义的。让我们观察两个特殊的例子〖CD2〗
为一个路
径名分配存储器,以及决定文件描述符数
路径名
很多程序需要为路径名分配存储器,典型地,在编辑时就为其分配了存储器,而且
使用了各
种幻数(其中很少值是正确的)作为数组长度:256,512,1024或标准I/O常数BUFS
IZ在头文
件<sys/paramh>中的43BSD常数MAXPATHLEN是正确值,但是很多43BSD应用程
序并未用
综。
POSIX1试图用PATH[CD#*2]MAX值来帮助我们,但是如果此值是不确定的,那么仍
是毫无帮
助的。程序22是一个我们在全书中都将使用的为路径名动态地分配存储器的函数
。
如若在<limitsh>中定义了常数PATH[CD#*2]MAX,那么就没有任何问题,如果没有
如若在<limitsh>中定义了常数PATH[CD#*2]MAX,那么就没有任何问题,如果没有
,则需调
用pathconf。因为pathconf的返回值是把第一个参数视为基于工作目录的相对路径
名。所以
我们指定根为第一个参数,并将得到的返回值加1作为结果值。如果pathconf指明
PATH[CD#*
2]MAX是不确定的那么我们就只得猜测某个值,在调用malloc时+1,是为了在尾端
加字符串
结束符null字符(PATH[CD#*2]MAX没有考虑综)。
处理不确定结果情况的正确方法与如何使用分配到存储空间有关。例如,如果我们
为getcwd
调用分配空间,(返回当前工作目录的绝对路径名,见422节)而分配到的空间太
小,于是
返回一个出,errno设置为ERANGE。然后我们可调用realloc以增加分配空间(见7
8节和练
习418)并再试。我们可以不断这样做,直至getcwd调用成功执行。
程序22〓为路径名动态地分配空间
最大打开文件数
在精灵进程(是在后台运行,不与终端相连接的一种进程)中一个常见的代码序列是
关闭所有
打开文件。某些程序中有下列形式的代码序列:
#include <sys/paramh>
for(i=0;i<NOFILE;i++)
for(i=0;i<NOFILE;i++)
close(i);
这段程序假定在<sys/parmh>头文件中定义了常数NOFILE。另外一些程序则使用
某些<stdi
oh>版本提供作为上限的常数[CD#*2]NFILE。某些程序则直接将其上限值定为20
。
我们希望用POSIX1的OPEN[CD#*2]MAX确定此值以提高可移植性,但是如果此值是
不确定的
,则仍然有问题,如果我们使用下列代码
#include <unistdh>
for(i=0;i<sysconf([CD#*2]SC[CD#*2]OPEN[CD#*2]MAX);i++)
close(i);
而且如果OPEN[CD#*2]MAX是不确定的,那么sysconf将返回-1,于是,for循环根本
不会执行
。在这种情况下,最好的选择就是关闭所有描述符直至某个任意的限制值(例如25
6)。如同
上面的路径名一样,这并不能保证在所有情况下都能正确工作,但这却是我们所能
选择的最
好方法。我们在程序23中使用了这种技术。
程序23〓确定文件描述符数
我们可以耐心地调用close,直至得到一个出错返回,但是从close出错返回(EBAD
F)并不区
分无效描述符和并未打开的描述符。如果我们试用此技术,而且描述符9未打开,
,大多数实现在这些头文件中也加上了它们自己的定义。如果在编辑一道程序时,
希望它只
使用POSIX定义而不使用任何实现定义的限制,那么我们就需定义常数[CD#*2]POS
IX[CD#*2]
SOURCE,所有POSIX1头文件中都使用此常数,当该常数定义时,就能排除任何实
现专有的
定义。
常数[CD#*2]POSIX[CD#*2]SOURCE及其对应的常数[CD#*2]XOPEN[CD#*2]SOURCE被称
之为功能
测试宏,所有功能测试宏都以下划线开始,当要使用它们时,通常在CC命令行中以
下列方式
定义它们:
CC -D[CD#*2]POSIX[CD#*2]SOURCE filec
这使得在C程序包括任何头文件之前,定义了功能测试宏。如果我们只要使用POSI
X1定义
,那么也可将源文件的第一行设置为:
#define [CD#*2]POSIX[CD#*2]SOURCE 1
另一个功能测试宏是:[CD#*2][CD#*2]STDC[CD#*2][CD#*2],它是由符合ANSI C标
准的编辑
程序自动定义的。这样就允许我们编写ANSI C编辑程序和非ANSI C编辑程序都能编
辑的程序
。例如,在一个头文件可能会是:
# ifdef [CD#*2]STDC[CD#*2]
# ifdef [CD#*2]STDC[CD#*2]
void *myfunc(const char *,int);
#else
void *myfunc();
#endif
这样就能发挥ANSI C原型功能的长处,要注意在开始和结束处的两个连续的下划线
常常打印
成一个长下划线(如同上面一个样本源代码中一样)。
27〓基本系统数据类型
历史上,某些Unix变量已与某些C数据类型联系在一起,例如,主、次设备号在历
史上存放
在一个16位的短整型中,用8位表示主设备号,另外8位表示次设备号。但是,很多
较大的系
统需要用多于256个值来表示其设备号,于是,就需要有一种不同的技术。(确实,
SVR4用32
位表示设备号:14位用于主设备号,18位用于次设备号)。
头文件<sys/typesh>中定义了某些与实现有关的数据类型,它们被称之为基本系
统数据类
型。有很多这种数据类型定义在其它头文件中。在头文件中这些数据类型都是用C
的typedef
设施来定义的。它们绝大多数都以[CD#*2]t结尾。图28中列出了本书将使用的基
本系统数
据类型。
据类型。
图28〓基本系统数据类型
用这种方式定义了这些数据类型后,我们在编辑时就不再需要考虑附系统不同而变
的实施细
节,在本书中涉及到这些数据类型处,我们会说明为什么使用它们。
28〓标准之间的冲突
就整体而言,这些不同的标准之间配合得是相当好的。但是我们也很关注它们之间
的差别,
特别是ANSIC标准和POSIX1之间的差别。(因为XPG3是一个较尽的正在被修订的标
准,FIPS
则是一个要求更严的POSIX1。)
ANSI C定义了函数clock,它返回进程使用的CPU时间量,返回值是clock[CD#*2]t
类型值。
为了将此值变换成以秒为单位,将其除以在<timeh>头文件中定义的CLOCKS[CD#
*2]PER[CD
#
*2]SEC。POSIX1定义了函数times,它返回其调用者及其所有终止子进程的CPU时
间以及时
钟时间,所有这些值都是clock[CD#*2]t类型值。IEEEStd、10031-1988将符号C
LK[CD#*2
]TCK定义为每秒滴答数,上述clock[CD#*2]t值都是以此度量的。而1990 POSIX
1标准中则
说明不再使用,CLK[CD#*2]TCK应当使用sysconf函数来获得每秒滴答数,并将其用
据类型。
图28〓基本系统数据类型
用这种方式定义了这些数据类型后,我们在编辑时就不再需要考虑附系统不同而变
的实施细
节,在本书中涉及到这些数据类型处,我们会说明为什么使用它们。
28〓标准之间的冲突
就整体而言,这些不同的标准之间配合得是相当好的。但是我们也很关注它们之间
的差别,
特别是ANSIC标准和POSIX1之间的差别。(因为XPG3是一个较尽的正在被修订的标
准,FIPS
则是一个要求更严的POSIX1。)
ANSI C定义了函数clock,它返回进程使用的CPU时间量,返回值是clock[CD#*2]t
类型值。
为了将此值变换成以秒为单位,将其除以在<timeh>头文件中定义的CLOCKS[CD#
*2]PER[CD
#
*2]SEC。POSIX1定义了函数times,它返回其调用者及其所有终止子进程的CPU时
间以及时
钟时间,所有这些值都是clock[CD#*2]t类型值。IEEEStd、10031-1988将符号C
LK[CD#*2
]TCK定义为每秒滴答数,上述clock[CD#*2]t值都是以此度量的。而1990 POSIX
1标准中则
说明不再使用,CLK[CD#*2]TCK应当使用sysconf函数来获得每秒滴答数,并将其用
据类型。
图28〓基本系统数据类型
用这种方式定义了这些数据类型后,我们在编辑时就不再需要考虑附系统不同而变
的实施细
节,在本书中涉及到这些数据类型处,我们会说明为什么使用它们。
28〓标准之间的冲突
就整体而言,这些不同的标准之间配合得是相当好的。但是我们也很关注它们之间
的差别,
特别是ANSIC标准和POSIX1之间的差别。(因为XPG3是一个较尽的正在被修订的标
准,FIPS
则是一个要求更严的POSIX1。)
ANSI C定义了函数clock,它返回进程使用的CPU时间量,返回值是clock[CD#*2]t
类型值。
为了将此值变换成以秒为单位,将其除以在<timeh>头文件中定义的CLOCKS[CD#
*2]PER[CD
#
*2]SEC。POSIX1定义了函数times,它返回其调用者及其所有终止子进程的CPU时
间以及时
钟时间,所有这些值都是clock[CD#*2]t类型值。IEEEStd、10031-1988将符号C
LK[CD#*2
]TCK定义为每秒滴答数,上述clock[CD#*2]t值都是以此度量的。而1990 POSIX
1标准中则
说明不再使用,CLK[CD#*2]TCK应当使用sysconf函数来获得每秒滴答数,并将其用
说明不再使用,CLK[CD#*2]TCK应当使用sysconf函数来获得每秒滴答数,并将其用
于times
函数的返回值。术语是同一个,每秒滴答数,但ANSI C和POSIX1的定义却不同。
这两个标
准也用同一数据类型(clock[CD#*2]t)来保存这些不同的值,这种差别可以在SVR4
中看到,
其中clock返回微秒数(CLOCK[CD#*2]PER[CD#*2]SEC是一百万),而CLK[CD#*2]TCK
通常是50
,60或100(与CPU类型有关)。
另一个可能产生冲突的区域是:在ANSI C标准说明函数时,ANSI C所说明的函数可
能会没有
考虑到POSIX1的某些要求。有些函数在POSIX环境下可能要求有一个与C环境下不
同的实现
,因为POSIX环境中有多个进程,而C语言环境则很少会考虑宿主操作系统。尽管如
此,很多
POSIX依从的系统为了兼容性的关系也实现ANSI C函数,signal函数就是一个例子
。如果我
们在不了解的情况下使用了SVR4所提供的signal函数(希望编写可在ANSI C环境和
较早Unix
系统中运行的可兼容程序),那么它提供了与POSIX1 sigaction函数不同的语义
。在第十
章中我们会对signal函数作更多说明。
29〓摘要
说明不再使用,CLK[CD#*2]TCK应当使用sysconf函数来获得每秒滴答数,并将其用
于times
函数的返回值。术语是同一个,每秒滴答数,但ANSI C和POSIX1的定义却不同。
这两个标
准也用同一数据类型(clock[CD#*2]t)来保存这些不同的值,这种差别可以在SVR4
中看到,
其中clock返回微秒数(CLOCK[CD#*2]PER[CD#*2]SEC是一百万),而CLK[CD#*2]TCK
通常是50
,60或100(与CPU类型有关)。
另一个可能产生冲突的区域是:在ANSI C标准说明函数时,ANSI C所说明的函数可
能会没有
考虑到POSIX1的某些要求。有些函数在POSIX环境下可能要求有一个与C环境下不
同的实现
,因为POSIX环境中有多个进程,而C语言环境则很少会考虑宿主操作系统。尽管如
此,很多
POSIX依从的系统为了兼容性的关系也实现ANSI C函数,signal函数就是一个例子
。如果我
们在不了解的情况下使用了SVR4所提供的signal函数(希望编写可在ANSI C环境和
较早Unix
系统中运行的可兼容程序),那么它提供了与POSIX1 sigaction函数不同的语义
。在第十
章中我们会对signal函数作更多说明。
29〓摘要
参数(这些常数定义在<fcntl.h>头文件中)。
O_RDONLY 只读打开, O_WRONLY 只写打开,O_RDWR 读、写打开.很多实现将O_RDONLY定
义为0,O_WRONLY定义为1,O_RDWR定义为2,以与较早的系统兼容.在这三个常数中应当也
只应指定一个.下列常数则是可选择的:
O_APPEND--在每次写时都加到文件的尾端。我们将在3.11中详细说明此选择项。
O_CREAT--若此文件不存在则创建它。使用此可选项时,需同时说明第三个参数mode,
用其说明该新文件的存取许可权位。(我们将在45节中说明文件的许可权位,那时就能
了解如何说明mode,以及如何用进程的vmask值修改它。)
O_EXCL--如果同时指定了O_CREAT,而文件已经存在,则出错.这使得测试一个文件是否存
在,如果不存在则创建此文件成为一个原子操作.我们将在3.11节中较详细的说明原子操作
O_TRUNC--如果此文件存在,而且为只读或只写成功打开,则将其长度截短为0。
O_NOCTTY--如果pathname指的是终端设备,则不将此设备分配作为此进程的控制终端.我
们在9.6节中说明控制终端。
O_NONBLOCK--如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此
选择项为此文件的本次打开操作和后续的I/O操作设置非阻塞方式。在12.2中我们将说明
此工作方式。
较早的系统Ⅴ版本引入了O_NDELAY(不延迟)标志,它与O_NONBLOCK(不阻塞)选择项类似,
但在读操作的返回值中具有两义性。如果不能从管道、FIFO或设备读得数据,则不延迟
选择项使read返回0,这与表示已读到文件尾端的返回值0相冲突。SVR4仍支持这种语义
的不延迟选择项,但是新的应用程序应当使用不阻塞选择项以代替之。
O_SYNC--使每次write都等到物理I/O操作完成。我们将在3.13使用此选择项。
由Open返回的文件描述符一定是最小的未用描述符数字。这一类被很多应用程序用来在
标准输入、标准输出或标准出错输出上打开一个新的文件。例如,一个应用程序可以先
标准输入、标准输出或标准出错输出上打开一个新的文件。例如,一个应用程序可以先
关闭标准输出(通常是文件描述符1),然后打开另一个文件,事先就能了解到该文件一定
会在文件描述符1上打开。在3.12节说明dup函数时我们可以了解到有更好的方法能保证在
一个给定的描述符上打开一个文件。
文件名和路径名截短--如果NAME_MAX是14,而我们却试图在当前目录中创建一个其文件名
包含15个字符的新文件,此时会发生什么呢?按照传统的系统Ⅴ版本,允许这种使用方法,
但是总是将文件名截短为14个字符,而BSD类的系统则返回出错ENAMETOOLONG。这一问题
不仅仅与创建新文件有关.如果NAME_MAX是14,而存在一个其文件名恰恰就是14个字符的
文件,那么以pathname作为其参数的任一函数(open,stat等)都会遇到这一问题。
在POSIX.1中,常数_POSIX_NO_TRUNC决定了是否要截短过长的文件名或路径名,或者返回
一个出错。在第十二章中,我们将说明此值可以针对各个不同的文件系统进行更变。
FIPS151-1要求返回出错.SVR4对传统的系统Ⅴ文件系统(S5)并不保证返回出错(见图2.6),
但是对BSD风格的文件系统(UFS),SVR4保证返回出错,4.3+BSD总是返回出错.若
_POSIX_NO_TRUNC有效,则在整个路径名超过PATH_MAX,或路径名中的任一文件名分量超
过NAME_MAX时,返回出错ENAMETOOLONG。

3.4--Creat函数
也可用creat函数创建一个新文件。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat(const char *pathname,mode_t mode);
返回:若成功为只写打开的文件描述符,出错为-1
返回:若成功为只写打开的文件描述符,出错为-1
注意,此函数等效于:
open(pathname,O_WRONLY|O_CREAT|O_TRUNC,mode);
在早期的Unix版本中,open的第2个参数只能是0,1或2。没有办法打开一个尚未存
在的文件,因此需要另一个系统调用creat以创建新文件。现在,open函数提供了可选项
O_CREAT和O_TRUNC,于是也就不再需要creat函数了.在4.5节中详细说明文件存取权时,
我们将说明如何指定mode。
creat的一个不足之处是它以只写方式打开所创建的文件。在提供open的新版本之
前,如果我们要创建一个临时文件,并要先写该文件,然后又读该文件,则必须先调用
creat,close,然后再调用open。现在则可用下列方式调用open:
open(pathname,O_RDWR|O_CREAT|O_TRUNC,mode);

3.5--close函数
用close函数关闭一个打开文件:
#include <unistd.h>
int close (int filedes);
返回:若成功为0,出错为-1
关闭一个文件时也释放该进程加在该文件上的所有记录锁。将在12.3节中讨论这一点。
当一个进程终止时,它们所有打开文件都由系统核自动关闭。很多程序都使用这一功能
而不显式地用close关闭打开文件。实例见程序1.2。

3.6--lseek函数
返回:若成功为只写打开的文件描述符,出错为-1
返回:若成功为只写打开的文件描述符,出错为-1
注意,此函数等效于:
open(pathname,O_WRONLY|O_CREAT|O_TRUNC,mode);
在早期的Unix版本中,open的第2个参数只能是0,1或2。没有办法打开一个尚未存
在的文件,因此需要另一个系统调用creat以创建新文件。现在,open函数提供了可选项
O_CREAT和O_TRUNC,于是也就不再需要creat函数了.在4.5节中详细说明文件存取权时,
我们将说明如何指定mode。
creat的一个不足之处是它以只写方式打开所创建的文件。在提供open的新版本之
前,如果我们要创建一个临时文件,并要先写该文件,然后又读该文件,则必须先调用
creat,close,然后再调用open。现在则可用下列方式调用open:
open(pathname,O_RDWR|O_CREAT|O_TRUNC,mode);

3.5--close函数
用close函数关闭一个打开文件:
#include <unistd.h>
int close (int filedes);
返回:若成功为0,出错为-1
关闭一个文件时也释放该进程加在该文件上的所有记录锁。将在12.3节中讨论这一点。
当一个进程终止时,它们所有打开文件都由系统核自动关闭。很多程序都使用这一功能
而不显式地用close关闭打开文件。实例见程序1.2。

3.6--lseek函数
每个打开文件都有一个与其相关联的"当前文件位移量"。它是一个非负整数,用以
每个打开文件都有一个与其相关联的"当前文件位移量"。它是一个非负整数,用以
度量从文件开始处计算的字节数。(在本节稍后处,我们将对"非负"这一修饰词的某些例
外进行说明)通常,读、写操作都从当前文件位移量处开始,并使位移量增加所读或写
的字节数。按系统默认,当打开一个文件时,除非指定O_APPEND选择项,否则该位移量
被设置为0.可以调用lseek显式地定位一个打开文件。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int filedes,off_t offset,int whence);
返回:若成功为新的文件位移,出错为-1
对参数offset的解释与参数whence的值有关。
·若whence是SEEK_SET,则将该文件的位移量设置为距文件开始处offset个字节。
·若whence是SEEK[CD#*2]CUR,则将该文件的位移量设置为其当前值加offset。offset
  可为正或负。
·若whence是SEEK[CD#*2]END,则将该文件的位移量设置为文件长度加offset,offset可
  为正或负。
若lseek成功执行,则返回新的文件位移量,为此可以用下列方式确定一个打开文
件的当前
位移量:
off[CD#*2]t currpos;
currpos=lseek(fd,0,SEEK[CD#*2]CUR);
这种方法也可用来确定所涉及的文件是否是可以设置位移量的。如果文件描述符引
用的是一
个管道或FIFO,则lseek返回-1,并将errno设置为EPIPE。
个管道或FIFO,则lseek返回-1,并将errno设置为EPIPE。
三个符号常数SEEK[CD#*2]SET,SEEK[CD#*2]CUR和SEEK[CD#*2]END是由系统Ⅴ引进
的。在系
统Ⅴ之前,whence被指定为0(绝对位移量),1(相对于当前位置的位移量)或2(相对
文件尾端
的位移量)。很多软件仍直接使用这些数字进行编码。
在lseek中的字符l表示长整型。在引入off[CD#*2]t数据类型之前,offset参数和
返回值是
长整型的。lseek是由Version 7引进的,当时C语言中增加了长整型。(在Version
 6中,用
函数seek和tell提供类似功能。)
实例

程序31测试其标准输入能否被设置位移量。
程序31〓测试标准输入能否被设置位移量。
如果我们用交互方式调用此程序,则可得:
$ aout < /etc/motd
seek OK
$ cat < /etc/motd |aout
cannot seek
$ aout < /var/spool/cron/FIFO
cannot seek
通常,一个文件的当前位移量应当是一个非负整数,但是,某些设备也可能允许负
通常,一个文件的当前位移量应当是一个非负整数,但是,某些设备也可能允许负
的位移量
。但对于普通文件,则其位移量必须是非负值。因为位移量可能是负值,所以在比
较lseek
的返回值时应谨慎,不要测试它是否小于0,而要测试它是否等于-1。
在80386上运行的SVR4支持/dev/kmem设备可以具有负的位移量。
因为位移量是带符号数据类型(off[CD#*2]t)(见图28),所以最大文件长度减少
一半。例
如,若off[CD#*2]t是32位整型,则最大文件长度是231字节。
lseek仅将当前的文件位移量记录在系统核内,它并不引起任何I/O操作。然后,该
位移量用
于下一个读或写操作。
文件位移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将延长
该文件,
并在文件中构成一个空调,这一点是允许的。位于文件中但没有写过的字节都被读
为0。
实例
程序32创建一个具有空洞的文件
程序32创建一个具有空洞的文件
运行该程序得到:
$ aout
$ ls -1 filehole 〓〓〓〓〓〓〖WB〗检查其大小
-rw-r--r--〓1 stevens〓50 Jul 31 05:50 filehole
通常,一个文件的当前位移量应当是一个非负整数,但是,某些设备也可能允许负
的位移量
。但对于普通文件,则其位移量必须是非负值。因为位移量可能是负值,所以在比
较lseek
的返回值时应谨慎,不要测试它是否小于0,而要测试它是否等于-1。
在80386上运行的SVR4支持/dev/kmem设备可以具有负的位移量。
因为位移量是带符号数据类型(off[CD#*2]t)(见图28),所以最大文件长度减少
一半。例
如,若off[CD#*2]t是32位整型,则最大文件长度是231字节。
lseek仅将当前的文件位移量记录在系统核内,它并不引起任何I/O操作。然后,该
位移量用
于下一个读或写操作。
文件位移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将延长
该文件,
并在文件中构成一个空调,这一点是允许的。位于文件中但没有写过的字节都被读
为0。
实例
程序32创建一个具有空洞的文件
程序32创建一个具有空洞的文件
运行该程序得到:
$ aout
$ ls -1 filehole 〓〓〓〓〓〓〖WB〗检查其大小
-rw-r--r--〓1 stevens〓50 Jul 31 05:50 filehole
ssize[CD#*2]t read(int filedes,void *buff,size[CD#*2]t nbytes
);
返回:读到的字节数,若已到文件尾为0,出错为-1
如read成功则返回读到的字节数。如果已到达文件的尾端,则返回0。
有多种情况可使实际读到的字节数少于要求读字节数:
读普通文件时,在读到要求字节数之前已到达了文件尾端。例如,若在到达文件尾
端之前还
有30个字节,而我们却要求读100个字节,则read返回30,下一次我们再调用read
时,它将
返回0(文件尾端)。
·当从终端设备读时,通常一次最多读一行(在第十一章中将介绍如何改变这一点
)。
·当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数。
·某些面向记录的设备,例如磁带,一次最多返回一个记录。
读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加实际读得的字
节数。
POSIX1在几个方面对此函数的原型作了更改。其经典定义是:
int read(int fildes,char *buff,unsigned nbytes);
首先,为了与ANSI C一致,其第2个参数由char *改为void *。在ANSI C中,类型
void *用
于表示类属指针。其次,其返回值必须是一个带符号整数(ssize[CD#*2]t),以返
回正字节
数、0(表示文件尾端)或-1(出错)。最后,第3个参数在历史上是个不带符号整数,
数、0(表示文件尾端)或-1(出错)。最后,第3个参数在历史上是个不带符号整数,
以允许一
个16位的实现可以一次读或写至65534个字节。在1990 POSIX1标准中,引进了新
的基本系
统数据类型ssize[CD#*2]t以提供带符号的返回值,size[CD#*2]t则被用于第3个参
数(回忆
图27中的SSIZE〖CD#*2]MAX常数。)
38〓write函数
用write函数向打开文件写数据。
#include <unistdh>
ssize[CD#*2]t write(int filedes,const void *buff,size[CD#*2]t 
nbytes
);
返回:若成功为已写的字节数,出错为-1
其返回值通常导于参数nbyte,否则表示出错。write出错的一个常见原因是:或者
磁盘已写
满,或者超过了对一个给定进程的文件长度限制(见711节及练习1011)
对于普通文件,写操作从文件的当前位移量处开始。如果在打开该文件时,指定了
O[CD#*2]
APPEND选择项,则在每次写操作之前,将文件位移量设置在文件的当前结尾处,在
一次成功
写之后,该文件位移量增加实际写的字节数。
39〓I/O的效率
39〓I/O的效率
程序33只使用read和write函数来复制一个文件。关于该程序应注意下列各点:

·它从标准输入读,写至标准输出,这就假定在执行本程序之前,这些标准输入、
输出已由
shell安排好。确实,所有常用的Unix shell都提供一种方法,它在标准输入上打
开一个文
件用于读,在标准输出上创建(或重写)一个文件。
·很多应用程序假定标准输入是文件描述符0,标准输出是文件描述符1。在本例中
,我们则
用两个在<unistdh>中定义的名字STDIN[CD#*2]FILENO和STDOUT[CD#*2]FILENO。

·考虑到进程终止时,Unix会关闭所有打开文件描述符,所以此程序并不close输
入和输出
文件。
·在程序对文本文件和两进制代码文件都能工作,因为对Unix系统核而言,这两种
文件并无
区别。
程序33〓将标准输入复制到标准输出
我们没有回答的一个问题是如何选取BUFFSIZE值。在回答此问题之前,让我们先用
各种不同
的BUFFSIZE值来运行此程序。图31显示了用18种不同的缓存长度,读1,468,8
02字节文
02字节文
件所得到的结果。
程序33读文件,其标准输出则被重新空向到/dev/null上。此测试所用的文件系
统是贝克
莱快速文件系统,其块长为8192字节。(块长由st[CD#*2]blksize表示,在412中
说明为81
92)。系统CPU时间的最小值开始出现在BUFFSIZE为8192处,继续增加缓存长度对此
时间并我
影响。
我们以后还将回到这一实例上。在313节中我们将用此说明同步写的效果,在5
8节中,
则将比较不带缓存所用的时间及标准I/O库所用的时间。
310〓文件共享
Unix支持在不同进程间共享打开文件。在介绍dup函数之间,我们需要先说明这种
共享。为
此先说明系统核用于所有I/O的数据结构。
系统核使用了三个数据结构,它们之间的关系决定了文件共享方面一个进程对另一
个进程可
能产生的影响。
1每个进程在进程表中都有一个记录项,每个记录项中有一张打开文件描述符表
,我们可
将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:
(a)文件描述符标志,
(a)文件描述符标志,
(b)指向一个文件表项的指针。
图31〓不同缓存长度进行读操作的时间结果
2系统核为所有打开文件维持一张文件表。每个文件表项包含:
(a)文件状态标志(读,写,增写,同步,非阻塞等),
(b)当前文件位移量,
(c)指向该文件v-node(v字节)表项的指针。
3每个打开文件(或设备)都有一个V[CD#*2]node结构。V[CD#*2]node包含了文件
类型和对
此文件进行各种操作的函数的指针信息。对于大多数文件,v[CD#*2]node还包含了
该文件的
i[CD#*2]node(索引节点)。这些信息是在打开文件时从盘上读入内存的,所以所有
关于文件
的信息都是快速可供使用的。例如,i[CD#*2]node包含了文件的属主、文件长度、
文件所在
的设备、指向文件在盘上所使用的实际数据块的指针等等。(在414节中较详细地
说明Unix
文件系统时,会更多地说明i[CD#*2]node)。
我们忽略了某些并不影响我们讨论的实现细节。例如,打开文件描述符表通常在用
户区而不
在进程表中。在SVR4中,此数据结构是一个链接表结构。文件表可以用多种方法实
现一它不
一定是文件表项数组。在43+BSD中,V[CD#*2]node包含了实际i[CD#*2]node(如
一定是文件表项数组。在43+BSD中,V[CD#*2]node包含了实际i[CD#*2]node(如
图32中
所示)。SVR4对于大多数文件系统类型,将v[CD#*2]node存放在i[CD#*2〗node中。
这些实现
细节并不影响我们对文件共享的讨论。
图32图示了一个进程的这三张表之间的关系。该进程有两个不同的打开文件〖C
D2〗一个
文件打开为标准输入(文件描述符0),另一打开为标准输出(文件描述符为10。

图32〓打开文件的系统核数据结构
从Unix的早期版本〔Thompson1978〕以来,这三张表之间的基本关系一直保持至今
。这种安
排对于在不同进程之间共享文件的方式是非常重要的。在以后的章节中述及其它的
文件共享
方式时还会回到这张图上来。
v[CD#*2]node结构是近来增设的。当在一个给定的系统上对多种文件系统类型提供
支持时,
就需要这种结构,这一工作是由Peter Weinberger(Bell实验室)和Bill Joy(Sun
Microsyst
ems)分别独立完成的。Sun称此种文件系统为虚拟文件系统(Virtual File System
),称与文
件系统类型无关的i[CD#*2]node部分为v[CD#*2]node〔Kleiman 1986〕。当各个制
造商的实
造商的实
现增加了对Sun网络文件系统(NFS)的支持时,它们都广泛采用了v[CD#*2]node结构
。
在SVR4中,v[CD#*2]node代换了SVR3中的与文件系统类型无关的i[CD#*2]node结构
。
如果两个独立进程各自打开了同一文件,则有图33中所示的安排。我们假定第一
个进程使
该文件在文件描述符3上打开,而另一个进程则使此文件在文件描述符4上打开。打
开此文件
的每个进程得到一个文件表项,但对一个给定的文件只有一个v[CD#*2]node表项。
每个进程
都有自己的文件表项的一个理由是:这种安排使每个进程都有它自己的对该文件的
当前位移
量。
图33〓两个独立进程各自打开同一个文件
给出了这些数据结构后,我们现在对前面所述的操作作进一步说明。
·在完成每个write后,在文件表项中的当前文件位移量即增加所写的字节数。如
果这使当
前文件位移量超过了当前文件长度,则在i〖CD#*2〗node表项中的当前文件长度被
设置为当
前文件位移量(也就是该文件加长了)。
·如果用O[CD#*2]APPEND标志打开了一个文件,则相应标志也被设置到文件表项的
它自己的
造商的实
现增加了对Sun网络文件系统(NFS)的支持时,它们都广泛采用了v[CD#*2]node结构
。
在SVR4中,v[CD#*2]node代换了SVR3中的与文件系统类型无关的i[CD#*2]node结构
。
如果两个独立进程各自打开了同一文件,则有图33中所示的安排。我们假定第一
个进程使
该文件在文件描述符3上打开,而另一个进程则使此文件在文件描述符4上打开。打
开此文件
的每个进程得到一个文件表项,但对一个给定的文件只有一个v[CD#*2]node表项。
每个进程
都有自己的文件表项的一个理由是:这种安排使每个进程都有它自己的对该文件的
当前位移
量。
图33〓两个独立进程各自打开同一个文件
给出了这些数据结构后,我们现在对前面所述的操作作进一步说明。
·在完成每个write后,在文件表项中的当前文件位移量即增加所写的字节数。如
果这使当
前文件位移量超过了当前文件长度,则在i〖CD#*2〗node表项中的当前文件长度被
设置为当
前文件位移量(也就是该文件加长了)。
·如果用O[CD#*2]APPEND标志打开了一个文件,则相应标志也被设置到文件表项的
它自己的
它自己的
文件表项,在其中也就有它自己的当前文件位移量。但是,当多个进程写同一文件
时,则可
能产生非预期的结果。为了说明如何避免这种情况,我们需要理解原子操作的概念
。
311〓原子操作
添写至一个文件
考虑一个进程,它要将数据添写到一个文件尾端。较早的Unix版本并不支持open的
O[CD#*2]
APPEND选择项,所以程序被编写成下列形式:
if(lseek(fd,OL,2)<0)〓〓〓〓〓〓〖WB〗/*定位至文件尾端*/
err[CD#*2]sys("lseek error");
if (write(fd,buff,100)!=100)〖DW〗/*写*/
err[CD#*2]sys("write error");
对单个进程而言,这段程序能正常工作,但若有多个进程使用这种技术添写到同一
文件中,
则就会产生问题。(如果此程序由多个进程同时执行,各自将消息添写到一个日记
文件中,
则就会产生这种情况。)
假定有二个独立的进程A和B,都对同一文件进行添写操作。每个进程都已打开了该
文件,但
未使用O[CD#*2]APPEND标志。此时各数据结构之间的关系如图33中所示一样。每
个进程都
个进程都
有它自己的文件表项,但是共享一个v[CD#*2]node表项。假定进程A调用了lseek,
它将对于
进程A的该文件当前位移量设置为1500字节(当前文件尾端处)。然后系统核切换进
程使进程B
运行。进程B执行lseek,也将其对该文件的当前位移量设置为1500字节(当前文件
尾端处)。
然后B调用write,它将B的该文件当前文件位移量增至1600。因为该文件的长度已
经增加了
,所以系统核对v[CD#*2]node中的当前文件长度更新为1600。然后,系统核又进行
进程切换
使进程A恢复运行。当A调用write时,就从其当前文件位移量(1500)处将数据写到
文件中去
。这样也就代换了进程B刚写到该文件中的数据。
这里的问题出在逻辑操作"空档到文件尾端处,然后写"使用了二个分开的函数调用
。解决
问题的方法是使这两个操作对于其它进程而言成为一个原子操作。任何一个要求多
于1个函
数调用的操作都不能成为原子操作,因为在两个函数调用之间,系统核有可能会临
时挂起该
进程(正如我们前面所假定的)。
Unix提供了一种方法使这种操作成为原子操作,其方法就是在打开文件时设置O[C
D#*2]APPE
个进程都
有它自己的文件表项,但是共享一个v[CD#*2]node表项。假定进程A调用了lseek,
它将对于
进程A的该文件当前位移量设置为1500字节(当前文件尾端处)。然后系统核切换进
程使进程B
运行。进程B执行lseek,也将其对该文件的当前位移量设置为1500字节(当前文件
尾端处)。
然后B调用write,它将B的该文件当前文件位移量增至1600。因为该文件的长度已
经增加了
,所以系统核对v[CD#*2]node中的当前文件长度更新为1600。然后,系统核又进行
进程切换
使进程A恢复运行。当A调用write时,就从其当前文件位移量(1500)处将数据写到
文件中去
。这样也就代换了进程B刚写到该文件中的数据。
这里的问题出在逻辑操作"空档到文件尾端处,然后写"使用了二个分开的函数调用
。解决
问题的方法是使这两个操作对于其它进程而言成为一个原子操作。任何一个要求多
于1个函
数调用的操作都不能成为原子操作,因为在两个函数调用之间,系统核有可能会临
时挂起该
进程(正如我们前面所假定的)。
Unix提供了一种方法使这种操作成为原子操作,其方法就是在打开文件时设置O[C
D#*2]APPE
。

图34〓dup(1)后系统核数据结构
在此图中,我们假定进程执行了:
newfd=dup(1);
当此函数开始执行时,我们假定下一个可用的描述符是3(这是非常有可能的,因为
0,1和2
是由shell打开的)。因为两个描述符指向同一文件表项,所以它们共享同一文件状
态标志(
读、写、添写等)以及同一当前文件位移量。
每个文件描述符都有它自己的一套文件描述符标志。如我们在下一节中将说明的那
样,新描
述符的执行时关闭(close[CD#*2]on[CD#*2]exec)文件描述符标志总是由dup函数清
除。
复制一个描述符的另一种方法是使用fcntl函数,我们将在下一节对该函数进行说
明。确实
,调用
dup(filedes);
等效于
fcntl (filedes,F[CD#*2]DUPFD,0);
而调用
dup2(filedes,filedes2);
等效于
等效于
close(filedes2);
fcntl(filedes,F[CD#*2]DUPFD,filedes2);
在后一种情况下,dup2并不完全等同于close,然后跟附fcntl。它们之间的区别是
:
1dup2是一个原子操作,而close及fcntl则包括两个函数调用。有可能在close和
fcntl之
间插入执行信号捕获函数,它可能修改文件描述符(我们将在第十章说明信号。)

2在dup2和fcntl之间有某些不同的errno。
dup2系统调用起源于Version 7,然后传播至所有BSD版本。而复制文件描述符的f
cntl方法
则首先由系统Ⅲ使用,系统Ⅴ则继续采用之。SVR32取用了dup2函数,42BSD则
取用了fc
ntl函数及F[CD#*2]DUPFD功能。POSIX1要求dup2及fcntl的F[CD#*2]DUPFD功能两
者。
313〓fcntl函数
fcntl函数可以改变已经打开的文件的性质。
#include <sys/typesh>
#include <unistdh>
#include <fcntlh>
int fcntl(int filedes,int cmd,/* int arg */);
返回:若成功,依赖于cmd(见下),出错为-1
返回:若成功,依赖于cmd(见下),出错为-1
在本节的各实例中,第3个参数总是一个整数,与上面所示函数原型中的注释部分
相对应。
但是我们在123节中说明记录锁时,第3个参数则是指向一个结构的指针。
fcntl函数有五种功能:
·复制一个现存的描述符(cmd 剑啤迹茫模*常病紻UPFD),
·获得/设置文件描述符标记(cmd=F[CD#*2]GETFD或F[CD#*2]SETFD),
·获得/设置文件状态标志(cmd=F[CD#*2]GETFL或F[CD#*2]SETFL),
·获得/设置异步I/O属主权(cmd=F[CD#*2]GETOWN或F[CD#*2]SETOWN),
·获得/设置记录锁(cmd=F[CD#*2]GETLK,F[CD#*2]SETLK或F[CD#*2]SETLKW)。
我们先说明这十种命令值中的前七种(在123节中说明后三种,它们都与记录锁有
关)我们
将涉及与进程表项中各文件描述符相关联的文件描述符标志以及每个文件表项中的
文件状态
标志,所以请参阅图32。
F[CD#*2]DUPFD〓复制文件描述符filedes,新文件描述符作为函数值返回。它是尚
未打开的
各描述符中大于或等于第三个参数值(取为整型值)中各值的最小值。新描述符与f
iledes共
享同一文件表项(参见图34)但是,新描述符有它自己的一套文件描述符标志,其
FD[CD#*2
]CLOEXEC文件描述符标志则被清除(这表示该描述符在exec时仍保持开放,我们将
在第八章
在第八章
对此进行讨论。)
F[CD#*2]GETFD〓对应于filedes的文件描述符标志作为函数值返回。当前只定义了
一个文件
描述符标志FD[CD#*2]CLOEXEC标志。
F[CD#*2]SETFD〓对于filedes设置文件描述符标志。新标志值是按第3个参数(取为
整型值)
设置的。
应当了解很多现存的涉及文件描述符标志的程序并不使用常数FD[CD#*2]CLOEXEC。
代替之,
程序或将此标志设置为0(系统默认,在exec时不关闭),或1(在exec时关闭)。
F[CD#*2]GETFL〓对应于filedes的文件状态标志作为函数值返回。在我们说明ope
n函数时,
已说明了文件状态标志。它们列于图35中
图35〓对于fcntl的文件状态标志
不幸的是三个存取方式标志(O[CD#*2]RDONLY,O[CD#*2]WRONLY,以及O[CD#*2]RDWR
)并不各占
1位。(正如前述,这三种标志的值各是0,1和2由于历史原因。这三种值是互斥的
〖CD2〗一
个文件只能有这3种值之1。)因此首先必须用屏蔽字O[CD#*2]ACCMODE取得存取方式
位,然后
将结果与这三种值相比较。
F[CD#*2]SETFL〓将文件状态标志设置为第3个参数的值(取为整型值)。可以更改的
在第八章
对此进行讨论。)
F[CD#*2]GETFD〓对应于filedes的文件描述符标志作为函数值返回。当前只定义了
一个文件
描述符标志FD[CD#*2]CLOEXEC标志。
F[CD#*2]SETFD〓对于filedes设置文件描述符标志。新标志值是按第3个参数(取为
整型值)
设置的。
应当了解很多现存的涉及文件描述符标志的程序并不使用常数FD[CD#*2]CLOEXEC。
代替之,
程序或将此标志设置为0(系统默认,在exec时不关闭),或1(在exec时关闭)。
F[CD#*2]GETFL〓对应于filedes的文件状态标志作为函数值返回。在我们说明ope
n函数时,
已说明了文件状态标志。它们列于图35中
图35〓对于fcntl的文件状态标志
不幸的是三个存取方式标志(O[CD#*2]RDONLY,O[CD#*2]WRONLY,以及O[CD#*2]RDWR
)并不各占
1位。(正如前述,这三种标志的值各是0,1和2由于历史原因。这三种值是互斥的
〖CD2〗一
个文件只能有这3种值之1。)因此首先必须用屏蔽字O[CD#*2]ACCMODE取得存取方式
位,然后
将结果与这三种值相比较。
F[CD#*2]SETFL〓将文件状态标志设置为第3个参数的值(取为整型值)。可以更改的
有定义的文件存取标志。下面显示了从Kornshell调用该程序时的几种情况:
$ aout 0</dev/tty
read only
$ aout 1>tempfoo
$ cat tempfoo
write only
$ aout 2 2>>tempfoo
write only,append
$ aout 5 5<>tempfoo
read write
Kornshell子句5<>tempfoo表示在文件描述符5上打开文件tempfoo供读、写。

实例
当修改文件描述符标志或文件状态标志时,必须谨慎,先要取得现在的标志值,然
后按照希
望修改它,最后设置新标志值。不能只是执行F[CD#*2]SETFD或F[CD#*2]SETFL命令
,这样会
关闭以前设置的标志位。
程序35是一个对于一个文件描述符设置一个或多个文件状态标志的函数。

程序35〓对一个文件描述符打开一个或多个文件状态指标
如果我们将中间的一条语句改为
这就构成了另一个函数,我们称其为clr[CD#*2]fl,并将在某个后面的例子中用到
这就构成了另一个函数,我们称其为clr[CD#*2]fl,并将在某个后面的例子中用到
它。此语
句使当前文件状态标志值val与flags的反码逻辑与。
如果在程序33的开始处,加上下面1行以调用set[CD#*2]fl,则打开了同步写标
志。这就
造成每次write都要等待,直至数据已写到盘上再返回。在Unix中,通常write只是
将数据排
入队列,而实际的I/O操作则可能在以后的某个时刻进行。数据库系统很可能需要
使用O[CD#
*2]SYNC,这样,在系统崩溃情况下,它从write返回时就知道数据已确实写到了盘
上。
在程序运行时,设置O[CD#*2]SYNC标志会增加时钟时间。为了测试这一点,我们运
行程序3
3,它从盘上的一个文件中将15Mbyle复制到另一个文件中。然后,在此程序中
设置O[CD
#*2]SYNC标志,使其运行做上述同样工作,将两者得到的结果进行比较,这示于图
36中。

图36〓用同步写(O[CD#*2]SYNC)的时间结果
图36中的3行都是在BUFSIZ为8192的情况下测量得到的。图31中的结果所测量
的情况是
读一个盘文件,然后写到/dev/null,所以没有盘输出。图36中的第2行对应于读
一个盘文
一个盘文
件,然后写到另一个盘文件中。这就是为什么图36中第1,2行有差别的原因。在
写盘文件
时,系统时间增加了,其原因是系统核需要从进程中复制数据,并将数据排入队列
以便由盘
驱动器将其写到盘上去。当写至盘文件时,时钟时间也增加了。当进行同步写时,
系统时间
稍稍增加,而时钟时间则增加为6倍。
在这一例子中,我们看到了fcntl的必要性。我们的程序在一个描述符(标准输出)
上进行操
作,但是根本不知道由shell打开的相应文件的文件名。因为这是shell打开的,于
是可能在
打开时,按我们的要求设置O[CD#*2]SYNC标志。fcntl则允许当只知道打开文件的
描述符时
可以修改其性质。在说明非阻塞管道时(142节),我们也将了解到,由于我们对
pipe所具
有的标识只是其描述符,所以也需要使用fcntl的功能。
314〓ioctl函数
ioctl函数是I/O操作的杂物箱。不能用本章中其它函数表示的I/O操作通常都能用
ioctl表示
。终端I/O是ioctl的最大使用方面(在第十一章中,我们会了解到POSIX1已经用
新的函数
代替ioctl进行终端I/O操作。)
代替ioctl进行终端I/O操作。)
#include <unistdh> /* SVR4 */
#include <sys/ioctlh>/* 43+BSd */
int ioctl(int filedes,int request,);
返回:出错为-1,若成功则为其它源
ioctl函数不是POSIX1的一部分,但是,SVR4和43+BSD用其进行很多杂项设备
操作。
我们所示的原型是SVR4和43+BSD所使用的,而较早的贝克莱系统则将第2个参数
说明为uns
igned long。因为第2个参数总是一个头文件中的定义名,所以这种细节并没有什
么影响。

对于ANSI C原型,它用省略号表示其余参数。但是,通常只有另外一个参数,它常
常是指向
一个变量或结构的指针。
在此原型中,我们表示的只是ioctl函数本身所要求的头文件。通常,还要求另外
的设备专
用头文件。例如,在POSIX1所说明的基本操作之外,终端ioctl都需要头文件<t
ermiosh
>。
目前,ioctl的主要用途是什么呢?我们将43+BSD的ioctl操作分类示于图37中
。


图37〓43+BSD ioctl操作
磁带操作使我们可以在磁带上写一个文件结束标志,反绕磁带,越过指定个数的文
件或记录
等等,用本章中的其它函数(read,write,lseek等)都难于表示这些操作,所以,用
ioctl是
对这些设备进行操作的最容易方法。
在1112节中存取和设置终端窗口时,在124中说明流系统时,以及在197节中
述及仿终
端的高级功能时,我们都将使用ioctl。
315〓/dev/fd
比较新的系统都提供名为/dev/fd的目录,其目录项是名为0,1,2等的文件。打开
文件/dev
/fd/n等效于复制描述符n(假定描述符n是打开的)。
/dev/fd这种特征是由Tom Duff开发的,它首先出现在research Unix System的第
8版中,SV
R4和43+BSD支持这种特征。它不是POSIX1的组成部分
在函数中调用
fd=open("/dev/fd/0",mode);
大多数系统忽略所指定的mode,而另外一些则要求mode是所涉及的文件(在我们这
里则是标
准输入)原先打开时所使用的mode的子集。因为上面的打开等效于:
fd=dup(0);
fd=dup(0);
描述符0和fd共享同一文件表项(图34)。例如,若描述符0被只读打开,那么我们
也只对fd
进行读操作。即使系统忽略打开方式,并且下列调用成功:
fd=open("/dev/fd/0",O[CD#*2]RDWR);
我们仍然不能对fd进行写操作。
我们也可以用/dev/fd作为路径名参数调用creat,或调用open,并同时指定O[CD#
*2]CREAT
。这就允许调用creat的程序,如果路径名参数是/dev/fd/1等仍能工作。
某些系统提供路径名/dev/stdin,/dev/stdout和/dev/stderr。这些等效于/dev/f
d/0,/dev/
fd/1和/dev/fd/2。
/dev/fd文件主要由shell使用,这允许程序以对待其它路径名一样的方式使用路径
名参数来
处理标准输入和标准输出。例如,cat(1)程序将命令行中的一个单独的'-'特别解
释为一
个输入文件名,该文件指的是标准输入。例如,
filter file2 |cat file1-file3|lpr
首先cat读file1,接着读其标准输入(也就是filter file2命令的输出),然后读f
ile3,如
若支持/dev/fd,则可以删除cat对一的特殊处理,于是我们就可键入下列命令行:

filter file2 |cat file1 /dev/fd/0 file3 |lpr
fd=dup(0);
描述符0和fd共享同一文件表项(图34)。例如,若描述符0被只读打开,那么我们
也只对fd
进行读操作。即使系统忽略打开方式,并且下列调用成功:
fd=open("/dev/fd/0",O[CD#*2]RDWR);
我们仍然不能对fd进行写操作。
我们也可以用/dev/fd作为路径名参数调用creat,或调用open,并同时指定O[CD#
*2]CREAT
。这就允许调用creat的程序,如果路径名参数是/dev/fd/1等仍能工作。
某些系统提供路径名/dev/stdin,/dev/stdout和/dev/stderr。这些等效于/dev/f
d/0,/dev/
fd/1和/dev/fd/2。
/dev/fd文件主要由shell使用,这允许程序以对待其它路径名一样的方式使用路径
名参数来
处理标准输入和标准输出。例如,cat(1)程序将命令行中的一个单独的'-'特别解
释为一
个输入文件名,该文件指的是标准输入。例如,
filter file2 |cat file1-file3|lpr
首先cat读file1,接着读其标准输入(也就是filter file2命令的输出),然后读f
ile3,如
若支持/dev/fd,则可以删除cat对一的特殊处理,于是我们就可键入下列命令行:

filter file2 |cat file1 /dev/fd/0 file3 |lpr
发信人: scircle (yuanyuan), 信区: Security
标  题: unix环境高级编程--第4章 文件和目录 (上)
发信站: BBS 水木清华站 (Thu Mar 23 17:00:12 2000)

41〓引言
在上一章我们说明了执行I/O操作的基本函数。其讨论是围绕普通文件的I/O进行的
-打开-
文件,读或写一个文件。本章将观察文件系统的其它特征和文件的性质。我们从s
tat函数开
始,并逐个说明stat结构的每一个成员以了解文件的所有属性。在此过程中,我们
的说明修
改这些属性的各个函数(更改属主,更改许可数等)。我们也将更详细地察看Unix文
件系统的
结构以及符号连接。本章结束部分介绍对目录进行操作的各个函数,并且开发了一
个以降序
遍历目录层次结构的函数。
42〓stat,fstat以及lstat函数
本章的讨论的中心是三个stat函数以及它们所返回的信息。
#include<sys/typesh>
#include<sys/stath>
int stat(const char *pathname,struct stat *buf);
int fstat(int filedes,struct stat *buf);
int lstat(const char *pathname,struct stat *buf);
int lstat(const char *pathname,struct stat *buf);
三个函数的返回:若成功为0,出错为-1
给予一个pathname,stat函数返回一个与此命名文件有关的信息结构,fstat函数获
得已在插
述符filedes上打开的文件的有关信息。lstat函数类似于stat,但是当命名的文件
是一个符
号连接时,lstat返回该符号连接的有关信息,而不是由该符号连接引用的文件的
信息。(在
42)节中当降序周游目录层次结构时,我们需要lstat。在416节中的较详细地
说明符号
连接。)
lstat函数不属于POSIX 10031-1990标准,但很可能加到10031a中。SVR4和4
3+BSD支
持lstat。
第二个参数是个指针,它指向一个我们应提供的结构。这些函数填写由buf指向的
结构。该
结构的实际定义可能所实施而有所不同,但其基本形式是:
struct stat{
mode 迹茫模*常病絫 st 迹茫模*常病絤ode; /*文件类型和方式(许可数)*/
ino 迹茫模*常病絫 st 迹茫模*常病絠no;/* i-节点号(序列号)*/
dev 迹茫模*常病絫 st 迹茫模*常病絛ev;/*设备号(文件系统)*/
dev 迹茫模*常病絫 st 迹茫模*常病絩dev;/*特殊文件的设备号*/
nlink 迹茫模*常病絫 st 迹茫模*常病絥link;/*连接数*/
nlink 迹茫模*常病絫 st 迹茫模*常病絥link;/*连接数*/
uid 迹茫模*常病絫 st 迹茫模*常病絬id;/*属主的用户ID*/
gid 迹茫模*常病絫 st 迹茫模*常病絞id;/*属主的组ID*/
off 迹茫模*常病絫 st 迹茫模*常病絪ize;/*普通文件的字节长度*/
time 迹茫模*常病絫 st 迹茫模*常病絘time;/*最后存取时间*/
time 迹茫模*常病絫 st 迹茫模*常病絤time;/*最后修改存取时间*/
time 迹茫模*常病絫 st 迹茫模*常病絚time;/*最后文件状态更改时间*/
long st 迹茫模*常病絙lksize;/*最佳I/O块长*/
long st 迹茫模*常病絙locks;/*分配的512字节块块数
};
POSIX1未定义st 迹茫模*常病絩devst 迹茫模*常病絙lksige和st 迹茫模?
2〗blo
cks字段。SVR4和43+BSD则定义了这些字段。
注意,除最后两个以外,其它各成员都说明为基本系统数据类型(见27节)。我们
将说明此
结构的每个成员以了解文件属性。
stat函数的最大用户很可能是ls-l命令,用其可以获得有关一个文件的所有信息。

43〓文件类型
至今我们已介绍了两种不同的文件类型-普通文件和目录。Unix系统的大多数文件
是普通文
件或目录,但是也有另外一些文件类型:
1普通文件(Regular file)。这是最常见的文件类型,这种文件包含了某种形式
1普通文件(Regular file)。这是最常见的文件类型,这种文件包含了某种形式
的数据。
至于这种数据是文本还是二进制数据对于系统核而言并无区别。对普通文件内容的
解释由处
理该文件的应用程序进行。
2目录文件(Directory file)。这种文件包含了其它文件的名字以及指向与这些
文件有关
信息的指针。对一个目录文件具有读许可数的任一进程都可以读该目录的内容,但
只有系统
核可以写目录文件。
3字符特殊文件(Charocter special file)。这种文件用于系统中的某些类型的
设备。
4块特殊文件(Block special file)。这种文件典型地用于磁盘设备。系统中的
所有设备
或者是字符特殊文件,或者是块特殊文件。
5 FIFO。这种文件用于进程间的通信,有时也将其称为命名管道。在145对其
进行说明
。
6套接口(socket)。这种文件用于进程间的网络通信。套接口也可用于在一台宿
主机上的
进程之间的非网络通信。在第十五章,我们将用套接口进行进程间的通信。
只有43+BSD才返回套接口文件类型,虽然SVR4支持用套接口进行进程间通信,但
现在是经
1普通文件(Regular file)。这是最常见的文件类型,这种文件包含了某种形式
的数据。
至于这种数据是文本还是二进制数据对于系统核而言并无区别。对普通文件内容的
解释由处
理该文件的应用程序进行。
2目录文件(Directory file)。这种文件包含了其它文件的名字以及指向与这些
文件有关
信息的指针。对一个目录文件具有读许可数的任一进程都可以读该目录的内容,但
只有系统
核可以写目录文件。
3字符特殊文件(Charocter special file)。这种文件用于系统中的某些类型的
设备。
4块特殊文件(Block special file)。这种文件典型地用于磁盘设备。系统中的
所有设备
或者是字符特殊文件,或者是块特殊文件。
5 FIFO。这种文件用于进程间的通信,有时也将其称为命名管道。在145对其
进行说明
。
6套接口(socket)。这种文件用于进程间的网络通信。套接口也可用于在一台宿
主机上的
进程之间的非网络通信。在第十五章,我们将用套接口进行进程间的通信。
只有43+BSD才返回套接口文件类型,虽然SVR4支持用套接口进行进程间通信,但
现在是经
现在是经
由套接口函数库实现的,而不是通过系统核内的套接口文件类型,将来的SVR4版本
可能会支
持套接口文件类型。
7符号连接(Symbolic link)。这种文件指向另一个文件。我们在416中将更多
地述及符
号连接。
文件类型信息,包含在stat结构的st 迹茫模*常病絤ode成员中。我们可以用图4
1中的宏
确室文件类型。这些宏的参数都是stat结构中的st 迹茫模*常病絤ode成员。

图41〓在<sys/stath>中的文件类型宏
实例
程序41取其命令行参数,然后针对每一个命令行参数打印其文件类型。
程序41〓对每个命令行参数打印文件类型
程序41的样本输出是:
$ aout/vmunix/etc/dev/ttya/dev/sd0a/var/spool/cron/FIFO\
>/bin/dev/printer
/vmunix:普通
/etc:目录
/dev/ttya:字符特殊
/dev/sd0a:块
/var/spool/cron/FIFO:fifo
/var/spool/cron/FIFO:fifo
/bin:symbolic符号连接
/dev/printer:套接口
(其中,在第一命令行末端我们键入了一个反斜线,通知shell我们要在下一行继续
键入命令
,然后shell在下一行上用其第二提示符,>,提示我们特地使用了lstat函数而不是
stat函数
以便检测符号连接。如若使用了stat函数,则决不会观察到符号连接。
较早的Unix版本并不提供S 迹茫模*常病絀SXXX宏,于是就需要将st 迹茫模*常?
〗mode与
屏蔽字S 迹茫模*常病絀FMT逻辑与,然后与
名为s 迹茫模*常病絀FXXX的常数相比较。SVR4和43+BSD在文件<sys/stath>
中定义了
此屏蔽字和相关的常数。如若我们查看此文件,则可找到S 迹茫模*常病絀SDIR宏
定义为:

我们说过,普通文件是最主要的文件类型,但是观察一下在一个给定的系统中各种
文件的比
例是很有兴趣的。图42中显示了在一个中等规模的系统中的统计值。这一数据是
由421
节中的程序得到的。
图42〓不同类型文件的计数值和比例
44〓设置一用户 迹茫模*常病絀D和设置一组 迹茫模*常病絀D
FIPS 。 5|-1要求POSIX1的这种可选择特征。
通常,有效用户ID等于实际用户ID,以及有效组ID等于实际用户ID。
每个文件有一个属主和组属主,属主是由stat结构中的st 迹茫模*常病絬id表示
的,组属
主则由st 迹茫模*常病絞id成员表示。
当我们执行一个程序文件时,进程的有效用户ID通常就是实际用户ID,有效组ID通
常是实际
组ID。但是可以在文件方式字(st 迹茫模*常病絤ode)中设置一个特殊标志,其定
义是"当
执行此文件时
,将进程的有效用户ID设置为文件的属主(st 迹茫模*常病絬id)"。与此相类似,
在文件
方式字中可以设
置另一位,它使得执行此文件的进程的有效组ID设置为文件的组属主(st 迹茫模?
2〗gid
)。在文件方式字中的这两位被称之为设置一用户 迹茫模*常病絀D位和设置一组
 迹茫模?
2〗ID位。
例如,若文件属主是超级用户,而且设置了该文件的设置一用户 迹茫模*常病絀
D位,然后
当该程序由一
个进程运行时,则该进程具有超级用户优先数。不管执行此文件的进程的实际用户
ID是什么
ID是什么
,都作这种处理。作为一个例子,Unix程序passwd(1)允许任一用户改变其口令字
,该程序
是一个设置一用户 迹茫模*常病絀D程序。因为该程序应能将用户的新口令字写入
口令字文
件中(典型地
这是/etc/passwd或/etc/shadow),而只有超级用户才具有该文件的写许可数,所以
需要使用
设置一用户 迹茫模*常病絀D特征。因为运行设置一用户 迹茫模*常病絀D程序的
进程通常
得到额外的许
可数,所以要特别谨慎地编写这种程序。我们将在第八章更详细地讨论这种类型的
程序。
再返回到stat函数,设置一用户 迹茫模*常病絀D位及设置一组 迹茫模*常病絀
D位都包含
在st 迹茫模*常病絤ode值中。这两位可用常数S 迹茫模*常病絀SUID和S  CD
 *常病絀
SGID测试。
45〓文件存取许可数
st 迹茫模*常病絤ode值也包含了对文件的存取数位。当我们说及文件时,我们指
的是前面
所提到的任何
类型的文件。所有文件类型(目录,字符特别文件等)都有许可数。很多人认为只有
类型的文件。所有文件类型(目录,字符特别文件等)都有许可数。很多人认为只有
普通文件
有存取许可数,这是一种误解。
每个文件有9个存取数位,可将它们分成三类。这些都示于图44中。
图44〓9个存取数位(在<sys/stath>中定义)
在图44开头三行中,术语用户指的是文件属主。chmod(1)命令用于修改这9个许
可数位。
该命令允许我们用u表示用户(属主),用g表示组,用o表示其他。有些书把这三种
用户夫妇
别称之为属主,组和世界。这会造成混乱,因为chmod命令用o表示其他,而不是属
主(owner
)。我们将使用术语用户,组和其他,以便与chmod命令一致。
图中的三类存取数-读、写及执行-以各种方式由不同的函数使用。我们将这些不同
的使用
方法摘要列在下面,当说明这些函数时,再进一步作讨论。
第一个规则是,我们用名字要打开任一类型的文件时,对该名字中包含的每一个目
录,包括
它可能隐含的当前工作目录都应具有执行许可数。这就是为什么对于目录其执行许
可数位常
被称为搜索位的原因。
例如,为了打开文件/usr/dict/words,我们需要具有对目录/,/usr,/usr/dict的
执行许可
数。然后,我们需要对该文件本身的适当许可数,这取决于我们要以何种方式打开
数。然后,我们需要对该文件本身的适当许可数,这取决于我们要以何种方式打开
它(只读,读-写等)。
如果当前目录是/usr/dic,那么为了打开文件words,我们需要有对该目录的执行许
可数。这
就是隐含了当前目录的例子,我们在指定打开文件words时,没有显式地提及/usr
/dicwor
ds与/words两种表示方法是一致的。
注意,对于目录的读许可数和执行许可数的意义是不相同的。读许可数允许我们读
目录,获
得在该目录中所有文件名的列表。当一个目录是我们要存取文件的路径名的一个分
量时,对
该目录的执行许可数使我们可通过该目录。(也就是搜索该目录,寻找一个特定的
文件名。
)
引用隐含目录的另一个例子是,如果PATH环境变量(在84节中说明)指定了一个我
们不具有
存取数的目录,那么shell决不会在该目录下打到可执行文件。
·对于一个文件的读许可数决定了我们是否能够打开该文件进行读操作。这对应于
open函数
的O 迹茫模*常病絉DONLY和O 迹茫模*常病絉DWR标志。
·对于一个文件的写许可数决定了我们是否可能够打开该文件进行写操作这对应于
open函数
的O 迹茫模*常病絎RONLY和O 迹茫模*常病絉DWR标志。
的O 迹茫模*常病絎RONLY和O 迹茫模*常病絉DWR标志。
·对于一个文件的写许可数决定了我们是否能够打开该文件进行写操作。这对应于
open函数
的O 迹茫模*常病絎RONLY和O 迹茫模*常病絉DWR标志。
·为了在open函数中对一个文件指定O 迹茫模*常病絋RUNC标志,我们必须对该文
件具有写
操作许可数。
·为了在一个目录中创建一个新文件,我们对该目录需要具有写许可数和执行许可
数。
·为了删除一个文件,我们需要对包含该文件的目录具有写许可数和执行许可数。
对该文件
本身则不需要有读、写许可数。
·如果我们用6个exec函数(89节)中的任何一个执行某个文件,则我们需要对该
文件具有
执行许可数。
进程每次打开、创建或删除一个文件时,系统核就进行文件存取数测试,而这种测
试可能涉
及文件的属主(st 迹茫模*常病絬id和st 迹茫模*常病絞id)。进程的有效ID(有
效用户ID
和有效组ID)、以及进程的添加
组ID(若支持的话)。两个属主ID是文件的性质,而有效ID和添加组ID则是进程的性
质。系统
核进行的测试是:
核进行的测试是:
1若进程的有效用户ID是O(超级用户),则允许存取。这给于了超级用户对文件系
统进行处
理的最充分的自由。
2若进程的有效用户ID等于文件的属主ID(也就是该进程拥有此文件):
a若适当的属主用户存取数位是设置的,则允许存许,
b否则拒绝存取。
关于确当的存取数位,我们指的是,如若进程为读而打开该文件,是属主用户-读
位应为1
。若进程为写而打开该文件,则属主用户-写位必须为1。若进程将执行该文件,则
属主用
户-执行位必须为1。
3若进程的有效组ID或进程的添加组ID之一等于文件的组ID:
a若适当的组存取数位是设置的,则允许存取,
b否则拒绝存取。
4若适当的其他用户存取数位是设置的,则允许存取,否则拒绝存取。
按序试执行这四步。注意,如若进程拥有此文件(第2步),则按用户存取数批准或
拒绝该进
程对文件的存取-不查看组存取数。相类似,若进程并不拥有该文件。但进程属于
某个适当
的组,则按组存取数批准拒绝该进程对文件的存取-不查看其它用户存取数。
46〓新文件和目录的属主关系
在第三章中,当说明用open或creat创建新文件时,我们没有说明赋与新文件的用
在第三章中,当说明用open或creat创建新文件时,我们没有说明赋与新文件的用
户ID和组I
D的值是什么。在420中,我们将说明如何创建一个新目录以及mkdir函数。关于
新目录的
属主关系的规则与本节将说明的新文件的属主关系的规则相同。
新文件的用户ID设置为进程的有效用户ID。关于组ID,POSIX1允许选择下列之一
作为新文
件的组ID。
1新文件的组ID可以是进程的有效组ID。
2新文件的组ID可以是它所在目录的组ID。
在SVR4中,新文件的组ID决取于它所在的目录的设置一组 迹茫模*常病絀D位是否
设置。如
果该目录的这
一位已经设置,则新文件的组ID设置为目录的组ID;否则新文件 组ID设置为进程
的有效组I
D。
43+BSD总是使用目录的组ID作为新文件的组ID。
其它系统允许以一个文件系统作为单位在POSIX1所允许的两种方法中选择一种,
为此在mo
unt(1)命令中使用了一个特殊标志。
FIPS |5|-1要求一个新文件的组ID是它所在目录的组ID。
使用POSIX1所允许的第二种方法(继承目录的组ID)使得在某个目录下创建的文件
和目录都
和目录都
有该目录的组ID。于是文件和目录的组属主关系从该点就向下传递。例如,在/va
r/spcol目
录中就使用这种方法。
正如前面提到的,这种设置组属主关系的方法对43+BSD是系统默认的,对SVR4则
是可选择
的。在SVR4之下,我们必须设置设置一组 迹茫模*常病絀D位。更进一步,为供这
种方法能
够正常工作,
SVR4的mkdir函数要自动地传递一个目录的设置一组 迹茫模*常病絀D位。(在4
20节中我
们将说明,mkdir就是这样做的)。
47〓access函数
正如前面所说明的,当用open函数打开一个文件时,系统核以进程的有效用户ID和
有效组ID
为基础执行其存取数限测试。有时,进程也希望按其实际用户ID和实际组ID来测试
其存取能
力。例如当一个进程使用设置一用户 迹茫模*常病絀D,或设置一组 迹茫模*常?
〗ID特征
作为另一个用户(或组)运行
时,这可能就是需要的。即使一个进程可能已经设置一用户 迹茫模*常病絀D为根
,它仍可
能想验证实际
能想验证实际
用户能否存取一个给定的文件。access函数是按实际用户ID和实际组ID进行存取数
测试的。
(经过45节结束部分中所述的4个步骤,但将有效改为实际。)
#include<unistdh>
int access(const char *pathname,int mode);
返回:若成功为0,出错为-1
其中,mode是图45中所列常数的按位或。
图45
实例
程序42显示了access函数的使用。下面是该程序的一些运动结果:
$ ls -1 aout
-rwxrwxr-x 1 stevens 105216 Jan 18 08:48 aout
$ aout aout
read access OK
open for reading OK
$ ls -1/etc/uucp/Systems
-rw-r----- 1 uucp 1441 Jul 18 15:05/etc/uucp/Systems
$ aout/etc/uucp/Systems
access error for/etc/uucp/Systems:Permission denied
open error for /etc/uucp/Systems:Permission denied
$ su〓成为超级用户
Password:输入超级用户口令
Password:输入超级用户口令
# chown uucp aout
# chkmod u+s aout将文件用户ID改为uucp,打开设置用户ID位
程序42〓access函数的实例。
在本例中,设置一用户 迹茫模*常病絀D程序可以确定实际用户不能读某个文件,
而open函
数却能打开该文件。
在上面例子以及在第八章中,我们有时要成为超级用户,以便例示某些功能是如何
工作的。
如果你使用多用户系统,但无超级用户许可数,那么你就不能完整地重复这些实例
。
48〓umask函数
至此我们已说明了与每个文件相关联的9个存取数位,在此基础上我们可以说明与
每个进程
相关联的文件方式创建屏蔽字。
umask函数为进程设置文件方式创建屏蔽字,并返回以前的值。9这是少数几个没有
出错返回
的函数中的一个。)
#include<sys/typesh>
#include<sys/stath>
mode 迹茫模*常病絫 umask(mode 迹茫模*常病絫 cmask);
返回:以前的文件方式创建 帘为?
其中,参数cmask是由图44中的9个常数(S 迹茫模*常病絀RUSR,S 迹茫模*常?
其中,参数cmask是由图44中的9个常数(S 迹茫模*常病絀RUSR,S 迹茫模*常?
〗IWUSR等
)按位或构成的。
在进程创建一个新文件或一个新目录时,就一定会使用文件方式创建屏蔽字。(回
忆33和3
4节,在那里我们说明了open和creat函数。这两个函数都有一个参数mode,它指
定了新文
件的存取许可数位。)我们将在420节说明如何创建一个新目录,在文件方式创建
屏蔽字中
为1的位,在文件mode中的相应位则一定被转成0。
实例
程序43创建了两个文件,创建第一个时,umask值为0,创建第二个时,umask值
禁止所有
组和其它存取数。若运行此程序可得如下结果,从中可见存取数是如何设置的。

$ umask〓第一次打印当前文件方式创建 帘为?
02
$ aout-
4 ls -1 foo bar
-rw------- 1 stevens 0 Nov 16 16:23 bar
-rw -rw-rw- 1 stevens 0 Nov 16 16:23 foo
$ umask〓观察文件方式创建屏蔽是否更改
02
02
程序43〓umask函数的实例
49〓chmod和fchmod函数
这两个函数使我们可以更改现存文件的存取许可数。
#include<sys/typesh>
#include<sys/stath>
int chmod(const char *pathname,mode 迹茫模*常病絫 mode);
int fchmod(int filedes,mode 迹茫模*常病絫 mode);
二个函数返回:若成功为0,出错为-1
chmod函数在指定的文件上进行操作,而fchmod函数则对已打开的文件进行操作。

fchmod函数并不是POSIX1的组成部分。这是SVR4和43+BSD的扩充部分。
为了改变一个文件的许可数位,进程的有效用户ID必须等于文件的属主,或者该进
程必须具
有超级用户许可数。
参数mode是图46中所示常数的某种按位或。
图46〓chmod函数的mode常数(取自<sys/stath>)
注意,在图46中,有9项是取自图44中的9个文件存取许可数位。我们另外加上
了二位设
置 迹茫模*常病絀D常数(S 迹茫模*常病絀S〔UG〕ID),保存 迹茫模*常病 正文
常数(S〖
 茫模*常病絀SVTX),以及三个组合常数(S 迹茫模*常病絀RWX〔UGO〕)
。(在这里,我们使用了标准Unix字符类算符 病 ,表示方括号算符中的任何一
。(在这里,我们使用了标准Unix字符类算符 病 ,表示方括号算符中的任何一
个字符。
例如,最后一个,S 迹茫模*常病絀RWX〔UGO〕表示了三个常数:S 迹茫模*常?
〗IRWXU、
S 迹茫模*常病絀RWXG和S 迹茫模*常病絀RWXO。这一字符
类算符是大多数Unix shell和很多标准Unix应用程序都提供的正规表达式的一种形
式。)
保存 迹茫模*常病 正文位(S 迹茫模*常病絀SVTX)不是POSIX1的一部分。我们
在下一节
说明其目的。
实例
先回忆一下为例示umask函数我们运行程序43时,文件foo和bar的最后状态:

$ ls-1 foo bar
-rw------- 1 stevens 0 Nov 16 16:23 bar
-rw-rw-rw- 1 stenens 0 Nov 16 16:23 foo
程序44修改了这两个文件的方式。在运行程序44后,我们见到的这两个文件的
最后状态
是:
$ ls -1 foo bar
-rw-r--r-- 1 stevens 0 Nov 16 16:23 bar
-rw-rwlrw- 1 stenens 0 Nov 16 16:23 foo
在此例子中,我们相对于foo的当前状态设置其许可数。为此,先调用stat获得其
在此例子中,我们相对于foo的当前状态设置其许可数。为此,先调用stat获得其
当前许可
数,然后修改它。我们已显式地打开了设置一组 迹茫模*常病絀D位、关闭了组〖
 茫模*?
2〗执行位。对普通文件这样
做的结果是对该文件可以加强制性记录锁,我们将在123节中讨论强制性锁。注
意,ls命
令将组 迹茫模*常病 执行许可数表示为l,它表示对该文件可以加强制性记录锁
。对文件b
ar,不管其当前许可数位如何,我们将其许可数设置为一绝对值。
程序44〓chmod函数的实例
最后也要注意到。在我们运行程序44后ls命令列出的时间和日期并不改变。在4
18节中
,我们会了解到chmod函数更新的只是i-node最近一次被更改的时间。按系统默认
方式ls-l
列出的是最后修改文件内容的时间。
chmod函数在下列条件下自动清除2个许可数位。
·如果我们试图设置普通文件的粘住位(S 迹茫模*常病絀SVTX),而且又没有超级
用户优先
数,那么mode
中的粘住位自动被关闭。(我们将在下一节说明粘住位)。这意味着只有超级用户才
能设置普
通文件的粘住位。这样做的理由是可以防止不怀好意的用户设置粘诠位,并试图以
通文件的粘住位。这样做的理由是可以防止不怀好意的用户设置粘诠位,并试图以
此方式填
满交换区(如果系统支持保存 迹茫模*常病 正文特征的话)。
·新创建文件的组ID可能不是调用进程所属的组。回忆一下46节,新文件的组I
D可能是父
目录的组ID。特别地,如果新文件的组ID不等于进程的有效组ID或者进程添加组I
D中的一个
,以及进程没有超级用户优先数,那么设置一组 迹茫模*常病絀D位自动被关闭。
这就防止
了用户创建一个设置一组 迹茫模*常病絀D文件,而该文件是由并非该用户所属的
组拥有的
。
43+BSD和其它贝克莱导出的系统增加了另外的安全性特征以试图防止保获位的错
误使用。
如果一个没有超级用户优先数的进程写一个文件,则设置一用户 迹茫模*常病絀
D位和设置
一组 迹茫模*常病絀D位自动
被清除。如果一个不怀好意的用户找到一个他可以写的设置一组 迹茫模*常病絀
D和设置一
用户 迹茫模*常病絀D文件,即使他可以修改此文件,但失去了对该文件的特别优
先数。
410〓粘住位
S 迹茫模*常病絀SVTX位有一段有趣的历史。在Unix的早期版本,这一位被称之为
通文件的粘住位。这样做的理由是可以防止不怀好意的用户设置粘诠位,并试图以
此方式填
满交换区(如果系统支持保存 迹茫模*常病 正文特征的话)。
·新创建文件的组ID可能不是调用进程所属的组。回忆一下46节,新文件的组I
D可能是父
目录的组ID。特别地,如果新文件的组ID不等于进程的有效组ID或者进程添加组I
D中的一个
,以及进程没有超级用户优先数,那么设置一组 迹茫模*常病絀D位自动被关闭。
这就防止
了用户创建一个设置一组 迹茫模*常病絀D文件,而该文件是由并非该用户所属的
组拥有的
。
43+BSD和其它贝克莱导出的系统增加了另外的安全性特征以试图防止保获位的错
误使用。
如果一个没有超级用户优先数的进程写一个文件,则设置一用户 迹茫模*常病絀
D位和设置
一组 迹茫模*常病絀D位自动
被清除。如果一个不怀好意的用户找到一个他可以写的设置一组 迹茫模*常病絀
D和设置一
用户 迹茫模*常病絀D文件,即使他可以修改此文件,但失去了对该文件的特别优
先数。
410〓粘住位
S 迹茫模*常病絀SVTX位有一段有趣的历史。在Unix的早期版本,这一位被称之为
S 迹茫模*常病絀SVTX位有一段有趣的历史。在Unix的早期版本,这一位被称之为
粘住位。
如果一个可执
行程序文件的这一位被设置了,那么在该程序第一次执行并结束时,该程序正文的
一个文本
被保存在交换区。(程序的正文部分是机器指令部分。)这使得下次执行该程序时能
较快地将
其装入内存区。其原因是:在交换区,该文件是被连续存放的,而在一般的Unix文
件系统中
,文件的各数据块很可能是随机存放的。对于常用的应用程序,例如文本编辑程序
和编辑程
序的各部分常设置它们所在文件的粘住位。自然,对交换区中可以同时存放的设置
了粘住位
的文件数有一定限制,以免过多占用交换区空间,但无论如何这是一个有用的技术
。因为在
系统再次自草前,文件的正文部分总是在交换区中,所以使用了名字"粘住"。后来
的Unix
版本称之为保存 迹茫模*常病 正文位,因此也就有了常数S 迹茫模*常病絀SVT
X。现今较
新的Unix系统大多数都具有虚存系统,以及快速文件系统,所以可再需要使用这种
技术。
目前粘住位的主要作用是针对目录文件的。如果对一个目录设置了粘住位,则只有
对该目录
对该目录
文件具有写许可数的用户并且满足下列条件之一,才能删除或换名该目录下的文件
:
·拥有此文件
·拥有此目录,或者
·是超级用户
目录/tmp和/var/spool/uucppublic是设置粘住位的后选者-这两个目录是任何用户
都可在
其中创建文件的目录。这两个目录对任一用户(用户、组和其他)的许可数通常都是
读、写和
执行。但是用户不应能删除或换名属于其他人的文件,为此在这两个目录的文件方
式中都设
置了粘住位。
POSIX1没有定义粘住位。但SVR4和43+BSD则支持这种特征。
411〓chown,fchown和lchown函数
chown函数可用于更改文件的用户ID和组ID。
#include<sys/typesh>
#include<unistdh>
int chown(const char *pathname,uid 迹茫模*常病絫 owner,gid〖C
 模*常?
〗t group);
int fchown(int filedes,uid 迹茫模*常病絫 owner,gid 迹茫模*常?
〗t gro
基于贝克莱的系统一直规定只有超级用户才能更改一个文件的属主。这样做的原因
是防止用
户改变其文件的属主从而摆脱盘空间限额对他们的限制。但是,系统V则允许任一
用户更改
他们所拥有的文件的属主。
按照 迹茫模*常病絇OSIX 迹茫模*常病紺HOWN 迹茫模*常病絉ESTRICTED的值,
POSIX1
在这两种形式的操作中选用一种。FIPS | 担迹茫模*常病?要求 迹茫模*常?
〗POSIX
 迹茫模*常病紺HOWN 迹茫模*常病絉ESTRICTED。
对于SVR4,此功能是个配置可选择项,而43+BSD则总对chown施加了限制。
回忆图25,该常数可选地定义在头文件<unistdh>中,而且总是可以用pathco
nf或fpath
conf函数查询。此可选项还与所引用的文件有关-可在每个文件系统基础上,使该
任选项起
作用或不起作用。在下文中,我们如提及"若 迹茫模*常病絇OSIX 迹茫模*常病?
CHOWN〖
 茫模*常病絉ESTRICTED起作用",则表示
这适用于我们正在谈及的文件,而不管该实际常数是否在头文件中定义。(例如,
43+BSD
总有这种限制,而并不在头文件中定义此常数。)
若 迹茫模*常病絇OSIX 迹茫模*常病紺HOWN 迹茫模*常病絉ESTRICTED对指定的
文件起作
文件起作
用,则
1只有超级用户进程能更改该文件的用户ID。
2若满足下列条件,一个非超级用户进程可以更改该文件的组ID:
a进程拥有此文件(其有效用户ID等于该文件的用户ID),以及
b参数owner等于文件的用户 迹茫模*常病絀D,参数group等于进程的有效组ID
或进程的
添加组ID之一。
这意味着,当 迹茫模*常病絇OSIX 迹茫模*常病紺HOWN 迹茫模*常病絉ESTRIC
TED有效时
,你不能更改其
他用户的文件的用户ID。你可以更入你所拥用的文件的组ID,但只能改到你所属于
的组。
如果这些函数由非超级用户进程调用,则在成功返回时,该文件的设置一用户〖C
 模*常?
〗ID位和设置一组 迹茫模*常病絀D位都被清除。
412〓文件长度
stat结构的成员st 迹茫模*常病絪ige包含了以字节为单位的该文件的长度。此字
段只对普
通文件、目录文件和符号连接才是有意义的。
SVR4对管道也定义了文件长度,它表示可从该管道中读到的字节数,我们将在14
2中讨论
管道。
管道。
对于普通文件,其文件长度可以是0,在读这种文件时,将得到文件结束指示。

对于目录,文件长度通常是一个数,例如16或512的整倍数,我们将在421节中说
明读目录
操作。
对于符号连接,文件长度是在文件名中的实际字节数。例如,
lrwxrwxrwx 1 root 7 Sep 25 07:14 lib->usr/lib
其中,文件长度7就是路径名usr/lib的长度。(注意,因为符号连接文件长度总是
由st〖C
 模*常病絪ige指示,所以符号连接并不包含通常C语言用作名字结尾的null字符
。)
SVR4和43+BSD也提供字段st 迹茫模*常病絙lksige和st 迹茫模*常病絙locks
。第一个
是对文件I/O较好的块长度,第
二个是所分配的实际512字节块块数。回忆一下39节,其中提到了当我们将st〖
 茫模*?
2〗blksige用
于读操作时,读一个文件所需的最少时间量。为了效率的缘故,标准I/O库(我们将
在第五章
中说明)也试图一次读、写st 迹茫模*常病絙lksige字节。
要知道,不同的Unix版本其st-blocks所用的单位可能不是512-字节块。使用此值
并不是可
并不是可
移植的。
文件中的空洞
在36节中,我们提及普通文件可以包含"空洞"。在程序32中例示了这一点。空
洞是由
超过文件结尾端的位移量设置,并写了某些数据后造成的。作为一个例子,考虑下
列情况:

$ ls -1 core
-rw-r--r-- 1 stevens 8483248 Nov 18 12:18 core
$ du -s core
272 core
文件core的长度超过8兆字节,而du命令则报告该文件所使用的盘空间总量是272个
512字节
块(139,264字节)。(在很多贝克莱类的系统上,du命令报告1024字节块块数;SV
R4则报告5
12-字节块块数。)很明显,此文件有很多空洞。
正如我们在36节中提及的,read函数对于没有写过的字节位置读到的数据字节是
0。如果
我们执行:
$ wc -c core
8483248 core
从此可见,正常的I/O操作读至整个文件长度。带-c选择项的(wc(1)命令计算文件
从此可见,正常的I/O操作读至整个文件长度。带-c选择项的(wc(1)命令计算文件
中的字符(
字节)数。)
如果我们使用公用程序,例如cat,复制这种文件,那么所有这些空洞都被写成实际
数据字节
0。
$ cat core>corecopy
$ ls -1 core*
-rw-r--r-- 1 stevens 8483248 Nov 18 12:18 core
-rw-rw-r-- 1 stevens 8483248 Nov 18 12:27 corecopy
$ du -s core*
272 core
16592 corecopy
从中可见,新文件所用的字节数是8,495,104(512×16,592)。此长度与
ls命令报
告的长度之间的差别是由于文件系统使用了若干块以保持指向实际数据块的各指针
。
有兴趣的读者应当参阅Bach〔19 福丁 的42节和Leffler〔1989〕的72节,
以更详细
地了解文件的物理安排。

--
※ 来源:·BBS 水木清华站 smth.org·[FROM: 162.105.8.216]

--
☆ 来源:.BBS 荔园晨风站 bbs.szu.edu.cn.[FROM: bbs@192.168.28.106]


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

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