引言
我们在使用一些系统调用对文件描述符进行操作时,常常会碰到是否为文件描述符赋予CLOEXEC属性的情况,例如:
// open函数中的flags参数可指定O_CLOEXEC标志
int open(const char *pathname, int flags);
// fcntl函数可通过FD_GETFD、FD_SETFD操作设置FD_CLOEXEC标志
int fcntl(int fd, int cmd, ... /* arg */);
在上篇博客,我们新学到的系统调用eventfd同样也有:
// 参数flags可取EFD_NONBLOCK、EFD_CLOEXEC、EFD_SEMAPHORE
int eventfd(unsigned int initval, int flags);
那么,close-on-exec标志位究竟有什么用呢?
设置close-on-exec标志位的意义
close-on-exec字面意思即执行时关闭。进程中每个打开的描述符都有一个执行时关闭标志,若设置此标志,则在执行exec时关闭该描述符,否则该描述符仍打开。
设置该标志位的重要意义在于它可以方便我们关闭无用的文件描述符。
我们考虑这样的情况:父进程fork出一个子进程,子进程是父进程的副本,获得父进程的数据空间、堆和栈的副本,当然也包括父进程打开的文件描述符。
fork之后一般我们会调用exec执行另一个程序,此时会用全新的程序替换子进程的正文、数据、堆和栈等,此时保存文件描述符的变量当然也不存在了,我们就无法关闭无用的文件描述符了。所以通常我们会在fork子进程后,在子进程中直接执行close关掉无用的文件描述符,然后再执行exec。
但是在复杂系统中,有时我们fork出子进程时已经不知道打开了多少文件描述符了(包括socket句柄等),如果进行逐一清理难度很大。我们期望的是能在fork出子进程前、打开某个文件描述符时就指定好——这个文件描述符在我fork出子进程后、执行exec时就关闭。其实是有这样的方法的:即所谓的 close-on-exec。
测试代码
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{
int fd;
pid_t pid;
// 判断是否定义_O_CLOEXEC宏
#ifdef _O_CLOEXEC
if ((fd = open("my.txt", O_RDWR | O_CREAT | O_CLOEXEC, 0600)) < 0)
#else
if ((fd = open("my.txt", O_RDWR | O_CREAT, 0600)) < 0)
#endif
perror("open");
// 通过开启-D_O_CLOEXEC -D_FORK编译选项测试使用O_CLOEXEC模式的描述符在子进程中的状态
#ifndef _FORK
// 执行execl后进程名更改为sleep
if (execl("/bin/sleep", "sleep", "10000", (void*)0) < 0)
perror("execl");
#else
switch ((pid = fork())) {
case -1:
perror("fork");
case 0:
if (execl("/bin/sleep", "sleep", "10000", (void*)0) < 0)
perror("execl");
default:
sleep(10000);
break;
}
#endif
return 0;
}
测试结果:
1.(1)编译进O_CLOEXEC选项
进程资源中没有了my.txt
(2)不编译进O_CLOEXEC选项
进程资源中仍有my.txt
2.编译进O_CLOEXEC选项、_FORK选项
子进程资源中没有了my.txt,父进程中仍存在。
参考资料:
http://blog.csdn.net/chrisniu1984/article/details/7050663
http://blog.csdn.net/ubuntu_hao/article/details/51393632