文件描述符(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_FIFLENO,标准输入stdin
  • 1 :STDOUT_FILENO,标准输出stdout
  • 2 :STDERR_FILENO,标准错误stderror

fd原理

  • fd从0开始, 查找最小的未被使用的描述符, 把文件表指针与文件表描述符建立对应关系(VS pid是一直向上涨,满了再回来找)
  • 文件描述符就是一个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
  • UNIX使用三种数据结构描述打开的文件:每个进程中用于描述当前进程打开文件的文件描述符表,表示当前文件状态的文件状态标识表,和用于找到文件i节点(索引节点)的V节点表,Linux中并不使用这种Vnode结构,取而代之的是一种通用的inode结构,但本质没有区别,inode是在读取文件时通过文件系统从磁盘中导入的文件位置

文件描述符标志(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_CLOEXECopen()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 :以追加的方式打开文件, 默认写入结尾,在当下的Unix/Linux系统中,这个选项已经被定义为一个原子操作
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);

FQ:Why Bitwise ORed:

FA:猜想有以下模型:用一串某一位是1其余全是0的字符串表示一个选项, 选项们作 “按位与”就可得到0/1字符串, 表示整个flags的状态, Note: 低三位表示Access Mode

creat()

等价于以O_WRONLY |O_TRUNC|O_CREAT的flag调用open()

#include<fcntl.h>
int creat(const char *pathname, mode_t mode);

dup()、dup2()、dup3()

//复制一个文件描述符的指向,新的文件描述符的flags和原来的一样,成功返回new_file_descriptor, 失败返回-1并设errno
#include <unistd.h>
int dup(int oldfd); //使用未被占用的最小的文件描述符编号作为新的文件描述符
int dup2(int oldfd, int newfd);
#include <fcntl.h>
#include <unistd.h>
int dup3(int oldfd, int newfd, int flags);
#include<unistd.h>
#include<stdlib.h>
int res=dup2(fd,fd2);
if(-1==res){
perror("dup2"),exit(-1);

read()

//从fd对应的文件中读count个byte的数据到以buf开头的缓冲区中,成功返回成功读取到的byte的数目,失败返回-1设errno
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
#include <unistd.h>
#include<stdlib.h>
int res=read(fd,buf,6);
if(-1==fd)
perror("read"),exit(-1);

write()

//从buf指向的缓冲区中读取count个byte的数据写入到fd对应的文件中,成功返回成功写入的byte数目,文件的位置指针会向前移动这个数目,失败返回-1设errno
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);//不需要对buf操作, 所以有const, VS read()没有const
#include <unistd.h>
#include<stdlib.h>
int res=write(fd,"hello",sizeof("hello"));
if(-1==res)
perror("write"),exit(-1);

Note: 上例中即使只有一个字符’A’,也要写”A”,因为”A”才是地址,’A’只是个int

lseek()

l 表示long int, 历史原因

//根据移动基准whence和移动距离offset对文件的位置指针进行重新定位,返回移动后的位置指针与文件开头的距离,失败返回-1设errno
#include <unistd.h>
#include <sys/types.h>
off_t lseek(int fd, off_t offset, int whence);
/*whence:
SEEK_SET:以文件开头为基准进行偏移,0一般不能向前偏
SEEK_CUR:以当前位置指针的位置为基准进行偏移,1向前向后均可
SEEK_END:以文件的结尾为基准进行偏移,2向前向后均可向后形成”文件空洞”
#include<unistd.h>
#include<stdlib>
int len=lseek(fd,-3,SEEK_SET);
if(-1==len){
perror("lseek"),exit(-1);

fcntl()

//对fd进行各种操作,成功返回0,失败返回-1设errno
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... ); //...表示可变长参数
/*cmd:
Adversory record locking:
F_SETLK(struct flock*) //设建议锁
F_SETLKW(struct flock*) //设建议锁,如果文件上有冲突的锁,且在等待的时候捕获了一个信号,则调用被打断并在信号捕获之后立即返回一个错误,如果等待期间没有信号,则一直等待
F_GETLK(struct flock*) //尝试放锁,如果能放锁,则不会放锁,而是返回一个含有F_UNLCK而其他不变的l_type类型,如果不能放锁,那么fcntl()会将新类型的锁加在文件上,并把当前PID留在锁上
Duplicating a file descriptor:
F_DUPFD (int) //找到>=arg的最小的可以使用的文件描述符,并把这个文件描述符用作fd的一个副本
F_DUPFD_CLOEXEC(int)//和F_DUPFD一样,除了会在新的文件描述符上设置close-on-exec
F_GETFD (void) //读取fd的flag,忽略arg的值
F_SETFD (int) //将fd的flags设置成arg的值.
F_GETFL (void) //读取fd的Access Mode和其他的file status flags; 忽略arg
F_SETFL (long) //设置file status flags为arg
F_GETOWN(void) //返回fd上接受SIGIO和SIGURG的PID或进程组ID
F_SETOWN(int) //设置fd上接受SIGIO和SIGURG的PID或进程组ID为arg
F_GETOWN_EX(struct f_owner_ex*) //返回当前文件被之前的F_SETOWN_EX操作定义的文件描述符R
F_SETOWN_EX(struct f_owner_ex*) //和F_SETOWN类似,允许调用程序将fd的I/O信号处理权限直接交给一个线程,进程或进程组
F_GETSIG(void) //当文件的输入输出可用时返回一个信号
F_SETSIG(int) //当文件的输入输出可用时发送arg指定的信号
*/ /*…:
可选参素,是否需要得看cmd,如果是加锁,这里应是struct flock*
struct flock {
short l_type; //%d Type of lock: F_RDLCK(读锁), F_WRLCK(写锁), F_UNLCK(解锁)
short l_whence; //%d How to interpret l_start, 加锁的位置参考标准:SEEK_SET, SEEK_CUR, SEEK_END
off_t l_start; //%ld Starting offset for lock, 加锁的起始位置
off_t l_len; //%ld Number of bytes to lock , 锁定的字节数
pid_t l_pid; // PID of process blocking our lock, (F_GETLK only)加锁的进程号,,默认给-1
};
*/

建议锁(Adversory Lock)

限制加锁,但不限制读写, 所以只对加锁成功才读写的程序有效,用来解决不同的进程 同时同一个文件同一个位置 “写”导致的冲突问题

读锁是一把共享锁(S锁):共享锁+共享锁+共享锁+共享锁+共享锁+共享锁

写锁是一把排他锁(X锁):永远孤苦伶仃

释放锁的方法(逐级提高):

  • 将锁的类型改为:F_UNLCK, 再使用fcntl()函数重新设置
  • close()关闭fd时, 调用进程在该fd上加的所有锁都会自动释放
  • 进程结束时会自动释放所有该进程加过的文件锁

Q:为什么加了写锁还能gedit或vim写???

A:可以写, 锁只可以控制能否加锁成功, 不能控制对文件的读写, 所以叫”建议”锁, 我加了锁就是不想让你写, 你非要写我也没办法. vim/gedit不通过能否加锁成功来决定是否读写, 所以可以直接上

Q: So如何实现文件锁控制文件的读写操作????

A:可以在读操作前尝试加读锁, 写操作前尝试加写锁, 根据能否加锁成功决定能否进行读写操作

int fd=open("./a.txt",O_RDWR);					//得到fd
if(-1==fd)
perror("open"),exit(-1);
struct flock lock={F_RDLCK,SEEK_SET,2,5,-1}; //设置锁 //此处从第3个byte开始(包含第三)锁5byte
int res=fcntl(fd,F_SETLK,&lock); //给fd加锁
if(-1==res)
perror("fcntl"),exit(-1);

ioct1()

这个函数可以实现其他文件操作函数所没有的功能,大多数情况下都用在设备驱动程序里,每个设备驱动程序可以定义自己专用的一组ioctl命令,系统则为不同种类的设备提供通用的ioctl命令

//操作特殊文件的设备参数,成功返回0,失败返回-1设errno
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);
//d:an open file descriptor.
//request: a device-dependent request code

