#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 */ ); //返回文件描述符 STDIN_FILENO //标准输入 STDOUT_FILENO //标准输出 #include<fcntl.h> int creat(const char *path, mode_t moe); //返回文件描述符,负数表示错误 #include <unistd.h> int close(int fd); //关闭一个文件还会释放该进程在该文件上的所有锁记录 #include <unistd.h> off_t lseek(int fd, off_t offset, int whence); //1.若whence是 SEEK_SET,则将该文件的偏移量设置为距离文件开始出offset个 //字节 //2.若whence是 SEEK_CUR,则将该文件的偏移量设置为其当前值加offset, //offset可为正或负 //3.若whence是 SEEK_END,则将该文件的偏移量设置为文件长度加offset, //offset可正可负 #include <unistd.h> ssize_t read(int fd, void *buf, size_t nbytes); ssize_t write(int fd, const void *buf, size_t nbytes); //read成功,返回读到的字节数,如果已达到文件尾端则返回0 //读普通文件时,读要求的字节数之前已经达到文件尾端则返回0 /从终端设备读一次最多读一行 //从网络读,网络中的缓冲机制可能造成返回值小于所要求读的字节数 //当从管道或FIFO读时,若管道包含的字节少于所需的数量,那么read将返回实际 //可用的字节数 //当从某些面向记录的设备(如磁带)读时,一次最多返回一个记录 //当一信号造成中断,而已经读了部分数据时,在成功返回之前,该偏移量将增加 //实际读到的字节数 //write成功,返回实际写入的字节数 //原子性的执行读写操作 #include <unistd.h> ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset); ssize_t pwrite(int fd, void *buf, size_t nbytes, off_t offset); //复制文件描述符 #include <unistd.h> int dup(int fd); int dup2(int fd, int fd2); //同步刷新 #include <unistd.h> int fsync(int fd); int fdatasync(int fd); void sync(void); //sync只是将所有修改过的块缓冲区排入写队列,然后就返回,并不等待实际写 //磁盘操作结束 //一般系统守护进程30秒调用sync函数 //fsync函数只对文件描述符fd指定的一个文件起作用,并且等待写磁盘操作结束 //fdatasync函数类似fsync,但是只影响文件的数据部分,fsync还会同步更新 //文件的属性 //更改已经打开文件的属性 #include <fcntl.h> int fcntl(int fd, int cmd, /* int arg */); //fcntl函数有5种功能 //1.复制一个已经的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC) //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) //杂物箱函数 #include <unistd.h> #include <sys/ioctl.h> //BSD and linux int ioctl(int fd, int request);
open函数的oflag参数
参数 | 含义 |
O_RDONLY | 只读打开 |
O_WRONLY | 只写打开 |
O_RDWR | 读,写打开 |
O_EXEC | 只执行打开 |
O_SEARCH | 只搜索打开 |
O_APPEND | 每次写时都追加到文件尾端 |
O_CLOEXEC | 把FD_CLOEXEC常量设置为文件描述符 |
O_CREAT | 若文件不存在则创建它,同时需要第三个参数指明 新文件的访问权限 |
O_DIRECTORY | 如果path不是目录则出错 |
O_EXCL | 如果同时指定了O_CREAT,而文件已存在则出错 次功能可测试一个文件是否存在若不存在则创建 |
O_NOCTTY | 如果path引用的是终端设备,则不将该设备分配作为 进程的控制终端 |
O_NOFOLLOW | 如果path引用的是一个符号链接则出错 |
O_NONBLOCK | 如果path引用的是一个FIFO,一个块特文件或一个字符特殊文件,则 此选项为文件的本次打开操作和后续的I/O操作设置为非阻塞方式 |
O_SYNC | 每次write等待物理I/O操作完成,包括由该write操作引起的文件属性更新 所需的I/O |
O_TRUNC | 如果此文件存在,而且为只写或读-写成功打开,则将其长度截断为0 |
O_TTY_INIT | 如果打开一个还未打开的终端设备,设置非标准termios参数值,使其符合Single UNIX Specificationi |
O_DSYNC | 每次write要等待物理I/O操作完成,但如果该写操作并不影响读取写入的数据, 则不需要等待文件属性被更新 |
O_RSYNC | 使每一个以文件描述符作为参数进行的read操作等待,直至所有对文件同一部分挂起 的写操作都完成 |
写空洞的例子
#include <fcntl.h> #include <stdio.h> #include <unistd.h> char buf1[] = "abcdefghij"; char buf2[] = "ABCDEFGHIJ"; int main(int argc, char *argv[]) { if(argc < 2) { printf("input create file path!\r\n"); return 1; } int fd; if( (fd=creat(argv[1],0755)) < 0 ) { printf("create error"); printf("\r\n"); } if( write(fd,buf1,10) != 10 ) { printf("buf write error"); printf("\r\n"); } if( lseek(fd,16384, SEEK_SET) == -1) { printf("lseek error"); printf("\r\n"); } if( write(fd,buf2,10) != 10 ) { printf("buf2 write error"); printf("\r\n"); } return 0; }
读写文件的例子,将一个文件读取,写入到另一个文件中
#include <fcntl.h> #include <stdio.h> #include <unistd.h> #define BUFFSIZE 4096 int main(int argc, char *argv[]) { if(argc < 3) { printf("input read path, write path\r\n"); return 1; } int r_fd; if( (r_fd=open(argv[1],O_RDONLY)) < 0 ) { printf("open error\r\n"); } int w_fd; if( (w_fd=creat(argv[2],0755)) < 0 ) { printf("create error\r\n"); } int num; char buf[BUFFSIZE]; while( (num=read(r_fd, buf, BUFFSIZE)) > 0) { if( write(w_fd, buf, num) != num ) { printf("write error\r\n"); } } if( num < 0 ) { printf("read error\r\n"); } return 0; }
读写文件时候,buf大小不同对影响的影响,12核/12G内存
buf大小 | 真正时间 | 用户时间 | 系统时间 |
512 | 3.269s | 0.077s | 2.523s |
1024 | 1.793s | 0.039s | 1.723s |
2048 | 1.682s | 0.015s | 1.214s |
4096 | 2.410s | 0.017s | 2.184s |
8192 | 5.404s | 0.009s | 1.218s |
16384 | 5.433s | 0.013s | 1.175s |
32768 | 5.396s | 0.005s | 1.188s |
65535 | 5.383s | 0.004s | 1.205s |
8M | 0.878s | 0.000s | 0.876s |
如果没每一次,调用fsync(fd)将内存刷新到磁盘上,性能结果如下:
buf大小 | 真实时间 | 用户时间 | 系统时间 |
1024 | 1m52.317s | 0.386s | 12.616s |
2048 | 1m7.329s | 0.086s | 8.528s |
4096 | 36.584s | 0.036s | 4.183s |
8192 | 20.883s | 0.085s | 2.886s |
16384 | 13.335s | 0.026s | 2.200s |
32768 | 9.761s | 0.020s | 1.711s |
65535 | 6.901s | 0.010s | 1.462s |
8M | 5.675s | 0.001s | 1.199s |
UNIX支持在不同进程共享打开文件,内核使用三种数据结构表示打开文件
1.每个进程再进程表中都有一个记录项,记录项中包含一张打开文件描述符表,可将其视为一个矢量,每个
描述符 占用一项,与每个文件描述符相关联的是:
a)文件描述符标志(close_on_exec)
b)指向一个文件表项的指针
2.内核为所有打开文件维持一张文件表,每个文件表项包含:
a)文件状态标志(读,写,添写,同步和非阻塞等)
b)当前文件偏移量
c)指向该文件v节点表项的指针
3.每个打开文件(或设备)都有一个v节点,linux没有v节点而是用通用的 i节点结构。v节点包含了文件类型和对
此文件进行各种操作的函数指针,对于大多数文件,v节点还包含了该文件的 i节点(i-node)。这些信息是在
打开文件时从磁盘上读入内存的,所以文件的所有相关信息都是随时可用的。
打开文件的内核数据结构
两个独立进程各自打开同一个文件
原子操作
早起的unix版本不支持O_APPEND选项,所以程序被编写成下列形式
if( lseek(fd,0L2) < 0 ) { err_sys("lseek error"); } if( write(fd, buf, 100) != 100 ) { err_sys("write error"); }
创建一个文件
if( (fd=open(path,O_WRONLY)) < 0 ) { if(errno == ENOENT) { if( (fd=creat(path,mode)) < 0 ) { err_sys("create error"); } } else { err_sys("open error"); } }
fcntl函数的cm参数有11个,先说明前8个,后3个跟锁有关
参数 | 含义 |
F_DUPFD | 复制文件描述符fd |
F_DUPFD_CLOEXEC | 复制文件描述符,设置与新描述符关联的FD_CLOEXEC文件描述符 标志的值,返回新文件描述符 |
F_GETFD | 对应于fd的文件描述符标志作为函数值的返回。当前只定义了一个文件 描述符标志FD_CLOEXEC |
F_SETFD | 对于fd设置文件描述符标志 |
F_GETFD | 对于fd设置文件描述符标志 |
F_GETFL | 对应于fd的文件状态标志作为函数值返回 |
F_SETFL | 将文件状态标志设置为第三个参数的值,可以更改的几个标志是 O_APPEND,O_NONBLOCK,O_SYNC,O_DSYNC,O_RSYNC, O_FSYNC和O_ASYNC |
F_GETOWN | 获取当前接收SIGIO和SIGURG信号的进程ID或进程组ID |
F_SETOWN | 设置接收SIGIO和SIGURG信息的进程ID或进程组ID |
对于F_GETFL的函数返回值,在open函数中有说明,返回下列内容
文件状态标志 | 说明 |
O_RDONLY | 只读打开 |
O_WRONLY | 只写打开 |
O_RDWR | 读写打开 |
O_EXEC | 只执行打开 |
O_SEARCH | 只搜索目录打开 |
O_APPEND | 追加写 |
O_NONBLOCK | 非阻塞模式 |
O_SYNC | 等待写完成(数据和属性) |
O_DSYNC | 等待写完成(仅数据) |
O_RSYNC | 同步读和写 |
O_FSYNC | 等待写完成(仅BSD和Mac) |
O_ASYNC | 异步I/O(仅BSD和Mac) |
/dev/fd
较新的系统都提供名为 /dev/fd 的目录,其目录项是名为 0,1,2,等的文件,打开文件/dev/fd/n 等效于复制
描述符n
cat命令对其采用了一种特殊处理
filter file2 | cat file1 - file3 | ipr //首先cat读file1,然后是标准输入,然后是file3 //如果cat支持/dev/fd 也可以改成 filter file2 | cat file1 /dev/fd/0 file3 | ipr