The Linux Process Principle, PID、PGID、PPID、SID、TID、TTY

您所在的位置:网站首页 cpid是啥 The Linux Process Principle, PID、PGID、PPID、SID、TID、TTY

The Linux Process Principle, PID、PGID、PPID、SID、TID、TTY

2024-07-18 03:29:27| 来源: 网络整理| 查看: 265

目录

0. 引言 1. Linux进程 2. Linux进程的相关标识 3. 进程标识编程示例 4. 进程标志在Linux内核中的存储和表现形式 5. 后记

0. 引言

在进行Linux主机的系统状态安全监控的过程中,我们常常会涉及到对系统进程信息的收集、聚类、分析等技术,因此,研究Linux进程原理能帮助我们更好的明确以下几个问题

1. 针对Linux的进程状态,需要监控捕获哪些维度的信息,哪些信息能够更好地为安全人员描绘出黑客的入侵迹象 2. 监控很容易造成的现象就是会有短时间内的大量数据产生(杂志数据过滤后),如何对收集的数据进行聚类,使之表现出一些更高维度的、相对有用的信息 3. 要实现数据的聚类,就需要从现在的元数据中找到一些"连结标识",这些"连结标识"就是我们能够将低纬度的数据聚类扩展到高纬度的技术基础。

本文的技术研究会围绕这几点进行Linux操作系统进程的基本原理研究

1. Linux进程

0x1: 进程的表示

进程属于操作系统的资源,因此进程相关的元数据都保存在内核态RING0中,Linux内核涉及进程和程序的所有算法都围绕task_struct数据结构建立,该结构定义在include/sched.h中,这是操作系统中主要的一个结构,task_struct包含很多成员,将进程与各个内核子系统联系起来,关于task_struct结构体的相关知识,请参阅另一篇文章

http://www.cnblogs.com/LittleHann/p/3865490.html

0x2: 进程的产生方式

Linux下新进程是使用fork和exec系统调用产生的

1. fork 生成当前进程的一个相同副本,该副本称之为"子进程"。原进程的所有资源都以适当的方式复制到子进程,因此执行了该系统调用之后,原来的进程就有了2个独立的实例,包括 1) 同一组打开文件 2) 同样的工作目录 3) 内存中同样的数据(2个进程各有一个副本) .. 2. exec 从一个可执行的二进制文件来加载另一个应用程序,来"代替"当前运行的进程,即加载了一个新的进程。因为exec并不创建新进程,搜易必须首先使用fork复制一个旧的程序,然后调用exec在系统上创建另一个应用程序 //总体来说:fork负责产生空间、exec负责载入实际的需要执行的程序

除此之外,Linux还提供了clone系统调用,clone的工作原理基本上和fork相同,所区别的是

1. 新进程不是独立于父进程,而是可以和父进程共享某些资源 2. 可以指定需要共享和复制的资源种类,例如 1) 父进程的内存数据 2) 打开文件或安装的信号处理程序

关于Linux下进程创建的相关知识,请参阅另一篇文章

http://www.cnblogs.com/LittleHann/p/3853854.html

0x3: 命名空间

命名空间提供了虚拟化的一种轻量级形式,使得我们可以从不同的方面来查看运行系统的全局属性,本质上,命名空间建立了系统的不同视图。未使用命名空间之前的每一项全局资源都必须包装到容器数据结构中,而资源和包含资源的命名空间构成的二元组仍然是全局唯一的。

从图中可以看到:

1. 对于父命名空间来说,全局的ID依然不变,而在子命名空间中,每个子命名空间都有自己的一套ID命名序列 2. 虽然子容器(子命名空间)不了解系统中的其他容器,但父容器知道子命名空间的存在,也可以看到其中执行的所有进程 3. 在Linux的这种层次结构的命名空间的架构下,一个进程可能拥有多个ID值,至于哪一个是"正确"的,则依赖于具体的上下文

命名空间机制是Linux的一个重要的技术,对命名空间的支持已经有很长的时间了,主要是chroot系统调用,该方法可以将进程限制到文件系统的某一部分,因此是一种简单的命名空间机制。但真正的命名空间能够控制的功能远远超过"文件系统视图"

Relevant Link:

深入linux内核架构(中文版).pdf 第2章

2. Linux进程的相关标识

以下是Linux和进程相关的标识ID值,我们先学习它们的基本概念,在下一节我们会学习到这些ID值间的关系、以及Linux是如何保存和组织它们的

0x1: PID(Process ID 进程 ID号)

