免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2044 | 回复: 0
打印 上一主题 下一主题

unix环境高级编程--第4章 文件和目录(下) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2003-07-08 18:58 |只看该作者 |倒序浏览
4?13〓文件截短?
有时我们需要在文件尾端处截去一些数据以缩短文件。将一个文件的长度截短为0
是一个特
例,在open一个文件时指定O 迹茫模*常病絋RUNC标志就可以做到这一点。为了截
短文件可
以使用系统调用函数truncate和ftruncate。?
#include<sys/types?h>;?
#include<unistd?h>;?
int truncate(const char *?pathname,?off 迹茫模*常病絫 ?length);??

int ftruncate(int ?filedes,?off 迹茫模*常病絫 ?length);??
两个函数的返回;若成功为0,出错为-1?
这两个函数将由路径名pathname或打开文件描述符filedes指定的一个现存文件的
长度截短
为length。如果该文件以前的长度大于Length,则超过Length以外的数据就不再能
存取。如
果以前的长度短于Length,则其后果与系统有关。如果某个实现的处理是扩展该文
件,则在


以前的文件尾端和新的文件尾端之间的数据将读作为0(也就是在文件中创建了一个
空洞)。
?
SVR4和4?3+BSD提供了这两个函数。它们不是POSIX?1或XPG3的组成部分。?
SVR4截短或扩展一个文件。4?3+BSD只用这三个函数截短一个文件-不能用它们扩
展一个文
件。?
Unix从来就没有截短文件的一种标准方法。完全兼容的应用程序必须对文件制作一
个副本,
在制作它时只复制所希望的数据字节。?
SVR4的fcntl中有一个POSIX?1没有规定的命令F 迹茫模*常病紽REESP,它允许释
放一个文
件中的任何一部分,而不只是文件尾端处的一部分。?
在程序12?5中,我们使用了ftruncate函数,以便在获得对该文件的锁后,使一个
文件变完
。?
4?14〓文件系统?
为了说明文件连接的概念,先要对文件系统的结构有基本了解。同时,了解i〖C
模*常?
〗node和指向一个i 迹茫模*常病絥ode的目录项之间的区别也是很有益的。?
现在,有很多Unix文件系统的实现。例如,SVR4支持两种不同类型的盘文件系统:
传统的Un


ix系统V文件系统(称为55),以及统一文件系统(称为UFS)。在图2?6中,我们已看
到了这两
种文件系统的一个区别。UFS是以贝克莱快速文件系统为基础的。SVR4也支持另外
一些非磁
盘文件系统,两个分布式文件系统,以及一个自举文件系统,这些文件系统都不影
响下面的
讨论。本节讨论传统的Unix系统V文件系统。这种类型的文件系统可以回溯到Vers
ion7。?
我们可以把一个盘驱分成一个或多个分区。如图4?7中所示,每个分区可以包含一
个文件系
统。???
图4?7〓盘驱,分区和文件系统?
i 迹茫模*常病絥ode是固定长度的记录项,它包含有关文件的信息。?
在Version 7中,一个i 迹茫模*常病絥ode占用64字节,在4?3+BSD中,一个i〖
茫模*?
2〗node占用128字节。在SVR4
中,在磁盘上一个i 迹茫模*常病絥ode的长度与文件系统的类型有关:一个S5 i
迹茫模?
?2〗node占用64字节,而UFS i 迹茫模*常病絥ode占用128字节。?
如果在忽略自举块和超级块情况下,更细仔地观察文件系统,则可以得到图4?8中
所示的情
况。???


图4?8〓较详细的文件系统?
注意图4?8中的下列各点:?
·在图中有两个目录项指向同一i 迹茫模*常病絥ode。每个i 迹茫模*常病絥od
e中都有一
个连接计数,其值是指向该i 迹茫模*常病?
node的目录项数。只有当连接计数减少为0时,才可删除该文件(也就是可以释放该
文件占用
的数据块)。这就是为什么"解除对一个文件的连接"操作并不总是意味着"释放该文
件占
用的盘块"的原因。这也就是为什么删除一个目录项的函数被称之为unlink而不是
删除的原
因。在stat结构中,连接计数包含在st 迹茫模*常病絥link成员中,其基本系统
数据类型
是nlonk 迹茫模*常病絫。这
种连接类型称之为硬连接。回忆图2?7,其中,POSIX?1常数LINK 迹茫模*常病?
MAX指定
了一个文件连接数的最大值。?
·另外一种连接类型称之为符号连接。对于这种连接,该文件的实际内容(在数据
块中)包含
了该符号连接所指向的文件的名字。在下列例子中:?
lrwxrwxrwx 1 root 7 sep 25 07:14 lib->;urs/lib?
在该目录项中的文件名是lib,而在该文件中包含了7个数据字节usr/lib。在该i〖


