搜索
首页运维linux运维一起分析Linux经典技巧之进程ID号

本篇文章给大家带来了linux中进程ID号分析的相关知识,Linux进程总是会分配一个号码用于在其命名空间中唯一地标识它们。该号码被称作进程ID号,简称PID,下面就一起来看一下相关问题,希望对大家有帮助。

一起分析Linux经典技巧之进程ID号

  本文中的代码摘自 Linux内核5.15.13版本。

  Linux进程总是会分配一个号码用于在其命名空间中唯一地标识它们。该号码被称作进程ID号,简称PID。用fork或clone产生的每个进程都由内核自动地分配了一个新的唯一的PID值。

一、进程ID

1.1、其他ID

  每个进程除了PID这个特征值之外,还有其他的ID。有下列几种可能的类型

  1、 处于某个线程组(在一个进程中,以标志CLONE_THREAD来调用clone建立的该进程的不同的执行上下文,我们在后文会看到)中的所有进程都有统一的线程组ID( TGID)。如果进程没有使用线程,则其PID和TGID相同。线程组中的主进程被称作组长( group leader)。通过clone创建的所有线程的task_struct的group_leader成员,会指向组长的task_struct实例。

  2、另外,独立进程可以合并成进程组(使用setpgrp系统调用)。进程组成员的task_struct的pgrp属性值都是相同的,即进程组组长的PID。进程组简化了向组的所有成员发送信号的操作,这对于各种系统程序设计应用(参见系统程序设计方面的文献,例如[ SR05])是有用的。请注意,用管道连接的进程包含在同一个进程组中。

  3、 几个进程组可以合并成一个会话。会话中的所有进程都有同样的会话ID,保存在task_struct的session成员中。 SID可以使用setsid系统调用设置。它可以用于终端程序设计。

1.2、全局ID和局部ID

  名空间增加了PID管理的复杂性。 PID命名空间按层次组织。在建立一个新的命名空间时,该命名空间中的所有PID对父命名空间都是可见的,但子命名空间无法看到父命名空间的PID。但这意味着某些进程具有多个PID,凡可以看到该进程的命名空间,都会为其分配一个PID。 这必须反映在数据结构中。我们必须区分局部ID和全局ID。

  1、 全局ID是在内核本身和初始命名空间中的唯一ID号,在系统启动期间开始的init进程即属于初始命名空间。对每个ID类型,都有一个给定的全局ID,保证在整个系统中是唯一的。

  2、 局部ID属于某个特定的命名空间,不具备全局有效性。对每个ID类型,它们在所属的命名空间内部有效,但类型相同、值也相同的ID可能出现在不同的命名空间中。

1.3、ID实现

  全局PID和TGID直接保存在task_struct中,分别是task_struct的pid和tgid成员,在sched.h文件里:

struct task_struct {...pid_t pid;pid_t tgid;...}

  这两项都是pid_t类型,该类型定义为__kernel_pid_t,后者由各个体系结构分别定义。通常定义为int,即可以同时使用232个不同的ID。

二、管理PID

  一个小型的子系统称之为PID分配器( pid allocator)用于加速新ID的分配。此外,内核需要提供辅助函数,以实现通过ID及其类型查找进程的task_struct的功能,以及将ID的内核表示形式和用户空间可见的数值进行转换的功能。

2.1、PID命名空间的表示方式

  在pid_namespace.h文件内有如下定义:

struct pid_namespace {
	struct idr idr;
	struct rcu_head rcu;
	unsigned int pid_allocated;
	struct task_struct *child_reaper;
	struct kmem_cache *pid_cachep;
	unsigned int level;
	struct pid_namespace *parent;#ifdef CONFIG_BSD_PROCESS_ACCT
	struct fs_pin *bacct;#endif
	struct user_namespace *user_ns;
	struct ucounts *ucounts;
	int reboot;	/* group exit code if this pidns was rebooted */
	struct ns_common ns;} __randomize_layout;

  每个PID命名空间都具有一个进程,其发挥的作用相当于全局的init进程。 init的一个目的是对孤儿进程调用wait4,命名空间局部的init变体也必须完成该工作。 child_reaper保存了指向该进程的task_struct的指针。

  parent是指向父命名空间的指针, level表示当前命名空间在命名空间层次结构中的深度。初始命名空间的level为0,该命名空间的子空间level为1,下一层的子空间level为2,依次递推。level的计算比较重要,因为level较高的命名空间中的ID,对level较低的命名空间来说是可见的。从给定的level设置,内核即可推断进程会关联到多少个ID。

