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(出错);

一旦调用了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

原型:

#include #include #include int open(const char *pathname, int flags);

作用:用flags指定的操作打开pathname指定的文件。成功返回一个文件描述符,失败返回-1并设置errno。

常用的flags包括O_RDONLY, O_WRONLY, O_RDWR,O_CREAT, O_TRUNC, O_APPEND等等。它们直接可以在合理的前提下进行逻辑或,例如O_CREAT | O_TRUNC | O_WRONLY。

2、close

原型:

#include int close(int fd);

作用:注销描述符fd,关闭文件。失败则返回-1并设置errno。

3、read

原型:

#include ssize_t read(int fd, const void *buf, size_t count);

作用:从描述符fd引用的文件中读出count个字节到buf所指向的缓冲区。成功返回实际读出的字节数,失败返回-1并设置errno。

4、write

原型:

#include ssize_t write(int fd, const void *buf, size_t count);

作用:把buf指向的缓冲区中的count个字节写入到描述符fd引用的文件。成功返回实际写入的字节数,失败返回-1并设置errno。

5、ftruncate

原型:

#include int ftruncate(int fd, off_t length);

作用:把描述符fd引用的文件缩短到length指定的长度。成功返回0,失败返回-1并设置errno。

6、lseek

原型:

#include #include off_t lseek(int fd, off_t offset, int whence);

作用:定位操作文件的指针到相对whence偏移offset的位置。whence代表:SEEK_SET(开始于文件头), SEEK_CUR(开始于当前指针位置,这时offset可以为负数), SEEK_END(从文件结尾往回数)。成功返回指针的新位置,失败或出错返回-1并设置errno。

7、fsync

原型:

#include int fsync(int fd);

作用:把在fd上执行的写入操作同步到真正的磁盘或其它下层设备文件中。成功返回0,失败返回-1并设置errno。errno被设为以下的某个值:

EBADF: 文件描述词无效

EIO : 读写的过程中发生错误

EROFS, EINVAL:文件所在的文件系统不支持同步

8、fstat

原型:

#include #include int fstat(int fd, struct stat *buf);

对于普通文件stat结构可以获得12个以上的成员信息,然而当fd指代一个共享内存区对象时,只有四个成员含有信息。

struct stat{

mode_t st_mode;

uid_t st_uid;

gid_t st_gid;

off_t st_size;

};

作用:把描述符fd引用的文件的相关信息保存到buf指向的stat结构中。成功返回0,失败返回-1并设置errno。

其中stat结构的成员st_mode即文件位模式可以用S_INLINK(mode)、S_ISREG(mode)、S_ISDIR(mode)……等宏返回的真假判断其文件类型(目录、普通文件、符号链接、字符设备、块设备、FIFO管道、套接口等)。另外,st_atime成员可以用ctime这个函数转换为datetime格式的字符串。ctime的原型为:

#include char *ctime(const time_t *timer);

9、fchmod

原型:

#include #include int fchmod(int fd, mode_t mode);

作用:把fd引用的文件的模式改变为mode指定的模式,类似shell里面的chmod命令。但要注意八进制的表示方法为以0起头。成功返回0,失败返回-1并设置errno。

10、flock和fcntl

原型:

#include int flock(int fd, int operation);

#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);

作用:在打开文件前给文件上锁以防止其被其它IO进程访问而引起不可预计的后果。

flock的operation包括LOCK_SH, LOCK_EX, LOCK_UN。fcntl的cmd包括F_GETLK, F_SETLK,F_SETLKW……等等。成功返回0,失败返回-1.

fcntl 比flock更通用。除了符合POSIX标准外,fcntl还同时支持建议性锁(由程序来检查锁)和强制性锁(由操作系统内核检查锁),另外可以用于读取锁(共享锁)和写入锁(排斥锁)。fcntl除了可以上锁文件外,还可以控制文件的进程组、复制文件描述符等。

11、dup和dup2

原型:

#include int dup(int oldfd);

int dup2(int oldfd, int newfd);

作用:dup和dup2都用于复制文件描述符,它们调用成功都返回新的描述符,失败则返回-1并设置errno。其中dup2可以自定义新的文件描述符,通常用来重新打开或者重定向文件描述符。例如:

dup2(fd, STDOUT_FILENO);

重定向文件描述符fd到标准输出。

12、select

原型:

#include int select(int n, fd_set *readfds, fd_set *writefds, fd_set exceptfds, struct timeval *timeout);

作用:同时读/写fd_set结构中的描述符集合,n代表集合中的文件描述符最大值加1,不需要的参数设置为NULL。timeout定义select的阻塞等待时间,如果设置为0则为非阻塞式的I/O调用,如果设置为NULL则一直等到I/O操作发生或者出错。

select成功返回受监视的fds集合的文件描述符总数,如果timeout时集合中的文件描述符都没有该表状态则返回0,失败则返回-1并设置errno。

例如:

#include #include #include fd_set *writeable_fds;

select(maxfds, NULL, writefds, NULL, 10);

有四个宏函数可以对描述符集合fd_set进行操作:

#include FD_ZERO(fd _set *set); /* 清空集合set */

FD_SET(int fd, fd_set *set); /* 把描述符fd添加到集合set */

FD_CLR(int fd, fd_set *set); /* 从集合set中删除描述符fd */

FD_ISSET(int fd, fd_set *set); /* 判断描述符fd是否在集合set中 */

13、ioctl

原型:

#include int ioctl(int d, int request, ...);

作用:设置或检索文件的多种有关参数并对文件进行一些其它操作。是否可以是ioctl以及传递什么参数给ioctl因下层设备而异。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值