茫模*?
2〗node中的文件类型是S 迹茫模*常病絀FLNK,于是系统知道这是一个符号连接
。?
·i 迹茫模*常病絥ode包含了所有与文件有关的信息:文件类型,文件存取许可
数位,文
件长度,指向该
文件所占用的数据块的指针等等。stat结构中的大多数信息都取自i 迹茫模*常?
〗node。
只有
二项数据存放在目录项中:文件名和i 迹茫模*常病絥ode编号数。i 迹茫模*常?
〗node编
号数的数据类型是ino 迹茫模*常病絫。?
·因为在目录项中的i 迹茫模*常病絥ode编号数指向同一文件系统中的一个i〖C
模*常?
〗node,所以我们不能使一个
目录项指向另一个文件系统的i 迹茫模*常病絥ode。这就是为什么ln(1)命令(构造一
个指向一个现存文件的新目录项),不能跨越文件系统的原因。我们将在下一节说明
link函数。?
·当不更改文件系统情况下为一个文件改换名字时,该文件的实际内容并未移动
,需要做的
是构造一个指向现存i 迹茫模*常病絥ode的新目录项,并除去老的目录项。例如
,为将文


件/usr/lib/fo
o换名为/usr/foo,如果目录/usr/lib和/usr在同一文件系统上,则文件foo的内容
无需移动
。这就是mv(1)命令的通常操作方式。?
我们说明了普通文件的连接计数的概念,但是对于目录文件的连接计数字段又如何
呢?假定
我们在新目录中构造了一个新目录:?
$ mkdir testdir?
图4?9显示了其结果。注意,在该图中,我们显式地显示了?和??目录项。??
?
图4?9〓在创建了目录testdir后的样本文件系统?
编号2549的i 迹茫模*常病絥ode其类型字段表示它是一个目录,而连接计数2。任
何一个叶
目录(不包含任
何其它目录,也就是子目录的目录)其连接计数总是2,数值2来自于命名该目录的
目录项(te
stdir)以及在该目录中的?项。编号为1267的i 迹茫模*常病絥ode,其类型字段
表示它是
一个目录,而其
连接计数则大于或等于3。它大于或等于3的原因是,至少有由三个目录项指向它:
一个是命
名它的目录项(在图4?9中段有表示出来),第二个是在该目录中的?项,第三个是


在其子目
录testdir中的??项。注意,在工作目录中的每个子目录都使该工作目录的连接
计数增1。
?
正如前面所述,这是Unix文件系统的经典格式,在Bach〔1986〕一市的第四章中对
此作了说
明。关于贝克莱快速文件系统对此所作的更改请参阅Leffler etal〔1989〕中
的第七章
。?
4?15〓link,unlink,remove和rename函数?
如上节所所述,任何一个文件可以有多个目录项指向其i 迹茫模*常病絥ode。创
建一个向
现存文件的连接的方法是使用link函数。?
#include<unistd?h>;?
int link(const char *?existingpath,?const char *?newpath);??
返回:若成功为0,出错为-1?
此函数创建一个新目录项,newpath,它引用现存文件existingpath。如若newpath
已经存在
,则出错返回。?
创建新目录项以及增加连接计数应当是个原子操作。(请回忆在3?11节中对原子操
作的讨论
。)?


大多数实现,例如SVR4和4?3+BSD要求这两个路径名在同一个文件系统中。?
POSIX?1允许支持跨越文件系统的连接的实现。?
只有超级用户进程可以创建指向一个目录的新连接。其理由是这样做可能在文件系
统中形成
循环,大多数处理文件系统的公用程序都不能处理这种情况。(我们在4?16节中将
说明一个
由符号连接引入的循环的例子。)?
为了移去一个现存的目录项,我们调用unlink函数。?
#include <unistd?h>;?
int unlink(const char *?pathname);??
返回:若成功为0,出错为-1?
此函数移去目录项,并将由pathname所引用的文件的连接计数减1。如果该文件还
有其它连
接,则仍可通过其它连接存取该文件的数据。如果出错,则该文件作任何更改。?

我们在前面已经提及,为了解除一个对文件的连接,我们必须对包含该目录项的目
录具有写
和执行许可数。正如我们在4?10节中所述,如果对该目录设置了粘住位,则我们
对该目录
必须具有写许可数,并且?
·拥有该文件,或者?
·拥有该目录,或者?


·具有超级用户优先数?
只有当连接计数达到0时,该文件的内容再可被删除。另一个条件也阻止删除文件
的内容-
只要有一个进程使该文件打开,其内容也不能删除。当关闭一个文件时,系统核首
先检查使
该文件打开的进程计数。如果该计数达到0,然后系统核检查其连接计数,如果这
也是0,那
么就删除该文件的内容。?
实例?
程序4?5打开一个文件,然后unlink它。执行该程序的进程然后睡眠15秒钟,接着
就终止。
???
程序4?5〓打开一个文件,然后unlink它运行该程序,其结果是:?
$ ls -1 tempfile〓查看文件大小?
-rw-r--r-- 1 stevens 9240990 Jul 31 13:42 tempfile?
$ df/home〓检查空间区?
Filesystem kbytes used avail capacity Mounted on?
/dev/sd0h 282908 181979 72638 71% /home?
$ a?out &〓在后台运行程序4?5?
1364〓shell打印其进程ID?
$ file unlinked〓该文件是未连接的?
ls -1 tempfile〓观察文件是否仍旧存在?


