Linux pid_hash散列表

本文深入解析了Linux系统中如何通过进程ID(PID)高效查找进程描述符的具体实现机制,包括PID散列表的设计与使用。

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

linux系统中每个进程由一个进程id标识,在内核中对应一个task_struct结构的进程描述符,系统中所有进程的task_struct通过链表链接在一起,在内核中,经常需要通过进程pid来获取进程描述符,例如kill命令;最简单的方法可以通过遍历task_struct链表并对比pid的值来获取,但这样效率太低,尤其当系统中运行很多个进程的时候。

linux内核通过pids散列表来解决这一问题,能快速的通过进程PID获取到进程描述符。

PID散列表包含4个表,因为进程描述符包含了表示不同类型PID的字段,每种类型的PID需要自己的散列表。
进程pid散列表

在task_struct结构体中:

struct task_struct {
	pid_t pid;
	pid_t tgid;

	/* PID/PID hash table linkage. */
	struct pid_link pids[PIDTYPE_MAX];    //一个进程ID可能是多种身份,比如Session ID,进程组ID, 进程ID,所以指向多个pid节点
	struct list_head thread_group;
}
enum pid_type
{
	PIDTYPE_PID,
	PIDTYPE_PGID,
	PIDTYPE_SID,
	PIDTYPE_MAX
};
pid结构体和task_struct结构体之间的联系,通过pid_link结构体;
/*
 * What is struct pid?
 *
 * A struct pid is the kernel's internal notion of a process identifier.
 * It refers to individual tasks, process groups, and sessions.  While
 * there are processes attached to it the struct pid lives in a hash
 * table, so it and then the processes that it refers to can be found
 * quickly from the numeric pid value.  The attached processes may be
 * quickly accessed by following pointers from struct pid.
 *
 * Storing pid_t values in the kernel and referring to them later has a
 * problem.  The process originally with that pid may have exited and the
 * pid allocator wrapped, and another process could have come along
 * and been assigned that pid.
 *
 * Referring to user space processes by holding a reference to struct
 * task_struct has a problem.  When the user space process exits
 * the now useless task_struct is still kept.  A task_struct plus a
 * stack consumes around 10K of low kernel memory.  More precisely
 * this is THREAD_SIZE + sizeof(struct task_struct).  By comparison
 * a struct pid is about 64 bytes.
 *
 * Holding a reference to struct pid solves both of these problems.
 * It is small so holding a reference does not consume a lot of
 * resources, and since a new struct pid is allocated when the numeric pid
 * value is reused (when pids wrap around) we don't mistakenly refer to new
 * processes.
 */
/*
 * struct upid is used to get the id of the struct pid, as it is
 * seen in particular namespace. Later the struct pid is found with
 * find_pid_ns() using the int nr and struct pid_namespace *ns.
 */

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;
};

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];
};

struct pid_link
{
	struct hlist_node node;
	struct pid *pid;
};

看完基本的关联之后,利用pid是如何找到进程描述符的呢?

直接贴代码!

/*
 * Must be called under rcu_read_lock().
 */
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);
}

#define pid_hashfn(nr, ns)	\
	hash_long((unsigned long)nr + (unsigned long)ns, pidhash_shift)
static struct hlist_head *pid_hash;
static unsigned int pidhash_shift = 4;

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;
}

find_pid_ns() 在pid_hash的哈希表中根据哈希函数得到pid所在的链表头部,然后遍历得到 id 和 ns 都匹对的pid结构体指针;


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)         //pid中的task[type] 与task_struct.pid[type].node  指向的是同一个节点
			result = hlist_entry(first, struct task_struct, pids[(type)].node);   //node实体在task_struct结构中,所以可以利用first指针得到task_struct结构体指针
	}
	return result;
}

pid_task()函数中rcu_dereference_check()可以忽略,可以看做:first = hlist_first_rcu(&pid->tasks[type]); 

