一段错误的代码

首先看一段错误的代码:

#!/bin/bash

SLICE=100;
slppid=1;
pidfile=/var/run/vpnrulematch.pid # 停止之前的sleep
kill_prev() {
pid=$1;
/bin/kill -0 $pid;exist=$?
ppid=$(/bin/cat /proc/$pid/status|/usr/bin/awk -F ' ' '/PPid/{print $2}');
if [ "$exist" == 0 ] && [ "$ppid" == $$ ]; then
/bin/kill $pid;
fi
}
echo $$ >$pidfile
# 循环处理睡眠
while true; do
NOSTATE=0;
/bin/sleep $SLICE &
slppid=$!;
wait
...
done

以上代码的本意是在接收到信号的时候,停止先前的sleep,重新开始新的sleep。看看那个繁杂的kill_prev操作,之所以繁杂是因为做了“防误杀”处理,只有在该进程id指示的进程存在并且是sleep,而且还是本脚本的子进程的时候才进行kill操作。初看起来这没有任何问题,很严密,但是注意那个if判断和kill操作之间的间隙,如果在那段时间sleep完成了,并且系统中有一个新的进程恰好在那时开始运行,并且占据了刚才sleep进程的PID,该进程会马上被误杀!即使Linux的PID分配策略是尽可能的往后递增以防止这种现象,然而这还是受制于允许的PID的总数量,如果PID最大只能是10,那么这就会很容易发生!

        那该怎么办?答案就是将该脚本以及它的子进程等相关的PID和系统中其它进程的PID隔离开来,但是Linux可以做到吗?可以做到,使用namespace即可。

关于命名空间

所谓的命名空间其实就是一个编址空间(废话,等于没说!!),一样东西要想被识别必须要被编址,比如快递员按照你的地址找到你的家,这个家庭地址就是一个编址,所有的已有的以及还未使用的潜在家庭地址组成了一个命名空间。一个命名空间一般只服务于一种动作,不同的命名空间之间是不能交互的。

        一样东西可以在不同的命名空间被命名编址,比如盖乌斯.尤利乌斯.凯撒和Gaius Julius Caesar指的是同一个人,然而却是处在不同命名空间中的,你在意大利找到一个人,对他说盖乌斯.尤利乌斯.凯撒,他可能就不知道你在说什么,这就是说,你不能垮命名空间进行寻址;如果在中国,生了一个小孩,给他取名字Gaius Julius Caesar,那么它和盖乌斯.尤利乌斯.凯撒并没有任何关联,这就是说,不同命名空间的相同名字之间是没有任何关系的;但是如果你精通古罗马历史,并且同时精通中文和意大利语,那么你马上就能将盖乌斯.尤利乌斯.凯撒和Gaius Julius Caesar联系起来,并且可能会有意给自己儿子取名字为自己的偶像Gaius Julius Caesar,这就是说,在更高的层次上,可以做到跨命名空间的交互。

Linux的PID namespace结构以及实现

Linux的2.6内核引入了命名空间namespace,后来将PID也用ns实现了,这也许是为了更好的支持虚拟化吧。本质上一个进程可以属于不同的命名空间。Linux将PID namespace组织成了一个tree,子命名空间对父命名空间是可见的,反过来,父命名空间对子命名空间则不可见,Linux对PID namespace的实现如下图所示:



通过引入一个pid结构体和task_struct进行关联,所有的关于PID命名空间的实现全部在这个pid结构体中:

struct pid
{
atomic_t count;
unsigned int level;
/* lists of tasks that use this pid */
struct hlist_head tasks[PIDTYPE_MAX];
struct rcu_head rcu;
struct upid numbers[1];
};

最下面的upid数组就是表示该pid在多命名空间中的值的:

struct upid {
/* Try to keep pid_chain in the same cacheline as nr for find_vpid */
int nr;
struct pid_namespace *ns;
struct hlist_node pid_chain;
};

