linux修改内核pid,linux内核PID管理

1. 概念

什么是 PID ?

PID 是内核内部对进程的一个标识符,用来唯一标识一个进程(task)、进程组(process group)、会话(session)。PID 及其对应进程存储在一个哈希表中,方便依据 PID 快速访问进程 task_struct。

因此Linux 内核在设计管理ID的数据结构时,要充分考虑以下因素:

如何快速地根据进程的 task_struct、ID 类型、命名空间找到 PID ?

如何快速地根据 PID、命名空间、ID 类型找到对应进程的 task_struct ?

如何快速地给新进程在可见的命名空间内分配一个唯一的 PID ?

注:本文所附内核源码版本为 v4.11.6。

2. 数据结构

2.1 进程ID

struct pid

{

atomic_t count;

unsigned int level;

/* lists of tasks that use this pid */

struct hlist_head tasks[PIDTYPE_MAX];

struct rcu_head rcu;

struct upid numbers[1];

};

count 是该数据结构被引用的次数。

level 是该 pid 在 pid_namespace 中所处层级。当 level=0 时表示是 global namespace,即最高层。

tasks[i] 指向 PID 对应的 task_struct。PIDTYPE_MAX 是 pid 的类型数。一个或多个进程可以组成一个进程组,进程组ID(PGID)为进程组领导进程 (process group leader)的PID;一个或多个进程组可以组成一个会话,会话ID(SID)为会话领导进程(session leader)的PID。PIDTYPE_MAX定义在include/linux/pid.h中:

enum pid_type

{

PIDTYPE_PID,

PIDTYPE_PGID,

PIDTYPE_SID,

PIDTYPE_MAX

};

rcu 用于保证数据同步,具体机制另行讨论。

numbers[1] 域是一个可扩展 upid 结构体。 一个 PID 可以属于不同的 namespace , numbers[0] 表示 global namespace,numbers[i] 表示第 i 层 namespace,i 越大所在层级越低。upid 结构体定义include/linux/pid.h如下:

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是pid的值, 即 task_struct 中 pid_t pid 域的值。

ns指向该 pid 所处的 namespace。

pid_chain 是 pid_hash 哈希表节点。linux内核将所有进程的upid都存放在一个哈希表(pid_hash)中,以方便查找和统一管理。通过 pid_chain 能够找到该 upid 所在 pid_hash 中的位置。

2.2 进程命名空间

再来看 pid 命名空间 pid_namespace 的定义(include/linux/pid_namespace.h)

struct pid_namespace {

struct kref kref;

struct pidmap pidmap[PIDMAP_ENTRIES];

struct rcu_head rcu;

int last_pid;

unsigned int nr_hashed;

struct task_struct *child_reaper;

struct kmem_cache *pid_cachep;

unsigned int level;

struct pid_namespace *parent;

struct user_namespace *user_ns;

struct ucounts *ucounts;

struct work_struct proc_work;

kgid_t pid_gid;

int hide_pid;

int reboot;/* group exit code if this pidns was rebooted */

struct ns_common ns;

};

kref 表示指向 pid_namespace 的个数。

pidmap 结构体表示分配pid的位图,pidmap[PIDMAP_ENTRIES] 域存储了该 pid_namespace 下 pid 已分配情况。pidmap 结构体定义(include/linux/pid_namespace.h)如下:

struct pidmap {

atomic_t nr_free;

void *page;

};

#define BITS_PER_PAGE(PAGE_SIZE * 8)

#define BITS_PER_PAGE_MASK(BITS_PER_PAGE-1)

#define PIDMAP_ENTRIES((PID_MAX_LIMIT+BITS_PER_PAGE-1)/BITS_PER_PAGE)

// include/linux/threads.h

#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)

/*

* A maximum of 4 million PIDs should be enough for a while.

* [NOTE: PID/TIDs are limited to 2^29 ~= 500+ million, see futex.h.]

*/

#define PID_MAX_LIMIT (CONFIG_BASE_SMALL ? PAGE_SIZE * 8 : \

(sizeof(long) > 4 ? 4 * 1024 * 1024 : PID_MAX_DEFAULT))

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

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

rcu 同样用于保证数据同步。

last_pid 是最后一个已分配的 pid。

nr_hashed 统计该命名空间已分配PID个数。

child_reaper指向的是一个进程。 该进程的作用是当子进程结束时为其收尸(回收空间)。global namespace 中child_reaper 指向 init_task。

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

level 表示该命名空间所处层级。

parent 指向该命名空间的父命名空间。

2.3 进程描述符

进程描述符 task_struct 是 linux 内核中最重要的概念之一,其结构复杂(include/linux/sched.h),详情另行分析。其中,与 pid 有关的域如下:

