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

重要注意事项 見出しへのリンク

  1. 权限要求: tgkill需要适当的权限才能向目标线程发送信号
  2. 线程ID获取: 需要在线程内部调用syscall(SYS_gettid)获取真实的线程ID
  3. 错误处理: 必须检查返回值并适当处理错误
  4. 信号安全: 在信号处理函数中只能调用异步信号安全的函数
  5. 跨平台兼容性: tgkill是Linux特有的系统调用,不适用于其他操作系统

通过这些示例,你可以理解tgkill在精确线程控制方面的强大功能,它为多线程程序提供了精细化的控制能力。