Linux系统中总是会分配一个号码用于在其命名空间中唯一地标识它们,即进程ID号(PID),用fork或者cline产生的每个进程都由内核自动地分配一个新的唯一的PID值

值得注意的是,命名空间增加了PID管理的复杂性,PID命名空间按照层次组织,在建立一个新的命名空间时

1. 该命名空间中的所有PID对父命名空间都是可见的 2. 但子命名空间无法看到父命名空间的PID

这也就是意味着在"多层次命名空间"的状态下,进行具有多个PID,凡是可能看到该进程的的命名空间,都会为其分配一个PID,这种特征反映在了Linux的数据结构中,即局部ID、和全局ID

1. 全局ID 是在内核本身和"初始命名空间"中的唯一ID号,在系统启动期间开始的init进程即属于"初始命名空间"。对每个ID类型,都有一个给定的全局ID,保证在整个系统中是唯一的 2. 局部ID 属于某个特定的命名空间,不具备全局有效性。对每个ID类型,它们"只能"在所属的命名空间内部有效

0x2: TGID(Thread Group ID 线程组 ID号)

处于某个线程组(在一个进程中,通过标志CLONE_THREAD来调用clone建立的该进程的不同的执行上下文)中的所有进程都有统一的线程组ID(TGID)

如果进程没有使用线程,则它的PID和TGID相同

线程组中的"主线程"(Linux中线程也是进程)被称作"组长(group leader)",通过clone创建的所有线程的task_struct的group_leader成员,都会指向组长的task_struct。

在Linux系统中,一个线程组中的所有线程使用和该线程组的领头线程(该组中的第一个轻量级进程)相同的PID,并被存放在tgid成员中。只有线程组的领头线程的pid成员才会被设置为与tgid相同的值。注意,getpid()系统调用

返回的是当前进程的tgid值而不是pid值。

梳理一下这段概念,我们可以这么理解

1. 对于一个多线程的进程来说,它实际上是一个进程组,每个线程在调用getpid()时获取到的是自己的tgid值,而线程组领头的那个领头线程的pid和tgid是相同的 2. 对于独立进程,即没有使用线程的进程来说,它只有唯一一个线程,领头线程,所以它调用getpid()获取到的值就是它的pid

0x3: PGID(Process Group ID 进程组 ID号)

了解了进行ID、线程组(就是单线程下的进程)ID之后,我们继续学习"进程组ID",可以看出,Linux就是在将做原子的因素不断组合成更大的集合。

每个进程都会属于一个进程组(process group),每个进程组中可以包含多个进程。进程组会有一个进程组领导进程 (process group leader),领导进程的PID成为进程组的ID (process group ID, PGID),以识别进程组。

图中箭头表示父进程通过fork和exec机制产生子进程。ps和cat都是bash的子进程。进程组的领导进程的PID成为进程组ID。领导进程可以先终结。此时进程组依然存在,并持有相同的PGID,直到进程组中最后一个进程终结

进程组简化了向组内的所有成员发送信号的操作,进程组中的所有进程都会收到该信号,例如,用管道连接的进程包含在同一个进程组中(管道的原理就是在创建2个子进程)

或者输入pgrp也可以,pgrp和pgid是等价的

0x4: PPID( Parent process ID 父进程 ID号)

PPID是当前进程的父进程的PID

ps -o pid,pgid,ppid,comm | cat

因为ps、cat都是由bash启动的,所以它们的ppid都等于bash进程的pid

0x5: SID(Session ID 会话ID)

更进一步,在shell支持工作控制(job control)的前提下,多个进程组还可以构成一个会话 (session)。bash(Bourne-Again shell)支持工作控制,而sh(Bourne shell)并不支持

1. 每个会话有1个或多个进程组组成,可能有一个领头进程((session leader)),也可能没有 2. 会话领导进程的PID成为识别会话的SID(session ID) 3. 会话中的每个进程组称为一个工作(job) 4. 会话可以有一个进程组成为会话的前台工作(foreground),而其他的进程组是后台工作(background) 5. 每个会话可以连接一个控制终端(control terminal)。当控制终端有输入输出时,都传递给该会话的前台进程组。由终端产生的信号,比如CTRL+Z, CTRL+\,会传递到前台进程组。 6. 会话的意义在于将多个job(进程组)囊括在一个终端,并取其中的一个job(进程组)作为前台,来直接接收该终端的输入输出以及终端信号。 其他工作在后台运行

一个命令可以通过在末尾加上&方式让它在后台运行:

$ping localhost > log & [1] 10141 //括号中的1表示工作号,而10141为PGID

信号可以通过kill的方式来发送给工作组

