首页 > 系统相关 >Linux namespace之:pid namespace

Linux namespace之:pid namespace

时间:2023-09-20 23:13:12浏览次数:47  
标签:unshare namespace pid Linux 进程 proc bash


Linux namespace系列文章


理解pid namespace

PID namespace表示隔离一个具有独立PID的运行环境。在每一个pid namespace中,进程的pid都从1开始,且和其他pid namespace中的PID互不影响。这意味着,不同pid namespace中可以有相同的PID值。

因为PID namespace中的PID是独立的,每一个PID namespace都允许一些特殊的操作:允许pid namespace挂起、迁移以及恢复,就像虚拟机一样。

在介绍PID namespace之前,有必要回顾一下创建其他类型namespace时的进程关系。

# 在root namespace中
$ echo $$
12314
$ sudo unshare -u /bin/bash
[ns1]$ pstree -p | grep grep
   |   `-sshd(12313)---bash(12314)---sudo(14930)---bash(14931)-+-grep(14942)

其中sudo在root namespace中,其子进程bash(14931)在新创建的uts namespace ns1中。从上面输出的结果可知,创建其他类型namespace时,unshare进程会在创建新的namespace后被该namespace中的第一个进程给替换掉。如果忘记了,请回到前文uts namespace复习复习。

了解了创建其他类型namespace进程关系的基础后,再来对比介绍pid namespace。

创建新的pid namespace的方式:

# unshare --pid --fork [--mount-proc] <CMD>
# --pid或-p表示创建pid namespace
# --fork或-f表示创建pid namespace时,不是直接
#   替换unshare进程,而是fork unshare进程,
#   并使用CMD替换fork出来的子进程
# --mount-proc表示创建pid namespace时重新挂载procfs
sudo unshare -p -f -m -u --mount-proc /bin/bash

--fork以及--mount-proc选项稍后再解释。先看看创建pid namespace后的进程关系:

$ sudo unshare -p -f -m -u --mount-proc /bin/bash
root@longshuai-vm:/home/longshuai# hostname ns1
root@longshuai-vm:/home/longshuai# exec bash
root@ns1:/home/longshuai# pstree -p | grep bash
bash(1)-+-grep(11)    # pid namespace中,第一个进程bash其PID=1
                      # 该namespace中没有其他进程

# 在第二个shell窗口会话中执行
$ pstree -p | grep sudo
 |  `-sshd(12313)---bash(12314)---sudo(15070)---unshare(15071)---bash(15072)

注意上面输出的进程关系,和之前创建普通的namespace的进程关系不同,创建PID namespace时,sudo的子进程unshare进程保留了,这就是命令行中使用--fork的效果:在unshare中创建出pid namespace后,它将fork出它的子进程加入到新的pid namespace中,并在该子进程中exec加载指定的/bin/bash进程作为该pid namespace中的第一个进程。

所以使用--fork后导致的结果是:unshare进程被保留,且保留在原来的pid namespace中,而不是加入新的pid namespace中(在man pid_namespaces中明确指出了创建pid namespace的unshare或setns进程不会也不能进入新的pid namespace)。

# unshare进程和bash(12314)、sudo(15070)都在同一个pid namespace
$ pstree -p | grep sudo
 |  `-sshd(12313)---bash(12314)---sudo(15070)---unshare(15071)---bash(15072)

$ sudo ls -l /proc/12314/ns/pid
lrwxrwxrwx ... /proc/12314/ns/pid -> 'pid:[4026531836]'
$ sudo ls -l /proc/15070/ns/pid
lrwxrwxrwx ... /proc/15070/ns/pid -> 'pid:[4026531836]'
$ sudo ls -l /proc/15071/ns/pid
lrwxrwxrwx ... /proc/15071/ns/pid -> 'pid:[4026531836]'

# 但unshare的子进程bash在新的pid namespace中
$ sudo ls -l /proc/15072/ns/pid
lrwxrwxrwx ... /proc/15072/ns/pid -> 'pid:[4026532590]'

在新创建的pid namespace中使用ps命令查看进程信息:

root@ns1:~# ps j
   PPID   PID  PGID  SID TTY    TPGID STAT  UID   TIME COMMAND
      0     1     1    0 pts/0     21 S       0   0:00 bash
      1    21    21    0 pts/0     21 R+      0   0:00 ps j

关注输出结果中的两点:

1.pid namespace中的第一个进程bash,其PID=1

2.PID=1的进程的PPID=0,这和root namespace中pid=1的init进程的PPID=0是一样的

$ ps -elf
F S UID   PID    PPID  ... CMD
4 S root    1       0  ... /sbin/init splash
1 S root    2       0  ... [kthreadd]
...

在pid namespace中,PID=1的进程作为该pid namespace环境的【init】进程,当该pid namespace中的某个进程A的父进程退出了,进程A将称为孤儿进程,孤儿进程将被该pid namespace中PID=1的进程收养。

另外,观察旧namespace以及新pid namespace中的进程:

# 原来的namespace
$ pstree -p | grep sudo
 |  `-sshd(12313)---bash(12314)---sudo(15070)---unshare(15071)---bash(15072)
 