2.2、PID的管理

2.2.1、PID的数据结构

  PID的管理围绕两个数据结构展开: struct pid是内核对PID的内部表示,而struct upid则表示特定的命名空间中可见的信息。两个结构的定义在文件pid.h内,分别如下:

/*
 * What is struct pid?
 *
 * A struct pid is the kernel's internal notion of a process identifier.
 * It refers to inpidual 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 {
	int nr;
	struct pid_namespace *ns;};struct pid{
	refcount_t count;
	unsigned int level;
	spinlock_t lock;
	/* lists of tasks that use this pid */
	struct hlist_head tasks[PIDTYPE_MAX];
	struct hlist_head inodes;
	/* wait queue for pidfd notifications */
	wait_queue_head_t wait_pidfd;
	struct rcu_head rcu;
	struct upid numbers[1];};

  对于struct upid, nr表示ID的数值, ns是指向该ID所属的命名空间的指针。所有的upid实例都保存在一个散列表中。 pid_chain用内核的标准方法实现了散列溢出链表。struct pid的定义首先是一个引用计数器count。 tasks是一个数组,每个数组项都是一个散列表头,对应于一个ID类型。这样做是必要的,因为一个ID可能用于几个进程。所有共享同一给定ID的task_struct实例,都通过该列表连接起来。 PIDTYPE_MAX表示ID类型的数目:

enum pid_type{
	PIDTYPE_PID,
	PIDTYPE_TGID,
	PIDTYPE_PGID,
	PIDTYPE_SID,
	PIDTYPE_MAX,};

2.2.2、PID与进程的联系

  一个进程可能在多个命名空间中可见,而其在各个命名空间中的局部ID各不相同。 level表示可以看到该进程的命名空间的数目(换言之,即包含该进程的命名空间在命名空间层次结构中的深度),而numbers是一个upid实例的数组,每个数组项都对应于一个命名空间。注意该数组形式上只有一个数组项,如果一个进程只包含在全局命名空间中,那么确实如此。由于该数组位于结构的末尾,因此只要分配更多的内存空间,即可向数组添加附加的项。

  由于所有共享同一ID的task_struct实例都按进程存储在一个散列表中,因此需要在struct task_struct中增加一个散列表元素在sched.h文件内进程的结构头定义内有

struct task_struct {...
	/* PID/PID hash table linkage. */
	struct pid			*thread_pid;
	struct hlist_node		pid_links[PIDTYPE_MAX];
	struct list_head		thread_group;
	struct list_head		thread_node;...};

  将task_struct连接到表头在pid_links中的散列表上。

2.2.3、查找PID

  假如已经分配了struct pid的一个新实例,并设置用于给定的ID类型。它会如下附加到task_struct,在kernel/pid.c文件内:

static struct pid **task_pid_ptr(struct task_struct *task, enum pid_type type){
	return (type == PIDTYPE_PID) ?
		&task->thread_pid :
		&task->signal->pids[type];}/*
 * attach_pid() must be called with the tasklist_lock write-held.
 */void attach_pid(struct task_struct *task, enum pid_type type){
	struct pid *pid = *task_pid_ptr(task, type);
	hlist_add_head_rcu(&task->pid_links[type], &pid->tasks[type]);}

  这里建立了双向连接: task_struct可以通过task_struct->pids[type]->pid访问pid实例。而从pid实例开始,可以遍历tasks[type]散列表找到task_struct。 hlist_add_head_rcu是遍历散列表的标准函数。

三、生成唯一的PID

  除了管理PID之外,内核还负责提供机制来生成唯一的PID。为跟踪已经分配和仍然可用的PID,内核使用一个大的位图,其中每个PID由一个比特标识。 PID的值可通过对应比特在位图中的位置计算而来。因此,分配一个空闲的PID,本质上就等同于寻找位图中第一个值为0的比特,接下来将该比特设置为1。反之,释放一个PID可通过将对应的比特从1切换为0来实现。在建立一个新进程时,进程可能在多个命名空间中是可见的。对每个这样的命名空间,都需要生成一个局部PID。这是在alloc_pid中处理的,在文件kernel/pid.c内有:

struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid,
		      size_t set_tid_size){
	struct pid *pid;
	enum pid_type type;
	int i, nr;
	struct pid_namespace *tmp;
	struct upid *upid;
	int retval = -ENOMEM;

	/*
	 * set_tid_size contains the size of the set_tid array. Starting at
	 * the most nested currently active PID namespace it tells alloc_pid()
	 * which PID to set for a process in that most nested PID namespace
	 * up to set_tid_size PID namespaces. It does not have to set the PID
	 * for a process in all nested PID namespaces but set_tid_size must
	 * never be greater than the current ns->level + 1.
	 */
	if (set_tid_size > ns->level + 1)
		return ERR_PTR(-EINVAL);

	pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
	if (!pid)
		return ERR_PTR(retval);

	tmp = ns;
	pid->level = ns->level;

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

		if (set_tid_size) {
			tid = set_tid[ns->level - i];

			retval = -EINVAL;
			if (tid < 1 || tid >= pid_max)
				goto out_free;
			/*
			 * Also fail if a PID != 1 is requested and
			 * no PID 1 exists.
			 */
			if (tid != 1 && !tmp->child_reaper)
				goto out_free;
			retval = -EPERM;
			if (!checkpoint_restore_ns_capable(tmp->user_ns))
				goto out_free;
			set_tid_size--;
		}

		idr_preload(GFP_KERNEL);
		spin_lock_irq(&pidmap_lock);

		if (tid) {
			nr = idr_alloc(&tmp->idr, NULL, tid,
				       tid + 1, GFP_ATOMIC);
			/*
			 * If ENOSPC is returned it means that the PID is
			 * alreay in use. Return EEXIST in that case.
			 */
			if (nr == -ENOSPC)
				nr = -EEXIST;
		} else {
			int pid_min = 1;
			/*
			 * init really needs pid 1, but after reaching the
			 * maximum wrap back to RESERVED_PIDS
			 */
			if (idr_get_cursor(&tmp->idr) > RESERVED_PIDS)
				pid_min = RESERVED_PIDS;

			/*
			 * Store a null pointer so find_pid_ns does not find
			 * a partially initialized PID (see below).
			 */
			nr = idr_alloc_cyclic(&tmp->idr, NULL, pid_min,
					      pid_max, GFP_ATOMIC);
		}
		spin_unlock_irq(&pidmap_lock);
		idr_preload_end();

		if (nr < 0) {
			retval = (nr == -ENOSPC) ? -EAGAIN : nr;
			goto out_free;
		}

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

	/*
	 * ENOMEM is not the most obvious choice especially for the case
	 * where the child subreaper has already exited and the pid
	 * namespace denies the creation of any new processes. But ENOMEM
	 * is what we have exposed to userspace for a long time and it is
	 * documented behavior for pid namespaces. So we can't easily
	 * change it even if there were an error code better suited.
	 */
	retval = -ENOMEM;

	get_pid_ns(ns);
	refcount_set(&pid->count, 1);
	spin_lock_init(&pid->lock);
	for (type = 0; type < PIDTYPE_MAX; ++type)
		INIT_HLIST_HEAD(&pid->tasks[type]);

	init_waitqueue_head(&pid->wait_pidfd);
	INIT_HLIST_HEAD(&pid->inodes);

	upid = pid->numbers + ns->level;
	spin_lock_irq(&pidmap_lock);
	if (!(ns->pid_allocated & PIDNS_ADDING))
		goto out_unlock;
	for ( ; upid >= pid->numbers; --upid) {
		/* Make the PID visible to find_pid_ns. */
		idr_replace(&upid->ns->idr, pid, upid->nr);
		upid->ns->pid_allocated++;
	}
	spin_unlock_irq(&pidmap_lock);

	return pid;out_unlock:
	spin_unlock_irq(&pidmap_lock);
	put_pid_ns(ns);out_free:
	spin_lock_irq(&pidmap_lock);
	while (++i <= ns->level) {
		upid = pid->numbers + i;
		idr_remove(&upid->ns->idr, upid->nr);
	}

	/* On failure to allocate the first pid, reset the state */
	if (ns->pid_allocated == PIDNS_ADDING)
		idr_set_cursor(&ns->idr, 0);

	spin_unlock_irq(&pidmap_lock);

	kmem_cache_free(ns->pid_cachep, pid);
	return ERR_PTR(retval);}