1. $kill -SIGTERM -10141 //发送给PGID(通过在PGID前面加-来表示是一个PGID而不是PID) 2. $kill -SIGTERM %1 //发送给工作1(%1)

一个工作可以通过$fg从后台工作变为前台工作:

$cat > log & $fg %1 //当我们运行第一个命令后,由于工作在后台,我们无法对命令进行输入,直到我们将工作带入前台,才能向cat命令输入。在输入完成后,按下CTRL+D来通知shell输入结束

进程组(工作)的概念较为简单易懂。而会话主要是针对一个终端建立的。当我们打开多个终端窗口时,实际上就创建了多个终端会话。每个会话都会有自己的前台工作和后台工作。这样,我们就为进程增加了管理和运行的层次

Relevant Link:

http://www.cnblogs.com/vamei/archive/2012/10/07/2713023.html http://blog.csdn.net/zmxiangde_88/article/details/8027431

3. 进程标识编程示例

了解了进程标识的基本概念之后,接下来我们通过API编程方式来直观地了解下

0x1: 父子进程、组长进程和组员进程的关系

#include #include #include int main() { pid_t pid; /* 计算机程序设计中的分叉函数。返回值,若成功调用一次则返回两个值1 1. 子进程返回0 2. 父进程返回子进程标记 3. 否则,出错返回-1 */ if ((pid = fork())//child process printf("The Child Process PID is %d.\n", getpid()); printf("The Parent Process PPID is %d.\n", getppid()); printf("The Group ID PGID is %d.\n", getpgrp()); printf("The Group ID PGID is %d.\n", getpgid(0)); printf("The Group ID PGID is %d.\n", getpgid(getpid())); printf("The Session ID SID is %d.\n", getsid()); exit(0); } printf("\n\n\n"); //parent process sleep(3); printf("The Parent Process PID is %d.\n", getpid()); printf("The Group ID PGID is %d.\n", getpgrp()); printf("The Group ID PGID is %d.\n", getpgid(0)); printf("The Group ID PGID is %d.\n", getpgid(getpid())); printf("The Session ID SID is %d.\n", getsid()); return 0; }

从运行结果来看,我们可以得出几个结论

1. 组长进程 1) 组长进程标识: 其进程组ID == 其进程ID    2) 组长进程可以创建一个进程组,创建该进程组中的进程,然后终止    3) 只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关    4) 进程组生存期: 进程组创建到最后一个进程离开(终止或转移到另一个进程组) 2. 进程组id == 父进程id,即父进程为组长进程

0x2: 进程组更改

我们继续拿fork()产生父子进程的这个code example作为示例,因为正常情况下,父子进程具有相同的PGID,这个代码场景能帮助我们更好地去了解PGID的相关知识

#include #include #include int main() { pid_t pid; if ((pid=fork())//child process printf("The child process PID is %d.\n",getpid()); printf("The Group ID of child is %d.\n",getpgid(0)); printf("The Session ID of child is %d.\n",getsid(0)); setsid(); /* 子进程非组长进程 1. 故其成为新会话首进程(sessson leader) 2. 且成为组长进程(group leader) 所以 1. pgid = pid_child 2. sid = pid_child */ printf("Changed:\n"); printf("The child process PID is %d.\n",getpid()); printf("The Group ID of child is %d.\n",getpgid(0)); printf("The Session ID of child is %d.\n",getsid(0)); exit(0); } return 0; }

从程序的运行结果可以得到如下结论

1. 会话: 一个或多个进程组的集合 1) 开始于用户登录 2) 终止与用户退出    3) 此期间所有进程都属于这个会话期 2. 建立新会话: setsid()函数 1) 该调用进程是组长进程,则出错返回    2) 该调用进程不是组长进程,则创建一个新会话 2.1) 先调用fork父进程终止,子进程调用 2.2) 该进程变成新会话首进程(领头进程)(session header) 2.3) 该进程成为一个新进程组的组长进程。      2.4) 该进程没有控制终端,如果之前有,则会被中断 3) 组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程

下面这张图对整个PID、PGID、SID的关系做了一个梳理

Relevant Link:

http://www.cnblogs.com/nysanier/archive/2011/03/10/1979321.html http://www.cnblogs.com/forstudy/archive/2012/04/03/2427683.html

4. 进程标志在Linux内核中的存储和表现形式

了解了Linux中进程标识的基本概念和API编程方式后,我们接下来继续研究一下Linux在内核中是如何去存储、组织、表现这些标识的

在task_struct中,和进程标识ID相关的域有

