您现在的位置是:首页 > 文章详情

fcntl 锁在 Go 中通过 execve 之后不生效的问题

日期:2018-07-09点击:481

fcntl 锁在 Go 中通过 execve 之后不生效的问题

背景

man 2 fcntl

​ Record locks are not inherited by a child created via fork(2), but are preserved across an execve(2).

看到 fcntl 的介绍,我们想当然地认为 fcntl 的记录锁在 execve 之后都是能够保留的。

在我们使用 Go 来实现的时候,很快就发现了问题,请看如下代码:

package main import ( "fmt" "log" "os" "runtime" "syscall" "time" ) func main() { fmt.Println("Begin") fd, err := syscall.Open("lock", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777) if err != nil { log.Printf("%s", err) return } ft := &syscall.Flock_t{ Type: syscall.F_WRLCK, } err = syscall.FcntlFlock(uintptr(fd), syscall.F_SETLK, ft) if err != nil { log.Printf("%s", err) return } fmt.Printf("Pid %d fd %d \n", os.Getpid(), fd) time.Sleep(5 * time.Second) argv := []string{"./test.sh"} syscall.Exec(argv[0], argv, []string{}) }

这段代码在 Linux 上,是无法把 fcntl 的锁传递给 test.sh 进程的。然而在 macOS 上很正常。

另外通过 C/Python 来实现,也是没有问题的。

#include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> int main() { printf("begin %d\n", getpid()); int fd = open("lock", O_WRONLY | O_CREAT | O_TRUNC , 0777); if (fd < 0 ) { printf("fail to open"); return -1; } printf("fd %d\n", fd); struct flock fl ; fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; int errno = fcntl(fd, F_SETLK, &fl); if( errno != 0) { printf("err %d", errno); return -1; } sleep(5); char *env[] = { NULL }; char *argv[] = {"./test.sh", NULL}; execve(argv[0], argv, env); return 0; }

所以问题是出在 Go 语言本身吗?

原因

在黄老板手写了一个内核模块深入分析后,发现内核在 exceve 时关闭了锁。阅读 Linux Kernel 源码后,终于发现真相。

我们来看下 exceve 系统调用的部分实现。

do_execveat_common 中:

struct files_struct *displaced; retval = unshare_files(&displaced); ... if (displaced) put_files_struct(displaced);

我们需要关注的是 displaced 这个变量。

unshare_files 调用了 unshare_fd:

/* * Helper to unshare the files of the current task. * We don't want to expose copy_files internals to * the exec layer of the kernel. */ int unshare_files(struct files_struct **displaced) { struct task_struct *task = current; struct files_struct *copy = NULL; int error; error = unshare_fd(CLONE_FILES, &copy); if (error || !copy) { *displaced = NULL; return error; } *displaced = task->files; task_lock(task); task->files = copy; task_unlock(task); return 0; }

unshare_fd:

/* * Unshare file descriptor table if it is being shared */ static int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp) { struct files_struct *fd = current->files; int error = 0; if ((unshare_flags & CLONE_FILES) && (fd && atomic_read(&fd->count) > 1)) { *new_fdp = dup_fd(fd, &error); if (!*new_fdp) return error; } return 0; }

这里,当发现 fd 的引用计数大于 1 时,调用 dup_fd 复制一个新的 fdt 表。

之后在 put_files_struct 函数中 displaced 文件进行关闭操作。

void put_files_struct(struct files_struct *files) { if (atomic_dec_and_test(&files->count)) { struct fdtable *fdt = close_files(files); /* free the arrays if they are not embedded */ if (fdt != &files->fdtab) __free_fdtable(fdt); kmem_cache_free(files_cachep, files); } }

结论

所以在单线程环境中,fd 的引用计数等于 1,原来的 fdt 会被之后的进程继承。而在多线程环境下,旧的 fdt 会被替换并且关闭。

总之,多线程环境下,慎用 fcntl 记录锁。

当我们在 C 代码中加入线程后,就能发现 execve 之后 fcntl 锁没有保留。

#include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <pthread.h> #include <stdlib.h> void thread(){ for(int i=0;i<=5;i++) { printf("this is thread %d\n", i); sleep(5); } } int main() { printf("begin %d\n", getpid()); int fd = open("lock", O_WRONLY | O_CREAT | O_TRUNC , 0777); if (fd < 0 ) { printf("fail to open"); return -1; } printf("fd %d\n", fd); struct flock fl ; fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; int errno = fcntl(fd, F_SETLK, &fl); if( errno != 0) { printf("err %d", errno); return -1; } pthread_t id; //线程的标识符,unsigned long int. int ret,i=0; ret = pthread_create(&id,NULL,(void *)thread,NULL); if(ret!=0) // 线程创建成功返回0 { printf("To thread failed\n"); exit(0); } sleep(5); char *env[] = { NULL }; char *argv[] = {"./test.sh", NULL}; execve(argv[0], argv, env); return 0; }
原文链接:https://yq.aliyun.com/articles/608840
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章