fcntl 锁在 Go 中通过 execve 之后不生效的问题
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 系统调用的部分实现。
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, ©); 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; }

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java并发编程-volatile关键字介绍
前言 要学习好Java的多线程,就一定得对volatile关键字的作用机制了熟于胸。最近博主看了大量关于volatile的相关博客,对其有了一点初步的理解和认识,下面通过自己的话叙述整理一遍。 有什么用? volatile主要对所修饰的变量提供两个功能 可见性 防止指令重排序 本篇博客主要对volatile可见性进行探讨,以后发表关于指令重排序的博文。 什么是可见性? 一图胜千言上图已经把JAVA内存模型(JMM)展示得很详细了,简单概括一下 每个Thread有一个属于自己的工作内存(可以理解为每个厨师有一个属于自己的铁锅) 所有Thread共用一个主内存(餐厅所有的厨师共用同一个冰箱) 每个Thread操作数据之前都会去主内存中获取数据(厨师炒菜之前都要去冰箱里拿食材) Thread:厨师 工作内存:铁锅 store&load:放熟食,取食材 主内存:冰箱 读者可思考以下情景:餐厅来了一位顾客点了一份红烧肉,此时有两位大厨(假设大厨之间互不通信),由于互不通信,所以两位大厨都打开冰箱取出食材开始炒菜。最后炒出了两份红烧肉,顾客只要一份。为什么会造成这种结果? 由于大厨之间没有...
- 下一篇
序列化,数据库存多个字段数据
$old['xxx'] = (int) $_POST['ooo']; $res = serialize($old); 取出: var_dump(unserialize($res));die; 手册:http://php.net/manual/zh/function.serialize.php 序列化表单(Ajax中):https://blog.csdn.net/xinghuo0007/article/details/72654817
相关文章
文章评论
共有0条评论来说两句吧...