tgid vs pid / struct pid / upid in Linux

本文解析了Linux中线程与进程的关系,介绍了线程组ID(tgid)、线程ID(pid)及进程ID(tgid)的区别。深入探讨了task_struct结构、fork与clone系统调用、以及PID在不同命名空间中的表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

tgid vs pid

  • Linux中并没有明确的线程支持
  • Linux中的线程,即轻量级的进程
  • 从内核角度,以线程为单位,一个线程对应一个task_struct,对应一个pid
  • 从用户角度,以进程为单位,一个进程是多个线程组成的线程组,对应一个tgid(thread group id)
  • ps/top指令,系统调用getpid()返回的是tgid(用户态)
  • fork()派生进程,clone()派生线程,fork调用了clone()

task_struct

  • 描述一个线程的信息
struct task_struct  
{  
    //...  
    pid_t pid;  //线程号
    pid_t tgid;   //领头线程号,即进程号
    struct task_struct *group_leader;  //领头线程
    struct pid_link pids[PIDTYPE_MAX];  
    struct nsproxy *nsproxy;  
    //...  
};
  • group_leader:指向领头线程
  • nsproxy:指向命名空间域。nsproxy域包含了五个不同类型(uts_namespace、ipc_namespace、mnt_namespace、pid_namspace、net)的命名空间指针

struct pid

  • struct pid是内核对进程的PID的表示(与线程的pid_t pid区别)
  • 一个struct pid可以对应多个task_struct,即一个进程包含多个线程
  • 一个PID可以对应多个的命名空间(pid_namespace)
struct pid  
{  
        atomic_t count;  
        /* 使用该pid的进程的列表, lists of tasks that use this pid  */
        struct hlist_head tasks[PIDTYPE_MAX];  
        int level;  
        struct upid numbers[1];  
};
  • count:是指使用该PID的线程(task)的数目
  • level:该命名空间的层次信息
  • tasks[PIDTYPE_MAX]:每个数组元素都是一个表头,存储同一类型的task_struct实例(参见task_struct的枚举类型成员:pid_type)
  • numbers[1]:upid是特定命名空间可见的pid信息。保存了该进程的PID对不同命名空间的upid

struct upid

  • upid是特定命名空间可见的pid信息
struct upid
{  
    /* Try to keep pid_chain in the same cacheline as nr for find_vpid */
        int nr;  
        struct pid_namespace *ns;  
        struct hlist_node pid_chain;  
};  
  • nr:局部id(存在特定命名空间下,父ns可以看见子ns的nr,反之不行)
  • pid_namespace:指向与pid有关的命名空间

图与实例

linux下进程命名空间和进程的关系结构

  • 多个task_struct指向一个PID
  • PID的tasks数组里3个不同的task类型对task散列
  • 一个PID会属于多个命名空间

参考:链接: link.

### 编译错误原因分析 在安卓6.1内核中,`struct upid` 的定义并不包含 `pid_chain` 成员。根据提供的信息,`pid_chain` 实际上是 `struct upid` 中的一个字段,但该字段可能已被移除或重构[^2]。因此,在调用 `hlist_del_init_rcu(&proc_pid_struct->numbers[0].pid_chain)` 时会出现编译错误。 #### 具体原因 `struct upid` 的定义如下: ```c struct upid { int nr; struct pid_namespace *ns; struct hlist_node pid_chain; // 在某些内核版本中可能存在 }; ``` 然而,在安卓6.1内核中,`pid_chain` 字段已被移除或替换为其他实现方式。这意味着直接访问 `pid_chain` 会导致编译器报错:`no member named 'pid_chain' in 'struct upid'`[^2]。 --- ### 解决方案 为了解决此问题,可以采用以下方法: #### 方法一:使用 `find_pid_ns` 和 `pid_task` 替代直接操作 通过 `find_pid_ns` 函数可以从 PID 哈希表中查找指定命名空间下的 `struct pid` 指针。结合 `pid_task` 函数可以间接访问与 PID 相关的任务信息。示例代码如下: ```c struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns) { rcu_lockdep_assert(rcu_read_lock_held(), "find_task_by_pid_ns() needs rcu_read_lock() protection"); return pid_task(find_pid_ns(nr, ns), PIDTYPE_PID); } ``` #### 方法二:修改哈希链表操作逻辑 如果需要从 PID 哈希表中移除节点,可以通过以下方式实现: ```c void remove_from_pid_hash(struct pid *pid) { if (pid && !hlist_unhashed(&pid->numbers[0].pid_chain)) { hlist_del_init_rcu(&pid->numbers[0].pid_chain); // 替代 task->pids[PIDTYPE_PID].node } synchronize_rcu(); } ``` 上述代码检查了 `pid_chain` 是否已存在于哈希链表中,并在必要时调用 `hlist_del_init_rcu` 进行删除操作。 #### 方法三:动态适配不同内核版本 为了兼容不同版本的内核,可以通过条件编译实现动态适配。例如: ```c #if defined(CONFIG_HAVE_PID_CHAIN) #define GET_HLIST_NODE(pid) (&(pid)->numbers[0].pid_chain) #else #define GET_HLIST_NODE(pid) ((pid)->pids[PIDTYPE_PID].node) #endif void remove_from_pid_hash(struct pid *pid) { hlist_del_init_rcu(GET_HLIST_NODE(pid)); synchronize_rcu(); } ``` --- ### 内核代码分析 #### PID 哈希表初始化 内核在启动时会初始化 PID 哈希表,具体代码如下: ```c void __init pidhash_init(void) { int i, pidhash_size; pid_hash = alloc_large_system_hash("PID", sizeof(*pid_hash), 0, 18, HASH_EARLY | HASH_SMALL, &pidhash_shift, NULL, 4096); pidhash_size = 1 << pidhash_shift; for (i = 0; i < pidhash_size; i++) INIT_HLIST_HEAD(&pid_hash[i]); } ``` 此函数分配了一个大小为 `pidhash_size` 的数组,并为每个条目初始化了哈希链表头[^3]。 #### 查找 PID 节点 通过 `find_pid_ns` 函数可以在 PID 哈希表中查找指定命名空间下的 PID 节点: ```c struct pid *find_pid_ns(int nr, struct pid_namespace *ns) { struct upid *pnr; hlist_for_each_entry_rcu(pnr, &pid_hash[pid_hashfn(nr, ns)], pid_chain) if (pnr->nr == nr && pnr->ns == ns) return container_of(pnr, struct pid, numbers[ns->level]); return NULL; } ``` 此函数遍历 PID 哈希表中的链表节点,并匹配指定的 PID 和命名空间[^2]。 --- ### 注意事项 1. 在使用 `hlist_del_init_rcu` 时,必须确保调用 `synchronize_rcu()` 以完成 RCU 同步。 2. 如果需要跨线程组操作,应特别注意 `tgid` 和 `pid` 的区别。 3. 在安卓6.1内核中,`task_struct` 的 `pids` 成员被移除,导致无法直接访问 `pid_chain` 成员。可以通过替代方法实现相同功能。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值