以上的upid结构体包含了pid值本身以及一个namespace的引用,一个pid_namespace中包容了很多和进程控制相关的东西,比如独立的pid分配位图,1号进程引用,proc mount点等等,另外还有和自身组织相关的字段,比如parent指向父命名空间,level指示当前的命名空间深度。这里要说明的就是1号进程的作用,在UNIX中,1号进程非常重要,由于进入新的命名空间后,和父命名空间的1号进程将断绝来往,因此在新的命名空间需要一个新的1号进程,在Linux实现中,使用CLONE_NEWPID clone出来的进程担当1号进程的角色,实际上,它的进程号真的就是1。

        可以看一下alloc_pid的实现片断:

for (i = ns->level; i >= 0; i--) {
//tmp为当前遍历到的namespace,使用其独立的位图分配pid值
nr = alloc_pidmap(tmp);
if (nr < 0)
goto out_free; pid->numbers[i].nr = nr;
pid->numbers[i].ns = tmp;
tmp = tmp->parent;
}

可以看到,一直上溯到默认的pid namespace,每一个经过的pid namespace都会为该新进程分配一个pid值,因此处在独立的pid namespace中的进程会有多个pid值,每一个命名空间一个。

一个实验

终于到了小试牛刀的时候了,首先执行一下下面代码编译的程序:

#include <sched.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
char arg[16] = {0};
int new_ns(void *nul)
{
execl("/bin/bash", "/bin/bash", NULL);
}
int main(int argc, char **argv)
{
int res;
pid_t newid;
long ssize = sysconf(_SC_PAGESIZE);
void *stack = alloca(ssize) + ssize;
pid = clone(new_ns, stack,CLONE_NEWPID |CLONE_NEWNS, NULL);
//由于在属于该进程的内存空间分配的statck上运行,需要等待其结束
waitpid(newid, &res, 0);
}

代码超级简单,执行后就会进入新的pid命名空间了吗?试试吧!执行后,ps -e看了一下,发现还是原来的,1号进程依然是init!怎么回事啊?难道有什么不对吗?原来是procfs导致的。我们知道ps命令是解析procfs的内容得到结果的,而procfs根目录的进程pid目录是基于mount当时的pid namespace的,这个在procfs的get_sb回调中体现的。因此只需要重新mount一下proc即可:

mount -t proc proc /proc

不过也可以将以下的代码写入new_ns函数中去:

 mount("proc", "/proc", "proc", 0, "");

正确的代码

起初的那段错误的代码应该怎么改呢?Linux系统有个命令叫做unshare,然而好像不能unshare pid,于是不得不自己写一个,实际上也不用这么麻烦,直接将上面的代码改一下即可,在new_ns中不再exec bash,而是参数化,执行时,将最初那个脚本作为参数传入即可。然而还有一个问题,那就是既然已经到了新的pid namespace,以下的代码就不对了:

echo $$ >$pidfile

因为此时脚本的pid明显是1,而不在调用者的pid namespace内,写这个逻辑明显就是想让其它进程找到该脚本进程后给它发信号的,这样pid到了新的namespace,echo $$ >$pidfile写入的pid对于其它进程明显就不对了,然而到了新的namespace之后,脚本将无法自己知道它在父namespace中的pid是多少,因此其它进程只能通过ps -ef的方式去寻找它,因为虽然脚本到了新的namespace,然而它在父namespace中的pid还是有的。

        我不知道为何Linux没有提供诸如get_all_pid的系统调用,是因为这样不安全,违背了namespace互相隔离这种初衷?

