一段错误的代码
首先看一段错误的代码:
#!/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];
};