简介
Donald E. Porter 教授的 CSE 506: Operating Systems 教材 VFS 部分中提到Linux文件操作使用的一个标识
CLOSE_ON_EXEC – a bit that prevents file inheritance
if a new binary is exec’ed (set by open or fcntl)
这个标识位支持exec执行前关闭其从父进程继承来的文件描述符
使用
设置方法:
- 通过fcntl 设置
FD_CLOEXEC
int flags = fcntl(connfd, F_GETFD);
flags |= FD_CLOEXEC;
fcntl(connfd, F_SETFD, flags);
- 通过
O_CLOEXEC
open(path, O_CLOEXEC | flags)
socket(DOMAIN, SOCK_CLOEXEC | type, PROTOCOL)
accept4(int sockfd, struct sockaddr *addr, \
socklen_t *addrlen, SOCK_CLOEXEC | flags);
fopen(path, "re")
可以在以下这种模式下使用, fork后还是dup了父进程的文件描述符,exec后将自动关闭.
pid_t pid;
pid = fork();
if (pid == 0) {
exec(...)
};
验证
- 代码
server.c
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <fcntl.h>
void main()
{
int sockfd;
#ifdef __CLOEXEC
sockfd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, 0);
#else
sockfd = socket(AF_INET, SOCK_STREAM, 0);
#endif
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(8888);
int option = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof( option));
bind(sockfd, (const struct sockaddr*)&servaddr, sizeof(servaddr));
listen(sockfd, 5);
struct sockaddr_in cliaddr;
int connfd;
int cliaddlen = sizeof(cliaddr);
#ifdef __CLOEXEC
connfd = accept4(sockfd, (struct sockaddr *)&cliaddr, &cliaddlen, SOCK_CLOEXEC);
#else
connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &cliaddlen);
#endif
#ifdef __FCNTL
int flags = fcntl(connfd, F_GETFD);
flags |= FD_CLOEXEC;
fcntl(connfd, F_SETFD, flags);
#endif
char buf[100];
snprintf(buf, sizeof(buf), "%d", connfd);
pid_t pid = fork();
if ( pid == 0) {
char* argv[] = {"./serverson", buf, NULL };
char* envp[] = { NULL };
execve("./serverson", argv, envp) ;
printf(" child start\n");
exit(0);
}
printf(" parent start\n");
while (0){
//do nothing
}
printf("parent exit \n");
return ;
}
serverson.c
#include <stdio.h>
void
main (int argc, char *argv[])
{
printf("in child %s", argv[1]);
int connfd = atol(argv[1]);
printf ("in child running\n");
while (1)
{
char buff[10];
memset (buff, 0, sizeof (buff));
read (connfd, buff, 1);
printf ("%s", buff);
if (buff[0] == '#')
break;
}
return 0;
}
- 运行
### 编译
$ gcc server.c -o server
$ gcc serverson.c -o serverson
### 服务端运行
$ ./server
### 客户端
$ telnet ip 8888
通过运行结果可以看到如果不设置close-on-exec, 服务端子进程会处理请求屏幕打印客户端数据的字符串。重新编译开启close-on-exec后
gcc -D__CLOEXEC server.c -o server
gcc -D__FCNTL server.c -o server
客户端返回, exec执行是会关闭继承过来的文件描述符。
Connection closed by foreign host.
kernel相关
O_CLOEXEC
相关内核实现
与进程相关结构可以看到close_on_exec
定义,有可能存在多个fd,可以看到是指针类型.
/include/linux/fdtable.h
struct fdtable {
unsigned int max_fds;
struct file __rcu **fd;
/* current fd array */
unsigned long *close_on_exec;
unsigned long *open_fds;
unsigned long *full_fds_bits;
struct rcu_head rcu;
};
分配文件描述符时的处理,根据O_CLOEXEC
设置 fdtable
的close_on_exec
fs/file.c
int __alloc_fd(...) {
__set_open_fd(fd, fdt);
if (flags & O_CLOEXEC)
__set_close_on_exec(fd, fdt);
else
__clear_close_on_exec(fd, fdt);
}
static inline void __set_close_on_exec(...)
{
__set_bit(fd, fdt->close_on_exec);
}
在载入二进制文件时,load_elf_binary->begin_new_exec->do_close_on_exec
/fs/binfmt_elf.c
static int load_elf_binary(...) {
...
/* Flush all traces of the currently
running executable */
retval = begin_new_exec(bprm);
...
}
/fs/exec.c
int begin_new_exec(...)
{
...
do_close_on_exec(me->files);
...
}
可以看到获取进程每个文件是否设置了close_on_exec
来判断是否在调用exec后.需要关闭
/fs/file.c
void do_close_on_exec(struct files_struct *files)
{
unsigned i;
struct fdtable *fdt;
/* exec unshares first */
spin_lock(&files->file_lock);
for (i = 0; ; i++) {
unsigned long set;
unsigned fd = i * BITS_PER_LONG;
fdt = files_fdtable(files);
if (fd >= fdt->max_fds)
break;
set = fdt->close_on_exec[i];
if (!set)
continue;
fdt->close_on_exec[i] = 0;
for ( ; set ; fd++, set >>= 1) {
struct file *file;
if (!(set & 1))
continue;
file = fdt->fd[fd];
if (!file)
continue;
rcu_assign_pointer(fdt->fd[fd], NULL);
__put_unused_fd(files, fd);
spin_unlock(&files->file_lock);
filp_close(file, files);
cond_resched();
spin_lock(&files->file_lock);
}
}
spin_unlock(&files->file_lock);
}
参考
Excuse me son, but your code is leaking !!!
Introduce O_CLOEXEC (take >2)
close on exec
Be First to Comment