【Linux C | 文件I/O】fcntl函数详解 | 设置描述符非阻塞、文件(记录)锁
😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭
本文未经允许,不得转发!!!
目录
- 🎄一、fcntl 函数介绍
- 🎄二、复制文件描述符(F_DUPFD、F_DUPFD_CLOEXEC)
- ✨2.1 F_DUPFD(int)
- ✨2.2 F_DUPFD_CLOEXEC(int)
- 🎄三、获取/设置文件描述符标志(F_GETFD、F_SETFD)
- 🎄四、获取/设置文件状态标志(F_GETFL、F_SETFL)
- 🎄五、获取/设置记录锁(F_GETLK、F_SETLK、F_SETLKW)
- 🎄六、总结
🎄一、fcntl 函数介绍
函数原型:
#include #include int fcntl(int fd, int cmd, ... /* arg */ ); // arg表示可变参数,由cmd决定
fcntl()对打开的文件描述符fd执行下面描述的操作之一。操作由cmd决定。
fcntl()的第三个参数是可选。是否需要此参数由cmd决定。所需的参数类型在每个cmd名称后面的括号中指示(在大多数情况下,所需的类型是int,我们使用名称arg来标识参数),如果不需要参数,则指定void。
以下某些操作仅在特定的Linux内核版本之后才受支持。检查主机内核是否支持特定操作的首选方法是使用所需的cmd值调用fcntl(),然后使用EINVAL测试调用是否失败,这表明内核无法识别该值。
本文主要介绍下面4个功能:
- 1、复制文件描述符(F_DUPFD、F_DUPFD_CLOEXEC);
- 2、获取/设置文件描述符标志(F_GETFD、F_SETFD);
- 3、获取/设置文件状态标志(F_GETFL、F_SETFL);
- 4、获取/设置记录锁(F_GETLK、F_SETLK、F_SETLKW);
🎄二、复制文件描述符(F_DUPFD、F_DUPFD_CLOEXEC)
✨2.1 F_DUPFD(int)
F_DUPFD(int) 表示使用 F_DUPFD 作为cmd时,第三个参数需要传入int型数据。
cmd为F_DUPFD表示复制文件描述符fd。调用成功会返回新的描述符。新描述符使用大于或等于arg参数的编号最低的可用文件描述符复制文件描述符fd。新描述符与f似共享同一文件表项。但是,新描述符有它自己的一套文件描述符标志,其FD_CLOEXEC文件描述符标志被清除〈这表示该描述符在 exec 时仍保持打开状态)。
// fcntl_F_DUPFD.c #include #include #include #include int main() { int fd = open("./fcntl_F_DUPFD", O_RDWR | O_CREAT | O_TRUNC, 0775); int fcntlFd = fcntl(fd, F_DUPFD, 0); // 指定从 0 开始分配最小的可用描述符作为新描述符 int dupFd = dup(fd); // 等效于 fcntl(fd, F_DUPFD, 0); close(fd); close(fcntlFd); close(dupFd); return 0; }
✨2.2 F_DUPFD_CLOEXEC(int)
F_DUPFD_CLOEXEC(int) 表示使用 F_DUPFD_CLOEXEC 作为cmd时,第三个参数需要传入int型数据。
cmd为F_DUPFD_CLOEXEC的功能与F_DUPFD类似,区别在于F_DUPFD_CLOEXEC在复制的同时会设置文件描述符标志FD_CLOEXEC,表示在执行exec系列函数后,该描述符会关闭。
看例子:F_GETFD和dup函数都会清除新描述符的FD_CLOEXEC标志,F_DUPFD_CLOEXEC会复制并设置FD_CLOEXEC标志。
#include #include #include #include int main() { int fd = open("./fcntl_F_DUPFD_CLOEXEC", O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0775); int fcntlFd = fcntl(fd, F_DUPFD, 0); int fcntlCloFd = fcntl(fd, F_DUPFD_CLOEXEC, 0); int dupFd = dup(fd); int fdFlag = fcntl(fd, F_GETFD, 0); int fcntlFdFlag = fcntl(fcntlFd, F_GETFD, 0); int fcntlCloFdFlag = fcntl(fcntlCloFd, F_GETFD, 0); int dupFdFlag = fcntl(dupFd, F_GETFD, 0); // 结果是:fdFlag=1, fcntlFdFlag=0, fcntlCloFdFlag=1, dupFdFlag=0 printf("fdFlag=%d, fcntlFdFlag=%d, fcntlCloFdFlag=%d, dupFdFlag=%d\n", fdFlag,fcntlFdFlag,fcntlCloFdFlag,dupFdFlag); close(fd); close(fcntlFd); close(fcntlCloFd); close(dupFd); return 0; }
运行结果:
🎄三、获取/设置文件描述符标志(F_GETFD、F_SETFD)
当前只定义了一个文件描述符标志FD_CLOEXEC。用来表示该描述符在执行完fork+exec系列函数创建子进程时会自动关闭,以防止它们被传递给子进程。为什么要这样做呢?
因为当一个进程调用exec系列函数(比如execve)来创建子进程时,所有打开的文件描述符都会被传递给子进程。如果文件描述符没有设置FD_CLOEXEC标志,这些文件将保持打开状态并继续对子进程可见。这可能导致潜在的安全风险或者意外行为。
F_GETFD(void) :表示使用 F_GETFD 作为cmd时,不需要传入第三个参数。
功能:获取文件描述符标志。
返回值:
- 成功返回文件描述符标志
- 失败返回 -1.
F_SETFD(int):表示使用 F_SETFD 作为cmd时,传入第三个参数是int型的。
功能:设置文件描述符标志,第三个参数传入新的标志值。
返回值:
- 成功返回 0
- 失败返回 -1.
文件描述符的FD_CLOEXEC标志可以通过三个方法得到:
- 1、调用open函数是,指定 O_CLOEXEC
- 2、通过fcntl函数使用F_DUPFD_CLOEXEC复制文件描述符,新的描述符就是FD_CLOEXEC
- 3、通过fcntl函数使用F_SETFD直接设置FD_CLOEXEC。
看例子:
#include #include #include #include int main() { int fd = open("./fcntl_F_GETFD", O_RDWR | O_CREAT | O_TRUNC, 0775); int fdCloExec = open("./fcntl_F_GETFD2", O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0775); int fdCloExecDup = fcntl(fd, F_DUPFD_CLOEXEC, 0); int fdSetFd = dup(fd); fcntl(fdSetFd, F_SETFD, FD_CLOEXEC); int flagFd = fcntl(fd, F_GETFD); int flagFdCloExec = fcntl(fdCloExec, F_GETFD); int flagFdCloExecDup = fcntl(fdCloExecDup, F_GETFD); int flagFdSetFd = fcntl(fdSetFd, F_GETFD); // 打印结果:flagFd=0, flagFdCloExec=1, flagFdCloExecDup=1 flagFdSetFd=1 printf("flagFd=%d, flagFdCloExec=%d, flagFdCloExecDup=%d flagFdSetFd=%d\n", flagFd,flagFdCloExec,flagFdCloExecDup,flagFdSetFd); close(fd); close(fdCloExec); close(fdCloExecDup); close(fdSetFd); return 0; }
🎄四、获取/设置文件状态标志(F_GETFL、F_SETFL)
文件状态标志如下表:
文件状态标志 说明 十六进制值 O_RDONLY 只读打开 0x0 O_WRONLY 只写打开 0x1 O_RDWR 读、写打开 0x2 O_APPEND 追加写 0x400 O_NONBLOCK 非阻塞模式 0x800 O_SYNC 等待写完成(数据和属性) 0x O_DSYNC 等待写完成(仅数据) O_RSYNC 同步读和写 O_FSYNC 等待写完成 O_ASYNC 异步IO 0x2000 F_GETFL(void) :表示使用 F_GETFL 作为cmd时,不需要传入第三个参数。
功能:获取文件状态标志。
返回值:
- 成功返回文件状态标志
- 失败返回 -1.
访问方式标志:O_RDONLY 、O_WRONLY、O_RDWR。这3个值是互斥的,因此首先必须用屏蔽O_ACCMODE取得访问方式位,然后将结果与这3个值中的每一个相比较。
F_SETFL(int):表示使用 F_SETFL 作为cmd时,传入第三个参数是int型的。
功能:设置文件状态标志,第三个参数传入新的文件状态标志值。
返回值:
- 成功返回 0
- 失败返回 -1.
在Linux上,只能设置这5个文件状态标志:O_APPEND、 O_ASYNC、 O_DIRECT、 O_NOATIME、O_NONBLOCK,其中最常用的是将文件描述符设置成非阻塞(O_NONBLOCK),特别是在网络编程中很常见。
看例子,设置文件状态标志在日常使用中,就用来设置非阻塞,其他的可以先不关注。
#include #include #include #include int main() { int fd = open("./fcntl_F_GETFL", O_RDWR | O_CREAT | O_TRUNC | O_NONBLOCK, 0775); int flag = fcntl(fd, F_GETFL); if(flag ... short l_type;//锁的类型:F_RDLCK(读锁)、F_WRLCK(写锁)、F_UNLCK(解锁) short l_whence;//偏移的起点:SEEK_SET、SEEK_CUR、SEEK_END off_t l_start; //锁区偏移,从l_whence off_t l_len; //锁区长度(字节) pid_t l_pid; //阻塞我们加锁的进程ID ... } if (pLock == NULL) return -1; pLock-l_type=type; pLock->l_whence=whence; pLock->l_start=start; pLock->l_len=len; pLock->l_pid = -1; return 0; } int main() { int fd = open("./fcntl_lock", O_RDWR | O_CREAT | O_TRUNC, 0664); int i=0; for(i=0; i char num = i+1; write(fd, &num, 1); } // 加读锁 struct flock rlock; makeFlock(&rlock, F_RDLCK, SEEK_SET, 11, 10); // 读锁,锁第11~20个字节 int ret = fcntl(fd, F_SETLK, &rlock); if(ret return -1; } // 读取数据 printf("reading 11~20 byte:\n"); lseek(fd, 10, SEEK_SET); for(i=0; i char num; read(fd, &num, 1); printf("reading byte: %d\n",num); sleep(1); } sleep(30); // 释放锁 rlock.l_type = F_UNLCK; ret = fcntl(fd, F_SETLK, &rlock); if(ret == -1) { perror("rlock release failed"); return -1; } sleep(30); close(fd); return 0; } if (pLock == NULL) return -1; pLock-l_type=type; pLock-l_whence=whence; pLock-l_start=start; pLock->l_len=len; pLock->l_pid = -1; return 0; } int main() { int i=0; int fd = open("./fcntl_lock", O_RDWR); // 加读锁,与进程1重叠,仍然可以读 struct flock rlock; makeFlock(&rlock, F_RDLCK, SEEK_SET, 6, 10); // 读锁,锁第6~15个字节,与进程1的11~20有重叠 int ret = fcntl(fd, F_SETLK, &rlock); if(ret == -1) { return -1; } // 读取数据 printf("reading 6~15 byte:\n"); lseek(fd, 5, SEEK_SET); for(i=0; i char num; read(fd, &num, 1); printf("reading byte: %d\n",num); } // 释放读锁 rlock.l_type = F_UNLCK; ret = fcntl(fd, F_SETLK, &rlock); if(ret == -1) { perror("rlock release failed"); return -1; } // 加写锁,此时会失败,因为进程1加了读锁没释放 struct flock wlock; makeFlock(&wlock, F_WRLCK, SEEK_SET, 6, 10); // 写锁,锁第6~15个字节,与进程1的11~20有重叠 ret = fcntl(fd, F_SETLK, &wlock); if(ret == -1) { perror("rlock release failed"); } sleep(30); close(fd); return 0; }