`
emowuyi
  • 浏览: 1480135 次
文章分类
社区版块
存档分类
最新评论

进程ID号

 
阅读更多


关于进程ID号,在深入理解linux内核架构中已经讲得很清楚了。下面是主要的部分。

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

1.进程ID

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

处于某个线程组(在一个进程中,以标志CLONE_THREAD来调用clone建立的该进程的不同的执行上下文,我们在后文会看到)中的所有进程都有统一的线程组IDTGID)。如果进程没有使用线程,则其PIDTGID相同。

线程组中的主进程被称作组长(groupleader)。通过clone创建的所有线程的task_structgroup_leader成员,会指向组长的task_struct实例。

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

几个进程组可以合并成一个会话。会话中的所有进程都有同样的会话ID,保存在task_structsession成员中。SID可以使用setsid系统调用设置。它可以用于终端程序设计,但和我们这里的讨论不相干。

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

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

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

全局PIDTGID直接保存在task_struct中,分别是task_structpidtgid成员:

1<sched.h>

2structtask_struct{

3...

4pid_tpid;

5pid_ttgid;

6...

7}

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

会话和进程组ID不是直接包含在task_struct本身中,但保存在用于信号处理的结构中。task_struct->signal->__session表示全局SID,而全局PGID则保存在task_struct->signal->__pgrp。辅助函数set_task_sessionset_task_pgrp可用于修改这些值。

2.管理PID

除了这两个字段之外,内核还需要找一个办法来管理所有命名空间内部的局部量,以及其他ID(如TIDSID)。这需要几个相互连接的数据结构,以及许多辅助函数,并将在下文讨论。

数据结构

下文我将使用ID指代提到的任何进程ID。在必要的情况下,我会明确地说明ID类型(例如,TGID,即线程组ID)。

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

在介绍表示ID本身所需的数据结构之前,我需要讨论PID命名空间的表示方式。我们所需查看的代码如下所示:

8<pid_namespace.h>

9structpid_namespace{

10...

11structtask_struct*child_reaper;

12...

13intlevel;

14structpid_namespace*parent;

15};

实际上PID分配器也需要依靠该结构的某些部分来连续生成唯一ID,但我们目前对此无需关注。我们上述代码中给出的下列成员更感兴趣。

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

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

回想图2-3的内容,命名空间是按层次关联的。这有助于理解上述的定义。

PID的管理围绕两个数据结构展开:structpid是内核对PID的内部表示,而structupid则表示特定的命名空间中可见的信息。两个结构的定义如下:

16<pid.h>

17structupid{

18intnr;

19structpid_namespace*ns;

20structhlist_nodepid_chain;

21};

22

23structpid

24{

25atomic_tcount;

26/*使用该pid的进程的列表*/

27structhlist_headtasks[PIDTYPE_MAX];

28intlevel;

29structupidnumbers[1];

30};

由于这两个结构与其他一些数据结构存在广泛的联系,在分别讨论相关结构之前,图2-5对此进行了概述。

对于structupidnr表示ID的数值,ns是指向该ID所属的命名空间的指针。所有的upid实例都保存在一个散列表中,稍后我们会看到该结构。pid_chain用内核的标准方法实现了散列溢出链表。

structpid的定义首先是一个引用计数器counttasks是一个数组,每个数组项都是一个散列表头,对应于一个ID类型。这样做是必要的,因为一个ID可能用于几个进程。所有共享同一给定IDtask_struct实例,都通过该列表连接起来。PIDTYPE_MAX表示ID类型的数目:

1<pid.h>

2enumpid_type

3{

4PIDTYPE_PID,

5PIDTYPE_PGID,

6PIDTYPE_SID,

7PIDTYPE_MAX

8};


2.3.3 进程ID号(2

请注意,枚举类型中定义的ID类型不包括线程组ID!这是因为线程组ID无非是线程组组长的PID而已,因此再单独定义一项是不必要的。

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

由于所有共享同一IDtask_struct实例都按进程存储在一个散列表中,因此需要在structtask_struct中增加一个散列表元素:

1<sched.h>

2structtask_struct{

3...

4/*PIDPID散列表的联系。*/

5structpid_linkpids[PIDTYPE_MAX];

6...

7};

辅助数据结构pid_link可以将task_struct连接到表头在structpid中的散列表上:

8<pid.h>

9structpid_link

10{

11structhlist_nodenode;

12structpid*pid;

13};

pid指向进程所属的pid结构实例,node用作散列表元素。

为在给定的命名空间中查找对应于指定PID数值的pid结构实例,使用了一个散列表:

14kernel/pid.c

15staticstructhlist_head*pid_hash;

hlist_head是一个内核的标准数据结构,用于建立双链散列表(附录C描述了该散列表的结构,并介绍了用于处理该数据结构的几个辅助函数)。

pid_hash用作一个hlist_head数组。数组的元素数目取决于计算机的内存配置,大约在24=16212=4096之间。pidhash_init用于计算恰当的容量并分配所需的内存。

假如已经分配了structpid的一个新实例,并设置用于给定的ID类型。它会如下附加到task_struct

16kernel/pid.c

17intfastcallattach_pid(structtask_struct*task,enumpid_typetype,

18structpid*pid)

19{

20structpid_link*link;

21

22link=&task->pids[type];

23link->pidpid=pid;

24hlist_add_head_rcu(&link->node,&pid->tasks[type]);

25

26return0;

27}

这里建立了双向连接:task_struct可以通过task_struct->pids[type]->pid访问pid实例。而从pid实例开始,可以遍历tasks[type]散列表找到task_structhlist_add_head_rcu是遍历散列表的标准函数,此外还确保了遵守RCU机制(参见第5章)。因为,在其他内核组件并发地操作散列表时,可防止竞态条件(racecondition)出现。

函数

内核提供了若干辅助函数,用于操作和扫描上面描述的数据结构。本质上内核必须完成下面两个不同的任务。

(1)给出局部数字ID和对应的命名空间,查找此二元组描述的task_struct

(2)给出task_structID类型、命名空间,取得命名空间局部的数字ID

我们首先专注于如何将task_struct实例变为数字ID。这个过程包含下面两个步骤。

(1)获得与task_struct关联的pid实例。辅助函数task_pidtask_tgidtask_pgrptask_session分别用于取得不同类型的ID。获取PID的实现很简单:

28<sched.h>

29staticinlinestructpid*task_pid(structtask_struct*task)

30{

31returntask->pids[PIDTYPE_PID].pid;

32}

获取TGID的做法类似,因为TGID不过是线程组组长的PID而已。只要将上述实现替换为task->group_leader->pids[PIDTYPE_PID].pid即可。

找出进程组ID则需要使用PIDTYPE_PGID作为数组索引,但该ID仍然需要从线程组组长的task_struct实例获取:

33<sched.h>

34staticinlinestructpid*task_pgrp(structtask_struct*task)

35{

36returntask->group_leader->pids[PIDTYPE_PGID].pid;

37}

(2)在获得pid实例之后,从structpidnumbers数组中的uid信息,即可获得数字ID

38kernel/pid.c

39pid_tpid_nr_ns(structpid*pid,structpid_namespace*ns)

40{

41structupid*upid;

42pid_tnr=0;

43

44if(pid&&ns->level<=pid->level){

45upid=&pid->numbers[ns->level];

46if(upid->ns==ns)

47nr=upid->nr;

48}

49returnnr;

50}

因为父命名空间可以看到子命名空间中的PID,反过来却不行,内核必须确保当前命名空间的level小于或等于产生局部PID的命名空间的level

同样重要的是要注意到,内核只需要关注产生全局PID。因为全局命名空间中所有其他ID类型都会映射到PID,因此不必生成诸如全局TGIDSID

除了在第2步使用的pid_nr_ns之外,内核还可以使用下列辅助函数:

pid_vnr返回该ID所属的命名空间所看到的局部PID

pid_nr则获取从init进程看到的全局PID

这两个函数都依赖于pid_nr_ns,并自动选择适当的level0用于获取全局PID,而pid->level则用于获取局部PID

内核提供了几个辅助函数,合并了前述步骤:

51kernel/pid.c

52pid_ttask_pid_nr_ns(structtask_struct
*tsk,structpid_namespace*ns)

53pid_ttask_tgid_nr_ns(structtask_struct
*tsk,structpid_namespace*ns)

54pid_ttask_pgrp_nr_ns(structtask_struct
*tsk,structpid_namespace*ns)

55pid_ttask_session_nr_ns(structtask_struct
*tsk,structpid_namespace*ns)

从函数名可以明显推断其语义,因此我们不再赘述。

2.3.3 进程ID号(3

现在我们把注意力转向内核如何将数字PID和命名空间转换为pid实例。同样需要下面两个步骤。

(1)给出进程的局部数字PID和关联的命名空间(这是PID的用户空间表示),为确定pid实例(这是PID的内核表示),内核必须采用标准的散列方案。首先,根据PID和命名空间指针计算在pid_hash数组中的索引,然后遍历散列表直至找到所要的元素。这是通过辅助函数find_pid_ns处理的:

1kernel/pid.c

2structpid*fastcallfind_pid_ns(intnr,
structpid_namespace*ns)

structupid的实例保存在散列表中,由于这些实例直接包含在structpid中,内核可以使用container_of机制(参见附录C)推断出所要的信息。

(2)pid_task取出pid->tasks[type]散列表中的第一个task_struct实例。

这两个步骤可以通过辅助函数find_task_by_pid_type_ns完成:

3kernel/pid.c

4structtask_struct*find_task_by_pid_type_ns(inttype,intnr,

5structpid_namespace*ns)

6{

7returnpid_task(find_pid_ns(nr,ns),type);

8}

一些简单一点的辅助函数基于最一般性的find_task_by_pid_type_ns

find_task_by_pid_ns(pid_tnr,structpid_namespace*ns)根据给出的数字PID和进程的命名空间来查找task_struct实例。

find_task_by_vpid(pid_tvnr)通过局部数字PID查找进程。

find_task_by_pid(pid_tnr)通过全局数字PID查找进程。

内核源代码中许多地方都需要find_task_by_pid,因为很多特定于进程的操作(例如,使用kill发送一个信号)都通过PID标识目标进程。

3.生成唯一的PID

除了管理PID之外,内核还负责提供机制来生成唯一的PID(尚未分配)。在这种情况下,可以忽略各种不同类型的PID之间的差别,因为按一般的UNIX观念,只需要为PID生成唯一的数值即可。所有其他的ID都可以派生自PID,在下文讨论forkclone时会看到这一点。在随后的几节中,名词PID还是指一般的UNIX进程IDPIDTYPE_PID)。

为跟踪已经分配和仍然可用的PID,内核使用一个大的位图,其中每个PID由一个比特标识。PID的值可通过对应比特在位图中的位置计算而来。

因此,分配一个空闲的PID,本质上就等同于寻找位图中第一个值为0的比特,接下来将该比特设置为1。反之,释放一个PID可通过将对应的比特从1切换为0来实现。这些操作使用下述两个函数实现:

9kernel/pid.c

10staticintalloc_pidmap(structpid_namespace*pid_ns)

用于分配一个PID,而

11kernel/pid.c

12staticfastcallvoidfree_pidmap(struct
pid_namespace*pid_ns,intpid)

用于释放一个PID。我们这里不关注具体的实现方式,但它们必须能够在命名空间下工作。

在建立一个新进程时,进程可能在多个命名空间中是可见的。对每个这样的命名空间,都需要生成一个局部PID。这是在alloc_pid中处理的:

13kernel/pid.c

14structpid*alloc_pid(structpid_namespace*ns)

15{

16structpid*pid;

17enumpid_typetype;

18inti,nr;

19structpid_namespace*tmp;

20structupid*upid;

21...

22tmp=ns;

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

24nr=alloc_pidmap(tmp);

25...

26pid->numbers[i].nr=nr;

27pid->numbers[i].ns=tmp;

28tmptmp=tmp->parent;

29}

30pid->level=ns->level;

31...

起始于建立进程的命名空间,一直到初始的全局命名空间,内核会为此间的每个命名空间分别创建一个局部PID。包含在structpid中的所有upid都用重新生成的PID更新其数据。每个upid实例都必须置于PID散列表中:

32

总结:关于进程ID号,主要是确定上面5个结构体中字段的意义和结构体之间的关系,在内核中参看具体的函数可以进一步理解他们之间的关系。

分享到:
评论

相关推荐

    得到进程ID号

    得到进程ID号

    根据进程ID获取进程的用户名

    根据进程ID号,获取进程的用户名,包括系统用户名,系统登录这用户名,LOCALSERVICE NETWORKSERVICE 都可以获取到

    获得父进程的ID

    通过黑巧妙的方法获得父进程ID号,在进程枚举中作用大!

    外部程序嵌入到Qt进程界面---附源码

    项目需要将一个外部软件...2)根据进程id号得到主窗口句柄:通常情况下一个进程内有多个窗口句柄,还需要从得到的N个句柄中找到主窗口句柄(下面会统一讲到); 3)将HWND转为WId,进而将外部程序嵌入QWindow、widget:

    Shell脚本中获取进程ID的方法

    提问: 我想要知道运行中脚本子shell的进程id。我该如何在shell脚本中得到PID。 当我在执行shell脚本时,它会启动一个叫子shell的进程。作为主shell的子进程,子shell将shell脚本中的命令作为批处理运行(因此称为...

    PK查看XP,2003进程,强力杀除进程

    PK -L 可以查看进程ID号 PK ID号可以杀掉进程

    枚举电脑所有进程的父进程, 判断父进程 根据进程号获取进程路径函数 按进程ID取父进程ID和路径th32ParentProces

    枚举电脑所有进程的父进程, 判断父进程 根据进程号获取进程路径函数 按进程ID取父进程ID和路径th32ParentProces

    根据端口号得到打开该端口号的进程ID

    根据端口号得到打开该端口号的进程ID。根据端口号得到打开该端口号的进程ID。

    Sqlserver分析死锁进程

    Sqlserver分析死锁进程,分析死锁的进程ID号

    操作系统优先权调度算法课程设计

    //进程id号 int pr; //进程优先级 int ct; //进程已经运行时间片个数 int at; //进程还需多少运行时间片个数 int bt; //进程需要阻塞的时间片的个数 int sb; //进程运行多少时间片后进入阻塞 char state;//w(就绪...

    IIS中查看W3WP.exe进程对应的应用程序池的方法

    先在Windows任务管理器中点击查看--选择列--选择PID(进程标识符),这样在进程中就会显示进程ID号。 IIS6中查看w3wp进程:

    进程内存写入实现模块隐藏

    用进程内存写入模块实现模块隐藏,输入进程ID号就能实现注入自身并隐藏。

    操作系统-进程调度算法.zip

    操作系统进程调度算法 先进先出FIFO、最高优先级HPF(非抢占式)、时间片轮转算法RR ...进程id 号、 进程状态(1 就绪 2 等待 3 运行) 、所需时间 、优先数(0 级 最高) 输出: 进程执行序列 和等待时间 平均等待时间

    AdbHelp_20201201.zip

    3,进程id号,进程名,cpu使用率获取,杀进程,线程总数获取,进程内存实时显示。 4,获取安卓设备上进程实时cpu使用率,进程名,线程名,支持性能数据保存成excel文件。 5,安卓设备上已安装应用列表展示,卸载应用...

    android_proc_进程信息解析

    如何查看ANDROID进程信息呢,可以先进入ADB SHELL,然后在PROC文件夹下,有很多对应进程ID号的子文件夹,进入对应的文件夹内,可以看到有以下信息,就可以查询到你的进程信息了。参数如下:

    2024年网络安全技能竞赛应急响应之取证日志文件

    4.黑客成功登录系统后重启过几次数据库服务,将最后一次重启数据库服务后数据库服务的进程ID号作为Flag值提交; 5.黑客成功登录系统后修改了登录用户的用户名并对系统执行了多次重启操作,将黑客使用修改后的用户...

    CreateToolhelp32SnapshotForModule.zip_进程模块

    CreateToolhelp32SnapshotForModule 输入您要获取模块的进程ID号 得到模块路径/名称

    进程创建模拟实验

    参数: 1 pid(进程id)、 2 ppid(父进程id)、3 prio(优先级)。 示例:createpc(2,1,2) 。创建一个进程,其进程号为2,父进程号为1,优先级为2。 showdetail 显示进程信息命令。 exit 退出命令行。

    获取Windows进程:VC++进程管理器

    当用户拖动程序最小化窗口时,获得某一时刻系统的进程、堆(heap)、模块(module)或线程的快照信息,获得系统进程链表中第一个进程的信息,把代表进程的ID号,存入数组中,便于中止,获得系统进程链表中下一个进程...

    Linux考试题目及答案

    当登录Linux时,一个具有唯一进程ID号的shell将被调用,这个ID是什么( B ) A. NID B. PID C. UID D. CID 4. 下面哪个命令是用来定义shell的全局变量( D ) A. exportfs B. alias C. exports D. export 5. ...

Global site tag (gtag.js) - Google Analytics