Linux文件读写操作

文件描述符(File Descriptor)

a small, nonnegative integer for use in subsequent system calls (read(2), write(2), lseek(2), fcntl(2), etc.) ($man 2 open). 每个进程都会有一些与之关联的描述符, 一个程序开始运行时一般会有3个已经打开的文件描述符:

0 :标准输入stdin

1 :标准输出stdout

2 :标准错误stderror

fd工作原理

fd从0开始, 查找最小的未被使用的描述符, 把文件表指针与文件表描述符建立对应关系

文件描述符就是一个int, 用于代表一个打开的文件, 但是文件的管理信息不能够不是存放在文件描述符中,当使用open()函数打开一个文件时, OS会将文件的相关信息加载到文件表等数据结构中, 但出于安全和效率等因素的考虑, 文件表等数据结构并不适合直接操作, 而是给该结构指定一个编号, 使用编号来进行操作, 该编号就是文件描述符

OS会为每个进程内部维护一张文件描述符总表, 当有新的文件描述符需求时, 会去总表中查找最小的未被使用的描述符返回, 文件描述符虽然是int类型, 但其实是非负整数, 也就是0~OPEN_MAX(当前系统中为1024), 其中0,1,2已被系统占用,分别表示stdin, stdout,stderror

使用close()关闭fd时, 就是将fd和文件表结构之间的对应关系从总表中移除, 但不一定会删除文件表结构, 只有当文件表没有与其他任何fd对应时(也就是一个文件表可以同时对应多个fd)才会删除文件表, close()也不会改变文件描述符本身的整数值, 只会让该文件描述符无法代表一个文件而已

duplicate fdVS copy fd:dup是把old_fd对应的文件表指针复制给new_fd, 而不是int new_fd=old_fd

文件描述符标志(File Descriptor Flag)

当下的系统只有一个文件描述符标志close-on-exec,仅仅是一个标志,当进程fork一个子进程的时候,在子进程中调用了exec函数时就用到了该标志。意义是执行exec前是否要关闭这个文件描述符。

一般我们会调用exec执行另一个程序,此时会用全新的程序替换子进程的正文,数据,堆和栈等。此时保存文件描述符的变量当然也不存在了,我们就无法关闭无用的文件描述符了。所以通常我们会fork子进程后在子进程中直接执行close关掉无用的文件描述符,然后再执行exec。但是在复杂系统中,有时我们fork子进程时已经不知道打开了多少个文件描述符(包括socket句柄等),这此时进行逐一清理确实有很大难度。我们期望的是能在fork子进程前打开某个文件句柄时就指定好:这个句柄我在fork子进程后执行exec时就关闭”。所以就有了 close-on-exec

每个文件描述符都有一个close-on-exec标志。在系统默认情况下,这个标志最后一位被设置为0。即关闭了此标志。那么当子进程调用exec函数,子进程将不会关闭该文件描述符。此时,父子进程将共享该文件,它们具有同一个文件表项,也就有了同一个文件偏移量等。

fcntl()的FD_CLOEXEC和open()的O_CLOEXEC用来设置文件的close-on-exec,当将close-on-exec标志置为1时,即开启此标志, 此时子进程调用exec函数之前,系统就已经让子进程将此文件描述符关闭。

Note:虽然新版本支持在open时设置CLOEXEC,但是在编译的时候还是会提示错误 - error: ‘O_CLOEXEC’ undeclared (first use in this function)。这个功能需要设置宏(_GNU_SOURCE)打开。

#define _GNU_SOURCE //在源代码中加入 -D_GNU_SOURCE //在编译参数中加入 文件状态标志(File Status Flag)

File status flags 用来表示打开文件的属性,file status flag可以通过duplicate一个文件描述符来共享同一个打开的文件的状态,而file descrptor flag则不行

Access Modes: 指明文件的access方式:read-only, write-only,read-write。通过open()设置,通过fcntl()返回,但不能被改变

Open-time Flags: 指明在open()执行的时候的操作,open()执行完毕这个flag不会被保存

Operating Modes: 影响read,write操作,通过open()设置,但可以用fcntl()读取或改变

open() //给定一个文件路径名,按照相应的选项打开文件,就是将一个fd和文件连接到一起,成功返回文件描述符,失败返回-1设errno #include<fcntl.h> int open(const char *pathname, int flags) int open(const char *pathname, int flags, mode_t mode)//不是函数重载,C中没有重载, 是可变长参数列表 //pathname:文件或设备路径 //flags :file status,flags=Access mode+Open-time flags+Operating Modes、 /*Access Mode(必选一个): O_RDONLY:0 O_WRONLY:1 O_RDWR:2 */ /*Open-time Flags(Bitwise Or): O_CLOEXEC :为新打开的文件描述符使能close-on-exec。可以避免程序再用fcntl()的F_SETFD来设置FD_CLOEXEC O_CREAT :如果文件不存在就创建文件,并返回它的文件描述符,如果文件存在就忽略这个选项,必须在保护模式下使用,eg:0664 O_DIRECTORY :如果opendir()在一个FIFO或tape中调用的话,这个选项可以避免denial-of-service问题, 如果路径指向的不是一个目录,就会打开失败。 O_EXCL :确保open()能够穿件一个文件,如果文件已经存在,则会导致打开失败,总是和O_CREAT一同使用。 O_NOCTTY :如果路径指向一个终端设备,那么这个设备不会成为这个进程的控制终端,即使这个进程没有一个控制终端 O_NOFOLLOW :如果路径是一个符号链接,就打开它链接的文件//If pathname is a symbolic link, then the open fails. O_TMPFILE :创建一个无名的临时文件,文件系统中会创建一个无名的inode,当最后一个文件描述符被关闭的时候,所有写入这个文件的内容都会丢失,除非在此之前给了它一个名字 O_TRUNC :清空文件 O_TTY_INIT */ /*Operating Modes(Bitwise Or) O_APPEND :以追加的方式打开文件, 默认写入结尾 O_ASYNC :使能signal-driven I/O O_DIRECT :试图最小化来自I/O和这个文件的cache effect//Try to minimize cache effects of the I/O to and from this file. O_DSYNC :每次写操作都会等待I/O操作的完成,但如果文件属性的更新不影响读取刚刚写入的数据的话,就不会等待文件属性的更新 。 O_LARGEFILE :允许打开一个大小超过off_t(但没超过off64_t)表示范围的文件 O_NOATIME :不更改文件的st_time(last access time) O_NONBLOCK /O_NDELAY :如果可能的话,用nonblock模式打开文件 O_SYNC :每次写操作都会等待I/O操作的完成,包括write()引起的文件属性的更新。 O_PATH :获得一个能表示文件在文件系统中位置的文件描述符 #include<fcntl.h> #include<stdlib.h> int fd=open("b.txt",O_RDWR|O_CREAT|O_EXCL,0664); if(-1==fd) perror("open"),exit(-1);

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/990f9adedf1572e7292927ce99d85891.html