使用独立PID namespace防止误杀进程的更多相关文章

  1. 子PID namespace中获取父namespace中pid的方法

    在那篇< 使用独立PID namespace防止误杀进程>中的最后,我碰到了一个难题,那就是父PID namespace中的进程无法使用进入子PID namespace中通过echo $$ ...

  2. namaspace之pid namespace

    认识Namespace namespace 是 Linux 内核用来隔离内核资源的方式.通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的 ...

  3. 查杀进程小工具——WPF和MVVM初体验

    最近因为工作需要,研究了一下桌面应用程序.在winform.WPF.Electron等几种技术里,最终选择了WPF作为最后的选型.WPF最吸引我的地方,就是MVVM模式了.MVVM模式完全把界面和业务 ...

  4. shell脚本执行查找进程,然后查杀进程

    shell 执行查找进程,然后查杀进程脚本如下: ps -ef | grep 'IOE' |grep -v 'grep'| awk '{print \$2}' |while read pid; do ...

  5. linux如何查进程、杀进程

    本文系转载,转载原文地址:http://blog.sina.com.cn/s/blog_637112040100vl53.html 1.查进程   ps命令查找与进程相关的PID号:   ps a 显 ...

  6. windows使用命令行杀进程

    在windows有时使用任务管理器杀进程,一直杀不掉: 这个时候,可以使用命令行: 先使用tasklist 命令查看当前系统中的进程列表,然后针对你要杀的进程使用taskkill命令 如要杀nginx ...

  7. Linux进程管理:查杀进程

    一.查看进程 Linux下显示系统进程的命令ps,最常用的有ps -ef 和ps aux.这两个到底有什么区别呢? 两者没太大差别,讨论这个问题,要追溯到Unix系统中的两种风格,System V风格 ...

  8. windows 查看端口占用,杀进程

    查看 443端口占用 netstat -ano | findstr "443" ,得到如下信息: TCP [::]:443 [::]:0 LISTENING 2320 发现是被23 ...

  9. psutil 跨平台根据程序名杀进程

    笔者在项目中遇到过需要根据进程名杀进程的需求,利用python库psutil实现了此功能. 模块地址: https://pypi.python.org/pypi/psutil/   psutil功能 ...

随机推荐

  1. HDU3572_Task Schedule(网络流最大流)

    解题报告 题意: 工厂有m台机器,须要做n个任务.对于一个任务i.你须要花费一个机器Pi天,并且,開始做这个任务的时间要>=Si,完毕这个任务的时间<=Ei. 对于一个任务,仅仅能由一个机 ...

  2. Java学习——何为JNDI

    曾记得在做机房收费系统的时候就接触到了API,由于它的功能非常强大,可是自己对它却不怎么了解.所以当时是又爱又怕.现在,一路走来才明确,事实上它就是一组接口.仅仅要我们去了解它就会发现.它事实上也没想 ...

  3. Android应用公布的准备——渠道注冊与认证

    今天早上申请了一个早上的渠道账号,这工作真是太繁琐,申请的是企业账号,须要营业执照等相关资料,假设申请个人的话预计须要身份证相关信息.以下贴出国内主流的几个渠道.不全然,可是基本上涵盖了大部分. 36 ...

  4. EasyUI - Tabs 选项卡标签

    基本效果: 效果图: html代码: <div id="tab"> <div title="tab1" > <p>tab1& ...

  5. shell 调用mysql 存储过程判断真假

    mysql> create table TBL_STUDENT(id int,name char(10),CLASSNO int,BIRTH datetime); Query OK, 0 row ...

  6. 大数据高并发系统架构实战方案(LVS负载均衡、Nginx、共享存储、海量数据、队列缓存)

    课程简介: 随着互联网的发展,高并发.大数据量的网站要求越来越高.而这些高要求都是基础的技术和细节组合而成的.本课程就从实际案例出发给大家原景重现高并发架构常用技术点及详细演练. 通过该课程的学习,普 ...

  7. abap优化工具事务代码: ST05

  8. Mongdb 访问

    http://114.55.75.xx/pics/201607040751367d21a38035bd4da7abd4473783f85f7a

  9. eval 捕获dbi错误

    [root@dr-mysql01 ~]# cat t2.pl use DBI; my $dbUser='zabbix'; my $user="root"; my $passwd=& ...

  10. nohup命令与&区别,jobs,fg,bg,Ctrl-Z、Ctrl-C、Ctrl-D

    &方式: Unix/Linux下一般想让某个程序在后台运行,很多都是使用 & 在程序结尾来让程序自动运行.比如我们要运行mysql在后台:          /usr/local/my ...