tempfile not found〓目录项已删除?
$ df/home〓检查空间区有无变化?
Filesystem kbytes used avail capacity Mounted on?
/dev/sd0h 282908 181979 72638 71% /home?
$ done〓程序执行结束,所有打开文件皆关闭,相应盘空间成为空间?
df/home?
Filesystem kbytes used avail capacity Mounted on?
/dev/sd0h 282908 172939 81678 68% /home?
现在,盘空间区增加了9?2Mbytes?
unlink的这种特性常由程序用来确保即便程序崩溃它所创建的临时文件也不会遗留
下来。进
程用open或creat创建一个文件,然后立即调用unlink。因为该文件仍旧是打开的
,所以不
会将其内容删除。只有当进程关闭该文件或终止时(在这种情况下,系统核关闭该
进程所打
开的全部文件),该文件的内容再被删除。?
如若pathname是一符号连接,那么unlink涉及的是符号连接而不是由该连接所引用
的文件。
?
超级用户可以调用其参数pathname指定一个目录的unlink,但是通常不使用这种方
式,而应
当使用函数rmdir我们将在4?20节中说明rmdir函数。?


我们也可以用remove函数解除对一个文件或目录的连接。对于文件,remove的功能
与unlink
相同。对于目录,remove的功能与rmdir相同。?
#include<stdio?h>;?
int remove(const char *?pathname);??
返回:若成功为0,出错为-1?
ANSI C指定remove函数删除一个文件。这更改了Unix历来使用的名字unlink,其原
因是实现C
标准的大多数非Unix系统并不支持文件连接。?
文件或目录用rename函数换名。?
#include<stdio?h>;?
int rename(const char *?oldname,?const char *?newname);??
返回:若成功为0,出错为-1?
ANSI C 对文件定义了此函数。(C标准不处理目录。)POSIX?1扩展此定义包含了目
录。?
依赖于oldname是指文件还是目录,有两种情况要加以说明。我们也应说明如果ne
wname已经存在将会发生什么。?
1?如若oldname说明一个文件而不是目录,那么为该文件换名。在这种情况下,
如果newnam
e已存在,则它不能引用一个目录。如若newname已存在,而且不是一个目录,则先
将该目录
项删除然后将oldname换名为newname。对包含oldname的目录以及包含newname的目


录,调用
进程必须具有写许可数,因为将更改这两个目录。?
2?如若oldname说明一个目录,那么为该目录换名。如果newname已存在,则它必
须引用一
个目录,而且该目录应当是空目录。(我们提及空目录时,指的是该目录中只有?
和??项
。)如果newname存在(而且是一个空目录),则先将其删除,然后将oldname换名为
newname。
另外,当我们为一个目录换名时,newname不能包含oldname作为其路径前缀。例如
,我们不
能将/usr/foo换名为/usr/foo/testdir,因为老名字(/usr/foo)是新名字的路径前
缀,因而
不能将其移去。?
3?作为一个特例,如果oldname和newname引用同一文件,则函数并不作任何更改
而成功返
回。?
如若newname已经存在,则调用进程需要对其有写许可数(如同删除情况一样)。另
外,选用
进程将删除oldname目录项,并可能要创建newname目录项,所以它需要对包含old
name的目
录及包含newname的目录具有写和执行许可数。?
4?16〓符号连接?


一个符号连接是对一个文件的间接指针,它与上一节所述的硬连接有所不同,硬连
接直接指
向文件的i 迹茫模*常病絥ode。引进符号连接的原因是为了避免硬连接的一些限
制:(a)硬
连接通常要求
连接和文件位于同一文件系统中,(b)只有超级用户才能创建一个到一个目录的硬
连接。对
符号连接以及它指向什么没有文件系统限制,任何用户都可创建指向目录的符号连
接。符号
连接典型地用于将一个文件或整个目录结构移到系统中的某个其它位置。?
符号连接由4?2BSD引进,后来又得到SVR4的支持。在SVR4中,传统的系统V文件系
统(S5)和
统一文件系统(UFS)都支持符号连接。?
POSIX 1003?1-1990标准并不包括符号连接。但很可能会加到1003?1a中。?
当使用以名字引用一个文件的函数时,我们应当了解该函数是否处理符号连接功能
。也就是
是否跟随符号连接到达它所连接的文件。如若该函数处理符号连接功能,则该函数
的路径名
参数引用由符号连接指向的文件。否则,一个路径名参数引用连接本身,而不是由
该连接指
向的文件。图4?10摘要列出了本章中所说明的各个函数是否处理符号连接功能。
因为rmdir


并不是针对符号连接进行定义的(宏path是符号连接则出错返回),所以在图4?10
中没有列
出这一函数。因为对符号连接的处理是由返回文件描述符的函数进行的(通常是op
en),所以
以文件描述符作为参数的函数(fstat,fchmod等)也未列出。chown是否跟随符号连
接取决于
实现-各种有关细节请参阅4?11节。???
图4?10〓各个函数对符号连接的处理?
实例?
使用符号连接可能在文件系统中引进循环。大多数查找路径名的函数在这种情况发
生时都返
回值为ELOOP的errno考虑下列命令序列:?
$ mkdir foo〓构造-新目录?
$ touch foo/a〓创建O长文件?
$ln -s ??/foo foo/testdir 创建-符号连接?
$ ls -1 foo?
total 1?
-rw-rw-r-- 1 stevnens 0 Dec 6 06:06 a?
lrwxrwxrwx 1 stenens 6 Dec 6 06:06 testdir ->; ??/foo?
这创建了一个目录foo,它包含了一个名为a的文件以及一个符号连接,它指向foo
。在图4?
11中显示了这种结果,图中以园表示目录,以正方形表示一个文件。如若我们写一


