一段錯誤的代碼
首先看一段錯誤的代碼:
#!/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];
};