通俗易懂说多路复用(4)fcntl
多路复用系列:
通俗易懂说多路复用(1)select
https://blog.csdn.net/lqy971966/article/details/89173936
通俗易懂说多路复用(2)epoll
https://blog.csdn.net/lqy971966/article/details/89217648
通俗易懂说多路复用(3)eventfd 事件通知
https://blog.csdn.net/lqy971966/article/details/104751751
通俗易懂说多路复用(4)fcntl
https://blog.csdn.net/lqy971966/article/details/105390106
1. fcntl()
1.1 功能:
根据文件描述词来操作文件的特性。
1.2 fcntl 的三个接口,三种操作
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
1.3 返回值
fcntl()的返回值与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列三个命令有特定返回值:F_DUPFD , F_GETFD , F_GETFL以及F_GETOWN。
F_DUPFD 返回新的文件描述符
F_GETFD 返回相应标志
F_GETFL , F_GETOWN 返回一个正的进程ID或负的进程组ID
1.4 cntl函数有5种功能:
1.4.1. 复制一个现有的描述符(cmd=F_DUPFD).
1.4.2. 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD). 及代码使用
F_GETFD 取得与文件描述符fd联合的close-on-exec标志,类似FD_CLOEXEC。如果返回值和FD_CLOEXEC进行与运算结果是0的话,文件保持交叉式访问exec(),否则如果通过exec运行的话,文件将被关闭(arg 被忽略)
F_SETFD 设置close-on-exec标志,该标志以参数arg的FD_CLOEXEC位决定,应当了解很多现存的涉及文件描述符标志的程序并不使用常数 FD_CLOEXEC,而是将此标志设置为0(系统默认,在exec时不关闭)或1(在exec时关闭)
1.4.3. 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
1.4.4. 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
1.4.5. 获得/设置记录锁(cmd=F_GETLK , F_SETLK或F_SETLKW).
F_GETLK 通过第三个参数arg(一个指向flock的结构体)取得第一个阻塞lock description指向的锁。取得的信息将覆盖传到fcntl()的flock结构的信息。如果没有发现能够阻止本次锁(flock)生成的锁,这个结构将不被改变,除非锁的类型被设置成F_UNLCK
F_SETLK 按照指向结构体flock的指针的第三个参数arg所描述的锁的信息设置或者清除一个文件的segment锁。F_SETLK被用来实现共享(或读)锁(F_RDLCK)或独占(写)锁(F_WRLCK),同样可以去掉这两种锁(F_UNLCK)。如果共享锁或独占锁不能被设置,fcntl()将立即返回EAGAIN
结构体flock的指针
struct flcok
{
short int l_type; /* 锁定的状态*/
//以下的三个参数用于分段对文件加锁,若对整个文件加锁,则:l_whence=SEEK_SET, l_start=0, l_len=0
short int l_whence; /*决定l_start位置*/
off_t l_start; /*锁定区域的开头位置*/
off_t l_len; /*锁定区域的大小*/
pid_t l_pid; /*锁定动作的进程*/
};
-
l_type 有三种状态:
F_RDLCK 建立一个供读取用的锁定
F_WRLCK 建立一个供写入用的锁定
F_UNLCK 删除之前建立的锁定 -
l_whence 也有三种方式:
SEEK_SET 以文件开头为锁定的起始位置
SEEK_CUR 以目前文件读写位置为锁定的起始位置
SEEK_END 以文件结尾为锁定的起始位置
fcntl文件锁有两种类型:建议性锁和强制性锁
系统默认fcntl都是建议性锁,强制性锁是非POSIX标准的。
2. 代码例子
2.1 代码例子 F_GETFD 和 F_SETFD
int iEpfd = epoll_create(1);
if(0>iEpfd)
{ return error;}
iFlags = fcntl(iEpfd,F_GETFD);
if(iFlags >= 0)
{
iFlags |= FD_CLOEXEC; // 这个句柄我在fork子进程后执行exec时就关闭
fcntl(iEpfd,F_SETFD,iFlags);
}
2.2 解释
-
背景:
fork子进程时,子进程以写时复制(COW,Copy-On-Write)
方式获得父进程的数据空间、堆和栈副本,这其中也包括文件描述符 -
问题:
当子进程调用exec执行另一个程序,此时会用全新的程序替换子进程的正文,数据,堆和栈等。
此时保存文件描述符的变量当然也不存在了,我们就无法关闭无用的文件描述符了。
不能关闭子进程继承过来的父进程的文件描述符 -
解决:
通常我们会fork子进程后在子进程中直接执行close关掉无用的文件描述符,然后再执行exec但是在复杂系统中,有时我们fork子进程时已经不知道打开了多少个文件描述符(包括socket句柄等)
这此时进行逐一清理确实有很大难度。 -
结果:
在fork子进程前打开某个文件句柄时就指定好:“这个句柄我在fork子进程后执行exec时就关闭”。
其实时有这样的方法的:即所谓的 close-on-exec
close-on-exec 的实现只需要调用系统的fcntl就能实现,很简单几句代码就能实现。
这样,当fork子进程后,仍然可以使用fd。但执行exec后系统就会字段关闭子进程中的fd了。
参考:
https://www.cnblogs.com/embedded-linux/p/6753617.html
https://www.cnblogs.com/alantu2018/p/8492206.html