为什么在syscall clone创建的线程中无法正确调用某些glibc的API(例如函数<select ng-model="activeProfile.awardyear" ng-options="ay.display for ay in awardYears"></select>
,malloc()
或realloc()
)?
以下是我的测试代码:
free()
测试结果:线程int thread_func( void *arg )
{
void *ptr = malloc( 4096 );
printf( "tid=%d, ptr=%x\n", gettid(), ptr );
sleep(1);
if( ptr )
free( ptr );
return 0;
}
int main( int argc, char **argv )
{
int i, m;
void *stk;
int stksz = 1024 * 128;
int flag = CLONE_VM | CLONE _FILES | CLONE_FS | CLONE_SIGHAND;
for( i=m=0; i < 100; i++ )
{
stk = malloc( stksz );
if( !stk ) break;
if( clone( thread_func, stk+stksz, flags, NULL, NULL, NULL, NULL ) != -1 )
m++;
}
printf( "create %d thread\n", m );
sleep(10);
return 0;
}
或主线程thread_func
将在main
或malloc()
函数中随机阻止。或者有时会导致free()
或malloc()
崩溃。
我认为可能free()
和malloc()
需要某些TLS数据来区分每个帖子。
有谁知道原因,可以使用什么解决方案来解决这个问题?
答案 0 :(得分:1)
我认为可能是malloc()和free()需要某些TLS数据来区分每个线程。
Glibc的malloc()
和free()
不依赖于TLS。它们使用互斥锁来保护共享内存分配数据结构。为了减少对这些争用的争用,他们采用了一种策略,即使用独立的元数据和互斥体来维护单独的内存分配领域。这在their manual page上有记录。
在纠正代码中的语法错误并忽略对不存在的函数gettid()
的调用后(请参阅问题评论),我能够产生分段错误,但不能阻塞。也许你把程序的10秒睡眠造成的退出延迟与堵塞混淆了。
除了可能与您未公开的gettid()
实现相关的任何问题之外,您的程序还包含两个语义错误,每个错误都会产生未定义的行为:
正如我在评论中已经提到的,它传递了错误的子堆栈指针值。 *
它使用printf()
中的错误thread_func()
指令来打印指针。指针值的指令是%p
; %x
适用于unsigned int
类型的参数。
在我纠正了这些错误之后,程序也一直为我完成。修改后的代码:
int thread_func(void *arg) {
void *ptr = malloc(4096);
// printf( "tid=%d, ptr=%x\n", gettid(), ptr );
printf("tid=%d, ptr=%p\n", 1, ptr);
sleep(1);
if (ptr) {
free(ptr);
}
return 0;
}
int main(int argc, char **argv) {
int i, m;
char *stk; // Note: char * instead of void * to afford arithmetic
int stksz = 1024 * 128;
int flags = CLONE_VM | CLONE_FILES | CLONE_FS | CLONE_SIGHAND;
for (i = m = 0; i < 100; i++) {
stk = malloc( stksz );
if( !stk ) break;
if (clone(thread_func, stk + stksz - 1, flags, NULL, NULL, NULL, NULL ) != -1) {
m++;
}
}
printf("create %d thread\n", m);
sleep(10);
return 0;
}
然而,即使如此,一切都不是很完美:我看到程序输出中出现了各种异常,特别是在开头附近。
最重要的是,与你的断言相反, 你没有创建任何线程 ,至少在C库识别的意义上是这样。您只是创建具有类似于线程的行为的进程。对于某些目的而言,这可能就足够了,但您不能依赖系统来处理与线程相同的进程。
在Linux上,系统和标准库将识别的真正的线程是通过pthread_create()
启动的POSIX线程。 (我在此注意,修改程序以使用pthread_create()
代替clone()
解决了我的输出异常。)您可以为clone()
调用添加标记和参数得到的进程就像Linux实现的pthreads实际上是完全相同的,但为什么你会做这样的事情而不是仅仅使用真正的pthreads呢?
* 该程序还对void *
执行指针算术,C不允许。然而,GCC接受这个作为扩展,并且因为你的代码无论如何都是特定于Linux的,所以我只用这个笔记来放这个幻灯片。
答案 1 :(得分:0)
正确,malloc
和free
至少需要TLS以下内容:
errno
TLS变量(写入系统调用失败时)。所有这些项目都需要一个正确初始化的线程控制块(TCB),但奇怪的是,直到最近,就malloc
/ free
而言,如果创建一个线程几乎没有关系clone
与另一个TCB共享(以便数据不再是线程本地的):
线程基本上永远不会重新连接到不同的竞技场,因此竞技场TLS变量在初始化后实际上是只读的 - 多个线程可以共享一个竞技场。只要系统调用仅在其中一个正在进行共享的线程中失败,就可以共享errno
。堆栈保护器canary在进程启动后是只读的,无论如何它的值在整个线程中是相同的。
但所有这些都是一个实现细节,并且glibc 2.26中的东西在其malloc线程缓存中发生了根本性的变化:缓存在没有同步的情况下被读取和写入,因此您尝试执行的操作很可能会导致内存损坏。
这不是glibc 2.26中的重大变化,它总是如此:从clone
创建的线程调用任何 glibc函数是未定义的。 As John Bollinger pointed out,这大部分都是偶然发生的,但我可以向你保证,它一直都是完全未定义的。