- 通过fcntl设置FD_CLOEXEC标志有什么用?
- close on exec, not on-fork, 意为如果对描述符设置了FD_CLOEXEC,使用execl执行的程序里,此描述符被关闭,不能再使用它,但是在使用fork调用的子进程中,此描述符并不关闭,仍可使用。
子进程在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 WIN32
- SOCKET ss = ::socket(PF_INET, SOCK_STREAM, 0);
- #else
- SOCKET ss = ::socket(PF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
- #endif
- //方案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);
总结:父进程对文件描述符指定O_CLOEXEC标记,并且父进程fork了子进程,而子进程执行exec后,子进程从父进程继承过来的该文件描述符就会关闭。或者说任何一个进程对一个文件描述符指定了O_CLOEXEC标记,那该进程执行exec后,该句柄就会关闭。