千家信息网

Linux下文件输入/输出端口的试炼分析

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,这篇文章将为大家详细讲解有关Linux下文件输入/输出端口的试炼分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。文件描述符(File Descriptor)a s
千家信息网最后更新 2026年11月01日Linux下文件输入/输出端口的试炼分析

这篇文章将为大家详细讲解有关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_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  #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);

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); }

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-execF_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下文件输入/输出端口的试炼分析"这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

文件 进程 成功 系统 位置 标志 函数 程序 结构 数据 设备 信号 指针 输入 输出 最小 就是 时候 状态 路径 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 广东猪兼强互联网科技有限公司 互联网电商科技 互联网科技资迅 科技互联网技术 互联网未来科技 科技创新互联网企业 金融科技和互联网金融 互联网科技创新企业排名 哈尔滨秒房互联网科技有限公司 云创客互联网科技公司 互联网金融科技公司干啥的 世界互联网科技公司市值排名 想开互联网科技公司怎么样 长沙茂晟互联网科技有限公司 南博望互联网科技有限公司 山东子淼互联网科技有限公司 每天都有哪些好的互联网科技 启辰互联网科技 广西科技产业互联网是什么 六子台科技互联网展会 互联网属科技 天津摩尔互联网科技 睿民互联网科技 科技类的互联网平台 温州喔噻互联网科技有限公司 移动互联网年度科技创新 浙南科技城互联网大厦建设工程 洛阳共同富裕互联网科技有限公司 广东工业互联网大赛深圳学泰科技 西安左小互联网科技有限公司
0