问题描述
我写了下面的代码,目标是让线程自杀使用系统调用(不调用pthread_exit()
)。
所以我创建了两个线程并选择一个获取其线程 ID 的线程,然后使用 tkill()
问题是所有进程都终止了,不仅仅是选定的线程。
这是为什么?如何仅使用系统调用使线程自杀?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
void *myThreadFun(void *vargp)
{
int tid = syscall(SYS_gettid);
if(tid %2 ==0)
{
printf("kill thread id ! = %ld \n",tid);
syscall(SYS_tkill,tid,9);
}
while(1)
{
printf("Thread ID: %d\n",tid);
sleep(2);
}
}
int main()
{
int i;
pthread_t tid;
printf("main thread id = %ld \n",syscall(SYS_gettid));
for (i = 0; i < 2; i++)
pthread_create(&tid,NULL,myThreadFun,NULL);
char tmp;
scanf("%c",&tmp);
return 0;
}
输出:
main thread id = 11911
kill thread id ! = 11912
Killed
解决方法
没有“只杀死这个线程”signal disposition;只有“忽略”、“终止整个进程”、“终止整个进程并转储核心文件”、“停止(暂停)整个进程”和“如果停止(暂停)则继续整个进程” .你甚至不能选择性格本身;仅在该特定信号的默认处置、“忽略”或用户空间信号处理函数之间。
实际上只有一种选择:SYS_exit syscall。
在 C 中,你可以通过
#include <unistd.h>
#include <sys/syscall.h>
static inline void exit_thread(void) __attribute__((noreturn));
static inline void exit_thread(void)
{
syscall(SYS_exit,0);
}
我认为,真实世界的例子总是胜过片段。考虑以下程序:
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
void *thread_function(void *payload)
{
#ifdef DO_EXIT
const int exit_code = (int)(intptr_t)payload;
fprintf(stderr,"Running thread_function(%p); calling syscall(SYS_exit,%d).\n",payload,exit_code);
fflush(stderr);
syscall(SYS_exit,exit_code);
#else
fprintf(stderr,"Running thread_function(%p); calling pthread_exit(%p).\n",payload);
fflush(stderr);
pthread_exit(payload);
#endif
return NULL; /* Never reached */
}
int main(void)
{
void *const payload = thread_function; /* Just some random pointer value */
pthread_t thread_id;
void *thread_status;
int err;
printf("Calling pthread_create(&thread_id,NULL,thread_function,%p): ",payload);
fflush(stdout);
err = pthread_create(&thread_id,payload);
if (err) {
printf("Failed: %s.\n",strerror(errno));
return EXIT_FAILURE;
}
printf("Success.\n");
printf("Calling pthread_join(thread_id,&thread_status): ");
fflush(stdout);
err = pthread_join(thread_id,&thread_status);
if (err) {
printf("Failed: %s.\n",strerror(errno));
return EXIT_FAILURE;
}
printf("Success; thread_status == %p.\n",thread_status);
return EXIT_SUCCESS;
}
另存为example.c
,然后编译一个使用pthread_exit()的版本ex1
:
gcc -Wall -Wextra -O2 example.c -pthread -o ex1
以及另一个使用 syscall(SYS_exit,) 的 ex2
:
gcc -Wall -Wextra -DDO_EXIT -O2 example.c -pthread -o ex2
要确切了解发生了什么,请在 strace 下运行每个示例,记录程序进行的每个 clone、write、futex、exit 和 exit_group 系统调用:
strace -f -e clone,write,futex,exit,exit_group -o ex1.log ./ex1
strace -f -e clone,exit_group -o ex2.log ./ex2
在我的机器上,ex1.log
看起来像
28703 write(1,"Calling pthread_create(&thread_i"...,75) = 75
28703 clone(child_stack=0x7f11f9c14fb0,flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID,parent_tidptr=0x7f11f9c159d0,tls=0x7f11f9c15700,child_tidptr=0x7f11f9c159d0) = 28704
28703 write(1,"Success.\n",9) = 9
28703 write(1,"Calling pthread_join(thread_id,"...,49) = 49
28703 futex(0x7f11f9c159d0,FUTEX_WAIT,28704,NULL <unfinished ...>
28704 write(2,"Running thread_function(0x563f0c"...,79) = 79
28704 futex(0x7f11f94141a0,FUTEX_WAKE_PRIVATE,2147483647) = 0
28704 exit(0) = ?
28703 <... futex resumed> ) = 0
28704 +++ exited with 0 +++
28703 write(1,"Success; thread_status == 0x563f"...,42) = 42
28703 exit_group(0) = ?
28703 +++ exited with 0 +++
和 ex2.log
看起来像
28707 write(1,75) = 75
28707 clone(child_stack=0x7f42c7db8fb0,parent_tidptr=0x7f42c7db99d0,tls=0x7f42c7db9700,child_tidptr=0x7f42c7db99d0) = 28708
28707 write(1,9) = 9
28708 write(2,"Running thread_function(0x556b57"...,80 <unfinished ...>
28707 write(1,49 <unfinished ...>
28708 <... write resumed> ) = 80
28707 <... write resumed> ) = 49
28708 exit(1472596720 <unfinished ...>
28707 futex(0x7f42c7db99d0,28708,NULL <unfinished ...>
28708 <... exit resumed>) = ?
28707 <... futex resumed> ) = 0
28708 +++ exited with 240 +++
28707 write(1,"Success; thread_status == (nil)."...,33) = 33
28707 exit_group(0) = ?
28707 +++ exited with 0 +++
从它们的差异以及这是在 x86-64 架构上运行的 GNU C 库版本 2.27 的事实,我们可以得出一些重要的观察结果:
-
GNU pthreads 实现使用 futex 传递线程函数返回值,当线程退出并被另一个线程(使用
pthread_join()
)收割时。 -
除了用于传递返回值的 futex 之外,
pthread_exit()
调用exit
系统调用,退出状态为 0。(实际上,man 3 pthread_exit 手册页明确说明了这一点。)
-
当线程退出时(使用退出系统调用),退出状态代码无关紧要。
如果我们的线程函数直接使用syscall,atexit()
和pthread_push()
注册的cleanup函数不会被调用,如果在这个线程上调用pthread_join()
,返回值本质上是为 (void *)0
(Linux 中为 == NULL
,由 printf()/fprintf() (nil)
格式说明符打印为 %p
)。