关于pid和pid_hash、task_struct的一个问题
本文是原「蜗窝讨论区」的历史存档(2016-10-26),来自版块「Linux kernel技术问答」,共 13 帖。讨论区已停止服务,此处仅供查阅。
从一个pid_t(下面函数的nr),通过调用函数find_pid_ns,可以得到struct pid
[== Undefined ==]
struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
{
struct upid *pnr;
hlist_for_each_entry_rcu(pnr,
&pid_hash, pid_chain)
if (pnr->nr == nr && pnr->ns == ns)
return container_of(pnr, struct pid,
numbers[ns->level]);
return NULL;
}
那么我就可以通过container_of一步步得到task_struct,我觉得没必要在pid结构体中再设置字段struct hlist_head tasks;不知道这个字段有什么其他的用途吗?而且也没必要用一个struct hlist_head,就这个问题和大家交流一下
[== Undefined ==]
struct pid
{
atomic_t count;
unsigned int level;
/* lists of tasks that use this pid */
struct hlist_head tasks;
struct rcu_head rcu;
struct upid numbers[1];
};
挺好, 我就爱问这样的问题. 可惜我不会. 另外, 这儿等不到答案的话, 可以再去osdev上问, 我才发现那儿linux高人不少.
我也喜欢这样的问题,哈哈,我是这么理解的:
首先,我们先梳理一下内核中使用的ID: 1、线程ID 2、进程ID(或者线程组ID) 3、进程组ID 4、session ID
从用户层面,这些ID都体现在一个namespace中的一个具体的数字,那个具体的数字在内核中定义的数据类型是pid_t。由于还有一些其他的控制数据成员,因此而xxx ID(上面列出的线程ID、进程组ID等4种ID)则使用struct pid来表示。 我们知道,给定的namespace下,标识xxx ID的那个具体的数字(pid_t类型,后面用nr这个符号表示)是唯一的,因此,系统存在了一个hash table,可以很快的从namespace和nr找到对应的PID(struct pid类型)。但是PID是否是和task struct一一对应的呢?很可惜,不是。我们看下面的一个例子:
一个进程是由5个线程组成,线程ID分别是a1、a2、a3、a4、a5,每个线程在内核中对应一个task struct,a1是线程组leader,其对应的进程pid(一个struct pid类型的对象,内嵌的nr是a1)并非独享,而是5个线程共享的,因此,我们还需要从一个struct pid类型的数据,反向得到和该pid相关的所有的task struct的对象,这也就是为何pid数据结构中有tasks成员。 进程组ID和session ID的道理是类似的,不再赘述。
看来我是把pid这个结构体理解错了,还得再看看代码,谢谢linuxer的回答 BTW,linuxer好像一般都是上午回答问题啊 :cool:
linuxer 写道: 我也喜欢这样的问题,哈哈,我是这么理解的:
首先,我们先梳理一下内核中使用的ID: 1、线程ID 2、进程ID(或者线程组ID) 3、进程组ID 4、session ID
从用户层面,这些ID都体现在一个namespace中的一个具体的数字,那个具体的数字在内核中定义的数据类型是pid_t。由于还有一些其他的控制数据成员,因此而xxx ID(上面列出的线程ID、进程组ID等4种ID)则使用struct pid来表示。 我们知道,给定的namespace下,标识xxx ID的那个具体的数字(pid_t类型,后面用nr这个符号表示)是唯一的,因此,系统存在了一个hash table,可以很快的从namespace和nr找到对应的PID(struct pid类型)。但是PID是否是和task struct一一对应的呢?很可惜,不是。我们看下面的一个例子:
一个进程是由5个线程组成,线程ID分别是a1、a2、a3、a4、a5,每个线程在内核中对应一个task struct,a1是线程组leader,其对应的进程pid(一个struct pid类型的对象,内嵌的nr是a1)并非独享,而是5个线程共享的,因此,我们还需要从一个struct pid类型的数据,反向得到和该pid相关的所有的task struct的对象,这也就是为何pid数据结构中有tasks成员。 进程组ID和session ID的道理是类似的,不再赘述。
我又看了下代码,在fork一个进程的时候,即使说是要fork一个thread,代码中也会单独分配一个struct pid,我贴一下copy_process中的一段代码
[== Undefined ==]
if (pid != &init_struct_pid) {
pid = alloc_pid(p->nsproxy->pid_ns_for_children);
if (IS_ERR(pid)) {
retval = PTR_ERR(pid);
goto bad_fork_cleanup_io;
}
}
。。。
p->pid = pid_nr(pid);
。。。
if (likely(p->pid)) {
ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace);
init_task_pid(p, PIDTYPE_PID, pid);
所以我想一个task_struct还是和一个pid关联的啊,并没有说是一个struct pid会被多个进程(或者说线程)共享
linuxer 写道: 我也喜欢这样的问题,哈哈,我是这么理解的:
首先,我们先梳理一下内核中使用的ID: 1、线程ID 2、进程ID(或者线程组ID) 3、进程组ID 4、session ID
从用户层面,这些ID都体现在一个namespace中的一个具体的数字,那个具体的数字在内核中定义的数据类型是pid_t。由于还有一些其他的控制数据成员,因此而xxx ID(上面列出的线程ID、进程组ID等4种ID)则使用struct pid来表示。 我们知道,给定的namespace下,标识xxx ID的那个具体的数字(pid_t类型,后面用nr这个符号表示)是唯一的,因此,系统存在了一个hash table,可以很快的从namespace和nr找到对应的PID(struct pid类型)。但是PID是否是和task struct一一对应的呢?很可惜,不是。我们看下面的一个例子:
一个进程是由5个线程组成,线程ID分别是a1、a2、a3、a4、a5,每个线程在内核中对应一个task struct,a1是线程组leader,其对应的进程pid(一个struct pid类型的对象,内嵌的nr是a1)并非独享,而是5个线程共享的,因此,我们还需要从一个struct pid类型的数据,反向得到和该pid相关的所有的task struct的对象,这也就是为何pid数据结构中有tasks成员。 进程组ID和session ID的道理是类似的,不再赘述。
我一般有两个时间点回答问题,一是早晨上班前,二是晚上下班后,有时候我都是在晚上12点之后回答问题的
fork进程(或者线程)的时候,分配pid是一定的,但是也不是说明该进程(或者线程)和一个pid关联,本质上,这个新创建的进程(或者线程)是和若干个pid相关联。当然,process group pid和session pid是继承自parent process的资源。在fork中新分配的pid可能是thread pid,也可能是process pid,和调用fork时候的参数相关。
哦,我还想问一下,在task_struct中,有两个成员struct task_struct __rcu parent和struct task_struct group_leader,难道一个线程的父进程,不是这个线程组的组长吗?
[== Undefined ==]
struct task_struct {
...
struct task_struct __rcu *real_parent; /* real parent process */
struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4( reports */
struct task_struct *group_leader; /* threadgroup leader */
…
}
linuxer 写道: 我一般有两个时间点回答问题,一是早晨上班前,二是晚上下班后,有时候我都是在晚上12点之后回答问题的
fork进程(或者线程)的时候,分配pid是一定的,但是也不是说明该进程(或者线程)和一个pid关联,本质上,这个新创建的进程(或者线程)是和若干个pid相关联。当然,process group pid和session pid是继承自parent process的资源。在fork中新分配的pid可能是thread pid,也可能是process pid,和调用fork时候的参数相关。
父进程和进程组leader当然不是一回事了。 有a和b两个程序,a程序执行后fork了新的进程,然后exec b形成b进程,这样a进程就是b进程的父进程。b进程在执行过程中,clone了b1,b2和b3三个线程(它自己是b0主线程),这时候b进程是由b0、b1、b2、b3组成,b0是thread group leader,b0、b1、b2、b3这四个task struct的struct task_struct group_leader都指向b0对应的task struct,而他们的struct task_struct __rcu parent都指向a进程的task struct。
linuxer 写道: 父进程和进程组leader当然不是一回事了。 有a和b两个程序,a程序执行后fork了新的进程,然后exec b形成b进程,这样a进程就是b进程的父进程。b进程在执行过程中,clone了b1,b2和b3三个线程(它自己是b0主线程),这时候b进程是由b0、b1、b2、b3组成,b0是thread group leader,b0、b1、b2、b3这四个task struct的struct task_struct group_leader都指向b0对应的task struct,而他们的struct task_struct __rcu parent都指向a进程的task struct。
如果a继续fork了c进程,d进程,那么进程a,b,c,d是不是就组成了一个进程组,而组进程就是a?
子进程会继承其父进程的进程组ID,在a创建子进程之前,它应该属于某一个进程组,如果它就是进程组leader,那么进程组ID就是a的进程ID。你说的那种情况,只有在a fork b c d进程之前就已经是进程组leader的时候才成立,如果不是,那么虽然进程a,b,c,d是在一个进程组内,但是进程组ID并非是a进程的ID。
linuxer 写道: 子进程会继承其父进程的进程组ID,在a创建子进程之前,它应该属于某一个进程组,如果它就是进程组leader,那么进程组ID就是a的进程ID。你说的那种情况,只有在a fork b c d进程之前就已经是进程组leader的时候才成立,如果不是,那么虽然进程a,b,c,d是在一个进程组内,但是进程组ID并非是a进程的ID。
在fork进程的时候,有标志使得进程成为一个进程组吗,另外我在copy_process只找到一处是复制父进程PGID:
[== Undefined ==]
if (thread_group_leader(p)) {
init_task_pid(p, PIDTYPE_PGID, task_pgrp(current));----复制父进程PGID
init_task_pid(p, PIDTYPE_SID, task_session(current));
if (is_child_reaper(pid)) {
ns_of_pid(pid)->child_reaper = p;
p->signal->flags |= SIGNAL_UNKILLABLE;
}
p->signal->leader_pid = pid;
p->signal->tty = tty_kref_get(current->signal->tty);
list_add_tail(&p->sibling, &p->real_parent->children);
list_add_tail_rcu(&p->tasks, &init_task.tasks);
attach_pid(p, PIDTYPE_PGID);
attach_pid(p, PIDTYPE_SID);
__this_cpu_inc(process_counts);
} else {
current->signal->nr_threads++;
atomic_inc(&current->signal->live);
atomic_inc(&current->signal->sigcnt);
list_add_tail_rcu(&p->thread_group,
&p->group_leader->thread_group);
list_add_tail_rcu(&p->thread_node,
&p->signal->thread_head);
}
在fork进程的时候,只能是加入其父进程的进程组(你贴的代码中已经看到了复制父进程PGID的片段),如果想要创建一个新的进程组,可以有两种方法: 1、创建一个新的sesseion(setsid) 2、调用设定进程的进程组ID的接口函数(setpgid或者setpgrp)
linuxer 写道: 在fork进程的时候,只能是加入其父进程的进程组(你贴的代码中已经看到了复制父进程PGID的片段),如果想要创建一个新的进程组,可以有两种方法: 1、创建一个新的sesseion(setsid) 2、调用设定进程的进程组ID的接口函数(setpgid或者setpgrp)
又看了一下APUE进程那方面的介绍,现在有点清楚了,谢谢linuxer的回答
