linux 内核pid,linux内核PID管理 —— 转自网络

PID即进程描述符在linux kernel中的分配和管理比较复杂。

本文分析了其相关数据结构以及函数。 (代码基于v3.0.3)

和PID相关的数据结构有

structpid

{

atomic_t count;

unsigned intlevel;

structhlist_head tasks[PIDTYPE_MAX];

structrcu_head rcu;

structupid numbers[1];

};

其中 count是指向该数据结构的引用次数。

level是该pid在pid_namespace中处于第几层。当level=0时表示是global

namespace,即最高层。pid_namespace这个数据结构将在后面进行解释。

tasks[PIDTYPE_MAX]数组中每个元素都代表了不同的含义。PIDTYPE_MAX表示pid所表示的类型的最大数。

该值定义在enum pid_type中

enumpid_type

{

PIDTYPE_PID,

PIDTYPE_PGID,

PIDTYPE_SID,

PIDTYPE_MAX

};

PIDTYPE_PID代表进程描述符(PID)

。 PIDTYPE_PGID代表一组进程描述符。 一组进程(process)可以组成一个群组,并且有一个组描述符。

这样的好处是如果有一个信号是针对这个组描述符,该群组内的所有进程都可以接受到。

PIDTYPE_SID是对组描述符再做一个群组,形成一个session。这是更高一个层次的抽象。

tasks[i]指向的是一个哈希表。譬如说tasks[PIDTYPE_PID]指向的是PID的哈希表。

rcu域我也没有搞明白到底是做什么的:(

numbers[1]域指向的是upid结构体。 numbers数组的本意是想表示不同的pid_namespace。

一个PID可以属于不同的namespace, numbers[0]表示global

namespace,numbers[i]表示第i层namespace,i越大所在层级越低。目前该数组只有一个元素, 即global

namespace。所以namepace的概念虽然引入了pid,但是并未真正使用,在未来的版本可能会用到。

接下来我们再看看upid这个数据结构

structupid {

intnr;

structpid_namespace × ns;

structhlist_node pid_chain;

};

pid结构体中的numbers域指向了upid结构体。该结构体中

nr是pid的值, 即 task_struct中 pid_t pid域的值。

ns指向该pid所处的namespace。

linux内核将所有进程的upid都存放在一个哈希表中(pid_hash),以方便查找和统一管理。因此,pid结构体中的numbers[0]指向的upid

instance存放在pid_hash里。通过pid_chain即哈希表的节点就能够找到该upid所在pid_hash中的位置。

接下来再看看pid_namespace结构体

structpid_namespace {

structkref kref;

structpidmap pidmap[PIDMAP_ENTRIES];

intlast_pid;

structtask_struct *child_reaper;

structkmem_cache *pid_cachep;

unsigned intlevel;

structpid_namespace *parent;

};

kref表示指向pid_namespace的个数。

pidmap结构体表示分配pid的位图。当需要分配一个新的pid时只需查找位图,找到bit为0的位置并置1,然后更新统计数据域(nr_free)。

structpidmap {

atomic_t nr_free;

void*page;

};

nr_free表示还能分配的pid的数量。

page指向的是存放pid的物理页。

所以pidmap[PIDMAP_ENTRIES]域表示该pid_namespace下pid已分配情况。

last_pid用于pidmap的分配。指向最后一个分配的pid的位置。(不是特别确定)

child_reaper指向的是一个进程。 该进程的作用是当子进程结束时为其收尸(回收空间)。由于目前只支持global

namespace,这里child_reaper就指向init_task。

pid_cachep域指向分配pid的slab的地址。

level表示该namespace处于哪一层, 现在这里显然是0。

parent指向该namespace的父亲namespace。 现在一定是NULL。

介绍完pid_namespace相关的数据结构,我们来看看设计它们的本意是什么。

Linux中增加namespace这个概念的目的是为了虚拟化和方便管理。 比如在不同的namespace中可以有pid相同的进程。

pid_namespace的结构是层次化的。而且在child namespace中的进程一定会有parent

namespace的映射。这句话可能不太好理解。可以结合下面这张图

a4c26d1e5885305701be709a3d33442f.png

以上图为例子,此时pid_hash全局哈希表中此时会存放15个(9+3+3)upid的instance。

前面介绍了这么多关于pid, upid, pid_namespace的概念,

接下来我们再来看看它们和task_struct之间的关系

a4c26d1e5885305701be709a3d33442f.png

右下角的椭圆形虚线框是全局pid_hash,所有已分配的upid都会保存在该hash表中。

左下角的椭圆形虚线框表示的是pid_namespace的关系。 当然目前只有一层。

Linux内核通过task_struct来管理进程。在task_struct中,和pid相关的域有

structtask_struct

{

...

pid_t pid;

pid_t tgid;

structtask_struct *group_leader;

structpid_link pids[PIDTYPE_MAX];

structnsproxy *nsproxy;

...

};

pid指该进程的进程描述符。

后面会介绍在fork函数中如何对其进行赋值的。

tgid指该进程的线程描述符。在linux内核中对线程并没有做特殊的处理,还是由task_struct来管理。所以从内核的角度看,

用户态的线程本质上还是一个进程。对于同一个进程(用户态角度)中不同的线程其tgid是相同的,但是pid各不相同。

主线程即group_leader(主线程会创建其他所有的子线程)。如果是单线程进程(用户态角度),它的pid等于tgid。

对于用户态程序来说,调用getpid()函数其实返回的是tgid。想想是为什么?:)

