详细介绍Linux文件I/O的基本情况

文件描述符(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_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#includeint 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:0o_wronly:1o_rdwr:2*//*open-time flags(bitwise or):o_cloexec :为新打开的文件描述符使能close-on-exec。可以避免程序再用fcntl()的f_setfd来设置fd_cloexeco_creat :如果文件不存在就创建文件,并返回它的文件描述符,如果文件存在就忽略这个选项,必须在保护模式下使用,eg:0664o_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/oo_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#includeint 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()
#includeint creat(const char *pathname, mode_t mode);
dup()、dup2()、dup3()
//复制一个文件描述符的指向,新的文件描述符的flags和原来的一样,成功返回new_file_descriptor, 失败返回-1并设errno#include int dup(int oldfd); //使用未被占用的最小的文件描述符编号作为新的文件描述符int dup2(int oldfd, int newfd);
#include #include int dup3(int oldfd, int newfd, int flags);
#include#includeint res=dup2(fd,fd2);if(-1==res){ perror(dup2),exit(-1);
read()
//从fd对应的文件中读count个byte的数据到以buf开头的缓冲区中,成功返回成功读取到的byte的数目,失败返回-1设errno#include ssize_t read(int fd, void *buf, size_t count);
#include #includeint res=read(fd,buf,6);if(-1==fd) perror(read),exit(-1);
write()
//从buf指向的缓冲区中读取count个byte的数据写入到fd对应的文件中,成功返回成功写入的byte数目,文件的位置指针会向前移动这个数目,失败返回-1设errno#include ssize_t write(int fd, const void *buf, size_t count);//不需要对buf操作, 所以有const, vs read()没有const
#include #includeint 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 #include off_t lseek(int fd, off_t offset, int whence);/*whence:seek_set:以文件开头为基准进行偏移,0一般不能向前偏seek_cur:以当前位置指针的位置为基准进行偏移,1向前向后均可seek_end:以文件的结尾为基准进行偏移,2向前向后均可向后形成”文件空洞”
#include#includeint len=lseek(fd,-3,seek_set);if(-1==len){ perror(lseek),exit(-1);
fcntl()
//对fd进行各种操作,成功返回0,失败返回-1设errno#include #include 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-execf_getfd (void) //读取fd的flag,忽略arg的值f_setfd (int) //将fd的flags设置成arg的值.f_getfl (void) //读取fd的access mode和其他的file status flags; 忽略argf_setfl (long) //设置file status flags为argf_getown(void) //返回fd上接受sigio和sigurg的pid或进程组idf_setown(int) //设置fd上接受sigio和sigurg的pid或进程组id为argf_getown_ex(struct f_owner_ex*) //返回当前文件被之前的f_setown_ex操作定义的文件描述符rf_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); //得到fdif(-1==fd) perror(open),exit(-1);struct flock lock={f_rdlck,seek_set,2,5,-1}; //设置锁 //此处从第3个byte开始(包含第三)锁5byteint res=fcntl(fd,f_setlk,&lock); //给fd加锁if(-1==res) perror(fcntl),exit(-1);
ioct1()
这个函数可以实现其他文件操作函数所没有的功能,大多数情况下都用在设备驱动程序里,每个设备驱动程序可以定义自己专用的一组ioctl命令,系统则为不同种类的设备提供通用的ioctl命令
//操作特殊文件的设备参数,成功返回0,失败返回-1设errno#include int ioctl(int d, int request, ...);//d:an open file descriptor.//request: a device-dependent request code
close()
//关闭fd,这样这个fd就可以重新用于连接其他文件,成功返回0,失败返回-1设errno#include int close(int fd);
#include #includeint res=close(fd);if(-1==res) perror(close),exit(-1);

易微联多路WiFi开关模块PSF-B04 支持Alexa等智能语音音箱
一文知道光纤通信技术的发展趋势
预认证的互联简化IoT的应用
降压LED驱动器TMI5101概述、特征及应用
产品智能化成照明企业“必选题” 小匠物联交出标准答卷
详细介绍Linux文件I/O的基本情况
电视大屏化将成未来趋势
家用洗地机语音播放芯片选型
RECOM推出高性能新型RP10-RAW系列隔离式稳压转换器
助力中国本土企业——2013英飞凌伺服技术研讨会圆满落幕
飞奔的新能源汽车,连接器御风而行
电解质离子种类对电催化反应的影响—进展、挑战与展望
中芯国际披露登陆科创板以来首份三季报
电路仿真中不可不知的3大交流电容知识
涡旋压缩机结构图_涡旋压缩机优缺点
我国确定今年发布4G牌照 首推TD-LTE
扬尘在线监测系统——维护环境健康-欧森杰
利用单个转换器IC轻松将12V升压至140V
DGM宣布将加拿大的最大变电站做为加密货币矿场的供电所
人工智能和机器学习在海上油气行业的应用越发广泛