#define hlist_first_rcu(head)	(*((struct hlist_node __rcu **)(&(head)->first)))
#define hlist_entry(ptr, type, member) container_of(ptr,type,member)

pid_hash的初始化函数:

/*
 * The pid hash table is scaled according to the amount of memory in the
 * machine.  From a minimum of 16 slots up to 4096 slots at one gigabyte or
 * more.
 */
void __init pidhash_init(void)
{
	unsigned int i, pidhash_size;

	pid_hash = alloc_large_system_hash("PID", sizeof(*pid_hash), 0, 18,
					   HASH_EARLY | HASH_SMALL,
					   &pidhash_shift, NULL,
					   0, 4096);
	pidhash_size = 1U << pidhash_shift;

	for (i = 0; i < pidhash_size; i++)
		INIT_HLIST_HEAD(&pid_hash[i]);
}

从pidhash_init函数中可以看出,pid_hash至少含有16项,最多4096项,它是系统中所有进程的哈希表,根据id分配,但此哈希表与PGIDTGID并无直接关系;


/*
 * allocate a large system hash table from bootmem
 * - it is assumed that the hash table must contain an exact power-of-2
 *   quantity of entries
 * - limit is the number of hash buckets, not the total allocation size
 */
void *__init alloc_large_system_hash(const char *tablename,
				     unsigned long bucketsize,
				     unsigned long numentries,
				     int scale,
				     int flags,
				     unsigned int *_hash_shift,
				     unsigned int *_hash_mask,
				     unsigned long low_limit,
				     unsigned long high_limit)


注:所用内核版本为3.10

引用:http://blog.chinaunix.net/uid-20196318-id-134921.html


### Linux 中 `pid_entry` 的结构与用途 在 Linux 内核中,`pid_entry` 并不是一个标准的核心数据结构,但它可能出现在某些特定场景下用于实现进程 ID (PID) 空间的管理和映射。为了更好地理解其作用,可以从以下几个方面展开讨论。 #### 1. PID 空间管理背景 Linux 支持多级命名空间(namespace),其中包括 PID 命名空间。每个 PID 命名空间都有自己的独立 PID 范围和分配机制。这种设计允许容器或其他隔离环境拥有自己的一组 PIDs,而不会与其他命名空间冲突。因此,在内核源码中会涉及一些辅助数据结构来支持这些功能[^1]。 #### 2. 可能的定义形式 虽然官方文档未明确提及 `pid_entry` 的具体细节,但在部分社区补丁或者定制化版本中可能会看到类似的定义: ```c struct pid_entry { int nr; /* Entry number or index */ void *data; /* Pointer to associated data structure */ }; ``` 这里假设该结构体包含两个主要成员变量: - `nr`: 表示条目编号或索引值; - `data`: 存储指向关联对象的指针,例如某个具体的 task_struct 或其他元信息。 此类型的数组可以用来构建哈希表项列表或者其他集合表示方法以便快速定位目标实体。 #### 3. 使用场景分析 基于上述推测性的定义以及已知关于 hash 表的应用情况我们可以推断出如下几种潜在应用场景: ##### a) 进程查找优化 当需要频繁地依据给定条件(如PID号)获取对应的任务控制块(task_struct实例)时,利用预先建立好的`pid_entry[]`数组配合恰当算法能够显著提升效率. ##### b) 动态资源跟踪 除了单纯记录基本信息外还可以扩展存储更多动态变化的内容比如文件句柄计数器等附加属性从而便于全局监控系统状态变化趋势. ##### c) 安全审计日志生成 通过对每次创建销毁操作进行全面登记形成完整的生命周期轨迹有助于后续排查故障原因提供线索证据支撑. 以上只是理论上的可能性实际应用还需参照具体项目需求做出调整修改才能达到最佳效果. ```c // 示例代码片段展示如何遍历并打印所有注册过的 entries. void dump_pid_entries(struct pid_entry* table, size_t count){ for(int i=0;i<count;i++){ printf("Entry %d -> Data @%p\n",table[i].nr, table[i].data); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值