错宗复杂的进程标识PID

1.进程标识描述

1.1标识ID

TID:每一个线程唯一的标识,系统最小的执行单位
PID:每一个进程唯一的标识,系统最基本的分配资源单位,PID是在一组
PPID:每一个进程的会携带父进程的PID,能清楚知道每个进程的层次关系
PGID:一堆“进程”组合而形成的集合,该集合叫进程组。每组会有一个领头羊的进程,该进程PID则是PGID
SID: 一堆”进程组“而形成的集合,该集合叫会话。

1.2标识作用

PGID和SID在默认情况,创建进程时,会默认继承父进程所在进程组和会话。
它们俩方便用于作业控制,SID在用户登录时由shell产生的首个进程PID,它作用于登录的整个生命周期内,包括用户所有活动行为。进程组是在会话作用下,由一系列的命令关联起来形成进程组,从而完成一项任务。

2.层次关系

进程层次图

画了一幅简单、规整、清晰的进程层次图。
以公司架构来描述容易理解,每一个process都携带一个线程(为了整体清晰,只把小组部分给展开了)。

集团由多个子公司独立运营而组成,在集团资金资助下,每个子公司会划分为多个部门,互相协作、共同发展;部门会以不同项目而细分不同小组,小组中由多个不同职位的成员负责项目的每一块的业务。成员作为最小执行单位,推动项目进展等。

从图中一个进程可以由多个线程组成,在同一个进程中的N个线程都是共享同一资源的;其中有一个线程TID和进程的PID是相同的,无论是小组/部门/子公司都需要至少一位成员担任领头羊,所以会出现相同的现象。

3.Linux系统层面

3.1 PID_TYPE类型

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

 A struct pid is the kernel's internal notion of a process identifier.
 一个PID结构体是内核中的进程标识概念。
 It refers to individual tasks, process groups, and sessions.
 它指的是单个进程、进程组、会话。

3.2 PIDTYPE_TGID => pid

该类型就是进程的pid标识,由fork、vfork、clone等系统调用来创建新进程以及分配唯一的pid(命名空间内唯一)。在内核空间中是直接将由_do_fork函数处理的。
_do_fork

在用户空间获取pid的getpid函数,我们可以发现使用PIDTYPE_TGID获取进程pid,可以通过(层次关系)得知,pid本质就是进程的领头羊的tid。
getpid

3.3 PIDTYPE_PID => tid

在用户空间获取tid的gettid函数,代表每个执行单位的标识以及最基本的标识。其它ID标识(进程\进程组\会话)都是以tid作为自身进行描述。
gettid系统调用

注意:glibc库中没实现gettid函数,所以在linux系统中不能直接调用。直接调用syscall函数带上对应ID。
函数介绍

3.4 PIDTYPE_PGID => pgid

在用户空间获取pgid的getpgid函数,可以获取进程是属于哪一个进程组。形成进程组后,发送信号整个组内的进程都会收到。
getpgid函数调用

3.5 PIDTYPE_SID -> sid

在用户空间获取sid的getsid函数,可以获取进程是属于哪一个会话。
getsid函数

3.6 标识的产生

上述的进程标识,会在_do_fork函数处理产生;任何方式创建进程\线程都会通过_do_fork函数。
_do_fork函数主要做三件事:创建进程/线程及分配资源(copy_process)、确定PID(pid_vnr)、加入调度模块的队列(wake_up_new_task)

函数原型
long _do_fork(unsigned long clone_flags,
	      unsigned long stack_start,
	      unsigned long stack_size,
	      int __user *parent_tidptr,
	      int __user *child_tidptr,
	      unsigned long tls)	      

clone_flags 需要复制/共享哪些资源
stack_start 用户空间的栈地址
stack_size 用户空间的栈大小
parent_tidptr/child_tidptr 指向用户空间的地址,内容为父/子进程PID
tls 设置线程的本地存储区,依赖于体系结构

/*
 *  Ok, this is the main fork-routine.
 *
 * It copies the process, and if successful kick-starts
 * it and waits for it to finish using the VM if required.
 */