段简单的
程序,它使用标准函数ftw(3)以降序周游文件结构,打印每个遇到的路径名,则其
输出是:
?
foo?
foo/a?
foo/testdir?
foo/testdir/a?
foo/testdir/testdir?
foo/testdir/testdir/a?
foo/testdir/testdir/testdir?
foo/testdir/testdir/testdir/a?
(many more lines)?
ftw returned -1:Too many levels of symbolic links?
在4?21节中,提供了我们自己的ftw函数版本,它用lstat代替stat以阻止它跟随
符号连接
。???
图4?11〓创建一个循环的符号连接testdir?
这样一个连接很容易被删除-因为unlink并不跟随符号连接,所以我们可以unlink
文件foo/
testdir。但是如果我们创建了一个构成这种循环的硬连接,那么就很难去除它?
?。这就


是为什么link函数不允许构造指向目录的硬连接的原因。(除非进程具有超级用户
优先数。)
?
在open一个文件时,如若传递给open函数的路径名指定了一个符号连接,那么ope
n跟随此连
接到指定的文件。若此符号连接所指向的文件并不存在,则open出错返回,表示它
不能打开
该文件。这可能会使不熟悉符号连接的用户感到迷惑,例如:?
$ ln -s /no/such/file myfile〓创建-符号连接?
$ ls myfile?
myfile〓ls查到该文件?
$ cat myfile〓试图观看该文件?
cat:myfile:No such file or directory?
$ ls -1 myfile〓试-l选项?
lrwxrwxrwx 1 stevens 13 Dec 6 07:27 myfile->;/no/such/file?
文件myfile是存在的,但cat都称没有这一文件。其原因是myfile是个符号连接,
由该符号
连接所指向的文件并不存在。ls命令的-l选择项给与我们两个提示:第一个字符是
l,它表
示这是一个符号连接;而->;也表示这是一个符号连接。ls命令还有另一个选择项(
-F),它在
是符号连接的文件名后加一个@符号,在未使用-l选择项时,这可以帮助识别出符


