1.文件描述符
对于内核而言,所有打开的文件都通过文件描述符引用。 文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读,写一个文件时,使用open或create返回的文件描述符标识该文件,将其作为参数传送给read或write。
按照惯例,UNIX系统shell把文件描述符0与进程的标准输入关联,文件描述符1与标准输入关联,文件描述符1与标准输出关联,文件描述符2与标准错误关联。 这是各种shell以及很多应用程序使用的惯例,与UNIX内核无关。尽管如此,如果不遵循这种惯例,很多UNIX系统应用程序就不能正常工作。
在符合POSIX.1的应用程序中,幻数0,1,2虽然已被标准化,但应当把它们替换成符号常量STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO以提高可读性。这些常量都在头文件<unistd.h>中定义。
文件描述符的变化范围是0-OPEN_MAX-1。早期的UNIX系统实现采用的上限值是19,但现在很多系统将其上限值增加至63.
2.函数open和openat
调用open或openat函数可以打开或创建一个文件
#include<fcntl.h>
int open(const char *path,int oflag,.../* mode_t mode */);
int openat(int fd,const char *path,int oflag,.../* mode_t mode*/);
我们将最后一个参数写为…,ISO C用这种方法表明余下的参数的数量及类型是可变的。 对于open函数而言,仅当创建新文件时才使用最后这个参数。在函数原型中将此参数放置在注释中。
path参数是要打开或创建文件的名字。 oflag参数可用来说明此函数的多个选项。用下列一个或多个常量进行"或"运算构成oflag参数(这些常量在头文件<fcntl.h>中定义)。
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读,写打开。
(大多数实现将O_RDONLY定义为0,O_WRONLY定义为1,O_RDWR定义为2,以与早期的程序兼容)。
O_EXEC 只执行打开
O_SEARCH 只搜索打开
(O_SEARCH常量的目的在于目录打开时验证它的搜索权限。对目录的文件描述符的后续操作就不需要再次检查对该目录的搜索权限)
在这5个常量中必须指定一个且只能指定一个。下列常量则是可选的。
O_APPEND 再次写时都追加到文件的尾端。
O_CLOEXEC 把FD_CLOEXEC常量设置为文件描述符标志。
O_CREAT 若此文件不存在则创建它。使用此选项时,open函数须同时说明第三个参数mode,用mode指定该新文件的访问权限位。
O_DIRECTORY 如果path引用的不是目录,则出错
O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错。用此可以测试一个文件是否存在,若不存在,则创建此文件,这使得测试和创建两者成为一个原子操作。
O_NOCTTY 如果path引用的时终端设备,则不将该设备分配作为此进程的控制终端
O_NOFOLLOW 如果path引用的是一个符号链接,则出错
O_NONBLOCK 如果path引用的是一个FIFO,一个块特殊文件或一个字符特殊文件,则此选项为文件的本次打开操作和后续的I/O操作设置非阻塞方式。
O_SYNC 使每次write等待物理IO操作完成,包括由该write操作引起的文件属性更新所需的IO。
O_TRUNC 如果此文件存在,而且为只写或读-写成功打开,则将其长度截断为0.
O_TTY_INIT 如果打开一个还未打开的终端设备,设置非标准termios参数值,使其符合Single UNIX Specification中同步输入和输出选项的一部分。
O_DSYNC 使每次write要等待物理IO操作完成,但是如果该写操作并不影响读取刚写入的数据,则不需要等待文件属性被更新。
O_RSYNC 使每一个以文件描述符作为参数进行的read操作等地啊,直至所有对文件同一部分挂起的写操作都完成。
由open和openat函数返回的文件描述符一定是最小的未用描述符值。 这一点被某些应用程序用来在标准输入,标准输出或标准错误打开新的文件。例如,一个应用程序可以先关闭标准输出,然后打开另一个文件,执行打开操作前就能了解该文件一定会在文件描述符1上打开。
fd参数把open和openat函数区分开,有三种可能性:
(1)path参数指定的是绝对路径名,在这种情况下,fd参数被忽略,openat函数就相当于open函数
(2)path参数指定的是相对路径名,fd参数指出了相对路径名在文件系统中的开始地址。fd参数是通过打开相对路径名所在的目录来获取。
(3)path参数指定了相对路径名,fd参数具有特殊值AT_FDCWD。在这种情况下,路径名在当前工作目录中获取,openat函数在操作上与open函数类似。
openat函数是POSIX.1最新版本中新增的一类函数之一,希望解决两个问题。第一,让线程可以使用相对路径名打开目录中的文件,而不再只能打开当前工作目录。第二,可以避免time-of-check-to-time-of-use(TOCTTOU)错误。
TOCTTOU错误的基本思想是:
如果有两个基于文件的函数调用,其中第二个调用依赖于第一个调用的结果,那么程序是脆弱的。因为两个调用并不是原子操作,在两个函数调用之间文件可能改变了,这样也就造成了第一个调用的结果就不再有效,使得程序最终的结果是错误的。文件系统命名空间中的TOCTTOU错误通常处理的就是那些颠覆文件系统权限的小把戏,这些小把戏通过骗取特权程序降低特权文件的权限控制或者让特权文件打开一个安全漏洞等方式进行。
文件名和路径名截断
如果NAME_MAX是14,而我们却试图在当前目录中创建一个文件名包含15个字符的新文件,此时会发生什么呢?安装传统,早期的System V版本允许这种使用方法,但总是将文件名截断为14个字符,而且不给出任何信息,而BSD类的系统则返回出错状态,并将errno设置为ENAMETOOLONG。无声无息截断文件名会引起问题,而且它不仅仅影响到创建新文件。如果NAME_MAX是14,而存在一个文件名恰好就是14个字符的文件,那么以路径名作为其参数的任一函数都无法确定该文件的原始名是什么。其原因是这些函数无法判断改文件名是否被截断过。
在POSIX.1中,常量_POSIX_NO_TRUNC决定是要截断过长的文件名或路径名,还是返回一个出错。正如我们在第2章中已经见到,根据文件系统的类型,此值可以变化。我们可以用fpathconf或pathconf来查询目录中具体支持何种行为。
4.函数create
#include<fcntl.h>
int create(const char *path,mode_t mode);
此函数等效于:
open(path,O|WRONLY|O_CREAT|O_TRUNC,mode);
即只写打开,若没有该文件则创建一个,打开后,文件内容长度为0。
5.函数close
#include<unistd.h>
int close(int fd);
关闭一个文件时还会释放该进程加在该文件上的所有记录锁。
当一个进程终止时,内核自动关闭它所有打开的文件。很多程序都利用了这一功能而不显示地用close关闭打开的文件。
6.函数lseek
每个打开文件都有一个与其相关联的“当前文件偏移量”。它通常是一个非负整数,用以度量从文件开始处计算的字节数。通常,读写操作都从当前文件偏移量出开始,并使偏移量增加所读写的字节数。按系统默认的情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0。
可以调用lseek显示地为一个打开文件设置偏移量
off_t lseek(int fd,off_t offset,int where);
对参数offset的解释与参数whence的值有关。
①若whence是SEEK_SET,则将该文件的偏移量设置为距文件开始处offset个字节。
②若whence是SEEK_CUR,则将文件的偏移量设置为当前值加上offset,offset可正可负
③若whence是SEEK_END,则将该文件的偏移量设置为文件长度加offset,offset可正可负。
若lseek成功执行,则返回新的文件偏移量,为此可以用下列方式确定打开文件的当前偏移量。
off_t currpos;
currpos=lseek(fd,0,SEEK_CUR);
这种方法也可以用来确定所涉及的文件是否可以设置偏移量。如果文件描述符指向的是一个管道,FIFO或网络套接字,则lseek返回-1,并将errno设置为ESPIPE。
3个符号常量SEEK_SET,SEEK_CUR,SEEK_END是在System V中引入的,在System V之前,whence被指定为0(绝对偏移量),1(相对于当前位置的偏移量),2(相对文件尾端的偏移量)。
实例3-1:测试对其标准输入能否设置偏移量
#include "apue.h"
int
main(void)
{
if(lseek(STDIN_FILENO,0,SEEK_CUR)==-1)
printf("cannot seek\n");
else
printf("seek OK\n");
return 0;
}
通常,文件的当前偏移量应当是一个非负整数,但是,某些设备也可能允许负的偏移量。但对于普通文件,其偏移量必须是非负值。因为偏移量可能是负值,所以在比较lseek的返回值时应当谨慎,不要测试它是否小于0,而要测试它是否等于-1。
lseek仅仅将当前的文件偏移量记录在内核中,并不引起任何IO操作。
文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加上该文件,并在文件中构成一个空洞,这一点是允许的。位于文件中但没有写过的字节都被读为0。
文件的空洞并不要求在磁盘上占用存储区。具体处理方式与文件系统的实现有关,当定位到超出文件尾端之后写时,对于新写的数据需要分配磁盘块,但是对于源文件尾端和新开始写位置之间的部分则不需要分配磁盘块。
实例3-2:创建一个具有空洞的文件
#include "apue.h"
#include <fcntl.h>
char buf1[]="abcdefghij";
char buf2[]="SBCDEFGHIJ";
int
main(void)
{
int fd;
if((fd=creat("file.hole",FILE_MODE))<0)
err_sys("create error");
if(write(fd,buf1,10)!=10)
err_sys("buf1 write error");
if(lseek(fd,16384,SEEK_SET)==-1)
err_sys("lseek error");
if(write(fd,buf2,10)!=10)
err_sys("buf2 write error");
exit(0);
}
因为lseek使用的偏移量是用off_t类型表示的,所以允许具体实现根据各自特定的平台自行选择大小合适的数据类型。现今大多数平台提供两组接口以处理文件偏移量。一组使用32位文件偏移量,另一组则使用64位文件偏移量。
Single UNIX Specification 向应用程序提供了一种方法,使其通过sysconf函数确定支持何种环境。图3-3总结了定义的sysconf常量。
图3-3:
7.函数read
调用read函数从打开文件中读数据
#include<unistd.h>
ssize_t read(int fd,void *buf,size_t nbytes);
如果read成功,则返回读到的字节数,如已到达文件的尾端,则返回0。有多种情况可使实际读到的字节数少于要求读的字节数:
①读普通文件时,在读到要求字节数之前已达到文件尾端。例如若在到达文件尾端之前有30个字节,而要求读100个字节,则read返回30。下一次再调用read时,它将返回0(文件尾端)。
②当从终端设备读时,通常一次最多读一行
③当从网络读时,网络中的缓冲机制可能造成返回值小于所要读的字节数
④当从管道或FIFO读时,如若管道包含的字节少于所需的数量,那么read将只返回实际可用的字节数。
⑤当从某些面向记录的设备读时,一次最多返回一个记录。
⑥当一信号中断,而已经读了部分数据量时。我们将在10.5进一步讨论此种情况。
读操作从文件的当前偏移量处开始,在成功返回之前,该偏移量将增加实际读到的字节数。
POSIX.1从几个方面对read函数的原型作了更改。原型定义是:
int read(int fd,char *buf,unsigned nbytes);
①首先,为了与ISO C一致,第2个参数由char *改为void *。在ISO C中,类型void *用于表示通用指针。
②其次,返回值必须是一个带符号整型(ssize_t),以保证能够返回正整数字节数,0(表示文件尾端),-1(出错)。
③最后,第三个参数在历史上是一个无符号整型,这允许一个16位的实现一次读或写的数据可以多大65534个字节。在1990 POSIX.1标准中,引入了新的基本系统数据类型ssize_t以提供带符号的返回值,不带符号的size_t则用于第三个参数。
8.write函数
调用write函数向打开文件写数据
#include<unistd.h>
ssize_t write(int fd,const void *buf,size_t nbytes);
其返回值通常与参数nbytes的值相同,否则表示出错。write出错的一个常见原因是磁盘已写满,或者超过了一个给定进程的文件长度限制。
对于普通文件,写操作从文件的当前偏移量处开始。如果打开文件时,指定了O_APPEND选项,则在每次写操作之前,将文件偏移量设置在当前结尾处。在一次成功写之后,该文件偏移量增加实际写的字节数。
9.I/O的效率
实例3-5:使用read和write函数复制一个文件
#include "apue.h"
#define BUFFSIZE 4096
int
main(void)
{
int n;
char buf[BUFFSIZE];
while ((n=read(STDIN_FILENO,buf,BUFFSIZE))>0)
if(write(STDOUT_FILENO,buf,n)!=n)
err_sys("write error");
if(n<0)
err_sys("read error");
exit(0);
}
关于该程序应注意:
①它从标准输入读,写至标准输出,所以复制文件需要重定向输入输出即可。
②此程序并不关闭输入和输出文件
我们是如何选取BUFFSIZE值的呢?在回答问题之前,让我们用各种不同的BUFFSIZE值来运行此程序。图3-6显示了20中不同的缓冲区长度,读516581760字节的文件所得到的结果。
用3-5的代码读文件,其标准输出被重定向到/dev/null上。此测试所用的文件系统是Linux ext4文件系统,其磁盘块长度为4096字节。这也证明了图3-6中系统CPU时间的几个最小值差不多出现在BUFFSIZE为4096及以后的位置, 继续增加缓冲区长度对此时间几乎没有影响。
图3-6:
关于该程序应注意:
①它从标准输入读,写至标准输出,所以复制文件需要重定向输入输出即可。
②此程序并不关闭输入和输出文件
我们是如何选取BUFFSIZE值的呢?在回答问题之前,让我们用各种不同的BUFFSIZE值来运行此程序。图3-6显示了20中不同的缓冲区长度,读516581760字节的文件所得到的结果。
用3-5的代码读文件,其标准输出被重定向到/dev/null上。此测试所用的文件系统是Linux ext4文件系统,其磁盘块长度为4096字节。这也证明了图3-6中系统CPU时间的几个最小值差不多出现在BUFFSIZE为4096及以后的位置,继续增加缓冲区长度对此时间几乎没有影响。
大多数文件系统为改善性能都采用某种预读技术。当检测到正进行顺序读取时,系统就试图读入比应用所要求更多数据,并假想应用很快就会读这些数据。预读的效果可以从图3-6中看出,缓冲区长度小至32字节时的时钟时间与拥有较大缓冲区长度时的始终时间几乎一样。
10.文件共享
UNIX系统支持在不同进程共享打开文件。在介绍dup函数之前,先要说明这种共享。为此先介绍内核用于所有I/O的数据结构。
内核使用3中数据结构表示打开文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。
(1)每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:
a.文件描述符标志
b.指向一个文件表项的指针。
(2)内核为所有打开文件维持一张文件表。每个文件表项包含:
a.文件状态标志
b.当前文件偏移量
c.指向该文件v节点表项的指针。
(3)每个打开文件(或设备)都有一个v节点(v-node)结构。v节点包含了文件类型和对此文件进行各种操作函数的指针。对于大多数文件,v节点还包含了该文件的i节点(索引节点)。这些信息是在打开文件时从磁盘上读入内存的,所以,文件的所有相关信息都是随时可用的。例如,i节点包含了文件的所有者,文件长度,指向文件实际数据块在磁盘上所在位置的指针等。对于大多数文件,v节点还包含了该文件的i节点。这些信息是在打开文件时从磁盘上读入内存的,所以文件的所有相关信息都是随时可用的。
我们忽略了那些不影响讨论的实现细节。例如打开文件描述符可存放在用户空间,而非进程表中。这些表也可以用多种方式实现,不必一定是数组,例如,可将它们实现为结构的链表。
图3-7显示了一个进程对应的3张表之间的关系。
从UNIX系统的早期版本以来,这3张表之间的关系一直保持至今。这种关系对于在不同进程之间共享文件的方式非常重要,在以后的章节涉及其他文件共享方式时还会回到这张图上来。
从两个独立进程各自打开了同一文件,则有图3-8中所示的关系。
我们假定第一个进程在文件描述符3上打开该文件,而另一个进程在文件描述符4上打开该文件。打开该文件的每个进程都获得各自的一个文件表项,但对一个给定的文件只有一个v节点表项。之所以每个进程都获得自己的文件表项,是因为这可以使得每个进程都有它自己的对该文件的当前偏移量。
给出了这些数据结构后,现在对前面所述的操作进一步说明。
①在完成每个write后,在文件表项中,当前文件偏移量即增加所写入的字节数。如果这导致当前文件偏移量超出了当前文件长度,则将i节点表项中的当前文件长度设置为当前文件偏移量。
②如果用O_APPEND标志打开一个文件,则相应标志也被设置到文件表项的文件状态标志中。每次对这种具有追加写标志的文件执行写操作时,文件表项中的当前文件偏移量首先会被设置为i节点表项中的文件长度。这就是的每次写入的数据都追加到文件的当前尾端处。
③若一个文件用lseek定位到文件当前的尾端,则文件表项中的当前文件偏移量被设置为i节点表项。
④lseek函数只修改文件表项中的当前文件偏移量,不进行任何IO操作。
11.原子操作
(1)追加到一个文件
假定有两个独立的进程A和B都对同一个文件进行追加写操作。每个进程都已打开了该文件,但未使用O_APPEND标志。此时,各数据结构之间的关系如图3-8中所示。每个进程都有它自己的文件表项,但是共享一个v节点表项。假定进程A和B都调用了lseek,它们将该文件当前偏移量设置未1500字节,当B调用write将B对该文件的偏移量增加到了1600,之后B未写完但CPU时间片到,让出CPU,A恢复运行,也调用write函数,那么此时A的操作就会覆盖B的内容是B的内容无效。
问题出在两个分开的函数调用没有互斥,解决方法是使这两个函数调用对于其他进程而言成为一个原子操作。
UNIX系统为这样的操作系统提供了一种原子操作方法,即在打开文件时设置O_APPEND标志。正如前一节中所述,这样做使得内核在每次写操作之前,都将进程的当前偏移量设置到该文件的尾端处,于是每次写之前就不再需要调用lseek。
(2)函数pread和pwrite
pread和pwrite允许原子性地定位并执行IO。
#include<unistd.h>
ssize_t pread(int fd,void *buf,size_t nbytes,off_t offset);
ssize_t pwrite(int fd,const void *buf,size_t nbytes,off_t offset);
调用pread相当于调用lseek后调用read,但是pread又与这种顺序调用有以下重要区别。
①调用pread时,无法中断其定位和读操作
②不更新当前文件偏移量
调用pwrite相当于lseek后调用write。
12.函数dup和dup2
下面两个函数都可以用来复制一个现有的文件描述符。
#include<unistd.h>
int dup(int fd);
int dup2(int fd,int fd2);
由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。对于dup2,可以用fd2参数指定新描述符的值。如果fd2已经打开,则先将其关闭。如若fd等于fd2,而不关闭它。否则,fd2的FD_CLOEXEC文件描述符标志就被清除,这样fd2在进程调用exec时是打开状态。
这些函数返回的新文件描述符与原参数fd共享同一个文件表项。
13.函数sync,fsync和fdatasync
传统的UNIX系统实现在内核中设有缓冲区高速缓存或页高速缓存,大多数磁盘IO都通过缓冲区进行。大多数磁盘IO都通过缓冲区进行。当我们向文件写入数据时,内核通常先将数据复制到缓冲区中,然后排入队列,晚些时候再写入磁盘。这种方式被称为延迟写。
通常当内核需要重用缓冲区来存放其他磁盘块数据时,它会把所有延迟写数据块写入磁盘。为了保证磁盘上实际文件系统与缓冲区中内容的一致性,UNIX系统提供了sync,fsync和fdatasync三个函数。
#include<unistd.h>
int fsync(int fd);
int fdatasync(int fd);
void sync(void);
sync只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。
通常,成为update的系统守护进程周期性地调用(一般30秒)sync函数。这就保证了定期冲洗内核块缓冲区。命令sync(l)也调用sync函数。
fsync函数支队由文件描述符fd指定的一个文件起作用,并且等待写磁盘操作结束才返回。fsync可用于数据库这样的应用程序,这种应用程序需要确保修改过的块立即写到磁盘上。
fdatasync函数类似于fsync,但它之影响文件的数据部分。而出数据外,fsync还会同步更新文件的属性。
14.函数fcntl
fcntl函数可以改变已经打开文件的属性
#include<fcntl.h>
int fcntl(int fd,int cmd,.../* int arg */)
在本节的各实例中,第3个参数总是一个整数,与上面所示函数原型中的注释部分对应。但是在14.3节说明记录锁时,第3个参数则是指向一个结构的指针。
fcntl函数有以下5中功能:
(1)复制一个已有的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC)
(2)获取/设置文件描述符标志(cmd=F_GETFD或F_SETFD)
(3)获取/设置文件描述符标志(cmd=F_GETFL或F_SETFL)
(4)获取/设置异步IO所有权(cmd=F_GETOWN或F_SETOWN)
(5)获取/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)
我们先说明这11中cmd中的前8种。参照图3-7,我们将讨论与进程表项种各文件描述符相关联的文件描述符标志以及文件表项种的文件状态标志。
F_DUPFD:复制文件描述符fd,新文件描述符标志以及每个文件表项中的文件状态标志。各描述符中大于或等于第三个参数值中各个值的最小值。新描述符与fd共享同一文件表项。但是,新描述符有它自己的一套文件描述符标志,其FD_CLOEXEC文件描述符标志被清除。
F_DUPFD_CLOEXEC:复制文件描述符,设置与新描述符关联的FD_CLOEXEC文件描述符标志的值,返回新文件描述符。
F_GETFD:对应于fd的文件描述符标志作为函数值返回。当前只定义了一个文件描述符FD_CLOEXEC。
F_SETFD:对于fd设置文件描述符标志。新标志按第3个参数设置
F_GETFL:对应于fd的文件状态标志作为函数值返回。我们在说明open函数时,已描述了文件状态标志。
文件状态标志:
O_RDONLY,O_WRONLY,O_RDWR,O_EXEC以及O_SEARCH并不各占1位,而是互斥关系,一个文件的访问方式只能取这5个值之一。因此首先必须用屏蔽字O_ACCMODE取得访问方式位,然后将结果与这5个值中的每一个相比较。
在修改文件描述符标志或文件状态标志时必须谨慎,现有获得现在的标志值,然后按照期望修改它,最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以前设置的标志位。
实例3-12是对于一个文件描述符设置一个或多个文件状态标志的函数。
#include "apue.h"
#include <fcntl.h>
void
set_fl(int fd,int flags)
{
int val;
if((val=fcntl(fd,F_GETFL,0))<0)
err_sys("fcntl F_GETFL error");
val |=flags;
if(fcntl(fd,F_SETFL,val)<0)
err_sys("fcntl F_SETFL error");
}
如果将中间的一条语句改为:
val &= ~flags;
就构成另一个函数,我们成为clr_fl,并将在后面某些例子中用到它。此语句使得当前文件状态标志值val与flags的反码进行逻辑与运算。
15.函数ioctl
ioctl函数一直是I/O操作的杂物箱。不饿能用本章中其他函数表示的I/O操作通常都能用ioctl表示。终端IO是使用ioctl最多的地方
#include<unistd.h>
#include<sys/ioctl.h>
int ioctl(int fd,int request,....);
我们所示的函数原型对应于POSIX.1,FreeBSD8.0和Mac OS X10.6.8将第2个参数声明为unsigned long。因为第2个参数总是头文件中一个#defined的名字,所以这种细节并没有影响。
对于ISO C原型,它用省略号表示其余参数。但是通常只有另一个参数,它常常是指向一个变量或结构的指针。
在此原型中,我们表示的只是ioctl函数本身所要求的头文件。通常,还要求另外的设备专用头文件。例如,除POSIX.1所说明的基本操作之外。终端IO的ioctl命令头需要头文件<termios.h>。
每个设备驱动程序可以定义它自己专用的一组ioctl命令,系统则为不同种类的设备提供通用的ioctl命令。图3-15中总结了FreeBSD支持的通用ioctl命令的一些类别。
16./dev/fd
较新的系统都提供名为/dev/fd的目录,其目录项是名为0,1,2等的文件。打开文件/dev/fd/n等效于复制描述符n(假定描述符n是打开的)。
在下列函数调用中:
fd=open("/dev/fd/0",mode);
大多数系统忽略它所指定的mode,而另外一些系统则要求mode必须是所引用的文件(这里是标准输入)初始打开时所使用的打开模式的一个子集。因为上面的打开等效于
fd=dup(0)
所以描述符0和fd共享同一文件表项。例如,若描述符0先前被打开为只读,那么我们也只能对fd进行读操作。即使系统忽略打开模式,而且下列调用是成功的:
fd=open("/dev/fd/0",O_RDWR);
我们仍然不能进行写操作。
我们也可以用/dev/fd作为路径名参数调用creat,这与调用open时用O_CREAT作为第2个参数作用相同。例如,若一个程序调用creat,并且路径名参数是/dev/fd/1,那么仍然能工作。某些系统提供路径名/dev/stdin,/dev/stdout,/dev/stderr,这些等效于/dev/fd/0,/dev/fd/1,/dev/fd/2。