close()

//关闭fd,这样这个fd就可以重新用于连接其他文件,成功返回0,失败返回-1设errno
#include <unistd.h>
int close(int fd);
#include <unistd.h>
#include<stdlib.h>
int res=close(fd);
if(-1==res)
perror("close"),exit(-1);

Linux文件I/O的更多相关文章

  1. Linux实战教学笔记08:Linux 文件的属性(上半部分)

    第八节 Linux 文件的属性(上半部分) 标签(空格分隔):Linux实战教学笔记 第1章 Linux中的文件 1.1 文件属性概述(ls -lhi) linux里一切皆文件 Linux系统中的文件 ...

  2. Linux文件查找.md

    Linux 文件查找 在Linux系统的查找相关的命令: which 查看可执行文件的位置 whereis 查看文件的位置 locate 配合数据库查看文件位置 find 实际搜寻硬盘查询文件名称 w ...

  3. Linux 文件压缩与归档

    .note-content { font-family: "Helvetica Neue", Arial, "Hiragino Sans GB", STHeit ...

  4. Linux文件类型

    1.windows系统和Linux系统的区别 windows通过问价扩展名来区分文件类型,例如*.txt是一个文本文件, linux系统文件的扩展名和文件类型没有关系 为了容易区分文件类型,linux ...

  5. 修改linux文件权限

    Linux系统中的每个文件和目录都有访问许可权限,用它来确定谁可以通过何种方式对文件和目录进行访问和操作. 文件或目录的访问权限分为只读,只写和可执行三种.以文件为例,只读权限表示只允许读其内容,而禁 ...

  6. Linux文件和目录权限详细讲解

    转载请标明出处: http://www.cnblogs.com/why168888/p/5965180.html 本文出自:[Edwin博客园] Linux文件和目录权限解读 如何设置Linxu文件和 ...

  7. linux 文件系统结构及命令

    1.linux 文件系统结构 / 根目录 root |--mnt/ | |--sdcard/  挂载点 | |--usb0 | |--cdrom |--home | |--soft01 <- 用 ...

  8. linux 文件权限、类型、命名规则

    文件权限 -rwxr-x--t        文件类型 用户权限 组权限 其他用户权限 umask是一个掩码,设置文件的默认权限,会屏蔽掉不想授予该安全级别的权限,从对象的全权权限中减掉:对文件全权权 ...

  9. linux文件权限查看及修改(实用)

    查看Linux文件的权限:ls -l 文件名称 查看linux文件夹的权限:ls -ld 文件夹名称(所在目录) 修改文件及文件夹权限: sudo chmod -(代表类型)×××(所有者)×××(组 ...

  10. Linux文件权限查看及修改命令chmod

    查看权限 Linux文件访问权限分为可读,可写和可执行三种. 可用ls -l命令查看,例: ls -l或者 ll 显示为 -r--r--r--. 1 root root 21 Jan 5 23:02 ...

随机推荐

  1. Java面试题总结系列 Servlet

    Servlet技术主要是为了使用Web上的HTTP协议而设计的.servlet是在WEB服务器上运行的程序.Java Servlet可以用于处理客户请求或生成动态Web网页.先一个实例.然后解释. 先 ...

  2. Scalaz(51)- scalaz-stream: 资源使用安全-Resource Safety

    scalaz-stream是一个数据流处理工具库,对资源使用,包括:开启文件.连接网络.连接数据库等这些公共资源使用方面都必须确定使用过程的安全:要保证在作业终止时能进行事后处理程序(finalize ...

  3. HDU 1237 简单计算器 栈

    额,题目是中文的,题意就不用说了= =都看懂喽.写个字符串先把这行计算式存进去,不过不能存一个算一个,因为考虑到乘除法比加减法优先的原则,如果是加号减号就先存着等待计算,如果是乘号除号就直接算出来值就 ...

  4. Verilog学习笔记简单功能实现(七)...............接口设计(并行输入串行输出)

    利用状态机实现比较复杂的接口设计: 这是一个将并行数据转换为串行输出的变换器,利用双向总线输出.这是由EEPROM读写器的缩减得到的,首先对I2C总线特征介绍: I2C总线(inter integra ...

  5. mysql本地可以访问 网络不能访问

    远程登陆数据库的时候出现了下面出错信息: ERROR 2003 (HY000): Can't connect to MySQL server on 'xxx.xxx.xxx.xxx', 经过今天下午的 ...

  6. Tsql查询执行顺序(二)

    来源:http://www.cnblogs.com/gaiyang/archive/2011/04/13/2014356.html T-SQL查询处理这种逻辑处理顺序,是理论上的处理过程,实际情况还会 ...

  7. JBox - 模态窗口,工具提示和消息 jQuery 插件

    jBox 是一个强大而灵活的 jQuery 插件,可以帮助实现模态窗口,工具提示,通知和更多的功能.你可以使用 jQuery 选择器轻松地添加工具提示效果到元素上,您可以以同样的方式设置模态窗口.该库 ...

  8. 使用 SVG 动画实现弹性的页面元素效果

    Codrops 分享了一些给SVG元素加上弹性动画的灵感.实现的思路是把一个SVG元素整合成一个组件,然后从一个路径弹性动画到另一个.这种效果可以应用到像菜单,按钮或其它元素,使得交互更有趣,看起更原 ...

  9. Ampersand.js - 模块化的 JS 应用程序开发框架

    Ampersand.js 是一个高度模块化,松耦合,用于构建先进的 JavaScript 应用程序的框架.通过良好定义的方法,结合了一系列微小的 CommonJS 模块.条理清晰,没有多余的冗余代码. ...

  10. php随机生成指定长度的字符串 可以固定数字 字母 混合

    php 生成随机字符串 可以指定是纯数字 还是纯字母 或者混合的. 可以指定长度的. function rand_zifu($what,$number){ $string=''; for($i = 1 ...