long _do_fork(unsigned long clone_flags,
	      unsigned long stack_start,
	      unsigned long stack_size,
	      int __user *parent_tidptr,
	      int __user *child_tidptr,
	      unsigned long tls)
{
	...
	
	struct pid *pid;
	struct task_struct *p;
	int trace = 0;
	long nr;

	...
	/*进程/线程的创建、分配资源的处理,主要由以下copy_process体现*/
	p = copy_process(clone_flags, stack_start, stack_size,
			 child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
	...

	if (IS_ERR(p))
		return PTR_ERR(p);

	...
	/*确认PID: 取PID由当前命名空间决定,每层命名空间都对应一个PID。*/
	pid = get_task_pid(p, PIDTYPE_PID);
	nr = pid_vnr(pid);

	...
	/*把分配好的进程/线程添加到调度模块中队列,开始执行*/
	wake_up_new_task(p);

	...
	
	return nr;
}

该函数处理流程,主要关注标识如何产生的过程,过程中涉及到的命名空间概念,当作只有一层pid信息即可。
因代码过多而简化,详细内容请看【kernel/fork.c】
static __latent_entropy struct task_struct *copy_process(
					unsigned long clone_flags,
					unsigned long stack_start,
					unsigned long stack_size,
					int __user *child_tidptr,
					struct pid *pid,
					int trace,
					unsigned long tls,
					int node)
{
	【AAA\检查标志】
	/*
	 * Don't allow sharing the root directory with processes in a different
	 * namespace
	 */
	if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
		return ERR_PTR(-EINVAL);
		
	...

	【BBB\申请新进程/线程的对象】
	retval = -ENOMEM;
	p = dup_task_struct(current, node);
	if (!p)
		goto fork_out;
	
	...

	【CCC\检查资源限制】
	if (atomic_read(&p->real_cred->user->processes) >=
			task_rlimit(p, RLIMIT_NPROC)) {
		if (p->real_cred->user != INIT_USER &&
		    !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
			goto bad_fork_free;
	}	
	
	...

	【DDD\初始化新进程/线程的对象】
	delayacct_tsk_init(p);	/* Must remain after dup_task_struct() */
	p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER | PF_IDLE);
	p->flags |= PF_FORKNOEXEC;
	INIT_LIST_HEAD(&p->children);
	INIT_LIST_HEAD(&p->sibling);
	rcu_copy_process(p);
	p->vfork_done = NULL;
	spin_lock_init(&p->alloc_lock);
	
	...

	【EEE\设置调度相关配置(状态、调度器、优先级、CPU选项等)/* Perform scheduler related setup. Assign this task to a CPU. */
	retval = sched_fork(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_policy;

	...

	【FFF\设置复制/共享资源(semundo、files、fs、sighand、signal、mm、namespaces、io、thread_tls等)/* copy all the process information */
	shm_init_task(p);
	retval = security_task_alloc(p, clone_flags);
	if (retval)
		goto bad_fork_cleanup_audit;
	retval = copy_semundo(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_security;
	retval = copy_files(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_semundo;
	retval = copy_fs(clone_flags, p);
	
	...

	【GGG\设置进程标识、关系等】
	/*针对于进程\线程分配每一层命名空间的pid信息*/
	pid = alloc_pid(p->nsproxy->pid_ns_for_children);
	if (IS_ERR(pid)) {
		retval = PTR_ERR(pid);
		goto bad_fork_cleanup_thread;
	}

	...
	
	/*这块能看到进程和线程的区别处理,注意哦:如下处理流程中都是围绕顶层命名空间的PID处理*/
	/* ok, now we should be set up.. */
	p->pid = pid_nr(pid);				
	if (clone_flags & CLONE_THREAD) {/*线程处理*/
		p->exit_signal = -1;
		p->group_leader = current->group_leader;	/*获取当前进程中的组长对象信息*/
		p->tgid = current->tgid;					/*获取当前进程中的组长pid*/
	} else {
		if (clone_flags & CLONE_PARENT)
			p->exit_signal = current->group_leader->exit_signal;/*继承父进程的退出信号*/
		else
			p->exit_signal = (clone_flags & CSIGNAL); /*设置自身的退出信号*/
		p->group_leader = p;						/*相关pid就是它自己本身了,因资源独立了,没有必要依附于父进程资源*/
		p->tgid = p->pid;
	}
	
	...
	
	if (likely(p->pid)) {
		ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace);

		init_task_pid(p, PIDTYPE_PID, pid);
		if (thread_group_leader(p)) { 
			/*产生pid、pgid、sid, 注意:PIDTYPE_TGID自己本身*/
			init_task_pid(p, PIDTYPE_TGID, pid);
			init_task_pid(p, PIDTYPE_PGID, task_pgrp(current));
			init_task_pid(p, PIDTYPE_SID, task_session(current));
			
			...
			/*加入到init_task列表,以及父进程的子进程列表*/
			list_add_tail(&p->sibling, &p->real_parent->children);
			list_add_tail_rcu(&p->tasks, &init_task.tasks);
			
			...
			
		} else {
			/*统计进程下的线程数量*/
			current->signal->nr_threads++;
			
			...
			/*放到进程的线程列表*/
			list_add_tail_rcu(&p->thread_group,
					  &p->group_leader->thread_group);
			list_add_tail_rcu(&p->thread_node,
					  &p->signal->thread_head);
		}
		...
	}
}

—越简单,易接受。在折腾路上…

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值