分类: LINUX
2010-09-18 15:42:26
1. 文件描述符
内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。
习惯上,标准输入(standard input)的文件描述符是 0,标准输出(standard output)是 1,标准错误(standard error)是 2。尽管这种习惯并非 Unix 内核的特性,但是因为一些 shell 和很多应用程序都使用这种习惯,因此,如果内核不遵循这种习惯的话,很多应用程序将不能使用。
POSIX 定义了 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 来代替 0、1、2。这三个符号常量的定义位于头文件 unistd.h。
文件描述符的有效范围是 0 到 OPEN_MAX。一般来说,每个进程最多 可以打开 64 个文件(0 — 63)。对于 FreeBSD 5.2.1、Mac OS X 10.3 和 Solaris 9 来说,每个进程最多可以打开文件的多少取决于系统内存的大小,int 的大小,以及系统管理员设定的限制。Linux 2.4.22 强制规定最多不能超过 1,048,576 。
2、打开和关闭文件描述符
open,create,close
2.1、open 函数用于打开和创建文件。
open是一个非标准的低级文件I/O函数,返回的是文件的低级句柄 。以下是 open 函数的简单描述:
#include
#include
#include
int open(const char *pathname, int flags, ... /* mode_t mode */);
返回值:成功则返回文件描述符,否则返回 -1;
对于 open 函数来说,第三个参数(mode_t mode)仅当创建新文件时才使用(即O_CREAT的时候,必须指定mode参数),用于指定文件的访问权限位(access permission bits)。pathname 是待打开/创建文件的路径名(如:/home/null.c);flags 用于指定文件的打开/创建模式,这个参数可由以下常量(定义于 fcntl.h)通过逻辑或(|)构成。
O_RDONLY 只读模式
O_WRONLY 只写模式
O_RDWR 读写模式
打开/创建文件时,至少得使用上述三个常量中的一个。以下常量是选用的:
O_CREAT 如果指定文件不存在,则创建这个文件(0x0100)
O_TRUNC 如果文件存在,并且以只写/读写方式打开,则清空文件全部内容(0x0200)
O_EXCL 如果要创建的文件已存在,则返回 -1,并且修改 errno 的值(0x0400)
O_APPEND 每次写操作都写入文件的末尾(0x0800)
O_TEXT 打开文本文件翻译CR-LF控制字符(0x4000)
O_BINARY 打开二进制字符,不作CR-LF翻译(0x8000)
O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端。
O_NONBLOCK 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)
以下三个常量同样是选用的,它们用于同步输入输出:
O_DSYNC 等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。
O_RSYNC read 等待所有写入同一区域的写操作完成后再进行
O_SYNC 等待物理 I/O 结束后再 write,包括更新文件属性的 I/O
以下是定义于
S_IRUSR // user-read(文件所有者读)
S_IWUSR // user-write(文件所有者写)
S_IXUSR // user-execute(文件所有者执行)
S_IRGRP // group-read
S_IWGRP // group-write
S_IXGRP // group-execute
S_IROTH // other-read
S_IWOTH // other-write
S_IXOTH // other-execute
其中 user 指文件所有者,group 指文件所有者所在的组,other 指其他用户。
open 返回的文件描述符一定是最小的未被使用的描述符。
如果 NAME_MAX(文件名最大长度,不包括'\0')是 14,而我们想在当前目录下创建文件名长度超过 14 字节的文件,早期的 System V 系统(如 SVR2)会截断超出部分,只保留前 14 个字节;而由 BSD 衍生的(BSD-derived)系统会返回错误信息,并且把 errno 置为 ENAMETOOLONG。
POSIX.1 引入常量 _POSIX_NO_TRUNC 用于决定是否截断长文件名/长路径名。如果_POSIX_NO_TRUNC 设定为禁止截断,并且路径名长度超过 PATH_MAX(包括 '\0'),或者组成路径名的任意文件名长度超过 NAME_MAX,则返回错误信息,并且把 errno 置为 ENAMETOOLONG。
2.2、creat 函数用于创建新文件
#include
int creat(const char *pathname, mode_t mode);
返回值:文件描述符(成功)或者 -1(出错)
creat 函数等同于 open 函数的以下用法:
open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);
creat 函数只能以只读方式创建新文件。如果我们要以读写方式创建新文件,可以用 open 函数;creat 函数现在已经没什么用处了,因为 open 比 creat 好用多了。
2.3、 close函数用于关闭已打开的文件
#include
int close(int filedes);
返回值:0(成功)或者 -1(出错);
一旦调用了close,则该进程对文件所加的锁全都被释放,即使这些锁是通过别的文件描述符加上的,如果要被关闭的文件导致它的连接(硬连接或者软连接到该文件的连接数目)数为0,则该文件会被删除。如果这是和一个打开的文件相关联的最后(或唯一)的文件描述符,则释放打开文件表中对应文件的项。
3、读写文件描述符
3.1、 read
#include
ssize_t read(int filedes, void *buf, size_t nbytes);
返回值:读取到的字节数;0(读到 EOF);-1(出错)
read 函数从 filedes 指定的已打开文件中读取 nbytes 字节到 buf 中。以下几种情况会导致读取到的字节数小于 nbytes :
A. 读取普通文件时,读到文件末尾还不够 nbytes 字节。例如:如果文件只有 30 字节,而我们想读取 100 字节,那么实际读到的只有 30 字节,read 函数返回 30 。此时再使用 read 函数作用于这个文件会导致 read 返回 0 。
B. 从终端设备(terminal device)读取时,一般情况下每次只能读取一行。
C. 从网络读取时,网络缓存可能导致读取的字节数小于 nbytes 字节。
D. 读取 pipe 或者 FIFO 时,pipe 或 FIFO 里的字节数可能小于 nbytes 。
E. 从面向记录(record-oriented)的设备读取时,某些面向记录的设备(如磁带)每次最多只能返回一个记录。
F. 在读取了部分数据时被信号中断。
读操作始于 cfo 。在成功返回之前,cfo 增加,增量为实际读取到的字节数。
3.2、 write
#include
ssize_t write(int filedes, const void *buf, size_t nbytes);
返回值:写入文件的字节数(成功);-1(出错)
write 函数向 filedes 中写入 nbytes 字节数据,数据来源为 buf 。返回值一般总是等于 nbytes,否则就是出错了。常见的出错原因是磁盘空间满了或者超过了文件大小限制。
对于普通文件,写操作始于 cfo 。如果打开文件时使用了 O_APPEND,则每次写操作都将数据写入文件末尾。成功写入后,cfo 增加,增量为实际写入的字节数。
4、lseek定位文件指针
所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为 cfo。cfo 通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数。读写操作通常开始于 cfo,并且使 cfo 增大,增量为读写的字节数。文件被打开时,cfo 会被初始化为 0,除非使用了 O_APPEND 。使用 lseek 函数可以改变文件的 cfo 。
#include
off_t lseek(int filedes, off_t offset, int whence);
返回值:新的偏移量(成功),-1(失败)
参数 offset 的含义取决于参数 whence:
1. 如果 whence 是 SEEK_SET,文件偏移量将被设置为 offset。
2. 如果 whence 是 SEEK_CUR,文件偏移量将被设置为 cfo 加上 offset,offset 可以为正也可以为负。
3. 如果 whence 是 SEEK_END,文件偏移量将被设置为文件长度加上 offset,offset 可以为正也可以为负。
SEEK_SET、SEEK_CUR 和 SEEK_END 是 System V 引入的,在这之前使用的是 0、1 和 2。
lseek 的以下用法返回当前的偏移量:
off_t currpos;
currpos = lseek(fd, 0, SEEK_CUR);
这个技巧也可用于判断我们是否可以改变某个文件的偏移量。如果参数 fd(文件描述符)指定的是 pipe(管道)、FIFO 或者 socket,lseek 返回 -1 并且置 errno 为 ESPIPE。
对于普通文件(regular file),cfo 是一个非负整数。但对于特殊设备,cfo 有可能是负数。因此,我们不能简单地测试 lseek 的返回值是否小于 0 来判断 lseek 成功与否,而应该测试 lseek 的返回值是否等于 -1 来判断 lseek 成功与否。
lseek 仅将 cfo 保存于内核中,不会导致任何 I/O 操作。这个 cfo 将被用于之后的读写操作。
如果 offset 比文件的当前长度更大,下一个写操作就会把文件“撑大(extend)”。这就是所谓的在文件里创造“空洞(hole)”。没有被实际写入文件的所有字节由重复的 0 表示。空洞是否占用硬盘空间是由文件系统(file system)决定的。
以下程序创建一个有空洞的文件:
#include
#include
#include
#include
#include
char buf1[] = "abcdefghij";
char buf2[] = "ABCDEFGHIJ";
int main(void)
{
int fd, size;
if ((fd = creat("file.hole", S_IRUSR|S_IWUSR)) < 0){
perror("creat");
exit(EXIT_FAILURE);
}
size = sizeof buf1 - 1;
if (write(fd, buf1, size) != size){
perror("buf1 write error\n");
exit(EXIT_FAILURE);
}
if (lseek(fd, 16384, SEEK_SET) == -1){
perror("lseek error\n");
exit(EXIT_FAILURE);
}
size = sizeof buf2 - 1;
if (write(fd, buf2, size) != size){
perror("buf2 write error\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
运行后生成file.hole文件,可以用od命令查看空洞文件的情况。$od -c file.hole,
$ls -ls file.hole,可以看到文件有8个块,而没有空洞的文件应该是20个块。
5、fcntl函数
fcntl的原型有以下3种:
#include
#include
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
fd文件描述符,cmd是不同的命令,arg供命令使用的参数,*lock设置记录锁的具体状态。以下是flock结构体:
struct flock {
short l_type; /*F_RDLCK(读取锁),F_WRLCK(写入锁),F_UNLCK(解锁)*/
off_t l_start; /*相对偏移量(字节)*/
short l_whence; /*SEEK_SET ,SEEK_CUR ,SEEK_END */
off_t l_len; /*加锁区域长度*/
pid_t l_pid;
}
fcntl函数有5种功能(cmd选项):
1.复制一个现有的描述符(cmd=F_DUPFD).
2.获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
3.获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
4.获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
5.获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).
fcntl文件锁有两种类型:建议性锁和强制性锁
建议性锁是这样规定的:每个使用上锁文件的进程都要检查是否有锁存在,当然还得尊重已有的锁。内核和系统总体上都坚持不使用建议性锁,它们依靠程序员遵守这个规定。
强制性锁是由内核执行的。当文件被上锁来进行写入操作时,在锁定该文件的进程释放该锁之前,内核会阻止任何对该文件的读或写访问,每次读或写访问都得检查锁是否存在。
系统默认fcntl都是建议性锁,强制性锁是非POSIX标准的。如果要使用强制性锁,要使整个系统可以使用强制性锁,那么得需要重新挂载文件系统, mount使用参数 -0 mand打开强制性锁,或者关闭已加锁文件的组执行权限并且打开该文件的set-GID权限位。
建议性锁只在cooperating processes之间才有用,cooperating processes指的是会影响其它进程的进程或被别的进程所影响的进程。如:我们同时在两个窗口中运行同一个命令,对同一个文件进行操作,就是这两个进程就是cooperating processes。
当一个进程对文件加锁后,无论它是否释放所加的锁,只要文件关闭,内核都会自动释放加在文件上的建议性锁(这也是建议性锁和强制性锁的最大区别)。fcntl使用三个参数 F_SETLK/F_SETLKW, F_UNLCK和F_GETLK, 来分别要求、释放、测试record locks(共享锁),ead lock和wrtie lock(排他锁)不能共存
6、dup和dup2的使用
系统调用dup和dup2能够复制文件描述符,经常用来重定向进程的stdin(0)、stdout(1)和stderr(2)。
dup返回新的文件描述符(没有使用的文件描述符的最小的编号)
dup2可以让用户指定返回的文件描述符的值,如果需要,则首先接近newfd的值:
通常用来重新打开或者重定向一个文件描述符。
函数原型:
#include
int dup(int oldfd);
int dup2(int oldfd, int newfd);
dup和dup2都返回新的描述符,或者返回-1并设置errno变量。新老描述符共享文件的偏移量(位置)、标志和锁,但不共享close-on-exec标志。
dup可以复制一个描述符。传给该函数一个已有的描述符,它就会返回一个新的描述符(没有使用的文件描述符的最小的编号),这个新的描述符是旧文件描述符的拷贝。这意味着,这两个描述符共享同一个数据结构。
dup2函数允许调用者规定一个有效描述符(oldfd)和目标描述符的id(newfd),函数成功返回时,目标描述符(newfd)将变成旧描述符(oldfd)的复制品,换句话说,两个文件描述符现在都指向同一个文件,并且是函数第一个参数指向的文件。
实际上,调用dup等效与fcntl(oldfd, F_DUPFD, 0);
而调用dup2等效与 close(oldfd); fcntl(oldfd, F_DUPFD, newfd);
附录:
1、open