linux⽂件描述符函数,[转]操作⽂件描述符函数的整理
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
以下是定义于 中的九种⽂件访问权限位(⽤于构成参数 mode,也可以使⽤数值:如:0666):
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(出错);
shell创建文件并写入内容
⼀旦调⽤了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可以复制⼀个描述符。传给该函数⼀个已有的描述符,它就会返回⼀个新的描述符(没有使⽤的⽂件描述符的最⼩的编号),这个新的描述符是旧⽂件描述符的拷贝。这意味着,这两个描述符共享同⼀
个数据结构。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。