struct task_struct { ... pid_t pid; pid_t tgid; struct task_struct *group_leader; struct pid_link pids[PIDTYPE_MAX]; struct nsproxy *nsproxy; ... };

如果显示不完整,请另存到本地看

0x1: Linux内核Hash表

要谈Linux内核中进程标识的存储和组织,我们首先要了解Linux内核的Hash表机制,在内核中,查找是必不可少的,例如

1. 内核管理这么多用户进程,现在要快速定位某一个进程,这儿需要查找 2. 一个进程的地址空间中有多个虚存区,内核要快速定位进程地址空间的某个虚存区,这儿也需要查找 ..

查找技术属于数据结构算法的范畴,常用的查找算法有如下几种

1. 基于树的查找: 红黑树 2. 基于计算的查找: 哈希查找 //两者(基于树、基于计算)的查找的效率高,而且适应内核的情况 3. 基于线性表的查找: 二分查找 //尽管效率高但不能适应内核里面的情况,现在版本的内核几乎不可能使用数组管理一些数据

本文主要学习的进程标识的存储和查找就是基于计算的HASH表查找方式

0x2: Linux pid_hash散列表

在内核中,经常需要通过进程PID来获取进程描述符,例如

kill命令: 最简单的方法可以通过遍历task_struct链表并对比pid的值来获取,但这样效率太低,尤其当系统中运行很多个进程的时候

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

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

//Hash表的类型 字段名 说明 1. PIDTYPE_PID pid 进程的PID 2. PIDTYPE_TGID tgid 线程组领头进程的PID 3. PIDTYPE_PGID pgrp 进程组领头进程的PID 4. PIDTYPE_SID session 会话领头进程的PID

0x3: 进程标识在内核中的存储

一个PID只对应着一个进程,但是一个PGID,TGID和SID可能对应着多个进程,所以在pid结构体中,把拥有同样PID(广义的PID)的进程放进名为tasks的成员表示的数组中,当然,不同类型的ID放在相应的数组元素中。

考虑下面四个进程:

1. 进程A: PID=12345, PGID=12344, SID=12300 2. 进程B: PID=12344, PGID=12344, SID=12300,它是进程组12344的组长进程 3. 进程C: PID=12346, PGID=12344, SID=12300 4. 进程D: PID=12347, PGID=12347, SID=12300

分别用task_a, task_b, task_c和task_d表示它们的task_struct,则它们之间的联系是:

1. task_a.pids[PIDTYPE_PGID].pid.tasks[PIDTYPE_PGID]指向有进程A-B-C构成的列表 2. task_a.pids[PIDTYPE_SID].pid.tasks[PIDTYPE_SID]指向有进程A-B-C-D构成的列表

内核初始化期间动态地为4个散列表分配空间,并把它们的地址存入pid_hash数组(就是struct->pids[PIDTYPE_MAX]中)

0x4: 进程标识ID在内核中的表示和使用

内核用pid_hashfn宏把PID转换为表索引

kernel/pid.c

#define pid_hashfn(nr, ns) \ hash_long((unsigned long)nr + (unsigned long)ns, pidhash_shift)

这个宏就负责把一个PID转换为一个index,我们继续跟进hash_long这个函数

\include\linux\hash.h

/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */ #define GOLDEN_RATIO_PRIME_32 0x9e370001UL /* 2^63 + 2^61 - 2^57 + 2^54 - 2^51 - 2^18 + 1 */ #define GOLDEN_RATIO_PRIME_64 0x9e37fffffffc0001UL #if BITS_PER_LONG == 32 #define GOLDEN_RATIO_PRIME GOLDEN_RATIO_PRIME_32 #define hash_long(val, bits) hash_32(val, bits) #elif BITS_PER_LONG == 64 #define hash_long(val, bits) hash_64(val, bits) #define GOLDEN_RATIO_PRIME GOLDEN_RATIO_PRIME_64 #else #error Wordsize not 32 or 64 #endif static inline u64 hash_64(u64 val, unsigned int bits) { u64 hash = val; /* Sigh, gcc can't optimise this alone like it does for 32 bits. */ u64 n = hash; n


【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


    图片新闻

    实验室药品柜的特性有哪些
    实验室药品柜是实验室家具的重要组成部分之一,主要
    小学科学实验中有哪些教学
    计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
    实验室各种仪器原理动图讲
    1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
    高中化学常见仪器及实验装
    1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
    微生物操作主要设备和器具
    今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
    浅谈通风柜使用基本常识
     众所周知,通风柜功能中最主要的就是排气功能。在

    专题文章

      CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