# 新创建的pid namespace
root@ns1:/home/longshuai# pstree -p | grep bash
bash(1)-+-grep(11)

其实unshare的子进程bash(15072)和pid namespace中的bash(1)是同一个进程。查看它们的pid namespace的inode可知:

# 原来的namespace中执行
$ sudo readlink /proc/16158/ns/pid
pid:[4026532593]

# 新的pid namespace中执行
root@longshuai-vm:/home/longshuai# readlink /proc/$$/ns/pid
pid:[4026532593]

所以可得知一个结论:当在namespace ns1中创建一个pid namespace ns2,祖先ns1中可查看ns2中的所有进程,但ns2中无法查看祖先ns1中的进程。

另外需要注意的是,--fork并非一定是结合--pid创建pid namespace使用的,它也可以直接使用或结合其他类型的namespace选项使用。例如:

$ sudo unshare --fork sleep 3

这仅仅表示在一个子进程中执行sleep程序,这里的unshare并没有创建出namespace,所以上面的命令和在子shell中执行sleep命令没什么区别,例如(sleep 3)

嵌套pid namespace

其实,对于pid namespace来说,是存在嵌套关系的。所有的子孙pid namespace中的进程信息都会保存在父级以及祖先级namespace中,只不过在不同嵌套层级中,同一个进程对应的PID不同。

例如,创建嵌套了2层的pid namespace:

# 当前namespace为ns0

# 创建pid namespace ns1
$ sudo unshare -mupf --mount-proc /bin/bash

# 在ns1中创建pid namespace ns2
root@longshuai-vm:/home/longshuai# unshare -mupf --mount-proc /bin/bash

# 在ns2中创建pid namespace ns3
root@longshuai-vm:/home/longshuai# unshare -mupf --mount-proc /bin/bash

现在ns0有3个子孙级的pid namespace:ns1、ns2和ns3。

在ns0中查看进程关系:

$ pstree -lp | grep -oE 'sudo.*'
sudo(16510)---unshare(16511)---bash(16513)---unshare(16531)---bash(16532)---unshare(16539)---bash(16540)

其中每一对unshare---bash代表一个pid namespace(但注意,unshare不在其子进程bash所在的pid namespace中)。

unshare(16511)---bash(16513)
unshare(16531)---bash(16532)
unshare(16539)---bash(16540)

这三个bash进程在各自的pid namespace中的PID=1。但从输出结果可知,在ns0中,这三个pid namespace中的PID分别为:

bash(16513)
bash(16532)
bash(16540)

PID=16513对应ns1中的bash,PID=16532对应ns2中的bash,PID=16540对应ns3中的bash。

/proc/<PID>/status中的NSPID字段记录了当前进程<PID>在各父级pid namespace中对应的PID值。

例如,在ns0中查看ns3中的bash(pid=16540)进程:

$ grep 'NSpid' /proc/16540/status
NSpid:  16540    25      9       1
#       当前ns   ns1     ns2     ns3

这表示pid=16540这个进程对应ns1中的pid=25,对应ns2中的pid=9,对应ns3中的pid=1。

同样地,可以进入到ns1中(bash pid=16513)去查看进程pid=25的进程对应关系:

# nsenter命令可进入一个已存在的namespace,
#   -t PID指定进入哪一个目标(target)namespace,
#      PID可以是属于目标namespace中的任何一个进程
#   -F 表示不fork nsenter进程,而是直接使用/bin/bash替换nsenter进程
#      所以,nsenter允许不fork,但unshare创建pid namespace时必须fork
# 就像登录系统一样,比如ssh登录时会启动bash(或其他shell),
# 这可以看作是进入root namespace并启动一个bash环境
# nsenter用法很简单,可查看nsenter --help或man nsenter
$ sudo nsenter -m -u -p -F -t 16513 /bin/bash

# 在ns1中查看进程关系
# bash(1)是当前ns1中的第一个进程
# bash(17)是ns2中的第一个进程
# bash(25)是ns3中的第一个进程
root@longshuai-vm:/# pstree -pl | grep unshare
bash(1)---unshare(16)---bash(17)---unshare(24)---bash(25)