号连接。
?
4?17〓symlink和readlink函数?
symlink函数创建一个符号连接。?
#include<unistd?h>;?
int symlink(const char *?actualpath,?const char *?sympath);??
返回:若成功为0,出错为-1?
该函数创建了一个指向actualpath的新目录项sympath,在创建此符号连接时,并
不要求act
ualpath已经存在。(在上一节结束部分的例子中我们已经看到了这一点。)同时,
?
?场迹耍仟常病 在编写本节时,作者在自己的系统? 作为一个实验做了这一点。
文件系统
变得错误百出,正常的fsck(1)公共程序不能解决问题。为了修复此文件系统不得
不使用了
并不推荐使用的工具clri(和dcheck(。actualpath和sympath并不必须位于同
一文件系
统中。?
因为open函数跟随符号连接,所以我们需要有一种方法打开该连接本身,并读在该
连接中的
名字。readlink函数提供这种功能。?
#include <unistd?h>;?


int readlink(const char *?pathname,?char *?buf?,int ?bufsize);??

返回:若成功为读的字节数,出错为-1?
此函数组合了open,read和close的所有操作。?
如果此函数成功,则它返回读入buf的字节数。在buf中返回的符号连接的内容不以
null字符
终止。?
4?18〓文件的时间?
对每个文件保持有三个时间字段。它们的意义示于图4?12中中。???
图4?12〓与每个文件相关的三个时间值?
注意修改时间(st 迹茫模*常病絤time)和更改状态时间(st 迹茫模*常病絚time
)之间的区
别。修改时间是文件内容最后
一次被修改的时间。更改状态时间是该文件的i 迹茫模*常病絥ode最后一次被修
改的时间
。在本章中我们
已说明了很多操作,它们影响到i 迹茫模*常病絥ode,但并没有更改文件的实际内
容:更改
文件的存取数
、更改用户ID、更改连接数等等。因为在i 迹茫模*常病絥ode中的所有信息都是
与文件的
实际内容分布存放的,所以,除了文件数据修改时间以外,需要更改状态时间。?



注意,系统并不保持有对一个i 迹茫模*常病絥ode的最后一次存取时间。所以ac
cess和sta
t函数并不更改这三个时间中的任一个。?
系统管理员常常使用存取时间来删除在一定的时间范围内没有存取过的文件。典型
的例子是
删除在过去一周内没有存取过的名为a?out或core的文件。find(1)命令常被用来
进行这种
操作。?
修改时间和更改状态时间可被用来归档其内容已经被修改或其i 迹茫模*常病絥o
de已经被
更改的那些文件。?
ls命令按这三个时间值中的一个排序进行显示。按系统默认(用-l或-t选择项调用
时),它按
文件的修改时间的先后排序显示。-u选择项使其用存取时间排序,-c选择项则使其
用更改状
态时间排序。?
用4?13摘要列出了我们已说明过的各种函数对这三个时间的作用。回忆4?14节中
所述,目
录是包含目录项(文件名和相关的i 迹茫模*常病絥ode编号)的文件,增加、删除
或修改目
录项会影响到与


其所在目录相关的三个时间。这就是在图4?13中包含而列的原因,其中一列是与
该文件(或
目录)相关的三个时间,另一列是与所引用的文件(或目录)的父目录相关的三个时
间。例如
,创建一个新文件影响到包含此新文件的目录,也影响该新文件的i 迹茫模*常?
〗node。
但是,读或写一
个文件只影响该文件的i 迹茫模*常病絥ode,而对父目录则无影响。(mkdir和rm
dir函数在
4?20节中说明
。utime函数在下一节中说明。6个exec函数在4?20节中讨论。在第十四章说明mk
fifo和pip
c函数。)???
图4?13〓各种函数对存取、修改和更改状态时间的作用?
4?19〓utime函数?
一个文件的存取和修改时间可以用utime函数更改。?
#include<sys/types?h>;?
#include<utime?h>;?
int utime(const char *?pathname,?const struct utimbuf *?times);??
返回:若成功为0,出错为-1?
此函数所使用的结构是:?
struct untimbuf{?


time 迹茫模*常病絫 actime; /*存取时间*/?
time 迹茫模*常病絫 modtime;/*修改时间*/?
}?
在此结构中的两个时间值是日历时间。如同1?10节中所述,这是1970?1?1,00
:00:00
以来国际标准时所经过的秒数。?
此函数的操作以及执行它所要求的优先数取决于times参数是否是NULL。?
1?如若times是一个空指针,则存取时间和修改时间两者都设置为当前时间。为了
执行此操
作必须满足下列两条件之一:(a)进程的有效用户ID必须等于该文件的属主ID;(b
)进程对该
文件必须具有写许可数。?
2?如若times是非空指针,则存取时间和修改时间被设置为times所指向的结构中
的值。此
时,进程的有效用户ID必须等于该文件的属主ID,或者进程必须是一个超级用户进
程。对文
件只具有写许可数是不够的。?
注意,我们不能对更改状态时间st 迹茫模*常病絚time指定一个值,当调用utim
e函数时,
此字段被自动更新。?
在某些Unix版本中,touch(1)命令使用此函数。另外,标准归档程序tar(1)和cpi
o(1)可选


地调用utime,以便将一个文件的时间值设置为将它归档时的值。?
实例?
程序4?6使用带O 迹茫模*常病絋RUNC选择项的open函数将文件长度截短为0,但
并不更改
其存取时间及修
改时间。为了做到这一点,首先用stat函数得到这些时间,然后截短文件,最后再
用utime
函数复置这两个时间。???
程序4?6〓utime函数的实例?
以下列方式运行程序4?6:?
$ ls -1 changemod times〓观察长度和最后修改时间?
-rwxrwxr-x 1 stevens 24676 Dec 4 16:13 changemod?
-rwxrwxr-x 1 stevens 24676 Dec 6 09:24 times?
$ ls -lu changemod times〓观察最后存取时间?
-rwxrwxr-x 1 stevens 24676 Feb 1 12:44 changemod?
-rwxrwxr-x 1 stevens 24676 Feb 1 12:44 times?
$ date〓打印今天日期?
Sun Feb 3 18:22:33 MST 1991?
$ a?out changemod times〓运行程序4?6?
$ ls -1 changemod times〓检查结果?
-rwxrwxr-x 1 stevens 0 Dec 4 16:13 changemod?
-rwxrwxr-x 1 stevens 0 Dec 6 09:24 times?


$ ls -lu changemod times〓检查最后存取时间?
-rwxrwxr-x 1 stevens 0 Feb 1 12:44 changemod?
-rwxrwxr-x 1 stevens 0 Feb 1 12:44 times?
$ ls -lc changemod times〓更改状态时间?
-rwxrwxr-x 1 stevens 0 Feb 3 18:23 changemod?
-rwxrwxr-x 1 stevens 0 Feb 3 18:23 times?
正如我们所予料的一样,最后修改时间和最后存取时间未变。但是,更改状态时间
则更改为
程序运行时的时间。(这两个文件的最后存取时间相同的原因是,这是它们的目录
用tar命令
归档时的时间。)?
4?20〓mkdir和rmdir函数?
用mkdir函数创建目录,用rmdir函数删除目录。?
#include<sys/types?h>;?
#include<sys/stat?h>;?
int mkdir(const char *?pathname,?mode 迹茫模*常病絫 ?mode?);?
返回:若成功为0,出错为-1?
此函数创建一个新的空目录。?和??目录项是自动创建的。所指定的文件存取许
可数mode
,由进程的文件方式创建屏蔽字修改。?
常见的错误是指定与文件相同的mode(只指定读、写许可权)。但是,对于目录我们
常至少要


设置1个执行许可权位,以允许存取该目录中的文件名。(见练习4?18。)?
按照4?6节中讨论的规则,设置新目录的用户ID和组ID。?
SVR4也使新目录继承父目录的设置一组 迹茫模*常病絀D位。这就使得在新目录中
创建的文
件将继承该目录的组ID。?
4?3+BSD并不要求继承此设置一组 迹茫模*常病絀D位,因为不论设置一组  CD
*常病?
ID位如何,新创建的文件和目录总是继承父目录的组 迹茫模*常病絀D。?
早期的Unix版本并没有mkdir函数,它是由4?2BSD和SVR3引进的。在早期版本中,
进程要调
用mknod函数以创建一个新目录。但是只有超级用户进程才能使用mknod函数。为了
避免这一
点,创建目录的命令mkdir(1)必须由根(root)拥有,而且打开了其设置一用户〖C
模*常?
〗ID位。进程为了创建一个目录,必须用system(3)函数调用mkdir命令(1)。?
用rmdir函数可以删除一个空目录。?
#include<unistd?h>;?
int rmdir(const char *?pathname);??
返回:若成功为0,出错为-1?
如果此调用使目录的连接计数成为0,并且也没有其它进程打开此目录,则释放由
此目录占
用的空间。如果在连接计数达到0时,有一个或几个进程打开了此目录,则在此函


数返回前
删除最后一个连接,删除?和??项。另外,在此目录中不能再创建新文件。但是
在最后一

进程关闭它之前并不释放此目录。(即使某些进程打开该目录,它们在此目录下,
因为为使r
mdir函数成功执行,该目录必须是空的。)?
4?21〓读目录?
对某个目录具有读取取数的任一用户都可读该目录。但是只有系统核才能写目录(
防止文件
系统发生混乱)。回忆4?5节,一个目录的写存取数限位和执行数限位决定了在该
目录中能
否创建新文件以及删除文件,它们并不表示能否写目录本身。?
目录的实际格式依赖于Unix的具体实现。早期的系统,例如Version 7,有一个比
较简单的
结构:每个目录项是16个字节,其中14个字节是文件名,2个字节是i 迹茫模*常?
〗node编
号数。而对于4
?2BSD而言,由于它允许相当长的文件名,所以每个目录项的长度是可变的。这就
意味着读
目录的程序与系统相关。为了简化这种情况UNIX现在包含了一套与读目录有关的例
程,它们


是POSIX?1的一部分。?
#include<sys/types?h>;?
#include<dirent?h>;?
DIR *opendir(const char *?pathname);??
返回:若成功为指针,出错为NULL?
struct dirent *readdir(DIR *?dp);??
返回:若成功为指针,在目录尾或出错为NULL?
void rewinddir(DIR *?dp?);?
int closedir(DIR *?dp?);?
返回:若成功为0,出错为-1?
回忆一下,我们在程序1?1中(ls命令的基本实现部分)使用了这些函数。?
定义在头文件<dirent?h>;中的dirent结构是与实现有关的。SVR4和4?3+BSD定义
此结构至
少包含下列两个成员:?
struct dirent {?
ino 迹茫模*常病絫 d 迹茫模*常病絠no;/*i-节点号*/?
char d 迹茫模*常病絥ame〔NAME 迹茫模*常病組AX+1〕;/*以null符终止的文件
名*/?
}?
POSIX?1并没有定义d 迹茫模*常病絠no,因为这是一个实现特征。POSIX?1在此
结构中只
定义d 迹茫模*常病絥ame项。?


注意,SVR4没有的NAME 迹茫模*常病組AX定义为一个常数-其值依赖于该目录所在
的文件
系统,并且通常
可用fpathconf函数取得。在BSD类文件系统中,NAME 迹茫模*常病組AX的常用值
是255。(
见图2?7。)但
是,因为文件名是以null字符结束的,所以在头文件中如何定义数组d-name并无多
大关系。
?
DIR结构是一个内部结构,它由这四个函数用来保存正被读的目录的有关信息。其
作用类似
于FILE结构。FILE结构由标准I/O库维护(我们将在第五章中说明)。?
由opendir返回的指向DIR结构的指针由另外三个函数使用。opendir执行初始化操
作,使第
一个readdir读目录中的第一个目录项。在目录中各目录项的顺序是与实现有关的
。它们通
常并不是按字母顺序排列的。?
实例?
我们将使用这些目录例程编写一个周游文件层次结构的程序。其目的是得到如同我
们在图4
?2中所示的各种类型的文件数。程序4?7只有一个参数,它说明起点路径名,从
该点开始


递归降序周游文件层次结构。系统V提供了一个实际周游此层次结构的函数ftw(3)
,对于每
一个文件它都调用一个用户定义函数。此函数的问题是:对于每一个文件,它都调
用stat函
数,这就使程序跟随符号连接。例如,如果从root开始,并且有一个名为/lib的符
号连接,
它指向/usr/lib,则所有在目录/usr/lib中的文件都两次计数。为了纠正这一点,
SVR4提供
了另一个函数nftw(3),它具有一个停止跟随符号连接的可选择项。尽管可以使用
nftw,但
是为了说明目录例程的使用方法,我们还是编写了一个简单的文件周游程序。??
?
程序4?7〓递归降序周游目录层次结构,并按文件类型计数?
在程序中,我们提供了比所要求的更多的通用性。所以这样做是为了例示实际ftw
函数的应
用情况。例如,函数myfunc总是返回0,但是调用它的函数却准备处理非0返回。?

关于降序周游文件系统的更多信息,以及在很多标准Unix命令(find,ls,tar等)中
使用这种
技术的情况,请参阅Fowler,Korn及Vo〔1989〕。4?3+BSD提供了一新套的目录周
游函数-
请参阅fts(3)手册页。?


4?22〓Chdir,fchdir和getcwd函数?
每个进程有一个当前工作目录,此目录是搜索所有相对路径名的起点(小以斜线开
始的路径
名为相对路径名)。当用户登录到Unix系统时,其当前工作目录通常是口令字文件
(/etc/pas
swd)中该用户记录项的第6个字段-用户的起始目录。当前工作目录是进程的一个属
性;起
始目录则是登录名的一个属性。进程调用chdir或fchdir函数可以更改当前工作目
录。?
#include<unistd?h>;?
int chdir(const char *?pathname);??
int fchdir(int ?filedes);??
两个函数的返回:若成功为0,出错为-1?
在这两个函数中,我们可以分别用pathname或打开文件描述符来指定新的当前工作
目录。?
fchdir不是POSIX?1的所属部分,SVR4和4?3+BSD则支持此函数。?
实例?
因为当前工作目录是一个进程的属性,所以它只影响调用chdir的进程本身,而不
影响其它
进程。(我们将在第八章较详细地说明进程之间的关系。)这就意味着程序4?8并不
会产生我
们希望得到的后果。如果编译程序4?8,并且调用其可执行目标代码文件,则可以


得到下列
结果:?
$ pwd?
/usr/lib?
$ mycd?
chdir to/tmp succeeded?
$ pwd?
/usr/lib?
从中可以看出,执行myccl程序的shell的当前工作目录并没有改变。由此可见,s
hell应当
直接调用chdir函数,所以cd命令的执行程序直接包含在shell程序中。?
因为系统核保持有当前工作目录的知识,所以我们应能取其当前值。不幸的是,系
统核为每
个进程只保存其当前工作目录的i 迹茫模*常病絥ode编号以及设备标识,系统核
并不保存
该目录的完整路径名。???
程序4?8〓chdir函数的实例?
我们需要一个函数,它从当前工作目录开始,找到其上一级的目录,然后读其目录
项,直到
该目录项中的i 迹茫模*常病絥ode编号数与工作目录i 迹茫模*常病絥ode编号数
相同,这
样地就找到了其对应的文件。


按照这种方法,逐层上移,直到遇到根(root),这样就得到了当前工作目录的绝对
路径名。
很幸运,现存函数getcwd就是提供这种功能的。?
#include<unistd?h>;?
char *getcwd(char *?buf?,size 迹茫模*常病絫 ?size?);?
返回:若成功为buf,出错为NULL?
向此函数传递两个参数,一个是缓存地址buf,另一个是缓存的长度size。该缓存
必须有足
够的长度以容纳绝对路径名再加上一个null终止字符,否则出错返回。(请回忆2?
5?7节中
有关为最大长度路径名分配空间的讨论。)?
某些getcwd实现允许第一个参数buf为NULL。在这种情况下,此函数调用malloc动
态地分配s
ize字节数的空间。这不是POSIX?1或XPG3的所属部分,应予避免。?
实例?
程序4?9将工作目录更改至一个特定的目录,然后调用getcwd,最后打印该工作目
录。如果
运行该程序,则可得:?
$ a?out?
cwd=/var/spool/uucppublic?
$ ls -1/usr/spool?
lrwxrwxrwx 1 root 12 Jan 31 07:57/usr/spool->;??/var/spool???


程序4?9〓getcwd函数的实例?
注意,chdir跟随符号连接(正如在图4?10中所示那样),但是当getcwd沿目录树上
溯遇到/v
ar/spool目录时,它并不了解该目录由符号连接/usr/spool所指向。这是符号连接
的一种特
性。?
4?23〓特殊设备文件?
st 迹茫模*常病絛ev和st 迹茫模*常病絩dev这两个字段经常引起混淆,当在11
?9节编写
ttyname函数时,我们需要使用这两个字段。有关规则是很简单的:?
·每个文件系统都由其主、次设备号而为人所知。设备号所用的数据类型是基本系
统数据类
型dev 迹茫模*常病絫。回忆图4?7,一个盘驱经常包含若干个文件系统。?
·我们通常可以使用两个大多数实现所定义的宏:major和minor来取得主、次设备
号。这就
意味着我们无需关心这两个数是如何存放在一个dev?t对象中的。?
早期的系统用16位整型存放设备号,8位用于主设备与,8号用于次设备号。SVR4使
用32位;
14位用于主设备号,18位用于次设备号。4?3+BSD则使用16位:8位用于主设备号
,8位用于
次设备号。?
POSIX?1说明dev 迹茫模*常病絫类型是存在的,但没有定义它包含什么,或如何


取得其内
容。大多数实现定义了宏major和minor,但在那一个头文件中定义它们则与实现有
关。?
·系统中每个文件名的st 迹茫模*常病絛ev值是文件系统的设备号,该文件系统
包含了该
文件名和其对应的i 迹茫模*常病絥ode。?
·只有字符特殊文件和块特殊文件才有st 迹茫模*常病絩dev值。此值包含该实际
设备的设
备号。?
实例?
程序4?10为每个命令行参数打印设备号,另外,若此参数引指的是字符特殊文件
或块特殊
文件,则也打印该特殊文件的st 迹茫模*常病絩dev值。???
程序4?10〓打印st 迹茫模*常病絛ev和st 迹茫模*常病絩dev值?
在SVR4中,为了定义宏major和minor,一定要包括头文件<sys/sysmacro?h>;。运
行此程序
得到下面的结果:?
$ a?out//home/stevens/dev/tty[ab]?
/:dev=7/0?
/home/stevens:dev=7/7?
/dev/ttya:dev=7/0(character)rdev=12/0?
/dev/ttyb:dev=7/0(character)rdev=12/1?


$ mount?
/dev/sd0a on /?
/dev/sd0h on/home?
$ ls -1/dev/sd0[ah]/dev/tty[ab]?
brw-r----- 1 root 7,0 Jan 31 08:23/dev/sd0a?
brw-r----- 1 root 7,7 Jan 31 08:23/dev/sd0h?
crw-rw-rw- 1 root 12,0 Jan 31 08:22/dev/ttya?
crw-rw-rw- 1 root 12,1 Jul 9 10:11/dev/ttyb?
传递给该程序的头两个参数是目录(根和/home/stevens),后两个是设备名/dev/tt
g[ab],
这两个设备是字符特殊设备。从程序的输出可见,根目录和/home/steven目录的设
备号不同
,这表示它们位于不同的文件系统中。运行mount命令证明了这一点。然后我们用
ls命令查
看由mount命令报告的两个盘设备,和两个终端设备。这两个盘设备是块特殊设备
,面向个
终端设备则是字符特殊设备。(通常,只有块特殊设备才能包含随机存取文件系统
,它们是
:硬、软盘驱动器和CD-ROM等。Unix的较老版本支持磁带存放文件系统,但这从未
广泛使用
过。)注意,两个终端设备的文件名和i-node在设备I/O上(st 迹茫模*常病絛ev,
这是根文


件系统,它包含了/dev文件系统),但是它们的实际设备号是:12/0和12/1。?
4?24〓sync和fsync函数?
传统的Unix实现在系统核中没有缓冲存储器,大多数磁盘I/O都通过缓存进行。当
交数据写
到文件上时,通常该数据先由系统核复制到其一个缓存中,如果该缓存尚未写满,
则并不将
其排入输出队列,而是等待其写满或者当系统核需要重用该缓存以便存放其它盘块
数据时,
再将该缓存排入输出队列,然后待其到达队前时,才进行实际的I/O操作。这种输
出方式被
称之为延迟写。延迟写减少了硝基读写次数,但是却降低了文件内容的更新速度,
使得欲写
到文件中的数据在一段时间内并没有写到硝基上。当系统发生故障时,这种延迟可
能造成文
件更新内容的丢失。为了保证磁盘上实际文件系统与缓存中内容的一致性,UNIX系
统提供了
sgnc和fsync二个系统的用函数。?
#include<unistd?h>;?
void sync(void);?
int fsync(int ?filedes);??
返回:若成功为0,出错为-1?
sync只是将所有修改过的块的缓存排入写队列,然后就返回,它并不等待实际I/O


操作结束
。?
系统精灵进程(通常称为update)一般每隔30秒调用一次sync函数。这就保证了定期
刷新系统
核的块缓存。命令sync(1)也调用sync函数。?
函数fsync只引用单个文件(由文件描述符filedes指定),它等待I/O结束,然后返
回。fsync
可用于数据库这样的应用程序,在确保修改过的块立即写到磁盘上。比较一下fsy
nc和O〖C
模*常病絊Y
NC标志(见3?13节)。当调用fsync时,它更新文件的内容,而对于O 迹茫模*常?
〗SYNC,
则是每次对文件调用write函数时就更新文件的内容。?
SVR4和4?3+BSD两者都支持sync和fsync,它们都不是POSIX?1的组成部分,但XPG
3要求fsync。?
4?25〓文件存取许可数位摘要?
我们已注说明了所有文件存取数位,其中某些位有多种用途。图4?14摘要列出了
所有这些许可数位,以及它们对普通文件和目录文件的作用。???
图4?14〓文件存取数位摘要?
最后9个常数分成3组。?
S 迹茫模*常病絀RWXU=S+IRUSR|S 迹茫模*常病絀WUSR|S 迹茫模*常病絀XUS
R?


P?
S 迹茫模*常病絀RWXO=S+IROTH|S 迹茫模*常病絀WOTH|S 迹茫模*常病絀XOT
H?
4?26〓摘要?
本章内容围绕stat函数,详细介绍了stat结构中的每一个成员。这使我们对Unix文
件的各个属性都有所了解。对文件的所有属性以及对文件进行操作的所有函数有完
整的了解对各种Unix程序设计是非常重要的。[LM]
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP