当前位置: 首页 > 科技观察

Linux文件描述符(FileDescriptor)下的文件输入-输出端口

时间:2023-03-13 04:27:40 科技观察

一个小的非负整数,供后续系统调用(read(2)、write(2)、lseek(2)、fcntl(2)等)使用。)($man2打开)。当一个程序开始运行时,一般有3个打开的文件描述符:0:STDIN_FIFLENO,标准输入stdin1:STDOUT_FILENO,标准输出stdout2:STDERR_FILENO,标准错误stderrorfd原理fd从0开始,找到最小未使用的描述符,建立一个文件表指针和文件表描述符的对应关系(VSpid一直在上升,满了再回来找)文件描述符是一个int,用来表示一个打开的文件,但是文件的管理信息不能存储在文件描述符中。当使用open()函数打开一个文件时,OS会将文件的相关信息加载到文件表等数据结构中,但是出于安全和效率等因素的考虑,文件表等数据结构并不是适合直接运算,但是给结构体赋一个数,用数来进行运算。数字是文件描述符,操作系统会在内部为每个进程维护一个文件。文件描述符表。当有新的文件描述符需求时,它会去表中找到最小的未使用描述符并返回。文件描述符虽然是int类型,但实际上是一个非负整数,即0~OPEN_MAX(当前系统为1024),其中0、1、2已经被系统占用,分别代表stdin,stdout,stderror使用close()关闭fd时,是从普通表中改变fd与文件表结构的对应关系,但并不一定删除文件表结构。只有当文件表不对应任何其他fd时(即一个文件表可以同时对应多个fd)才会删除文件表,close()不会改变文件描述符的整型值本身只会使文件描述符无法表示文件。duplicatefdVScopyfd:dup是将old_fd对应的文件表指针复制到new_fd中,而不是intnew_fd=old_fdUNIX使用三种数据结构描述打开的文件:文件描述符表,用于描述当前进程在每个文件中打开的文件过程,表示当前文件状态的文件状态标识表,和用于查找文件i节点(索引节点)的V节点表,Linux中没有使用这个Vnode结构,而是换了一个通用的inode结构,但是没有区别在本质上。inode是读取文件时通过文件系统从磁盘导入的文件位置文件描述符标志(FileDescriptorFlag)。当前系统只有一个文件描述符flagclose-on-exec,也就是一个flag。当进程fork出一个子进程时,在子进程中调用exec函数时会用到这个标志。意思是执行exec前是否关闭文件描述符。一般我们会调用exec来执行另一个程序。这时候子进程的text、data、heap和stack就会被新的程序替换掉。这个时候保存文件描述符的变量当然是不存在的,我们也不能关闭无用的文件描述符。所以通常我们会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函数之前,系统已经让子进程关闭了文件描述符。注:虽然新版本支持打开时设置CLOEXEC,但是编译时仍然提示错误-error:'O_CLOEXEC'undeclared(首次在此函数中使用)。此功能需要通过设置宏(_GNU_SOURCE)来启用。#define_GNU_SOURCE//在源代码中加入-D_GNU_SOURCE//在编译参数中加入文件状态标志(FileStatusFlag)。文件状态标志用于指示打开文件的属性。文件状态标志可以通过复制文件描述符来共享。打开文件的状态,但文件描述符标志不起作用。AccessModes:指定文件的访问模式:只读、只写、读写。open()设置,fcntl()返回,不可更改Open-timeFlags:表示open()执行时的操作,open()执行后flag不保存OperatingModes:影响读、写操作,由open()设置,但可以使用fcntl()读取或改变成功返回文件描述符,失败返回-1Seterrno#includeintopen(constchar*pathname,intflags)intopen(constchar*pathname,intflags,mode_tmode)//不是函数重载,没有C中的重载,是可变长参数列表//pathname:文件或设备路径//flags:filestatusflags=Accessmode+Open-timeflags+OperatingModes,/*AccessMode(必选):O_RDONLY:0O_WRONLY:1O_RDWR:2*//*Open-timeFlags(BitwiseOr):O_CLOEXEC:为新打开的文件描述符启用close-on-exec。它可以防止程序使用fcntl()的F_SETFD来设置FD_CLOEXECO_CREAT:如果文件不存在,则创建文件并返回其文件描述符。如果文件存在,忽略该选项,必须在保护模式下使用,eg:0664O_DIRECTORY:如果在FIFO或磁带中调用opendir(),该选项可以避免拒绝服务问题,如果路径不存在指向一个目录,它将无法打开。O_EXCL:确保open()可以创建文件。如果文件已经存在,会导致打开失败。始终与O_CREAT一起使用。O_NOCTTY:如果路径指向一个终端设备,那么这个设备不会成为这个进程的控制终端,即使进程没有控制终端O_NOFOLLOW:如果路径是一个符号链接,打开它链接到的文件//Ifpathnameisasymboliclink,thentheopenfails.O_TMPFILE:创建一个未命名的临时文件。将在文件系统中创建一个未命名的inode。当最后一个文件描述符关闭时,写入此文件的所有内容都将丢失,除非在它之前给定一个名称O_TRUNC:清空文件O_TTY_INIT*//*OperatingModes(BitwiseOr)O_APPEND:通过追加方式打开文件,并写入默认结束。在目前的Unix/Linux系统中,这个选项已经被定义为一个原子操作。O_ASYNC:Enablesignal-drivenI/OO_DIRECT:TrytominimizethecacheeffectfromI/Oandthisfile//TrytominimizecacheeffectsoftheI/Otoandfromthisfile.O_DSYNC:每个写操作都会等待I/O操作完成,但是如果更新fileattribute不影响read刚刚write如果数据输入了,就不会等待fileattributes的更新。O_LARGEFILE:允许打开大小超过off_t指示范围的文件(但不超过off64_t)O??_NOATIME:不改变文件的st_time(lastaccesstime)O_NONBLOCK/O_NDELAY:如果可能,以非块模式打开文件O_SYNC:Each写操作会等待I/O操作完成,包括write()引起的文件属性更新。O_PATH:获取一个文件描述符,可以指示文件在文件系统中的位置#include#includeintfd=open("b.txt",O_RDWR|O_CREAT|O_EXCL,0664);if(-1==fd)perror("打开"),退出(-1);FA:猜想有如下模型:用一串特定位为1其余全为0的字符串表示一个选项,选项调用“按位与”得到一个0/1的字符串,表示整个flags的状态,注:低三位表示AccessModecreat()相当于调用open(),flag为O_WRONLY|O_TRUNC|O_CREAT#includeintcreat(constchar*pathname,mode_tmode);dup(),dup2(),dup3()/复制一个文件描述符的指针,新文件描述符的flags和原来的一样,成功返回new_file_descriptor,失败返回-1并设置errno#includeintdup(intoldfd);//使用最小未占用文件描述符作为新文件描述符intdup2(intoldfd,intnewfd);#include#includeintdup3(intoldfd,intnewfd,intflags);#include#includeintres=dup2(fd,fd2);if(-1==res){perror("dup2"),exit(-1);}读()//从fd对应的文件中读取count个字节的数据到buf开头的buffer中,成功返回读取成功的字节数,返回-1seterrno#includessize_tread(intfd,void*buf,size_tcount);#include#includeintres=read(fd,buf,6);if(-1==fd)perror("read"),exit(-1);write()//从bufA指向的缓冲区读取计数字节数据写入fd对应的文件,成功返回写入成功的字节数,文件位置指针向前移动这个数,失败返回-1seterrno#includessize_twrite(intfd,constvoid*buf,size_tcount);//不需要对buf进行操作,所以有const,VSread()没有const#include#includeintres=write(fd,"hello",sizeof("hello"));if(-1==res)perror("write"),exit(-1);注意:即使上例中只有一个字符'A',也必须写'A',因为'A'是地址,'A'只是一个intlseek()l表示longint,历史原因//根据移动参考where和移动距离offset重新定位文件的位置指针,并返回移动位置指针到文件开头的距离,失败返回-1Seterrno#include#includeoff_tlseek(intfd,off_toffset,intwhence);/*whence:SEEK_SET:offsetbasedonthebeginningofthefile,0一般不能SEEK_CURforward:offsetbasedonthepositionofthecurrentpositionpointer,1可向前也可向后SEEK_END:以文件结尾为基准的偏移,2可向前也可向后?向后形成一个“文件洞”#include#includeintlen=lseek(fd,-3,SEEK_SET);if(-1==len){perror("lseek"),exit(-1);}fcntl()//对fd做各种操作,成功返回0,失败返回-1,seterrno#include#includeintfcntl(intfd,intcmd,...);//...表示变长参数/*cmd:Adversoryrecordlocking:F_SETLK(structflock*)//设置咨询锁F_SETLKW(structflock*)//设置咨询锁,如果文件上存在冲突锁,正在等待如果捕获到信号,调用将被中断,并在捕获到信号后立即返回错误。如果等待期间没有信号,就会等待F_GETLK(structflock*)//尝试释放锁。如果可以释放锁,则不会释放锁,而是返回一个包含F_UNLCK的l_type类型,其他不变。如果无法释放锁,那么fcntl()将向文件添加一种新类型的锁,并将当前PID留在锁上。Duplicatingafiledescriptor:F_DUPFD(int)//找到最小的可用文件描述符>=arg,并将此文件描述符作为fd的副本F_DUPFD_CLOEXEC(int)//同F_DUPFD,只是close-on会设置在新文件上descriptor-execF_GETFD(void)//读取fd的flag,忽略arg的值F_SETFD(int)//将fd的flags设置为arg的值。F_GETFL(void)//读取fd的AccessMode和其他filestatusflags;ignoreargF_SETFL(long)//设置filestatusflags为argF_GETOWN(void)//返回fd上接受SIGIO和SIGURG的PID或进程组IDF_SETOWN(int)//设置fd上接受SIGIO和SIGURG的PID或进程组ID为argF_GETOWN_EX(structf_owner_ex*)//返回前面F_SETOWN_EX操作定义的当前文件的文件描述符RF_SETOWN_EX(structf_owner_ex*)//类似于F_SETOWN,让调用程序直接将fd的I/O信号处理权限交给athread,processorprocessgroupF_GETSIG(void)//当文件的输入输出可用时返回一个信号F_SETSIG(int)//当文件的输入输出可用时发送arg指定的信号*//*...:可选参数,是否需要取决于cmd,如果被锁定,应该是structflock*structflock{shortl_type;//%dTypeoflock:F_RDLCK(读锁),F_WRLCK(写锁),F_UNLCK(解锁)shortl_whence;//%dHowtointerpretl_start,锁位置参考标准:SEEK_SET,SEEK_CUR,SEEK_ENDoff_tl_start;//%ldStartingoffsetforlock,startoflockPositionoff_tl_len;//%ldNumberofbytestolock,锁定的字节数pid_tl_pid;//PIDofprocessblockingourlock,(F_GETLKonly)锁定的进程号,默认为-1};*/AdversoryLock(AdversoryLock)限制加锁,但不限制读写,所以只对加锁成功后读写的程序有效。用于解决不同进程同时“写入”同一个文件的同一个位置所引起的冲突。读锁是共享锁(S锁):共享锁+共享锁+共享锁+共享锁+共享锁+共享锁写锁是排它锁(X锁):永远孤独。释放锁的方法(分步):将锁的类型改为:F_UNLCK,当使用fcntl()函数重新设置close()关闭fd时,调用进程在fd上加的所有锁都会被自动释放。在进程结束时,进程添加的所有文件锁都会自动释放。Q:为什么要加写锁?gedit或者vim可以写吗???A:可以写,锁只能控制锁能否成功加锁,不能控制文件的读写,所以称为“建议型”锁。我加锁是因为我不想让你写。你必须我别无选择,只能写作。vim/gedit并不会根据加锁成功与否来决定读写,所以可以直接去问:那么如何实现文件锁来控制文件的读写操作????A:在读操作前尝试加读锁,在写操作前尝试加写锁,根据能否成功加锁来判断是否可以进行读写操作intfd=open("./a.txt",O_RDWR);//获取fdif(-1==fd)perror("open"),exit(-1);结构群lock={F_RDLCK,SEEK_SET,2,5,-1};//设置锁//这里从第三个字节开始(包括第三个)lock5byteintres=fcntl(fd,F_SETLK,&lock);//为fd加锁if(-1==res)perror("fcntl"),退出(-1);ioct1()这个函数可以实现其他文件操作函数没有的功能,大多数情况下用在设备驱动中,每个设备驱动可以定义自己专用的ioctl命令集,系统针对不同类型提供了通用的ioctl命令ofdevices//操作特殊文件的设备参数,成功返回0,失败返回-1seterrno#includeintioctl(intd,intrequest,...);//d:anopenfiledescriptor.//request:adevice-dependentrequestcodeclose()//关闭fd,使这个fd可以重新用于连接其他文件,成功返回0,失败返回-1seterrno#includeintclose(intfd);#include#includeintres=close(fd);if(-1==res)perror("close"),exit(-1);