# 在ns1中查看ns3中的bash进程在各父级pid namespace中的对应关系
# ns3 bash在ns1中的进程PID=25
#         在ns2中的进程PID=9
#         在ns3中的进程PID=1
root@longshuai-vm:/# grep 'NSpid' /proc/25/status
NSpid:  25      9       1

pid namespace和procfs(/proc)

/proc目录是内核对外暴露的可供用户查看或修改的内核中所记录的信息,包括内核自身的部分信息以及每个进程的信息。比如对于pid=N的进程来说,它的信息保存在/proc/<N>目录下。

/proc是一个挂载点,是伪文件系统procfs的挂载点,文件系统类型是proc:

$ mount | grep proc
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)

在操作系统启动的过程中,会挂载procfs到/proc目录,它存在于root namespace中。

但是,创建新的pid namespace时不会自动重新挂载procfs,而是直接拷贝父级namespace的挂载点信息。这使得在新的pid namespace中仍然保留了父级namespace的/proc目录,也就是在新创建的这个pid namespace中仍然保留了父级的进程信息。

# 在root namespace中查看pid namespace的inode
$ readlink /proc/self/ns/pid
pid:[4026531836]

# 创建pid namespace,注意没有加上--mount-proc选项
$ sudo unshare -m -u -p -f -w"/" /bin/bash

# 进入pid namespace后,查看PID=1的进程pid inode
# 在pid namespace中,pid=1的进程是bash,查看结果
# 本该是新的pid namespace中的pid inode值,但实际
# 结果是root namespace中pid=1的init进程的pid inode
root@longshuai-vm:/# readlink /proc/1/ns/pid  
pid:[4026531836]

# 通过ps查看pid=1的进程,发现是systemd(init),而不是bash
root@longshuai-vm:/# ps -p 1 -o pid,ppid,comm
    PID    PPID COMMAND
      1       0 systemd

之所以有上述问题,其原因是在pid namespace中保留了root namespace中的/proc目录,而不是属于pid namespace自己的/proc。

但用户创建pid namespace时希望的是有完全独立的进程运行环境。这时,需要在pid namespace中重新挂载procfs,或者在创建pid namespace时指定--mount-proc选项。

# 重新挂载procfs
root@longshuai-vm:/# mount -t proc proc /proc

# 重新挂载procfs后,/proc目录保存的是当前
# pid namespace中的进程信息
root@longshuai-vm:/# ps -p 1 -o pid,ppid,comm
    PID    PPID COMMAND
      1       0 bash
      
root@longshuai-vm:/# readlink /proc/1/ns/pid  
pid:[4026532591]

# 或者直接在创建pid namespace时加上--mount-proc选项
# sudo unshare -p -f -m -u --mount-proc /bin/bash

pid namespace的信号问题

pid=1的进程是每一个pid namespace(无论是root namespace还是用户自己创建的pid namespace)的核心进程,它不仅负责收养其所在pid namespace中的孤儿进程,还影响整个pid namespace。

当pid namespace中pid=1的进程退出或终止,内核默认会发送SIGKILL信号给该pid namespace中的所有进程以便杀掉它们(如果该pid namespace中有子孙namespace,也会直接被杀)。

在创建pid namespace时可以通过--kill-child选项指定pid=1的进程终止后内核要发送给pid namespace中进程的信号,其默认信号便是SIGKILL。

# 例如指定发送SIGHUP信号
sudo unshare -p -f -m -u --mount-proc --kill-child=SIGHUP /bin/bash

在pid namespace内部,只能向pid=1的进程发送那些在pid=1的进程中注册了信号处理程序的信号。

例如,如果pid namespace中/bin/bash作为pid=1的进程,那么在此环境中,只能向该bash进程发送设置了trap的信号,其他信号都被直接忽略,这样可以避免无法信号导致pid namespace被自己内部的进程终止。

但对于pid=1的/bin/bash做测试,发现SIGHUP信号总是有效。

$ sudo unshare -p -f -m -u --mount-proc -w'/' /bin/bash

# 设置SIGINT信号的信号处理程序,现在bash进程只接收SIGINT信号
root@longshuai-vm:/# trap 'echo int' INT

# 发送INT信号有效
root@longshuai-vm:/# /bin/kill -INT 1
int

# 发送其他信号无效,即便是STOP和KILL信号
root@longshuai-vm:/# /bin/kill -QUIT 1
root@longshuai-vm:/# /bin/kill -STOP 1
root@longshuai-vm:/# /bin/kill -KILL 1

# 发送HUP信号也有效,即使没有设置HUP信号处理程序
root@longshuai-vm:/# /bin/kill -HUP 1
$  # 已退出pid namespace

