Donald E. Porter 教授的 CSE 506: Operating Systems 教材 VFS 部分中提到Linux文件操作使用的一个标识
1
2
| CLOSE_ON_EXEC – a bit that prevents file inheritance
if a new binary is exec’ed (set by open or fcntl)
|
这个标识位支持exec执行前关闭其从父进程继承来的文件描述符
设置方法:
1
2
3
| int flags = fcntl(connfd, F_GETFD);
flags |= FD_CLOEXEC;
fcntl(connfd, F_SETFD, flags);
|
1
2
3
4
5
| 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后将自动关闭.
1
2
3
4
5
| pid_t pid;
pid = fork();
if (pid == 0) {
exec(...)
};
|
server.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
| #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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| #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;
}
|
1
2
3
4
5
6
7
8
9
| ### 编译
$ gcc server.c -o server
$ gcc serverson.c -o serverson
### 服务端运行
$ ./server
### 客户端
$ telnet ip 8888
|
通过运行结果可以看到如果不设置close-on-exec, 服务端子进程会处理请求屏幕打印客户端数据的字符串。重新编译开启close-on-exec后
1
2
| gcc -D__CLOEXEC server.c -o server
gcc -D__FCNTL server.c -o server
|
客户端返回, exec执行是会关闭继承过来的文件描述符。
1
| Connection closed by foreign host.
|
kernel相关#
与进程相关结构可以看到close_on_exec定义,有可能存在多个fd,可以看到是指针类型.
/include/linux/fdtable.h
1
2
3
4
5
6
7
8
9
| 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
1
2
3
4
5
6
7
8
9
10
11
12
| 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
1
2
3
4
5
6
7
8
9
| static int load_elf_binary(...) {
...
/* Flush all traces of the currently
running executable */
retval = begin_new_exec(bprm);
...
}
|
/fs/exec.c
1
2
3
4
5
6
| int begin_new_exec(...)
{
...
do_close_on_exec(me->files);
...
}
|
可以看到获取进程每个文件是否设置了close_on_exec来判断是否在调用exec后.需要关闭
/fs/file.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| 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