在 Ubuntu Linux 20.04.4(Linux 内核 5.13)上,
man tgkill
说:
int tgkill(int tgid, int tid, int sig);
tgkill() 将信号 sig 发送到具有线程 ID 的线程 线程组中的tidtgid。
我的问题是,系统真的可以同时拥有两个具有相同tid的线程(由
gettid()
获取)吗?
如果不能,那为什么
tgkill
强制用户提供tgid参数呢?系统应该能够自己从特定的tgid
查询对应的tid
。
顺便说一句:我知道,要从
tgid
(例如 1554725)手动查询 tid
,我们可以 cat /proc/1554725/status
并抓取 Tgid
字段。
上面的说法可以用下面的
threadtid.cpp
来验证:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
void* threadFunc(void *arg)
{
(void)arg;
int child_tid = (int)gettid();
printf("Child: tid=%d\n", child_tid);
sleep(30);
return nullptr;
}
int main(int argc, char *argv[])
{
pthread_t t1;
int err = pthread_create(&t1, nullptr, threadFunc, nullptr);
if (err != 0)
exit(4);
int parent_tid = (int)gettid();
printf("Parent: tid=%d\n", parent_tid);
void* childres = 0;
err = pthread_join(t1, &childres);
if (err != 0)
exit(4);
printf("Done.\n");
exit(EXIT_SUCCESS);
}
我们看到:
Name: threadtid.out
Umask: 0002
State: T (stopped)
Tgid: 1554724
Ngid: 0
Pid: 1554725
PPid: 37645
...
系统真的可以同时拥有两个具有相同tid(由gettid()获取)的线程吗?
不。线程 ID 在系统中在任何给定时间都是唯一的,但它们确实会被回收。
线程组 ID(和进程 ID)是线程 ID 的子集(请参阅线程 ID 和进程 ID 之间的关系),根据情况进行选择:新进程的第一个线程的 TID 被分配为该线程的 TGID 和该进程的 TGID PID。当在任何其他上下文中创建新线程时,它们都会继承创建它们的线程的 PID 和 TGID。 PID 和 TGID 之间的区别表明 Linus* 可能已经预料到允许一个进程拥有多个线程组,但迄今为止,这还没有实现。
为什么
强制用户提供tgid参数?系统应该能够自己从特定的tgkill
查询对应的tgid
。tid
tgid参数本身并不用于识别要终止的线程。 TID 足以让系统识别要发出信号的线程(如果存在)。 TGID 是为了最大限度地减少程序因终止线程而发出错误信号的可能性,并且其 TID 被回收用于新线程。 对于 TGID,只有当该 TGID 所属的进程本身生成大量线程时才会发生这种情况。如果没有,它可能是由于系统上其他进程的行为而发生的,而这些其他进程不需要特殊权限。我可以想象这是一个可利用的弱点。我不确定是否创建了实际的漏洞利用程序,但我注意到 Linux 有一个过时的系统调用
TKILL
,它实际上与
tgkill()
做同样的事情,而不需要指定 TGID。 Linux 很少(如果有的话)实际上删除系统调用,但是不应该使用 TKILL
,并且 Glibc 没有为其提供包装函数。
这都是 Linux 特定的。