相关推荐:《Linux视频教程

以上是一起分析Linux经典技巧之进程ID号的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:CSDN。如有侵权,请联系admin@php.cn删除
Linux操作系统的5个核心组件Linux操作系统的5个核心组件May 08, 2025 am 12:08 AM

Linux操作系统的5个核心组件是:1.内核,2.系统库,3.系统工具,4.系统服务,5.文件系统。这些组件协同工作,确保系统的稳定和高效运行,共同构成了一个强大而灵活的操作系统。

Linux的5个基本要素:解释Linux的5个基本要素:解释May 07, 2025 am 12:14 AM

Linux的五个核心元素是:1.内核,2.命令行界面,3.文件系统,4.包管理,5.社区与开源。这些元素共同定义了Linux的本质和功能。

Linux操作:安全和用户管理Linux操作:安全和用户管理May 06, 2025 am 12:04 AM

Linux用户管理和安全性可以通过以下步骤实现:1.创建用户和组,使用命令如sudouseradd-m-gdevelopers-s/bin/bashjohn。2.批量创建用户和设置密码策略,使用for循环和chpasswd命令。3.检查和修复常见错误,如家目录和shell设置。4.实施最佳实践,如强密码策略、定期审计和最小权限原则。5.优化性能,使用sudo和调整PAM模块配置。通过这些方法,可以有效管理用户和提升系统安全性。

Linux操作:文件系统,进程等Linux操作:文件系统,进程等May 05, 2025 am 12:16 AM

Linux文件系统和进程管理的核心操作包括文件系统的管理和进程的控制。1)文件系统操作包括创建、删除、复制和移动文件或目录,使用命令如mkdir、rmdir、cp和mv。2)进程管理涉及启动、监控和终止进程,使用命令如./my_script.sh&、top和kill。

Linux操作:外壳脚本和自动化Linux操作:外壳脚本和自动化May 04, 2025 am 12:15 AM

Shell脚本是Linux系统中用于自动化执行命令的强大工具。1)Shell脚本通过解释器逐行执行命令,处理变量替换和条件判断。2)基本用法包括备份操作,如使用tar命令备份目录。3)高级用法涉及使用函数和case语句管理服务。4)调试技巧包括使用set-x开启调试模式和set-e在命令失败时退出。5)性能优化建议避免子Shell,使用数组和优化循环。

Linux操作:了解核心功能Linux操作:了解核心功能May 03, 2025 am 12:09 AM

Linux是一个基于Unix的多用户、多任务操作系统,强调简单性、模块化和开放性。其核心功能包括:文件系统:以树状结构组织,支持多种文件系统如ext4、XFS、Btrfs,使用df-T查看文件系统类型。进程管理:通过ps命令查看进程,使用PID管理进程,涉及优先级设置和信号处理。网络配置:灵活设置IP地址和管理网络服务,使用sudoipaddradd配置IP。这些功能在实际操作中通过基本命令和高级脚本自动化得以应用,提升效率并减少错误。

Linux:进入和退出维护模式Linux:进入和退出维护模式May 02, 2025 am 12:01 AM

进入Linux维护模式的方法包括:1.编辑GRUB配置文件,添加"single"或"1"参数并更新GRUB配置;2.在GRUB菜单中编辑启动参数,添加"single"或"1"。退出维护模式只需重启系统。通过这些步骤,你可以在需要时快速进入维护模式,并安全地退出,确保系统的稳定性和安全性。

了解Linux:定义的核心组件了解Linux:定义的核心组件May 01, 2025 am 12:19 AM

Linux的核心组件包括内核、shell、文件系统、进程管理和内存管理。1)内核管理系统资源,2)shell提供用户交互界面,3)文件系统支持多种格式,4)进程管理通过fork等系统调用实现,5)内存管理使用虚拟内存技术。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具