使用独立PID namespace防止误杀进程
一段错误的代码
首先看一段错误的代码:
#!/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防止误杀进程的更多相关文章
- 子PID namespace中获取父namespace中pid的方法
在那篇< 使用独立PID namespace防止误杀进程>中的最后,我碰到了一个难题,那就是父PID namespace中的进程无法使用进入子PID namespace中通过echo $$ ...
- namaspace之pid namespace
认识Namespace namespace 是 Linux 内核用来隔离内核资源的方式.通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的 ...
- 查杀进程小工具——WPF和MVVM初体验
最近因为工作需要,研究了一下桌面应用程序.在winform.WPF.Electron等几种技术里,最终选择了WPF作为最后的选型.WPF最吸引我的地方,就是MVVM模式了.MVVM模式完全把界面和业务 ...
- shell脚本执行查找进程,然后查杀进程
shell 执行查找进程,然后查杀进程脚本如下: ps -ef | grep 'IOE' |grep -v 'grep'| awk '{print \$2}' |while read pid; do ...
- linux如何查进程、杀进程
本文系转载,转载原文地址:http://blog.sina.com.cn/s/blog_637112040100vl53.html 1.查进程 ps命令查找与进程相关的PID号: ps a 显 ...
- windows使用命令行杀进程
在windows有时使用任务管理器杀进程,一直杀不掉: 这个时候,可以使用命令行: 先使用tasklist 命令查看当前系统中的进程列表,然后针对你要杀的进程使用taskkill命令 如要杀nginx ...
- Linux进程管理:查杀进程
一.查看进程 Linux下显示系统进程的命令ps,最常用的有ps -ef 和ps aux.这两个到底有什么区别呢? 两者没太大差别,讨论这个问题,要追溯到Unix系统中的两种风格,System V风格 ...
- windows 查看端口占用,杀进程
查看 443端口占用 netstat -ano | findstr "443" ,得到如下信息: TCP [::]:443 [::]:0 LISTENING 2320 发现是被23 ...
- psutil 跨平台根据程序名杀进程
笔者在项目中遇到过需要根据进程名杀进程的需求,利用python库psutil实现了此功能. 模块地址: https://pypi.python.org/pypi/psutil/ psutil功能 ...
随机推荐
- Swift - 禁用UIWebView和WKWebView的下拉拖动效果
使用UIWebView或WKWebView加载网页时,如果页面处于最顶端时,用户用手指往下拖动,会露出灰色空背景.同样页面在最底部的时候,继续向上拖动,下方也会露出空背景. 要禁止这个拖动效果,可进行 ...
- vim: 搭建vim看代码的环境
使用 vim + ctags + cscope + taglist 阅读源码 http://my.oschina.net/u/554995/blog/59927 vim tab变空格 http:// ...
- javascript笔记整理(window对象)
浏览器对象模型 (BOM--Browser Object Model),window对象是BOM中所有对象的核心 A.属性 1.(位置类型-获得浏览器的位置) IE:window.screenLeft ...
- Qt读取ANSI格式文件——利用QTextCodec将其他编码格式的QByteArray转换为Unicode格式,或者从文件中读出后直接做转换
t使用Unicode来表示字符串.但是通常需要访问一些非Unicode格式的字符串,例如打开一个GBK编码的中文文本文件,甚至一些非Unicode编码的日文,俄文等. Qt提供了QTextCodec类 ...
- linux 下vi中关于删除某段,某行,或者全部删除的命令
1,先打开某个文件: vi filename 2,转到文件结尾 在命令模式输入 G 转到10行 在命令模式输入 10G 4,删除所有内容:先用G 转到文件尾,然后使用下面命令: :1, ...
- (step 8.2.8)hdu 1079(Calendar Game)
题目大意是: 两个家伙在区域赛前夕闲的无聊,然后玩一种无限纠结的游戏,随即给定一个日期,每次只能移动day OR month.......... 而且如果下一个月没有当前day的话, 你就不能移动mo ...
- 图片热区——axure线框图部件库介绍
首先,我们将图片热区组建拖动到axure页面编辑区域 1. 图片热区为页面图片或者其他部件添加热区,添加交互 我们一般在做专题的时候,会遇到一些组合商品,但是又需要单独分别设置连接,如果是2张图片还好 ...
- 改变Emacs下的注释代码方式以支持当前行(未选中情况下)的注释/反注释
Emacs下支持多行代码的注释/反注释,命令是comment-or-uncomment-region. 我喜欢把它绑定在快捷键C-c C-/上,如下: (global-set-key [?\C-c ? ...
- <转载>CSS解决图片过大撑破DIV的方法
DIV+CSS网页内容中如果插入大于DIV层宽度显示,过大的图片将会撑破网页宽度显示从而网页严重变形,您是否遇到过?这里DIVCSS5给大家介绍几种解决图片撑破撑开网页DIV层方法. 图片撑破宽度解决 ...
- HOOK自绘原理 good
做“HOOK文件打开/保存对话框”的过程中,我首先研究了界面库的相关知识.界面库一般都是由C/C++这种中低级语言编码,这是因为在Windows下的界面库实现技术大都以直接操作控制Windows的消息 ...