linux c fcntl 函数 close_on_exec标志位的作用

当一个程序使用fork()函数创建了一个子进程时,通常会在该子进程中调用execve()函数加载执行另一个新程序。
此时子进程将完全被新程序替换掉,并在子进程中开始执行新程序。
描述进程的结构体中有一个close_on_exec,它是一个进程所有文件描述符(文件句柄)的位图标志,
每个比特位代表一个打开的文件描述符,用于确定在调用系统调用execve()时需要关闭的文件句柄(参见include/fcntl.h)。
若一个文件描述符在close_on_exec中的对应比特位被设置,那么在执行execve()时该描述符将被关闭,否则该描述符将始终处于打开状态,可以继续在execve 打开的进程中继续使用。
当打开一个文件时,默认情况下文件句柄在子进程中也处于打开状态。

测试代码 : app.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include int main()  
{  printf("--------------\n");pid_t pid; int fd; int ret ;time_t current_time;char *p ;fd = open("test.txt",O_RDWR|O_APPEND);  printf("fd = %d\n",fd);current_time = time(NULL);printf("time = %s\n",ctime(&current_time));  fcntl(fd, F_SETFD, 1);                      char *s="123456789\n";  pid = fork();  if(pid == 0){  execl("ass", "./ass", &fd, NULL);}wait(NULL); printf("wait return from child process\n"); p = ctime(&current_time);ret = write(fd, p, strlen(p));printf("retA = %d \n",ret );ret = write(fd, s, strlen(s));printf("retB = %d\n",ret);close(fd);  return 0;  
}
测试代码: ass.c
#include 
#include 
#include 
#include 
#include 
#include 
#include int main(int argc, char *argv[])  
{  int fd;int len;  printf("argc = %d ",argc);fd = *argv[1];  printf("fd = %d\n",fd);  char *s = "abcdefghijklmn\n";  len = write(fd, (void *)s, strlen(s));if(-1 == len){printf("write fail\n");}close(fd);  return 0;  
}
执行效果

首先手动创建一个 空的 test.txt 文件。

执行log

--------------
fd = 3
time = Fri Jul  2 10:28:28 2021argc = 2 fd = 3
write fail
wait return from child process
retA = 25
retB = 10

查看test.txt文件的内容

Fri Jul  2 10:28:28 2021
123456789
修改测试代码 app2.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include int main()  
{  printf("--------------\n");pid_t pid; int fd; int ret ;time_t current_time;char *p ;fd = open("test2.txt",O_RDWR|O_APPEND);  printf("fd = %d\n",fd);current_time = time(NULL);printf("time = %s\n",ctime(&current_time));  fcntl(fd, F_SETFD, 0);                      char *s="123456789\n";  pid = fork();  if(pid == 0){  execl("ass", "./ass", &fd, NULL);}wait(NULL); printf("wait return from child process\n"); p = ctime(&current_time);ret = write(fd, p, strlen(p));printf("retA = %d \n",ret );ret = write(fd, s, strlen(s));printf("retB = %d\n",ret);close(fd);  return 0;  
}
执行log
--------------
fd = 3
time = Fri Jul  2 10:30:26 2021argc = 2 fd = 3
wait return from child process
retA = 25
retB = 10

cat test2.txt

abcdefghijklmn
Fri Jul  2 10:30:26 2021
123456789

fcntl(fd, F_SETFD, 1) 此句将fd的close-on-exec 标志设置为1,开启此标志。那么当子进程调用execl函数时,execl执行ass,ass是不能向fd内写入的,因为在调用execl函数之前系统已经讲子进程的中复制的这个文件描述符关闭了。(attention:这里是子进程!)
但是如果将 fcntl(fd, F_SETFD, 1)改为fcntl(fd, F_SETFD, 0),或者直接将此句注释掉,那么,ass便可以向这个文件描述符中任意添写东西了~~

PS:如果将fcntl设置为开启,即设置为1,那么,此文件描述符依然是可以被主进程操作的。

fcntl(fd, F_SETFD, 1); 只是表示 主进程打开的 文件描述符,在子进程中被复制,子进程可以读写这个文件。
如果子进程执行execl函数,被子进程复制的这个文件描述符被关闭。
但是主进程中打开的文件描述符是不受影响的。
在子进程中关闭一个文件描述符,不会影响主进程中打开的文件描述符。

FD_CLOEXEC

/usr/include/x86_64-linux-gnu/bits/fcntl-linux.h

include/x86_64-linux-gnu/bits/fcntl-linux.h:# define F_DUPFD_CLOEXEC 1030       /* Duplicate file descriptor with
include/x86_64-linux-gnu/bits/fcntl-linux.h:#define FD_CLOEXEC  1       /* Actually anything with low bit set goes */
11

转载请注明出处:帘卷西风的专栏(http://blog.csdn.net/ljxfblog)

前几天写了一篇博客,讲述了端口占用情况的查看和解决。

关于linux系统端口查看和占用的解决方案

大部分这种问题都能够解决,在文章的最后,提到了一种特殊情况,就是父子进程中的端口占用情况。父进程监听一个端口后,fork出一个子进程,然后kill掉父进程,再重启父进程,这个时候提示端口占用,用netstat查看,子进程占用了父进程监听的端口。

原理其实很简单,子进程在fork出来的时候,使用了写时复制(COW,Copy-On-Write)方式获得父进程的数据空间、 堆和栈副本,这其中也包括文件描述符。刚刚fork成功时,父子进程中相同的文件描述符指向系统文件表中的同一项(这也意味着他们共享同一文件偏移量)。这其中当然也包含父进程创建的socket。

接着,一般我们会调用exec执行另一个程序,此时会用全新的程序替换子进程的正文,数据,堆和栈等。此时保存文件描述符的变量当然也不存在了,我们就无法关闭无用的文件描述符了。所以通常我们会fork子进程后在子进程中直接执行close关掉无用的文件描述符,然后再执行exec。

但是在复杂系统中,有时我们fork子进程时已经不知道打开了多少个文件描述符(包括socket句柄等),这此时进行逐一清理确实有很大难度。我们期望的是能在fork子进程前打开某个文件句柄时就指定好:“这个句柄我在fork子进程后执行exec时就关闭”。其实时有这样的方法的:即所谓 的 close-on-exec。

回到我们的应用场景中来,只要我们在创建socket的时候加上SOCK_CLOEXEC标志,就能够达到我们要求的效果,在fork子进程中执行exec的时候,会清理掉父进程创建的socket。

#ifdef WIN32SOCKET ss = ::socket(PF_INET, SOCK_STREAM, 0);
#elseSOCKET ss = ::socket(PF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
#endif

当然,其他的文件描述符也有类似的功能,例如文件,可以在打开的时候使用O_CLOEXEC标识(linux 2.6.23才开始支持此标记),达到和上面一样的效果。或者使用系统的fcntl函数设置FD_CLOEXEC即可。

//方案A
int fd = open(“foo.txt”,O_RDONLY);
int flags = fcntl(fd, F_GETFD);
flags |= FD_CLOEXEC;
fcntl(fd, F_SETFD, flags);
//方案B,linux 2.6.23后支持
int fd = open(“foo.txt”,O_RDONLY | O_CLOEXEC);

好了,现在我们终于可以完美的解决端口占用这个令人烦恼的问题了。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部