struct task_struct {

···

pid_t pid;

pid_t tgid;

struct task_struct *group_leader;

struct pid_link pids[PIDTYPE_MAX];

struct nsproxy *nsproxy;

···

};

pid 即是该进程的 id。使用 fork 或 clone 系统调用时产生的进程均会由内核分配一个新的唯一的PID值。

tgid 是线程组 id。在一个进程中,如果以 CLONE_THREAD 标志来调用 clone 建立的进程就是该进程的一个线程,它们处于一个线程组。处于相同的线程组中的所有进程都有相同的 TGID;线程组组长的 TGID 与其 PID 相同;一个进程没有使用线程,则其 TGID 与 PID 也相同。

group_leader 除了在多线程的模式下指向主线程外, 当一些进程组成一个群组时(PIDTYPE_PGID), 该域指向该群组的leader。

pids[PIDTYPE_MAX] 指向了和该 task_struct 相关的 pid 结构体。其定义(include/linux/pid.h)如下:

struct pid_link

{

struct hlist_node node;

struct pid *pid;

};

nsproxy指针指向namespace相关的域。其定义(include/linux/nsproxy.h)如下:

struct nsproxy {

atomic_t count;

struct uts_namespace *uts_ns;

struct ipc_namespace *ipc_ns;

struct mnt_namespace *mnt_ns;

struct pid_namespace *pid_ns_for_children;

struct net *net_ns;

struct cgroup_namespace *cgroup_ns;

};

与其它命名空间不同。此处 pid_ns_for_children 指向该进程的子进程会使用的 pid_namespace,该进程本身所属的 pid_namespace 可以通过 task_active_pid_ns 方法获得。

nsproxy 被所有共享命名空间的 task_struct 共享,随命名空间的复制而复制。

2.4 数据结构关系

举例来说,在level 2 的某个命名空间上新建了一个进程,分配给它的 pid 为45,映射到 level 1 的命名空间,分配给它的 pid 为 134;再映射到 level 0 的命名空间,分配给它的 pid 为289,对于这样的例子,如下图所示为其表示:

ee80f72de57e534300d7788957a97130.png

结构图示例,图片来源郭海林的博客

3. 方法/函数

现在就可以解决本文开篇提出的问题了。

3.1 查询PID

获取与 task_struct 相关的 pid 结构体实例

static inline struct pid *task_pid(struct task_struct *task)

{

return task->pids[PIDTYPE_PID].pid;

}

static inline struct pid *task_tgid(struct task_struct *task)

{

return task->group_leader->pids[PIDTYPE_PID].pid;

}

static inline struct pid *task_pgrp(struct task_struct *task)

{

return task->group_leader->pids[PIDTYPE_PGID].pid;

}

static inline struct pid *task_session(struct task_struct *task)

{

return task->group_leader->pids[PIDTYPE_SID].pid;

}

获取与 task_struct 相关的 PID 命名空间

struct pid_namespace *task_active_pid_ns(struct task_struct *tsk)

{

return ns_of_pid(task_pid(tsk));

}

static inline struct pid_namespace *ns_of_pid(struct pid *pid)

{

struct pid_namespace *ns = NULL;

if (pid)

ns = pid->numbers[pid->level].ns;

return ns;

}

获取 pid 实例中的 PID

pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns)

{

struct upid *upid;

pid_t nr = 0;

if (pid && ns->level <= pid->level) {

upid = &pid->numbers[ns->level];

if (upid->ns == ns)

nr = upid->nr;

}

return nr;

}

获取指定 ns 中的 PID时,需注意,由于 PID 命名空间的层次性,父命名空间能看到子命名空间的内容,反之则不能。因此,函数 中 需要确保当前命名空间的 level 小于等于产生局部 PID 的命名空间的 level。

此外,内核还封装有直接获取初始命名空间、当前命名空间对应 PID 的方法:

static inline pid_t pid_nr(struct pid *pid)

{

pid_t nr = 0;

if (pid)

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

return nr;

}

pid_t pid_vnr(struct pid *pid)

{

return pid_nr_ns(pid, task_active_pid_ns(current));

}

3.2 查找 task_struct

获得 pid 实体。

