文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符,用来指向被打开的文件。所有执行I/O操作的系统调用都会通过文件描述符。
值得注意的是:当读写一个文件的时候,使用open或create函数返回的文件描述符指向该文件,将其作为参数传递给read,write。
其中,0,1,2被标准化并在POSIX中将其替换为符号常量(在头文件<unistd.h>中):
STDIN_FILENOSTDOUT_FILENOSTDERR_FILENO012在UNIX中,上图中的i-node表实际上是V表,下面会重点讲
open和openat两个函数都可以==打开或创建一个文件。==不同点在于fd参数:
当path参数指定的是绝对路径名,这时,fd参数被忽略,openat与open无区分当path参数指定的是相对路径名,则fd参数指出相对路径名在文件系统中的开始地址。fd参数通过打开相对路径名所在的目录获取当path参数指定的是相对路径名,且fd参数位特殊值AT_FDCWD,此时,路径名在当前工作目录中获取。注意:第二条的fd获取方式如下:
目录文件描述符的取得通常分为两步,先用opendir()函数获得对应的DIR结构的目录指针,再使用int dirfd(DIR*)函数将其转换成目录描述符测试代码:
#include <apue.h> #include <fcntl.h> #include <dirent.h> int main() { int fd1,fd2,fd3,fd4; fd1 = open("/home/ubuntu/a.txt",O_RDWR | O_CREAT);//open常规用法 fd2 = openat(fd1,"/home/ubuntu/a.txt",O_RDWR | O_CREAT);//openat+绝对路径 DIR* dir = opendir("/home/ubuntu"); fd3 = dirfd(dir); fd4 = openat(fd3,"a.txt",O_RDWR | O_CREAT);//openat+相对路径 printf("fd1 = %d , fd2 = %d , fd4 = %d \n",fd1,fd2,fd4); return 0; }结果:
可见,均打开成功。
oflag参数可以用来说明函数的多个选项。用下列的常量多个进行或运算构成完整的oflag参数。
参数名称作用O_RDONLY只读打开O_WRONLY只写打开O_EXEC只执行打开O_EDWR读写打开O_SEARCH只搜索打开(仅限目录)O_APPEND每次写时都追加到文件末尾*O_CLOEXEC把FD_CLOEXEC设置为文件描述符O_CREAT若此文件不存在则创建它O_DIRECTORY如果path引用的不是目录则出错O_EXCL测试一个文件是否存在*O_NOCTTY如果path引用的是终端设备,则不将该设备分配作为此进程的控制终端*O_NOFOLLOW若path是符号链接,出错*O_NONBLOCK******************************************************************************************************************火星文,看不懂O_SYNC每次的write等待物理IO操作完成O_TRUNC若此文件存在,且为只写或读写成功打开,则长度截断为0*O_TTY_INIT******************************************************************************************************************火星文,看不懂*O_DSYNC每次的write等待物理IO操作完成,但是如果该写操作并不影响读取刚写的数据,不需等待文件属性更新。。。????????????????????*O_RSYNC使每一个以文件描述符作为参数的进行的read操作等待至所有对文件的统一部份挂起的写操作都完成打*的非重点。这么多,谁记得住~~手码累死······
此函数等效于:
open(path,O_WRONLY | O_CREAT | O_TRUNC,mode);
缺点:返回的文件描述符是只写打开,要读该文件必须得先close再open,很麻烦
现在我们都用:
open(path,O_RDWR | O_CREAT | O_TRUNC, mode);
==关闭一个文件的同时会释放该进程的记录锁。==当一个进程终止时,内核会自动关闭它打开的所有文件,因此很多程序在open一个文件后没有close也是允许的。
off_t类型用于指示文件的偏移量,常就是long类型,其默认为一个32位的整数,在gcc编译中会被编译为long int类型,在64位的Linux系统中则会被编译为long long int,这是一个64位的整数,其定义在unistd.h头文件中可以查看。
符号常量,代表的是函数原型中的whence,一般情况下常用的有三种:
SEEK_SETSEEK_CURSEEK_END将该处的文件偏移量设置为距离文件开始处offset字节将该文件的文件偏移量设置为当前值+offset,注意此处的offset可正可负将该处的文件偏移量设置为文件的长度+offset,offset可正可负由于文件偏移量通常为非负整数,但是某些设备可能也允许负的文件偏移量。但是对于普通文件,其文件偏移量必须为非负。
示例代码:
#include "apue.h" #include <malloc.h> #include <unistd.h> #include <fcntl.h> int main() { int fd; char *str1,*str2,*str3; str1 = (char*)malloc(sizeof(char)*30); str2 = (char*)malloc(sizeof(char)*30); str3 = (char*)malloc(sizeof(char)*30); fd = open("/home/ubuntu/workspqce/TestText.txt",O_RDWR); off_t sk; sk = lseek(fd,0,SEEK_SET); ssize_t rd1,rd2,rd3; rd1 = read(fd,str1,30); sk = lseek(fd,30,SEEK_SET); sk = lseek(fd,-30,SEEK_CUR); rd2 = read(fd,str2,30); sk = lseek(fd,-30,SEEK_END); rd3 = read(fd,str3,30); printf("str1 = %s\n",str1); printf("str2 = %s\n",str2); printf("str3 = %s\n",str3); return 0; }运行效果:测试文件TestText.txt见附件Codes and workspace
需要注意的是:==文件偏移量可以大于文件的长度。这种情况下,下一次的写操作会加长该文件,造成文件中的一个空洞。位于文件中且没有被写过的文件都读为0。==有些文件系统可以对空洞部分不实际分配存储空间。
ssize_t是有符号整型,在32位机器上等同与int,在64位机器上等同与long int,有没有注意到,它和long数据类型有啥区别?其实就是一样的。size_t 就是无符号型的ssize_t,也就是unsigned long/ unsigned int (在32位下),不同的编译器或系统可能会有区别,主要是因为在32位机器上int和long是一样的。在64位没有测试,但是参见百度百科,size_t 在64位下是64位,那么size_t的正确定义应该是typedef unsigned long size_t。
还需要示例吗?前面一个已经使用过了
需要注意的点不多:
返回值通常和nbytes一致如果不一致,要么出错,要么磁盘写满,要么超过了一个给定进程的文件长度限制除非打开该文件时使用了O_APPEND参数,否则write的地方是当前文件偏移量注意!!!不管是read还是write,运行后的当前文件偏移量是原文件偏移量+nbytes
示例代码:
#include "apue.h" #include <unistd.h> #include <malloc.h> #include <fcntl.h> int main() { int fd = open("/home/ubuntu/workspqce/TestText.txt",O_RDWR); char *buf = "\n----ADELE----\n"; int sk = lseek(fd,0,SEEK_SET); int wr1 = write(fd,buf,16); sk = lseek(fd,0,SEEK_END); int wr2 = write(fd,buf,16); printf("wr1 = %d , wr2 = %d\n",wr1,wr2); return 0; }