文件描述符:一个非负整数,范围是0~OPEN_MAX-1
。内核用它来标识进程正在访问的文件。当进程创建时,默认为它打开了3个文件描述符,它们都链接向终端:
通常我们应该使用STDIN_FILENO
,STDOUT_FILENO
和 STDERR_FILENO
来替代这三个幻数,从而提高可读性。这三个常量位于<unistd.h>
中。
2. 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 */);
```
- 参数:
- `path`:要打开或者创建文件的名字
- `oflag`:用于指定函数的操作行为,定义在`<fcntl.h>`中。
- `mode`:文件访问权限。文件访问权限常量在 `<sys/stat.h>` 中定义,有下列九个:
- `S_IRUSR`:用户读
- `S_IWUSR`:用户写
- `S_IXUSR`:用户执行
- `S_IRGRP`:组读
- `S_IWGRP`:组写
- `S_IXGRP`:组执行
- `S_IROTH`:其他读
- `S_IWOTH`:其他写
- `S_IXOTH`:其他执行
- 对于`openat`函数,被打开的文件名由`fd`和`path`共同决定:
**- 如果`path`指定的是绝对路径,此时`fd`被忽略。`openat`等价于`open`**
**- 如果`path`指定的是相对路径名,则`fd`是一个目录打开的文件描述符。被打开的文件的绝对路径由该`fd`描述符对应的目录加上`path`组合而成**
**- 如果`path`是一个相对路径名,而`fd`是常量`AT_FDCWD`,则`path`相对于当前工作目录。被打开文件在当前工作目录中查找。**
- 返回值:
- 成功:返回文件描述符。
- 失败:返回 -1
由 `open/openat` 返回的文件描述符一定是最小的未使用的描述符数字。
3. creat
函数:创建一个新文件
```
#include<fcntl.h>
int creat(const char*path,mode_t mode);
```
该函数等价于`open(path,O_WRONLY|O_CREAT|O_TRUNC,mode)`。注意:
- 它以只写方式打开,因此若要读取该文件,则必须先关闭,然后重新以读方式打开。
- 若文件已存在则将文件截断为0。
-
4. close
函数:关闭文件
```
#include<unistd.h>
int close(int fd);
```
- 参数:
- `fd`:待关闭文件的文件描述符
- 返回值:
- 成功:返回 0
- 失败:返回 -1
注意:
- 进程关闭一个文件会释放它加在该文件上的所有记录锁。
- 当一个进程终止时,内核会自动关闭它所有的打开的文件。
lseek
函数:设置打开文件的偏移量
#include<unistd.h>
off_t lseek(int fd, off_t offset,int whence);
fd
:打开的文件的文件描述符whence
:必须是 SEEK_SET
、SEEK_CUR
、SEEK_END
三个常量之一offset
: whence
是SEEK_SET
,则将该文件的偏移量设置为距离文件开始处offset
个字节whence
是 SEEK_CUR
,则将该文件的偏移量设置为当前值加上offset
个字节,offset
可正,可负whence
是 SEEK_END
,则将该文件的偏移量设置为文件长度加上offset
个字节,offset
可正,可负每个打开的文件都有一个与其关联的“当前文件偏移量”。它通常是个非负整数,用于度量从文件开始处计算的字节数。通常读、写操作都从当前文件偏移量处开始,并且使偏移量增加所读写的字节数。注意:
O_APPEND
选项,否则系统默认将该偏移量设为0lseek
将返回 -1 ,并且将 errno
设置为 ESPIPE
。lseek
的结果时,不能根据它小于0 就认为出错。要根据是否等于 -1 来判断是否出错。lseek
并不会引起任何 I/O 操作,lseek
仅仅将当前文件的偏移量记录在内核中。/* test lseek function for std file io */
#include "apue.h"
int main(void)
{
if (lseek(STDIN_FILENO,0,SEEK_CUR) == -1) /*there is must be is equal operation*/
printf("cannot seek\n");
else
printf("seek ok\n");
exit(0);
}
read
函数:读取文件内容
#include<unistd.h>
ssize_t read(int fd,void *buf,size_t nbytes);
fd
:打开的文件的文件描述符buf
:存放读取内容的缓冲区的地址(由程序员手动分配)nbytes
:期望读到的字节数读操作从文件的当前偏移量开始,在成功返回之前,文件的当前偏移量会增加实际读到的字节数。有多种情况可能导致实际读到的字节数少于期望读到的字节数:
FIFO
读时,若管道包含的字节少于所需的数量,则 read
只返回实际可用的字节数write
函数:想文件写数据
#include<unistd.h>
ssize_t write(int fd,const void *buf,size_t nbytes);
fd
:打开的文件的文件描述符buf
:存放待写的数据内容的缓冲区的地址(由程序员手动分配)nbytes
:期望写入文件的字节数write
的返回值通常都是与nbytes
相同。否则表示出错。write
出错的一个常见原因是磁盘写满,或者超过了一个给定进行的文件长度限制
对于普通文件,写操作从文件的当前偏移量处开始。如果打开文件时指定了O_APPEND
选项,则每次写操作之前,都会将文件偏移量设置在文件的当前结尾处。在一次成功写之后,该文件偏移量增加实际写的字节数。
测试lseek,read,write
:
#include <stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<errno.h>
void test_read(int fd,void* read_buffer,ssize_t len)
{
int read_num;
read_num=read(fd,read_buffer,len);
if(read_num<0)
{
printf("\tread error!\n");
fprintf(stderr,"\tBeause:%s\n",strerror(errno));
}else
{
printf("\tread num:%d(expexted read :%d)\n",read_num,len);
}
}
void test_write(int fd,void*write_buffer,ssize_t len)
{
int write_num;
write_num=write(fd,write_buffer,len);
if(write_num<0)
{
printf("\twrite error!\n");
fprintf(stderr,"\tBeause:%s\n",strerror(errno));
}else
{
printf("\twrite num:%d(expexted write :%d)\n",write_num,len);
}
}
void test_lseek(int fd,off_t offset,int loc)
{
int seek_ok;
seek_ok=lseek(fd,offset,loc);
if(-1==seek_ok)
{
printf("\tlseek error!\n");
fprintf(stderr,"\tBeause:%s\n",strerror(errno));
}else
{
printf("\tlseek ok.New offset is:%d\n",seek_ok);
}
}
int main(int argc, char *argv[])
{
int fd;
char read_buffer[20];
char write_buffer[10];
strcpy(write_buffer,"123456789");
fd=openat(AT_FDCWD,"test",O_RDWR|O_TRUNC);
printf("File is empty:\n");
test_read(fd,read_buffer,20);
test_write(fd,write_buffer,10);
test_read(fd,read_buffer,20);
printf("Lseek to begin:\n");
test_lseek(fd,0,SEEK_SET);
test_read(fd,read_buffer,20);
printf("Lseek to end+10:\n");
test_lseek(fd,10,SEEK_END);
test_read(fd,read_buffer,20);
test_write(fd,write_buffer,10);
test_read(fd,read_buffer,20);
printf("Lseek to begin:\n");
test_lseek(fd,0,SEEK_SET);
test_read(fd,read_buffer,20);
test_read(fd,read_buffer,20);
return 0;
}
测试序列为:
read
只读取0write
置于文件结尾。此时读取0个字节lseek
将当前文件偏移量重置到文件开头,返回0(新的文件偏移量)lseek
将文件偏移量放到文件末尾之后的10个字节,返回20(新的文件偏移量)write
置于文件结尾。此时读取0个字节lseek
将当前文件偏移量重置到文件开头,返回0(新的文件偏移量)内核使用三种数据结构描述打开文件。它们之间的关系决定了一个进程与另一个进程在打开的文件之间的相互影响。
这些信息都是在打开文件时从磁盘读入内存的。如 i 结点包含了文件的所有者、文件长度、指向文件实际数据在磁盘上所在位置的指针等等。 v 结点结构和 i 结点结构实际上代表了文件的实体。
现在假设进程 A 打开文件 file1
,返回文件描述符 3;进程 B 也打开文件 file2
,返回文件描述符 4:
write
之后,进程 A 对应的文件表项的当前文件偏移量即增加所写入的字节数。 O_APPEND
标志打开一个文件,在相应标志也设置到进程 B 对于的文件表项的文件状态标志中。 lseek
定位到文件当前的尾端,则进程 B 对应的文件表项的当前文件偏移量设置为 i 结点中的当前长度lseek
函数只是修改文件表项中的当前文件偏移量,不进行任何 I/O 操作可能一个进程中有多个文件描述符指向同一个文件表项。
原子操作:
lseek
到末尾然后write
。要用O_APPEND
选项打开文件,然后直接write
。 lseek
到末尾然后write
时,如果多个进程同时执行这两个操作,则会引起竞争条件O_APPEND
选项打开文件,然后直接write
时,内核每一次在写操作之前,都会将进程的当前偏移量设置到文件的末尾,于是就不需要执行lseek
定位操作pread/pwrite
可以执行原子性的定位读/定位写O_CREAT|O_EXCL
选项打开文件时,可以原子性的检查文件是否存在和创建文件这两个操作。pread/pwrite
:原子定位读和原子定位写
#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);
fd
:打开的文件描述符buf
:读出数据存放的缓冲区/ 写到文件的数据的缓冲区nbytes
:预期读出/写入文件的字节数offset
:从文件指定偏移量开始执行read/write
调用pread
相当于先调用lseek
再调用read
.但是调用pread
时,无法中断其定位和读操作,并且不更新当前文件偏移量;调用pwrite
相当于先调用lseek
再调用write
.但是调用pwrite
时,无法中断其定位和写操作,并且不更新当前文件偏移量
dup/dup2
:复制一个现有的文件描述符:
#include<unistd.h>
int dup(int fd);
int dup2(int fd,int fd2);
fd
:被复制的文件描述符(已被打开)fd2
:指定的新的文件描述符(待生成) 对于dup
函数,返回的新的文件描述符一定是当前可用的文件描述符中最小的数字。对于dup2
函数:
fd2
已经是被打开的文件描述符且不等于fd
,则先将其关闭,然后再打开(注意关闭再打开是一个原子操作)fd2
等于fd
,则直接返回fd2
(也等于fd
),而不作任何操作任何情况下,这个返回的新的文明描述符与参数fd
共享同一个文件表项(因此文件状态标志以及文件偏移量都会共享)。 任何情况下,这个返回的新的文明描述符的close-on-exec
标志总是被清除
UNIX操作系统在内核中设有缓冲区,大多数磁盘 I/O 都通过缓冲区进行。当我们想文件写入数据时,内核通常都首先将数据复制到缓冲区中,然后排入队列,晚些时候再写入磁盘。这种方式称为延迟写。
#include<unistd.h>
int fsync(int fd);
int fdatasync(int fd);
void sync(void);
参数(前两个函数):
fd
:指定的打开的文件描述符返回值(前两个函数):
区别:
sync
:将所有修改过的块缓冲区排入写队列,然后返回,它并不等待时机写磁盘结束fsync
:只对由fd
指定的单个文件起作用,等待写磁盘操作结束才返回fdatasync
:只对由fd
指定的单个文件起作用,等待写磁盘操作结束才返回,但是它只影响文件的数据部分(fsync
会同步更新文件的属性)
update
守护进程会周期性的调用sync
函数。命令sync
也会调用sync
函数
fcntl
函数:改变已经打开的文件的属性
#include<fcntl.h>
int fcntl(int fd,int cmd,.../* int arg */);
参数:
fd
:已打开文件的描述符cmd
:有下列若干种: F_DUPEF
常量:复制文件描述符 fd
。新文件描述符作为函数值返回。它是尚未打开的个描述符中大于或等于arg
中的最小值。新文件描述符与fd
共享同一个文件表项,但是新描述符有自己的一套文件描述符标志,其中FD_CLOEXEC
文件描述符标志被清除F_DUPFD_CLOEXEC
常量:复制文件描述符。新文件描述符作为函数值返回。它是尚未打开的个描述符中大于或等于arg
中的最小值。新文件描述符与fd
共享同一个文件表项,但是新描述符有自己的一套文件描述符标志,其中FD_CLOEXEC
文件描述符标志被设置F_GETFD
常量:对应于fd
的文件描述符标志作为函数值返回。当前只定义了一个文件描述符标志FD_CLOEXEC
F_SETFD
常量:设置fd
的文件描述符标志为arg
F_GETFL
常量:返回fd
的文件状态标志。文件状态标志必须首先用屏蔽字 O_ACCMODE
取得访问方式位, O_RDONLY
、O_WRONLY
、O_RDWR
、O_EXEC
、O_SEARCH
比较 O_APPEND
、O_NONBLOCK
、O_SYNC
O_DSYNC
、O_RSYNC
、F_ASYNC
、O_ASYNC
F_SETFL
常量:设置fd
的文件状态标志为 arg
。可以更改的标志是: O_APPEND
、O_NONBLOCK
、O_SYNC
、O_DSYNC
、O_RSYNC
、F_ASYNC
、O_ASYNC
F_GETOWN
常量:获取当前接收 SIGIO
和SIGURG
信号的进程 ID
或者进程组 ID
F_SETOWN
常量:设置当前接收 SIGIO
和SIGURG
信号的进程 ID
或者进程组 ID
为arg
。若 arg
是个正值,则设定进程 ID
;若 arg
是个负值,则设定进程组ID
F_GETLK
、F_SETLK
、F_SETLKW
:获取/设置文件记录锁arg
:依赖于具体的命令 返回值:
#include <stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
void print_error(int fd,const char* action,int result)
{
if(result==-1)
{
printf("\t %s on fd(%d) error:beause %s!\n",action,fd,strerror(errno));
}
}
void test_get_fd(int fd)
{
printf("\tget_fd on fd(%d):",fd);
int result;
result=fcntl(fd,F_GETFD);
print_error(fd,"F_GETFD",result);
if(result!=-1)
printf("return:%d !\n",result);
}
void test_set_fd(int fd, int flag)
{
printf("\tset_fd on fd(%d) of flag(%d):",fd,flag);
int result;
result=fcntl(fd,F_SETFD,flag);
print_error(fd,"F_SETFD",result);
if(result!=-1)
printf("set_fd ok !\n");
}
int test_dup_fd(int fd,int min_fd)
{
printf("\tdup_fd on fd(%d),set min_fd(%d),:",fd,min_fd);
int result;
result=fcntl(fd,F_DUPFD,min_fd);
print_error(fd,"F_DUPFD",result);
if(result!=-1)
printf("return:%d !\n",result);
return result;
}
int test_dup_exec_fd(int fd,int min_fd)
{
printf("\tdup_exec_fd on fd(%d),set min_fd(%d),:",fd,min_fd);
int result;
result=fcntl(fd,F_DUPFD_CLOEXEC,min_fd);
print_error(fd,"F_DUPFD_CLOEXEC",result);
if(result!=-1)
printf("return:%d !\n",result);
return result;
}
void test_get_fl(int fd)
{
printf("\tget_fl on fd(%d):",fd);
int result;
result=fcntl(fd,F_GETFL);
print_error(fd,"F_GETFL",result);
if(result!=-1)
{
printf("F_GETFL on fd(%d) has ",fd);
if(result&O_APPEND) printf("\tO_APPEND;");
if(result&O_NONBLOCK) printf("\tO_NONBLOCK;");
if(result&O_SYNC) printf("\tO_SYNC;");
if(result&O_DSYNC) printf("\tO_DSYNC;");
if(result&O_RSYNC) printf("\tO_RSYNC;");
if(result&O_FSYNC) printf("\tO_FSYNC;");
if(result&O_ASYNC) printf("\thas O_ASYNC;");
if((result&O_ACCMODE)==O_RDONLY)printf("\tO_RDONLY;");
if((result&O_ACCMODE)==O_WRONLY)printf("\thas O_WRONLY;");
if((result&O_ACCMODE)==O_RDWR)printf("\tO_RDWR;");
printf("\n");
}
}
void test_set_fl(int fd,int flag)
{
printf("\tset_fl on fd(%d) of flag(%d):",fd,flag);
int result;
result=fcntl(fd,F_SETFL,flag);
print_error(fd,"F_SETFL",result);
if(result!=-1)
printf("set_fl ok !\n");
}
void test_get_own(int fd)
{
printf("\tget_own on fd(%d):",fd);
int result;
result=fcntl(fd,F_GETOWN);
print_error(fd,"F_GETOWN",result);
if(result!=-1)
printf("return:%d !\n",result);
}
void test_set_own(int fd,int pid)
{
printf("\tset_own on fd(%d) of pid(%d):",fd,pid);
int result;
result=fcntl(fd,F_SETOWN,pid);
print_error(fd,"F_SETOWN",result);
if(result!=-1)
printf("set_own ok !\n");
}
int main(int argc, char *argv[])
{
int fd;
fd=openat(AT_FDCWD,"test.txt",O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR);
printf("Test dup:\n");
test_get_fd(test_dup_fd(fd,10));
test_get_fd(test_dup_fd(fd,0));
test_get_fd(test_dup_exec_fd(fd,10));
test_get_fd(test_dup_exec_fd(fd,0));
printf("Test set_get_fd:\n");
test_get_fd(fd);
test_set_fd(fd,~FD_CLOEXEC);
test_get_fd(fd);
test_set_fd(fd,FD_CLOEXEC);
test_get_fd(fd);
printf("Test set_get_fl:\n");
test_get_fl(fd);
test_set_fl(fd,O_RDWR);
test_get_fl(fd);
test_set_fl(fd,O_RDONLY|O_NONBLOCK);
test_get_fl(fd);
printf("Test set_get own:\n");
test_get_own(fd);
test_set_fl(fd,1);
test_get_own(fd);
return 0;
}
注意:
F_EXEC与
, F_SEARCH
(result&O_ACCMODE)==O_RDONLY
表达式中, &
优先级较低F_SETFL
命令:当文件读打开时,你无法将文件状态标志修改为O_WRONLY
、O_WRWR
这两种中任何一个。你只能修改:O_APPEND
、O_NONBLOCK
、O_SYNC
、O_DSYNC
、O_RSYNC
、F_ASYNC
、O_ASYNC
等标志/dev/fd
目录:该目录下是名为0、1、2
等的文件。打开文件/dev/fd/n
等效于复制描述符(假定描述符n
是打开的)
fd=open("/dev/fd/0",mod)
:fd
和文件描述符0
共享同一个文件表项。mod
参数/dev/fd/0
是个例外,它是个底层物理文件的符号链接。因此在它上面调用creat
会导致底层文件被截断