tgkill函数详解 链接到标题
1. 函数介绍 链接到标题
tgkill函数是Linux系统中一个精确的进程控制函数,它的名字来源于"Thread Group Kill"(线程组杀死)。在Linux中,每个进程实际上是一个线程组,主线程的ID就是进程ID。tgkill允许你精确地向指定进程组中的特定线程发送信号。
可以把tgkill想象成一个"精确制导导弹",它不仅能指定攻击哪个"军队"(进程组),还能精确指定攻击该军队中的哪个"士兵"(特定线程)。相比之下,传统的kill函数更像是"地毯式轰炸",可能会影响整个进程组。
使用场景:
- 多线程程序中精确控制特定线程
- 线程调试和测试
- 实现线程间通信机制
- 精确的线程终止控制
2. 函数原型 链接到标题
#include <sys/syscall.h>
#include <signal.h>
long syscall(SYS_tgkill, int tgid, int tid, int sig);
// 或者使用封装函数(如果系统提供)
int tgkill(int tgid, int tid, int sig);
注意:tgkill不是标准C库函数,通常需要通过系统调用来使用。
3. 功能 链接到标题
tgkill函数的主要功能是向指定的线程组中的特定线程发送信号。它提供了比传统kill函数更高的精确度:
- tgid(Thread Group ID):线程组ID,通常是进程ID
- tid(Thread ID):线程ID,指定线程组中的具体线程
- sig:要发送的信号
4. 参数 链接到标题
-
tgid: 线程组ID(Thread Group ID)
- 类型:int
- 含义:目标线程所属的线程组ID,通常等于进程ID(PID)
-
tid: 线程ID(Thread ID)
- 类型:int
- 含义:线程组中具体线程的ID,必须是该线程组中的有效线程
-
sig: 信号编号
- 类型:int
- 含义:要发送给目标线程的信号,如SIGTERM、SIGKILL、SIGUSR1等
5. 返回值 链接到标题
- 成功: 返回0
- 失败: 返回-1,并设置errno错误码
- EINVAL:信号编号无效
- ESRCH:找不到指定的线程组或线程
- EPERM:没有权限向目标线程发送信号
- EAGAIN:系统资源不足
6. 相似函数或关联函数 链接到标题
- kill(): 向进程发送信号
- pthread_kill(): 向POSIX线程发送信号
- raise(): 向当前进程发送信号
- signal()/sigaction(): 设置信号处理函数
- getpid(): 获取当前进程ID
- gettid(): 获取当前线程ID
7. 示例代码 链接到标题
示例1:基础tgkill使用 - 精确控制线程 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <signal.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
// 获取线程ID的辅助函数
pid_t gettid(void) {
return syscall(SYS_gettid);
}
// 线程处理函数
void signal_handler(int sig) {
printf("线程 %d 收到信号 %d (%s)\n", gettid(), sig, strsignal(sig));
}
// 工作线程函数
void* worker_thread(void* arg) {
int thread_num = *(int*)arg;
pid_t tid = gettid();
printf("工作线程 %d 启动,线程ID: %d\n", thread_num, tid);
// 设置信号处理函数
signal(SIGUSR1, signal_handler);
signal(SIGTERM, signal_handler);
// 线程主循环
while(1) {
printf("线程 %d 正在工作...\n", tid);
sleep(2);
}
return NULL;
}
// tgkill系统调用封装
int tgkill(int tgid, int tid, int sig) {
return syscall(SYS_tgkill, tgid, tid, sig);
}
int main() {
pthread_t threads[3];
int thread_nums[3] = {1, 2, 3};
pid_t main_tid = gettid();
pid_t pids[3];
int i;
printf("主线程ID: %d, 进程ID: %d\n", main_tid, getpid());
// 创建多个工作线程
for(i = 0; i < 3; i++) {
if(pthread_create(&threads[i], NULL, worker_thread, &thread_nums[i]) != 0) {
perror("创建线程失败");
exit(EXIT_FAILURE);
}
// 给线程一些时间启动
sleep(1);
pids[i] = gettid(); // 这里简化处理,实际应该从线程中获取
}
// 让线程运行一段时间
sleep(3);
// 向特定线程发送自定义信号
printf("\n=== 向线程发送SIGUSR1信号 ===\n");
if(tgkill(getpid(), pids[0], SIGUSR1) == 0) {
printf("成功向线程 %d 发送 SIGUSR1 信号\n", pids[0]);
} else {
perror("tgkill发送信号失败");
}
sleep(2);
// 向另一个线程发送终止信号
printf("\n=== 向线程发送SIGTERM信号 ===\n");
if(tgkill(getpid(), pids[1], SIGTERM) == 0) {
printf("成功向线程 %d 发送 SIGTERM 信号\n", pids[1]);
} else {
perror("tgkill发送信号失败");
}
sleep(2);
// 演示错误处理
printf("\n=== 演示错误处理 ===\n");
if(tgkill(999999, 999999, SIGTERM) == -1) {
printf("向不存在的线程发送信号失败(预期行为): %s\n", strerror(errno));
}
// 等待几秒观察结果
sleep(3);
// 强制终止所有线程
printf("\n=== 终止所有线程 ===\n");
for(i = 0; i < 3; i++) {
tgkill(getpid(), pids[i], SIGKILL);
}
return 0;
}
示例2:线程监控和管理 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <signal.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <time.h>
#define MAX_THREADS 5
// 线程信息结构体
typedef struct {
pthread_t thread;
pid_t tid;
int id;
int running;
time_t start_time;
} thread_info_t;
thread_info_t threads[MAX_THREADS];
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 获取线程ID
pid_t gettid(void) {
return syscall(SYS_gettid);
}
// 信号处理函数
void signal_handler(int sig) {
pid_t tid = gettid();
printf("[信号处理] 线程 %d 收到信号: %s\n", tid, strsignal(sig));
if(sig == SIGUSR1) {
printf("[信号处理] 线程 %d 将暂停工作\n", tid);
sleep(5);
printf("[信号处理] 线程 %d 恢复工作\n", tid);
} else if(sig == SIGTERM) {
printf("[信号处理] 线程 %d 准备退出\n", tid);
pthread_exit(NULL);
}
}
// 工作线程函数
void* worker_thread(void* arg) {
thread_info_t* info = (thread_info_t*)arg;
info->tid = gettid();
info->running = 1;
info->start_time = time(NULL);
printf("工作线程 %d 启动,系统线程ID: %d\n", info->id, info->tid);
// 设置信号处理
signal(SIGUSR1, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
// 工作循环
while(info->running) {
printf("线程 %d 正在处理任务...\n", info->tid);
sleep(3);
}
printf("线程 %d 正常退出\n", info->tid);
return NULL;
}
// tgkill封装函数
int tgkill(int tgid, int tid, int sig) {
return syscall(SYS_tgkill, tgid, tid, sig);
}
// 显示所有线程状态
void show_thread_status() {
printf("\n=== 线程状态 ===\n");
pthread_mutex_lock(&mutex);
for(int i = 0; i < MAX_THREADS; i++) {
if(threads[i].tid != 0) {
time_t uptime = time(NULL) - threads[i].start_time;
printf("线程 %d: TID=%d, 状态=%s, 运行时间=%lds\n",
threads[i].id, threads[i].tid,
threads[i].running ? "运行中" : "已停止",
uptime);
}
}
pthread_mutex_unlock(&mutex);
printf("================\n\n");
}
int main() {
int i;
printf("主线程监控程序启动,PID: %d\n", getpid());
// 初始化线程信息
memset(threads, 0, sizeof(threads));
// 创建工作线程
for(i = 0; i < MAX_THREADS; i++) {
threads[i].id = i + 1;
if(pthread_create(&threads[i].thread, NULL, worker_thread, &threads[i]) != 0) {
perror("创建线程失败");
exit(EXIT_FAILURE);
}
printf("创建线程 %d\n", i + 1);
}
// 给线程一些启动时间
sleep(2);
// 显示初始状态
show_thread_status();
// 演示各种tgkill操作
printf("=== 演示tgkill操作 ===\n");
// 向第一个线程发送暂停信号
if(tgkill(getpid(), threads[0].tid, SIGUSR1) == 0) {
printf("向线程 %d 发送暂停信号\n", threads[0].tid);
}
sleep(1);
// 向第三个线程发送终止信号
if(tgkill(getpid(), threads[2].tid, SIGTERM) == 0) {
printf("向线程 %d 发送终止信号\n", threads[2].tid);
pthread_mutex_lock(&mutex);
threads[2].running = 0;
pthread_mutex_unlock(&mutex);
}
sleep(2);
show_thread_status();
// 向所有线程发送中断信号
printf("向所有线程发送中断信号...\n");
pthread_mutex_lock(&mutex);
for(i = 0; i < MAX_THREADS; i++) {
if(threads[i].tid != 0 && threads[i].running) {
if(tgkill(getpid(), threads[i].tid, SIGINT) == 0) {
printf("向线程 %d 发送中断信号\n", threads[i].tid);
}
}
}
pthread_mutex_unlock(&mutex);
sleep(2);
// 等待所有线程结束
for(i = 0; i < MAX_THREADS; i++) {
if(threads[i].tid != 0) {
pthread_join(threads[i].thread, NULL);
printf("线程 %d 已结束\n", threads[i].id);
}
}
printf("所有线程已安全退出,程序结束\n");
return 0;
}
示例3:错误处理和权限检查 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
// tgkill封装函数
int tgkill(int tgid, int tid, int sig) {
return syscall(SYS_tgkill, tgid, tid, sig);
}
// 错误处理函数
void handle_error(const char* operation) {
switch(errno) {
case EINVAL:
printf("%s 失败: 无效的信号编号\n", operation);
break;
case ESRCH:
printf("%s 失败: 找不到指定的线程或进程\n", operation);
break;
case EPERM:
printf("%s 失败: 权限不足\n", operation);
break;
default:
printf("%s 失败: %s\n", operation, strerror(errno));
break;
}
}
int main() {
pid_t my_pid = getpid();
pid_t my_tid = syscall(SYS_gettid);
printf("当前进程信息:\n");
printf(" 进程ID (PID): %d\n", my_pid);
printf(" 线程ID (TID): %d\n", my_tid);
printf(" 用户ID (UID): %d\n", getuid());
printf(" 有效用户ID (EUID): %d\n", geteuid());
printf("\n");
// 测试1: 向自己发送信号(应该成功)
printf("=== 测试1: 向当前线程发送信号 ===\n");
if(tgkill(my_pid, my_tid, SIGUSR1) == 0) {
printf("✓ 成功向当前线程发送信号\n");
} else {
handle_error("向当前线程发送信号");
}
// 测试2: 使用无效信号编号
printf("\n=== 测试2: 使用无效信号编号 ===\n");
if(tgkill(my_pid, my_tid, 999) == -1) {
handle_error("使用无效信号");
}
// 测试3: 向不存在的进程发送信号
printf("\n=== 测试3: 向不存在的进程发送信号 ===\n");
if(tgkill(999999, 999999, SIGTERM) == -1) {
handle_error("向不存在的进程发送信号");
}
// 测试4: 向不存在的线程发送信号
printf("\n=== 测试4: 向不存在的线程发送信号 ===\n");
if(tgkill(my_pid, 999999, SIGTERM) == -1) {
handle_error("向不存在的线程发送信号");
}
// 测试5: 发送各种标准信号
printf("\n=== 测试5: 发送标准信号 ===\n");
int signals[] = {SIGHUP, SIGINT, SIGQUIT, SIGUSR1, SIGUSR2};
const char* signal_names[] = {"SIGHUP", "SIGINT", "SIGQUIT", "SIGUSR1", "SIGUSR2"};
int num_signals = sizeof(signals) / sizeof(signals[0]);
for(int i = 0; i < num_signals; i++) {
printf("发送 %s (信号 %d)... ", signal_names[i], signals[i]);
if(tgkill(my_pid, my_tid, signals[i]) == 0) {
printf("成功\n");
} else {
printf("失败\n");
}
}
printf("\n=== 完成所有测试 ===\n");
printf("注意: 某些信号可能被系统忽略或有特殊处理\n");
return 0;
}
编译和运行 链接到标题
# 编译示例1(需要链接pthread库)
gcc -o tgkill_example1 tgkill_example1.c -lpthread
./tgkill_example1
# 编译示例2
gcc -o tgkill_example2 tgkill_example2.c -lpthread
./tgkill_example2
# 编译示例3
gcc -o tgkill_example3 tgkill_example3.c
./tgkill_example3
重要注意事项 链接到标题
- 权限要求: tgkill需要适当的权限才能向目标线程发送信号
- 线程ID获取: 需要在线程内部调用
syscall(SYS_gettid)
获取真实的线程ID - 错误处理: 必须检查返回值并适当处理错误
- 信号安全: 在信号处理函数中只能调用异步信号安全的函数
- 跨平台兼容性: tgkill是Linux特有的系统调用,不适用于其他操作系统
通过这些示例,你可以理解tgkill在精确线程控制方面的强大功能,它为多线程程序提供了精细化的控制能力。