目录
文件描述符(File Descriptor)" >文件描述符(File Descriptor)
fd原理" >fd原理
文件描述符标志(File Descriptor Flag)" >文件描述符标志(File Descriptor Flag)
文件状态标志(File Status Flag)" >文件状态标志(File Status Flag)
open()" >open()
creat()" >creat()
dup()、dup2()、dup3()" >dup()、dup2()、dup3()
read()" >read()
write()" >write()
lseek()" >lseek()
fcntl()" >fcntl()
建议锁(Adversory Lock)" >建议锁(Adversory Lock)
释放锁的方法(逐级提高):" >释放锁的方法(逐级提高):
ioct1()" >ioct1()
close()" >close()
首页 系统教程 操作系统 Linux文件I/O:原理与方法

Linux文件I/O:原理与方法

Feb 09, 2024 pm 06:27 PM
linux linux教程 linux系统 linux命令 外壳脚本 嵌入式linux linux入门 linux学习

文件是Linux系统中最基本和最常用的数据存储方式,它们可以是文本文件,二进制文件,设备文件,目录文件等。文件的读写是Linux程序设计中最重要的操作之一,它涉及到文件描述符,缓冲区,系统调用,库函数等概念。在本文中,我们将介绍Linux文件I/O的基本原理和方法,包括打开,关闭,读取,写入,定位,截断,同步等操作,并举例说明它们的使用方法和注意事项。

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是在读取文件时通过文件系统从磁盘中导入的文件位置
    Linux文件I/O:原理与方法
Linux文件I/O:原理与方法

文件描述符标志(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
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
#include
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
int 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
#include
int res=dup2(fd,fd2);
if(-1==res){
        perror("dup2"),exit(-1);
登录后复制
Linux文件I/O:原理与方法

read()

//从fd对应的文件中读count个byte的数据到以buf开头的缓冲区中,成功返回成功读取到的byte的数目,失败返回-1设errno
#include 
ssize_t read(int fd, void *buf, size_t count);
#include 
#include
int 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 
#include
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 
#include 
off_t lseek(int fd, off_t offset, int whence);
/*whence:
SEEK_SET:以文件开头为基准进行偏移,0一般不能向前偏
SEEK_CUR:以当前位置指针的位置为基准进行偏移,1向前向后均可
SEEK_END:以文件的结尾为基准进行偏移,2向前向后均可向后形成”文件空洞”
#include
#include
int 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-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 
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 
#include
int res=close(fd);
if(-1==res)
        perror("close"),exit(-1);
登录后复制

通过本文,我们了解了Linux文件I/O的基本原理和方法,它们可以满足我们对文件的各种操作需求。我们应该根据实际需求选择合适的方法,并遵循一些基本原则,如关闭不用的文件描述符,检查错误返回值,使用合适的缓冲区大小等。文件I/O是Linux程序设计中不可或缺的一部分,它可以实现数据的持久化和交换,也可以提升程序的功能和性能。希望本文能够对你有所帮助和启发。

以上是Linux文件I/O:原理与方法的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

如何解决Linux终端中查看Python版本时遇到的权限问题? 如何解决Linux终端中查看Python版本时遇到的权限问题? Apr 01, 2025 pm 05:09 PM

Linux终端中查看Python版本时遇到权限问题的解决方法当你在Linux终端中尝试查看Python的版本时,输入python...

在Docker环境中使用PECL安装扩展时为什么会报错?如何解决? 在Docker环境中使用PECL安装扩展时为什么会报错?如何解决? Apr 01, 2025 pm 03:06 PM

在Docker环境中使用PECL安装扩展时报错的原因及解决方法在使用Docker环境时,我们常常会遇到一些令人头疼的问�...

如何在LAMP架构下高效整合Node.js或Python服务? 如何在LAMP架构下高效整合Node.js或Python服务? Apr 01, 2025 pm 02:48 PM

在LAMP架构下整合Node.js或Python服务许多网站开发者都面临这样的问题:已有的LAMP(Linux Apache MySQL PHP)架构网站需要...

在Linux终端中使用python --version命令时如何解决权限问题? 在Linux终端中使用python --version命令时如何解决权限问题? Apr 02, 2025 am 06:36 AM

Linux终端中使用python...

如何在macOS上将apscheduler定时任务配置为服务? 如何在macOS上将apscheduler定时任务配置为服务? Apr 01, 2025 pm 06:09 PM

在macOS上将apscheduler定时任务配置为服务在macOS平台上,如果你想将apscheduler定时任务配置为一个服务,类似于ngin...

c语言多线程的四种实现方式 c语言多线程的四种实现方式 Apr 03, 2025 pm 03:00 PM

语言多线程可以大大提升程序效率,C 语言中多线程的实现方式主要有四种:创建独立进程:创建多个独立运行的进程,每个进程拥有自己的内存空间。伪多线程:在一个进程中创建多个执行流,这些执行流共享同一内存空间,并交替执行。多线程库:使用pthreads等多线程库创建和管理线程,提供了丰富的线程操作函数。协程:一种轻量级的多线程实现,将任务划分成小的子任务,轮流执行。

Linux系统自带Python解释器能删除吗? Linux系统自带Python解释器能删除吗? Apr 02, 2025 am 07:00 AM

关于Linux系统自带Python解释器的删除问题许多Linux发行版在安装时会预装Python解释器,它并非通过软件包管理器�...

web.xml怎么打开 web.xml怎么打开 Apr 03, 2025 am 06:51 AM

要打开 web.xml 文件,可以使用以下方法:使用文本编辑器(如记事本或 TextEdit)使用集成开发环境(如 Eclipse 或 NetBeans)使用命令行编辑命令(Windows:notepad web.xml;Mac/Linux:open -a TextEdit web.xml)

See all articles