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函数处理的。
在用户空间获取pid的getpid函数,我们可以发现使用PIDTYPE_TGID获取进程pid,可以通过(层次关系)得知,pid本质就是进程的领头羊的tid。
3.3 PIDTYPE_PID => tid
在用户空间获取tid的gettid函数,代表每个执行单位的标识以及最基本的标识。其它ID标识(进程\进程组\会话)都是以tid作为自身进行描述。
注意:glibc库中没实现gettid函数,所以在linux系统中不能直接调用。直接调用syscall函数带上对应ID。
3.4 PIDTYPE_PGID => pgid
在用户空间获取pgid的getpgid函数,可以获取进程是属于哪一个进程组。形成进程组后,发送信号整个组内的进程都会收到。
3.5 PIDTYPE_SID -> sid
在用户空间获取sid的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);
}
...
}
}
—越简单,易接受。在折腾路上…