在pid namespace外部,只有其祖先级namespace可发送信号给该pid namespace中的进程,因为其他namespace中看不到该pid namespace中的进程信息。但是发送信号时要找准pid namespace中的进程在祖先namespace中的PID值。

例如:

# 在root namespace中,创建pid namespace ns1,并睡眠30秒
$ sudo unshare -p -f -m -u --mount-proc -w'/' /bin/bash
root@longshuai-vm:/# sleep 300

# 打开第二个会话窗口,在root namespace中杀掉ns1中的sleep进程
$ pstree -lp | grep -o "sleep.*"
sleep(24685)
$ sudo kill 24685

标签:unshare,namespace,pid,Linux,进程,proc,bash
From: https://www.cnblogs.com/f-ck-need-u/p/17718773.html

相关文章

  • Linux namespace之:network namespace
    Linuxnamespace系列文章理解networknamespacenetworknamespace用来隔离网络环境,在networknamespace中,网络设备、端口、套接字、网络协议栈、路由表、防火墙规则等都是独立的。因networknamespace中具有独立的网络协议栈,因此每个networknamespace中都有一个lo接口,但lo接......
  • 测试linux代码运行时间
    1. 前置知识  (1)gettid内核给线程(轻量级进程)分配的进程id,全局(所有进程中)唯一;  (2)pthread_self()是在用户态实现的,获取的是相对于进程的线程控制块的首地址,只是在当前进程空间中是唯一的。不能保证同一进程先后多个线程具有不同的id。(当前一个线程结束其生命周期,进程又新......
  • Linux Namespace
    1LinuxNamespace概述Namespace是对全局系统资源的一种封装隔离,使得处于不同namespace的进程拥有独立的全局资源,改变一个namespace中的系统资源只会影响当前namespace里的进程,对其他namespace中的进程没有影响。1.1Linux内核支持的namespaces目前,Linux内核里面实现了7种不同类型的......
  • Metasploitable-Linux靶机配置
    Metasploitable和kali是课上下载好的。直接打开进入欢迎界面用默认账号登录了之后,修改root密码(sudopasswdroot),登录root账号,查看本机IP(ifconfig)切换到kali,把kali的IP改为192.168.72.129,且能ping通192.168.72.131在kali的浏览器中输入靶机的IP,可以看到靶机的界面......
  • linux中grep与find的区别,Linux三剑客【grep、sed和awk】
    在使用linux时,经常需要进行文件查找。其中查找的命令主要有find和grep。两个命令是有区的。区别:(1)find命令是根据文件的属性进行查找,如文件名,文件大小,所有者,所属组,是否为空,访问时间,修改时间等。(2)grep是根据文件的内容进行查找,会对文件的每一行按照给定的模式(patter)......
  • linux第四周技术博客
    这周我们学习了Linux的文件的操作之前我们已经在/home目录中创建了/swxy目录我们首先来学习touch命令,用户可以通过touch命令来创建一个空白文件,也可以设置文件属性cd/home/swxytouchabc.txtecho‘abc'>>abc.txt我们可以看到在swxy这个目录中已经创建了abc.txt这个文件,......
  • 1-Linux操作系统 的介绍和安装教程
    一、Linux的介绍1)常见的操作系统Windows,它微软公司开发的一款桌面操作系统(闭源系统)。版本有dos、win98、winNT、winXP、win7、winvista、win8、win10。服务器操作系统:winserver2003、winserver2008、winserver2012。Mac,苹果公司开发的一款操作系统(闭源系统),目前最......
  • linux网络配置
    linux网络配置一:网络配置的相关概念1:网关网关就是连接不同网段的,可以让不同网段的主机进行通信,就相当于是一个网段鹅出口,必须通过这个出口出去,才能与外界进行通信,在linux中有默认的网关,NAT模式中默认的网关就是以.2结尾比如Ip为192.168.10.10它的网关就是192.168.10.2......
  • 《Linux命令行与shell脚本编程大全.第3版》电子书PDF+源代码
    精通Linux命令行与shell脚本编程,尽在本书中本书是关于Linux命令行和shell命令的全面参考资料,涵盖详尽的动手教程和实际应用指南,并提供相关参考信息和背景资料,带你从Linux命令行基础入手,直到写出自己的shell。时隔四年后的这一版本,针对Linux的新特性和实践,进行了全面更新:使用......
  • Linux用户和组命令
    用户和组配置文件1.用户:Linux基于用户身份对资源进行控制用户账号root用户程序用户:不需要登录系统,服务于应用程序,维护系统的运行普通用户:可以登录系统的一般用户组账号基本组(私有组):当用户创建文件和文件夹时,默认的属组,只能有一个附加组(公共组):用户可以有多个附加组UID和GIDUI......