cgroups 是Linux内核提供的一种可以限制单个进程或者多个进程所使用资源的机制,可以对 cpu,内存等资源实现精细化的控制,目前越来越火的轻量级容器 Docker 就使用了 cgroups 提供的资源限制能力来完成cpu,内存等部分的资源控制。
另外,开发者也可以使用 cgroups 提供的精细化控制能力,限制某一个或者某一组进程的资源使用。比如在一个既部署了前端 web 服务,也部署了后端计算模块的八核服务器上,可以使用 cgroups 限制 web server 仅可以使用其中的六个核,把剩下的两个核留给后端计算模块。
文章目录
cgroups子系统
cgroups 的全称是control groups,cgroups为每种可以控制的资源定义了一个子系统
。典型的子系统介绍如下:
- cpu 子系统,主要限制进程的 cpu 使用率。
- cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告。
- cpuset 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点。
- memory 子系统,可以限制进程的 memory 使用量。
- blkio 子系统,可以限制进程的块设备 io。
- devices 子系统,可以控制进程能够访问某些设备。
- net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制。
- freezer 子系统,可以挂起或者恢复 cgroups 中的进程。
- ns 子系统,可以使不同 cgroups 下面的进程使用不同的 namespace。
这里面每一个子系统都需要与内核的其他模块配合来完成资源的控制,比如对 cpu 资源的限制是通过进程调度模块根据 cpu 子系统的配置来完成的
;对内存资源的限制则是内存模块根据 memory 子系统的配置来完成的
,而对网络数据包的控制则需要 Traffic Control 子系统来配合完成
。
cgroups层级结构
内核使用 cgroup 结构体
来表示一个 control group 对某一个或者某几个
cgroups 子系统的资源限制(也就是一个cgroup层级下面可以 attach,或者说管理多个维度的 子系统)。
cgroup 结构体可以组织成一颗树的形式,每一棵cgroup 结构体组成的树称之为一个 cgroups 层级结构
。cgroups层级结构可以 attach 一个或者几个 cgroups 子系统,当前层级结构可以对其 attach 的 cgroups 子系统进行资源的限制。每一个 cgroups 子系统只能被 attach 到一个 cpu 层级结构中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KXLxzLYv-1594865228400)(/Users/lvchentao/Desktop/cloudPoint/学习随笔/docker-张晋涛/b3270d03.png)]
比如上图表示两个cgroups层级结构,每一个层级结构中是一颗树形结构,树的每一个节点是一个 cgroup 结构体
(比如cpu_cgrp, memory_cgrp)。第一个 cgroups 层级结构 attach 了 cpu 子系统和 cpuacct 子系统
, 当前 cgroups 层级结构中的 cgroup 结构体就可以对 cpu 的资源进行限制,并且对进程的 cpu 使用情况进行统计。 第二个 cgroups 层级结构 attach 了 memory 子系统,当前 cgroups 层级结构中的 cgroup 结构体就可以对 memory 的资源进行限制。
在每一个 cgroups 层级结构中,每一个节点(cgroup 结构体)可以设置对资源不同的限制权重
。比如上图中 cgrp1 组中的进程可以使用60%的 cpu 时间片,而 cgrp2 组中的进程可以使用20%的 cpu 时间片。
每一个层级结构下,可以有多级的子目录,每个子目录继承上一级 cgroup的默认设置,但是可以重写一些设置,比如cpu所在的层级下有 pod1,pod2两个子目录,每个子目录可以单独设置自己的控制组信息,比如cpu60%,cpu20%这种,然后在每个子组下控制自己的tasks
一个已经附加在某个hierarchy之上的 subSystem,无法附加在其他的hierarchy之上
也就是cpu已经在某个路径下了,无法再附加在别的路径下
而某个task(进程)也不能在同一个hierarchy下的不同cgroup。很好理解,这是为了避免冲突,因为不同的cgroup下 可能对同一个资源的 限制都不同。
fork出来的子进程在初始状态和父进程处于同一个cgroup
所以:一个进程,可以加入不同的hierarchy下面的不同cgroup中,同样,一个cgroup可以包含多个task,所以进程和cgroup的关系是 多对多
上面这个图从整体结构上描述了进程与 cgroups 之间的关系。最下面的P
代表一个进程。每一个进程的描述符中有一个指针指向了一个辅助数据结构css_set
(cgroups subsystem set)。 指向某一个css_set
的进程会被加入到当前css_set
的进程链表中。一个进程只能隶属于一个css_set
,一个css_set
可以包含多个进程,隶属于同一css_set
的进程受到同一个css_set
所关联的资源限制。
上图中的”M×N Linkage”说明的是css_set
通过辅助数据结构可以与 cgroups 节点进行多对多的关联。但是 cgroups 的实现不允许css_set
同时关联同一个cgroups层级结构下多个节点(比如同时关联到图中的/cgrp1和/cgrp2)。 这是因为 cgroups 对同一种资源不允许有多个限制配置。
一个css_set
关联多个 cgroups 层级结构的节点时,表明需要对当前css_set
下的进程进行多种资源的控制。而一个 cgroups 节点关联多个css_set
时,表明多个css_set
下的进程列表受到同一份资源的相同限制。
图中的MxN Linkage 可以类比做 数据库中的多对多关系表?
每个css_set其实是一个 子系统的集合,通过这个集合和 进程建立了一对多的关系(正因为是子系统集合和进程是一对多关系,所以子系统和进程是 多对多关系)
每一个css_set 可以看做是不同的子系统的集合(对于那些使用了具体的某些子系统 资源限定的process来说,他们就属于 这些特定子系统组成的css_set)
cgroups文件系统
inux 使用了多种数据结构在内核中实现了 cgroups 的配置,关联了进程和 cgroups 节点,那么 Linux 又是如何让用户态的进程使用到 cgroups 的功能呢? Linux内核有一个很强大的模块叫 VFS (Virtual File System)。 VFS 能够把具体文件系统的细节隐藏起来,给用户态进程提供一个统一的文件系统 API 接口。 cgroups 也是通过 VFS 把功能暴露给用户态的,cgroups 与 VFS 之间的衔接部分称之为 cgroups 文件系统。下面先介绍一下 VFS 的基础知识,然后再介绍下 cgroups 文件系统的实现。
VFS
VFS 是一个内核抽象层,能够隐藏具体文件系统的实现细节,从而给用户态进程提供一套统一的 API 接口。VFS 使用了一种通用文件系统的设计,具体的文件系统只要实现了 VFS 的设计接口,就能够注册到 VFS 中,从而使内核可以读写这种文件系统。 这很像面向对象设计中的抽象类与子类之间的关系,抽象类负责对外接口的设计,子类负责具体的实现。其实,VFS本身就是用 c 语言实现的一套面向对象的接口。
可以这么猜测或者理解:
对于用户态进程来说,使用VFS提供的接口方法即可使用 操作文件系统的方式来写常规文件,socket文件或者操作cgroup文件,而这些文件实现的差异被内核屏蔽,他们内部通过不同的file_operations 去做不同的实现,这对于用户态进程无需关心。
通用文件模型
VFS 通用文件模型中包含以下四种元数据结构:
- 超级块对象(superblock object),用于存放已经注册的文件系统的信息。比如ext2,ext3等这些基础的磁盘文件系统,还有用于读写socket的socket文件系统,以及当前的用于读写cgroups配置信息的 cgroups 文件系统等。
- 索引节点对象(inode object),用于存放具体文件的信息。对于一般的磁盘文件系统而言,
inode 节点中一般会存放文件在硬盘中的存储块等信息(就是这个文件存储在那些block中?)
;对于socket文件系统,inode会存放socket的相关属性,而对于cgroups这样的特殊文件系统,inode会存放与 cgroup 节点相关的属性信息。这里面比较重要的一个部分是一个叫做 inode_operations 的结构体,这个结构体定义了在具体文件系统中创建文件,删除文件等的具体实现。 - 文件对象(file object),一个文件对象表示
进程内打开的一个文件
,文件对象是存放在进程的文件描述符表里面的。同样这个文件中比较重要的部分是一个叫 file_operations 的结构体,这个结构体描述了具体的文件系统的读写实现。当进程在某一个文件描述符上调用读写操作时,实际调用的是 file_operations 中定义的方法。 对于普通的磁盘文件系统,file_operations 中定义的就是普通的块设备读写操作;对于socket文件系统,file_operations 中定义的就是 socket 对应的 send/recv 等操作;而对于cgroups这样的特殊文件系统,file_operations 中定义的就是操作 cgroup 结构体等具体的实现。 - 目录项对象(dentry object),在每个文件系统中,内核在查找某一个路径中的文件时,会为内核路径上的每一个分量都生成一个目录项对象,通过目录项对象能够找到对应的 inode 对象,目录项对象一般会被缓存,从而提高内核查找速度。
这些数据结构其实都保存的是跟文件数据无关的数据,要么是文件的元数据,要么是对特定类型文件的一些file_operations结构体
基于 VFS 实现的文件系统,都必须实现 VFS 通用文件模型定义的这些对象,并实现这些对象中定义的部分函数。cgroup 文件系统也不例外,下面来看一下 cgroups 中这些对象的定义。
首先看一下 cgroups 文件系统类型的结构体:
static struct file_system_type cgroup_fs_type = {
.name = "cgroup",
.mount = cgroup_mount,
.kill_sb = cgroup_kill_sb,
};
这里面两个函数分别代表安装和卸载某一个 cgroup 文件系统所需要执行的函数。每次把某一个 cgroups 子系统安装到某一个装载点的时候,cgroup_mount 方法就会被调用,这个方法会生成一个 cgroups_root(cgroups层级结构的根)并封装成超级快对象。
然后看一下 cgroups 超级块对象定义的操作:
static const struct super_operations cgroup_ops = {
.statfs = simple_statfs,
.drop_inode = generic_delete_inode,
.show_options = cgroup_show_options,
.remount_fs = cgroup_remount,
};
这里只有部分函数的实现,这是因为对于特定的文件系统而言,所支持的操作可能仅是 super_operations 中所定义操作的一个子集,比如说对于块设备上的文件对象,肯定是支持类似 fseek 的查找某个位置的操作,但是对于 socket 或者 cgroups 这样特殊的文件系统,就不支持这样的操作。
同样简单看下 cgroups 文件系统对 inode 对象和 file 对象定义的特殊实现函数:
static const struct inode_operations cgroup_dir_inode_operations = {
.lookup = cgroup_lookup,
.mkdir = cgroup_mkdir,
.rmdir = cgroup_rmdir,
.rename = cgroup_rename,
};
static const struct file_operations cgroup_file_operations = {
.read = cgroup_file_read,
.write = cgroup_file_write,
.llseek = generic_file_llseek,
.open = cgroup_file_open,
.release = cgroup_file_release,
};
本文并不去研究这些函数的代码实现是什么样的,但是从这些代码可以推断出,cgroups 通过实现 VFS 的通用文件系统模型,把维护 cgroups 层级结构的细节,隐藏在 cgroups 文件系统的这些实现函数中。
从另一个方面说,用户在用户态对 cgroups 文件系统的操作,通过 VFS 转化为对 cgroups 层级结构的维护。通过这样的方式,内核把 cgroups 的功能暴露给了用户态的进程。
cgroups使用方法
cgroups文件系统挂载
Linux中,用户可以使用mount命令挂载 cgroups 文件系统,格式为: mount -t cgroup -o subsystems name /cgroup/name
,其中 subsystems 表示需要挂载的 cgroups 子系统, /cgroup/name 表示挂载点,如上文所提,这条命令同时在内核中创建了一个cgroups 层级结构。
比如挂载 cpuset, cpu, cpuacct, memory 4个subsystem到/cgroup/cpu_and_mem 目录下,就可以使用 mount -t cgroup -o remount,cpu,cpuset,memory cpu_and_mem /cgroup/cpu_and_mem
(cpu_and_mem
是给这个hierarchy起的名字)
在centos下面,在使用yum install libcgroup
安装了cgroups模块之后,在 /etc/cgconfig.conf 文件中会自动生成 cgroups 子系统的挂载点:
mount {
cpuset = /cgroup/cpuset;
cpu = /cgroup/cpu;
cpuacct = /cgroup/cpuacct;
memory = /cgroup/memory;
devices = /cgroup/devices;
freezer = /cgroup/freezer;
net_cls = /cgroup/net_cls;
blkio = /cgroup/blkio;
}
上面的每一条配置都等价于展开的 mount 命令,例如mount -t cgroup -o cpuset cpuset /cgroup/cpuset
。这样系统启动之后会自动把这些子系统挂载到相应的挂载点上。
子节点和进程
挂载某一个 cgroups 子系统到挂载点之后,就可以通过在挂载点下面建立文件夹或者使用cgcreate命令的方法创建 cgroups 层级结构中的节点。比如通过命令cgcreate -t sankuai:sankuai -g cpu:test
就可以在 cpu 子系统下建立一个名为 test 的节点。结果如下所示:
[root@idx cpu]# ls
cgroup.event_control cgroup.procs cpu.cfs_period_us cpu.cfs_quota_us cpu.rt_period_us cpu.rt_runtime_us cpu.shares cpu.stat lxc notify_on_release release_agent tasks test
然后可以通过写入需要的值到 test 下面的不同文件,来配置需要限制的资源。每个子系统下面都可以进行多种不同的配置,需要配置的参数各不相同,详细的参数设置需要参考 cgroups 手册。使用 cgset 命令也可以设置 cgroups 子系统的参数,格式为 cgset -r parameter=value path_to_cgroup
。
当需要删除某一个 cgroups 节点的时候,可以使用 cgdelete 命令,比如要删除上述的 test 节点,可以使用 cgdelete -r cpu:test
命令进行删除
把进程加入到 cgroups 子节点也有多种方法,可以直接把 pid 写入到子节点下面的 task 文件中。也可以通过 cgclassify 添加进程,格式为 cgclassify -g subsystems:path_to_cgroup pidlist
,也可以直接使用 cgexec 在某一个 cgroups 下启动进程,格式为gexec -g subsystems:path_to_cgroup command arguments
.
cgroup driver
当 Linux 的 init 系统发展到 systemd 之后,systemd 与 cgroups 发生了融合(或者说 systemd 提供了 cgroups 的使用和管理接口
Systemd 依赖 cgroups
要理解 systemd 与 cgroups 的关系,我们需要先区分 cgroups 的两个方面:层级结构(A)和资源控制(B)。首先 cgroups 是以层级结构组织并标识进程的一种方式,同时它也是在该层级结构上执行资源限制的一种方式。
我们简单的把 cgroups 的层级结构称为 A,把 cgrpups 的资源控制能力称为 B
。
对于 systemd 来说,A 是必须的,如果没有 A,systemd 将不能很好的工作。而 B 则是可选的,如果你不需要对资源进行控制,那么在编译 Linux 内核时完全可以去掉 B 相关的编译选项。
Systemd 默认挂载的 cgroups 系统
在系统的开机阶段,systemd 会把支持的 controllers (subsystem 子系统)挂载到默认的 /sys/fs/cgroup/ 目录下面:
除了 systemd 目录外,其它目录都是对应的 subsystem。
/sys/fs/cgroup/systemd 目录是 systemd 维护的自己使用的非 subsystem
的 cgroups 层级结构。
这玩意儿是 systemd 自己使用的,换句话说就是,并不允许其它的程序动这个目录下的内容。其实 /sys/fs/cgroup/systemd 目录对应的 cgroups 层级结构就是 systemd 用来使用 cgoups 中 feature A 的。
/sys/fs/cgroup/systemd 只是systemd 创建的cgroups hierarchy,但是不具备资源控制能力
Cgroup 的默认层级
通过将 cgroup 层级系统与 systemd unit 树绑定,systemd 可以把资源管理的设置从进程级别移至应用程序级别。因此,我们可以使用 systemctl 指令,或者通过修改 systemd unit 的配置文件来管理 unit 相关的资源
。
也就是说,通过systemd 管理的进程或者程序,都可以用systemctl 来管理他们的cgroup
而且systemd 下面的进程,都会自己有单独的hierarchy挂载
默认情况下,systemd 会自动创建 slice、scope 和 service unit 的层级
(slice、scope 和 service 都是 systemd 的 unit 类型,参考《初识 systemd》),来为 cgroup 树提供统一的层级结构(就是为systemd Unit提供自己的hierarchy,默认每一个service都会有自己的层级?
)。
系统中运行的所有进程,都是 systemd init 进程的子进程。在资源管控方面,systemd 提供了三种 unit 类型:
- service:一个或一组进程,由 systemd 依据 unit 配置文件启动。service 对指定进程进行封装,这样进程可以作为一个整体被启动或终止。
- scope:一组外部创建的进程。由进程通过 fork() 函数启动和终止、之后被 systemd 在运行时注册的进程,scope 会将其封装。例如:
用户会话、 容器和虚拟机被认为是 scope
。 - slice:一组按层级排列的 unit。slice 并不包含进程,但会组建一个层级,并将 scope 和 service 都放置其中。真正的进程包含在 scope 或 service 中。在这一被划分层级的树中,每一个 slice 单位的名字对应通向层级中一个位置的路径。(是包括一组service的上级)
我们可以通过 systemd-cgls
命令来查看 cgroups 的层级结构:
service、scope 和 slice unit 被直接映射到 cgroup 树中的对象。当这些 unit 被激活时,它们会直接一一映射到由 unit 名建立的 cgroup 路径中。例如,cron.service 属于 system.slice,会直接映射到 cgroup system.slice/cron.service/ 中。
注意,所有的用户会话、虚拟机和容器进程会被自动放置在一个单独的 scope 单元中。
默认情况下,系统会创建四种 slice:
- -.slice:根 slice
- system.slice:所有系统 service 的默认位置
- user.slice:所有用户会话的默认位置
- machine.slice:所有虚拟机和 Linux 容器的默认位置
对于使用systemd创建的cgroup层次结构,其名字为docker.slice,kubepods.slice
而使用cgroupfs创建的,则只是为Kubepods,docker这种
这里的scope也是一个systemd的unit,只是代表非systemd创建的进程,比如容器内的kube-proxy进程就不是systemd创建的
而且看上去,即便是cgroupfs 创建cgroup,也同样会在/sys/fs/cgroup/systemd/ 下面创建docker/ 或者kubepods 相关的路径
应该是为了systemd进行进程管理
使用systemd来完成cgroup资源限制:
对资源管理的设置可以是 transient(临时的),也可以是 persistent (永久的)。我们先来介绍如何创建临时的 cgroup。
需要使用 systemd-run
命令创建临时的 cgroup,它可以创建并启动临时的 service 或 scope unit
,并在此 unit 中运行程序。systemd-run 命令默认创建 service 类型的 unit,比如我们创建名称为 toptest 的 service 运行 top 命令:
systemd-run --unit=toptest --slice=test top -b
#然后查看一下 test.slice 的状态:
systemctl status test.slice
● test.slice
Loaded: loaded
Active: active since Wed 2020-07-08 14:25:25 CST; 18min ago
Memory: 2.2M
CGroup: /test.slice
└─toptest.service
└─1577 /usr/bin/top -b
#创建了一个 test.slice/toptest.service cgroup 层级关系。再看看 toptest.service 的状态:
systemctl status toptest.service
● toptest.service - /usr/bin/top -b
Loaded: loaded (/run/systemd/system/toptest.service; static; vendor preset: disabled)
Drop-In: /run/systemd/system/toptest.service.d
└─50-CPUShares.conf, 50-Description.conf, 50-ExecStart.conf, 50-MemoryLimit.conf, 50-Slice.conf
Active: active (running) since Wed 2020-07-08 14:25:25 CST; 19min ago
Main PID: 1577 (top)
Memory: 344.0K (limit: 500.0M)
CGroup: /test.slice/toptest.service
└─1577 /usr/bin/top -b
# 可以看到,top命令被封装为一个Service运行在后台
# 接下来我们就可以通过 systemctl 命令来限制 toptest.service 的资源了。在限制前让我们先来看一看 top 进程的 cgroup 信息
cat /proc/1577/cgroup
12:rdma:/
11:memory:/test.slice/
10:pids:/
9:blkio:/test.slice
8:freezer:/
7:net_cls,net_prio:/
6:hugetlb:/
5:perf_event:/
4:cpuset:/
3:devices:/test.slice
2:cpu,cpuacct:/test.slice
1:name=systemd:/test.slice
0::/
#我们限制 toptest.service 的 CPUShares 为 600,可用内存的上限为 550M:
systemctl set-property toptest.service CPUShares=600 MemoryLimit=500M
# 再次检查 top 进程的 cgroup 信息:
cat /proc/1577/cgroup
12:rdma:/
11:memory:/test.slice/toptest.service
10:pids:/
9:blkio:/test.slice
8:freezer:/
7:net_cls,net_prio:/
6:hugetlb:/
5:perf_event:/
4:cpuset:/
3:devices:/test.slice
2:cpu,cpuacct:/test.slice/toptest.service
1:name=systemd:/test.slice/toptest.service
0::/
#在 CPU 和 memory 子系统中都出现了 toptest.service 的名字。同时去查看 /sys/fs/cgroup/memory/test.slice 和 /sys/fs/cgroup/cpu/test.slice 目录,这两个目录下都多出了一个 toptest.service 目录。我们设置的 CPUShares=600 MemoryLimit=500M 被分别写入了这些目录下的对应文件中。
# 可以看到,这些对cpu的资源限制仍然是挂载到了 cpu这个subsystem
cat /sys/fs/cgroup/cpu/test.slice/toptest.service/cpu.shares
600
cat /sys/fs/cgroup/systemd/test.slice/toptest.service/cgroup.procs
1577
cat /sys/fs/cgroup/cpu/test.slice/toptest.service/cgroup.procs
1577
通过以上systemd 创建的cgroup 流程可以看出,一旦通过systemd 方式给service添加资源限制后,其会在对应的 cpu,memory等subSystem下创建对应的控制组
至于为何 docker,kubelet 我们使用了cgroupfs 驱动后,我们通过
systemd-cgls
会看到仍然能看到在/sys/fs/cgroup/systemd/下 有对应的docker,kubepod等路径
,这应该是kublet进程做的,将进程纳入到systemd这个层级结构管理,是为了让systemd可以管理容器产生的子进程systemd 利用了 Linux 内核的特性即 cgroups 来完成跟踪的任务。当停止服务时,通过查询 cgroups ,systemd 可以确保找到所有的相关进程,从而干净地停止服务。(也就是为了跟踪 使用systemd/cgroup下面的)
cgroups 已经出现了很久,它主要用来实现系统资源配额管理。cgroups 提供了类似文件系统的接口,使用方便。当进程创建子进程时,子进程会继承父进程的 cgroups 。因此无论服务如何启动新的子进程,所有的这些相关进程都会属于同一个 cgroups ,systemd 只需要简单地遍历指定的 cgroups 即可正确地找到所有的相关进程,将它们一一停止即可。
在创建 CGroup 时,就已经生成了一堆文件,一个 CGroup 目录中的内容大概可以分为四类:
- Subsystem Conf: 如附加了 CPU Subsystem 的 CGroup 目录下的 cpu* 文件均为 CPU Subsystem 配置
- Tasks:
在该 CGroup 下的 Tasks,分为两个文件,tasks 和 cgroup.procs,两者记录的都是在该进程 PID 列表
,但是有所区别。 - CGroup Conf: CGroup 的一些通用配置,比如 notify_on_release 用于在 CGroup 结构变更时执行 release_agent 中的命令,cgroup.clone_children 用于在 Child CGroup 创建时,自动继承父 Child CGroup 的配置,目前只有 cpuset SubSystem 支持
- Child CGroups: 除以上三种文件外的子目录,如Ubuntu16.04中,每个 Root CGroup 下都有个 docker 目录,它由 Docker 创建,用于管理Docker容器的资源配置
关于 tasks 和 cgroup.procs,网上很多文章将 cgroup 的 Task 简单解释为 OS 进程,这其实不够准确,更精确地说,cgroup.procs 文件中的 PID 列表才是我们通常意义上的进程列表
,而 tasks 文件中包含的 PID 实际上可以是 Linux 轻量级进程(LWP) 的 PID,而由于 Linux pthread 库的线程实际上轻量级进程实现的(Linux 内核不支持真正的线程,可通过getconf GNU_LIBPTHREAD_VERSION
查看使用的 pthread 线程库版本,Ubuntu16.04上是NPTL2.23(Native Posix Thread Lib),简单来说,Linux 进程主线程 PID = 进程 PID,而其它线程的 PID (LWP PID)则是独立分配的,可通过syscall(SYS_gettid)
得到。LWP 在 ps 命令中默认是被隐藏的,在/proc/目录下可以看到。为了区分方便,我们将以 Proc 来表示传统意义上的进程,以 Thread 表示 LWP 进程。
我们可以通过 ps 命令的 -T 参数将 LWP 在 SPID 列显示出来:
root# ps -ef | wc -l
218
root# ps -efT | wc -l
816
root# ps -p 28051 -lfT
F S UID PID SPID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD
0 Z root 28051 28051 26889 0 80 0 - 0 exit 10:30 pts/10 00:00:00 [a.out] <defunct>
1 R root 28051 28054 26889 99 80 0 - 12409 - 10:30 pts/10 00:00:10 [a.out] <defunct>
1 R root 28051 28055 26889 99 80 0 - 12409 - 10:30 pts/10 00:00:10 [a.out] <defunct>
以上示例中,Proc 28051 下有两个 Thread (28054,28055),即开了两个子线程。总的来说,Linux 下这种通过 LWP 来实现线程的方式,在一些时候会给用户一些困惑,比如如果我 kill -9 28055
(默认在 ps 下看不到),按照 POSIX 标准,28055 “线程”所在的进程会被 Kill掉,因此28051,28054,28055三个进程都会被杀掉,感觉就很诡异。感兴趣的可以看看这篇文章)。
当要向某个 CGroup 加入 Thread 时,将Thread PID 写入 tasks 或 cgroup.procs 即可,cgroup.procs 会自动变更为该 Task 所属的 Proc PID
。如果要加入 Proc 时,则只能写入到 cgroup.procs 文件,tasks 文件会自动更新为该 Proc 下所有的 Thread PID。可以通过cat /proc/PID/cgroup
查看某个 Proc/Thread 的 CGroup 信息,
参考链接:
https://tech.meituan.com/2015/03/31/cgroups.html
https://www.cnblogs.com/sparkdev/p/9523194.html