根据 PID 以及指定命名空间计算在 pid_hash 数组中的索引,然后遍历散列表找到所要的 upid, 再根据内核的 container_of 机制找到 pid 实例。代码(kernel/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实例:

struct pid *find_vpid(int nr)

{

return find_pid_ns(nr, task_active_pid_ns(current));

}

根据 pid 及 PID 类型获取 task_struct

struct task_struct *pid_task(struct pid *pid, enum pid_type type)

{

struct task_struct *result = NULL;

if (pid) {

struct hlist_node *first;

first = rcu_dereference_check(hlist_first_rcu(&pid->tasks[type]),

lockdep_tasklist_lock_is_held());

if (first)

result = hlist_entry(first, struct task_struct, pids[(type)].node);

}

return result;

}

3.3 分配PID

为新进程 task_struct 分配 pid 域

新的进程使用 alloc_pid 方法分配 pid,代码(kernel/pid.c)如下:

struct pid *alloc_pid(struct pid_namespace *ns)

{

struct pid *pid;

enum pid_type type;

int i, nr;

struct pid_namespace *tmp;

struct upid *upid;

int retval = -ENOMEM;

// 从命名空间分配一个 pid 结构体

pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);

if (!pid)

return ERR_PTR(retval);

// 初始化进程在各级命名空间的 PID,直到全局命名空间(level 为0)为止

tmp = ns;

pid->level = ns->level;

for (i = ns->level; i >= 0; i--) {

nr = alloc_pidmap(tmp); //分配一个局部PID

if (nr < 0) {

retval = nr;

goto out_free;

}

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

pid->numbers[i].ns = tmp;

tmp = tmp->parent;

}

// 若为命名空间的初始进程

if (unlikely(is_child_reaper(pid))) {

// 0

if (pid_ns_prepare_proc(ns))

goto out_free;

}

get_pid_ns(ns);

atomic_set(&pid->count, 1);

for (type = 0; type < PIDTYPE_MAX; ++type)

INIT_HLIST_HEAD(&pid->tasks[type]); // // 初始化 pid->task[] 结构体,值为NULL

upid = pid->numbers + ns->level;

spin_lock_irq(&pidmap_lock);

if (!(ns->nr_hashed & PIDNS_HASH_ADDING))

goto out_unlock;

for ( ; upid >= pid->numbers; --upid) {

// 将每个命名空间经过哈希之后加入到散列表中

hlist_add_head_rcu(&upid->pid_chain,

&pid_hash[pid_hashfn(upid->nr, upid->ns)]);

upid->ns->nr_hashed++;

}

spin_unlock_irq(&pidmap_lock);

return pid;

out_unlock:

spin_unlock_irq(&pidmap_lock);

put_pid_ns(ns);

out_free:

while (++i <= ns->level)

free_pidmap(pid->numbers + i);

kmem_cache_free(ns->pid_cachep, pid);

return ERR_PTR(retval);

}

从指定命名空间中分配唯一PID

static int alloc_pidmap(struct pid_namespace *pid_ns)

{

int i, offset, max_scan, pid, last = pid_ns->last_pid;

struct pidmap *map;

pid = last + 1;

// 默认最大值在 include/linux/threads.h 中定义为 (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)

if (pid >= pid_max)

pid = RESERVED_PIDS; // RESERVED_PIDS = 300

offset = pid & BITS_PER_PAGE_MASK;

map = &pid_ns->pidmap[pid/BITS_PER_PAGE];

/*

* If last_pid points into the middle of the map->page we

* want to scan this bitmap block twice, the second time

* we start with offset == 0 (or RESERVED_PIDS).

*/

max_scan = DIV_ROUND_UP(pid_max, BITS_PER_PAGE) - !offset;

for (i = 0; i <= max_scan; ++i) {

if (unlikely(!map->page)) {

void *page = kzalloc(PAGE_SIZE, GFP_KERNEL);

/*

* Free the page if someone raced with us

* installing it:

*/

spin_lock_irq(&pidmap_lock);

if (!map->page) {

map->page = page;

page = NULL;

}

spin_unlock_irq(&pidmap_lock);

kfree(page);

if (unlikely(!map->page))

return -ENOMEM;

}

if (likely(atomic_read(&map->nr_free))) {

for ( ; ; ) {

if (!test_and_set_bit(offset, map->page)) {

atomic_dec(&map->nr_free);

set_last_pid(pid_ns, last, pid);

return pid;

}

offset = find_next_offset(map, offset);

if (offset >= BITS_PER_PAGE)

break;

pid = mk_pid(pid_ns, map, offset);

if (pid >= pid_max)

break;

}

}

if (map < &pid_ns->pidmap[(pid_max-1)/BITS_PER_PAGE]) {

++map;

offset = 0;

} else {

map = &pid_ns->pidmap[0];

offset = RESERVED_PIDS;

if (unlikely(last == offset))

break;

}

pid = mk_pid(pid_ns, map, offset);

}

return -EAGAIN;

}

回收PID

static void free_pidmap(struct upid *upid)

{

int nr = upid->nr;

struct pidmap *map = upid->ns->pidmap + nr / BITS_PER_PAGE;

int offset = nr & BITS_PER_PAGE_MASK;

clear_bit(offset, map->page);

atomic_inc(&map->nr_free);

}

4. 参考

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值