group_leader除了在多线程的模式下指向主线程,还有一个用处, 当一些进程组成一个群组时(PIDTYPE_PGID),

该域指向该群组的leader。

nsproxy指针指向namespace相关的域。

structnsproxy {

atomic_t count;

structuts_namespace *uts_ns;

structipc_namespace *ipc_ns;

structmnt_namespace *mnt_ns;

structpid_namespace *pid_ns;

structnet *net_ns;

};

通过nsproxy域可以知道该task_struct属于哪个pid_namespace,

当然现在一定是global namespace。(已经讲了很多次了:))

其他一些域也是namespace相关,这里就不展开解释了。

pids[PIDTYPE_MAX]指向了和该task_struct相关的pid结构体。

pid_link的定义如下

structpid_link

{

structhlist_node node;

structpid *pid;

};

在linux内核中如果想获得该task_struct所对应的pid可以调用task_pid()函数, 这个函数的实现非常简单

staticinlinestructpid *task_pid(structtask_struct *task)

{

returntask->pids[PIDTYPE_PID].pid;

}

自此我已将pid相关的数据结构介绍完了, 下面我们再看看和pid相关的使用。

(1)fork函数中如何分配一个新的pid?

fork(), vfork()还有clone()函数最终都是通过调用do_fork()来进行工作。

分配新的pid是在copy_process()函数实现的。 do_fork()函数会调用copy_process(),

它们之间的关系我会在以后的文章中进行介绍。

staticstructtask_struct *copy_process(unsignedlongclone_flags,

unsigned longstack_start,

structpt_regs *regs,

unsigned longstack_size,

int__user *child_tidptr,

structpid *pid,

inttrace)

{

...

if(pid != &init_struct_pid) {

retval = -ENOMEM;

pid = alloc_pid(p->nsproxy->pid_ns);

if(!pid)

gotobad_fork_cleanup_io;

}

p->pid = pid_nr(pid);

p->tgid = p->pid;

if(clone_flags & CLONE_THREAD)

p->tgid = current->tgid;

...

}

我只将和pid分配的代码列出来了。

alloc_pid函数将分配一个新的pid struct。 简单的说该函数的功能是在pidmap上找到一个未用的pid

bit,如若找不着,着说明已经没有可用的pid了,该namespace所在pid配给全部用完。

然后将其保存到pid_hash的哈希表里,然后再将pid结构体返回。

pid_nr函数的实现也很简单

staticinlinepid_t pid_nr(structpid *pid)

{

pid_t nr = 0;

if(pid)

nr = pid->numbers[0].nr;

returnnr;

}

返回该pid所在global

namespace的值。

后面几行代码用于区分进程和线程中tgid的值。

和pid相关的数据结构,函数定义可以在 include/linux/pid.h

include/linux/pid_namespace.h 以及 kernel/pid.c

kernel/pid_namespace.c中找到。

注:

(1)本文中如果发现任何错误请帮我指出。

非常感谢!

(2)欢迎和大家进行交流。

(3)本文系原创,如需转载请标明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值