很显然,有一个错误在kernel的fork代码中,在线程引入到FreeBSD以前,程序一直以单线程的方式运行,但是现在情况变了,以前rfork有个标志参数,可以把当前进程的文件列表给清空,在多线程下是不安全的,如果不采取锁保护机制,很可能导致系统崩溃,下面的代码可以演示这个过程:
#include
#include
#include
void *thr(void *arg)
{
for (;;)
close(open("./test.dat", O_RDWR|O_CREAT, 0666));
return (NULL);
}
int main()
{
pthread_t td;
pthread_create(&td, NULL, thr, NULL);
for (;;)
rfork(RFCFDG);
return (0);
}
主要原因是那里没有锁来保护这个指向文件列表的指针,而这个指针可以被rfork替换,内存被释放,如果其他的线程正在操作文件列表,可以导致崩溃。
一个quick fix就是以下的补丁:
Index: kern_fork.c
===================================================================
RCS file: /home/ncvs/src/sys/kern/kern_fork.c,v
retrieving revision 1.257
diff -u -r1.257 kern_fork.c
--- kern_fork.c 8 Feb 2006 08:09:16 -0000 1.257
+++ kern_fork.c 15 Mar 2006 07:19:27 -0000
@@ -220,6 +220,16 @@
* certain parts of a process from itself.
*/
if ((flags & RFPROC) == 0) {
+ if ((p1->p_flag & P_HADTHREADS) &&
+ (flags & (RFCFDG | RFFDG))) {
+ PROC_LOCK(p1);
+ if (thread_single(SINGLE_BOUNDARY)) {
+ PROC_UNLOCK(p1);
+ return (ERESTART);
+ }
+ PROC_UNLOCK(p1);
+ }
+
vm_forkproc(td, NULL, NULL, flags);
/*
@@ -237,6 +247,13 @@
*/
if (flags & RFFDG)
fdunshare(p1, td);
+
+ if ((p1->p_flag & P_HADTHREADS) &&
+ (flags & (RFCFDG | RFFDG))) {
+ PROC_LOCK(p1);
+ thread_single_end();
+ PROC_UNLOCK(p1);
+ }
*procp = NULL;
return (0);
}
原理就是让所有别的线程停靠到用户-系统边界上,那里是个安全点,线程应当没有在那里做文件操作的,当前调用 rfork的线程可以安全的做文件列表操作,可以是彻底删除和重新创建。
有点降低性能,幸运的是,这条执行路径不太用,不是hot path, 慢点也无所谓。几乎只有非常古老的程序才用这个rfork(RFCFDG).