概述
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,对于这样的例子,如下图所示为其表示:
结构图示例,图片来源郭海林的博客
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. 参考
最后
以上就是精明小伙为你收集整理的linux修改内核pid,linux内核PID管理的全部内容,希望文章能够帮你解决linux修改内核pid,linux内核PID管理所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复