最近被安排做一些应急响应的工作,所以学习了一下Linux进程相关的知识,越学越多,那就记下来吧!
在Linux中:
通常我们都是通过以上两种方式来获得一个shell,之后运行程序的,此时我需要纠正一个概念,我们通常都说获得一个shell,本质上来说,我们获取了一个session(会话,以下session都是会话)
拿两种常见情况进行举例
我们输入
ping www.baidu.com
大家都知道,此时我们启动了一个程序 ping ,并且创建了一个进程,我们再开一个终端ssh连接这个服务器看一下
可以看到,我们起了一个PID为1779的进程,进程在不断向我们打印ping的结果,那么本质上来讲是什么样的呢?
我们使用 ps ajfx 来看一下
我们输入
ping www.baidu.com &
可以看到,ping百度 这个操作的“交互”已经放到后台了,但是依旧像终端输出,我们可以正常输入命令ls,pwd等,执行返回也都正常
ps ajfx
同样的过程就不重复了,不一样的地方在于
这里是 ps 命令的 STAT 列,具体字符含义如下
下面一些是BSD风格的参数
可以看出
如果上面涉及的所有概念你都能清晰的理解,那么下面的内容你也可以看一看,毕竟来都来了...
进程的概念大家都能理解的话,进程组就很好说了,其实就是一堆进程捆一起了,之后形成一个组就叫进程组了
这么做肯定是有意义的,不然Linux也不会这么搞,主要还是为了方便管理。
公司为了方便管理,给人分组,方便分配工作;社会为了方便管理,给人区分成年人,未成年人,老人;我们又因为爱好,信念等被分成了各种各样的小组...
系统把同一个job(作业)的进程分成一个组,既然有组织肯定得有组长,组的ID(PGID)就采用组长的PID
这里有一个问题,如果组长进程死亡了,小组还存在吗?如果存在组长归谁?
如果组长进程死亡了,小组只要还剩下进程就会存在,此时组长不会变,PGID也不会变;就像纪念一样...
实验一下:
#include <unistd.h>
#include <stdio.h>
int main()
{
setbuf(stdout, NULL);
pid_t pid;
pid = fork();
if(pid == 0){
printf("child pid: %d\n", getpid());
while(1){
sleep(1);
printf("child\n");
}
} else {
printf("father pid %d\n", getpid());
while(1){
sleep(1);
printf("father\n");
}
}
}
从ps的结果可以看到,我们的程序创建了两个进程,两个进程属于同一个进程组,PGID为29938
现在我们kill 掉进程组leader 29938
kill -9 29938
当我们kill掉进程leader之后,立马father就不打印了,但是child依旧在打印,这说明父进程被杀死,子进程还活着,接下来看看子进程活得怎么样
好家伙,父进程被杀死后,子进程直接把PPID设置为1,但是进程组PGID依旧没变,还是29938 ,session的id SID也没有发生变化,还是29756
此时这个子进程被称为孤儿进程
这里我们就需要注意了,一个木马或者后门如果主进程还存在子进程,仅仅 kill -9 pid 杀死主进程可能是没用的,因为不会杀死子进程
问题来了,如果我想把这些木马病毒进程都干掉,怎么操作?
我见过各种骚操作,有的是写脚本,有的是手动挨个杀,用killall、pkill等等,这种回复一看就是没遇到那种进程pid,进程名称一直变化的
其实非常简单,我们只需要把这个进程组给杀死就好了
kill -9 -PGID
没有看错,其实就是在 PGID前面加个减号
实验开始:
可以看到,父子进程都起来了,pid分别为29949和29950
这个时候我们杀掉这个进程组
kill -9 -29949
可以看到,这个进程组已经没有了,渣都不剩!
这里一定要注意,你杀的是一个进程组,一定要注意,进程组里是否有正常业务进程,别杀错了
其实文章开头我们已经简单提到过了,我们一般讨论的都是shell session,我们打开一个新的终端就会创建一个session,每个session都是由一个或者多个进程组组成的,每个进程组称为 job,这里job不是任务,而叫作业
从描述中可以看出,session管理的范围要比进程组大,打开一个终端,你执行100条命令,只要没有新的session生成(调用 setsid()函数可以生成新的session ),那么这些命令可以通过session进行统一管理,当然最常见的管理方式还是全部杀死,但是这个杀伤力太大了,所以一般不使用,主要还是了解session的概念,从web安全过来对于session这种机制应该很容易理解。
session中的第一个进程(一般是bash)的PID就是session的SID
现在大招来了,如何干掉整个session呢?
pkill -s SID
实验开始
可以看到,fk的SID为29756
pkill -e -s 29756
可以看到,杀掉了这个SID下的三个进程,分别为 29756, 29957, 29958
-e 参数是现实杀掉了谁,多人性化
可以看到,杀掉了bash进程后,ssh链接就断开了
守护进程这个词经常听到,名字还挺温暖,遗憾的是总是在处理linux挖矿病毒的案例中听到,简直破坏美感 守护进程的一个特点就是进程不受任何终端控制
不受任何终端控制这个定义似乎有些模糊,所以我试图去找到一些限定条件,大部分人是这样说的:
这其中很明显不完全准确,但是也都是基于实际情况分析出来的,所以我一直在纠结后台进程、nohup起的后台进程和守护进程是什么关系,直到遇到了这篇文章,我觉得才是说的比较透彻的
https://www.cnblogs.com/lvyahui/p/7389554.html
我直接摘过来:
守护进程在后台默默提供着服务,但是不接受任何终端的管控,没有标准输入、标准输出、标准错误,比较典型的有mysqld, sshd等,当然我们也是可以创建一个守护进程的,步骤如下:
直接摘抄吧:
执行一个fork(),之后父进程退出,子进程继续执行。
(结果就是daemon成为了init进程的子进程。)之所以要做这一步是因为下面两个原因:子进程调用setsid()开启一个新回话并释放它与控制终端之间的所有关联关系。
结果就是使子进程: (a)成为新会话的首进程,(b)成为一个新进程组的组长进程,(c)没有控制终端。在setsid()调用之后执行第二个fork()
,然后再次让父进程退出并让孙子进程继续执行。这样就确保了子进程不会成为会话组长,因此根据System V中获取终端的规则,进程永远不会重新请求一个控制终端。(多一个fork()调用不会带来任何坏处。)清除进程的umask以确保当daemon创建文件和目录时拥有所需的权限。
修改进程的当前工作目录,通常会改为根目录(/)。
这样做是有必要的,因为daemon通常会一直运行直至系统关闭为止。如果daemon的当前工作目录为不包含/的文件系统,那么就无法卸载该文件系统。或者daemon可以将工作目录改为完成任务时所在的目录或在配置文件中定义一个目录,只要包含这个目录的文件系统永远不会被卸载即可。关闭daemon从其父进程继承而来的所有打开着的文件描述符。
(daemon可能需要保持继承而来的文件描述的打开状态,因此这一步是可选的或者可变更的。)之所以这样做的原因有很多。由于daemon失去了控制终端并且是在后台运行的,因此让daemon保持文件描述符0(标准输入)、1(标准输出)和2(标准错误)的打开状态毫无意义,因为它们指向的就是控制终端。此外,无法卸载长时间运行的daemon打开的文件所在的文件系统。因此,通常的做法是关闭所有无用的打开着的文件描述符,因为文件描述符是一种有限的资源。在关闭了文件描述符0、1和2之后,daemon通常会打开/dev/null并使用dup2()(或类似的函数)使所有这些描述符指向这个设备。
之所以要这样做是因为下面两个原因:说了这么多,还是那一个实际的守护进程出来看一下吧,以sshd为例
因为守护进程PPID为1,而且是在单独的进程组、单独的session中,所以PID=PGID=SID,同时终端处值为 ? , 终端前台进程组ID设置为-1
杀死守护进程没啥特别的,该杀杀,当然前提是权限要够
看到这里已经可以了,基本上知识点都接触到了,下面是我在关于进程相关知识学习过程中思考的一些问题,不解决不舒服那种,无聊的可以看一看
1. ping www.baidu.com & 这种后台进程是不是守护进程
不是
存在标准输出和标准错误
2. nohup ping www.baidu.com &
不是
还是存在标准输出,只不过是重定向到 nohup.out中了
3. ping www.baidu.com > /dev/null 2>&1 & 更像是守护进程了吗
更像了,但还不是
这种形式确实是不在存在标准输出,标准输出,标准错误,但是PPID还不是1
4. 不就是PPID=1吗?上代码
#include <unistd.h>
#include <stdio.h>
int main()
{
setbuf(stdout, NULL);
pid_t pid;
pid = fork();
if(pid == 0){
system("ping www.baidu.com > /dev/null 2>&1 &");
} else {
exit(0);
}
}
现在更像是守护进程了,但是PID,PGID,SID还是不相等,终端处值不为 ? , 终端前台进程组ID也不是-1,目录也不是根目录,换句话说还是受到终端的控制。
具体创建一个守护进程的代码网上有的是,自己搜索吧,既有直接使用daemon()函数生成的,也有一步一步按照上面描述去生成的,推荐先看看后者。
5. 我们ssh断开链接后session还在吗?
我使用两个终端连接同一个服务器的ssh
可以看到,现在有两个SID,我们使用 1682 这个session来进行执行ping www.baidu.com
之后ctrl+c 中断,exit退出连接
我们使用1731的shell来查看
SID为1682的session不存在了,ping 的命令也被我们中断了
现在我们还是使用两个终端连接ssh
我们使用 1788的shell来执行
ping www.baidu.com & 之后exit退出ssh连接
从这里可以看到,虽然我们把ssh连接退出了,但是后台进行依旧在这个session上执行,还属于这个会话,所以如果session存在还在执行的后台进程,即使关闭终端或者断开ssh等远程连接,session还是会存在的
6. nohup 命令意义难道仅仅就是将标准输出,标准错误重定向到 nohup.out 吗?
如果仅仅是输出重定向,我们可以直接使用 > ,为什么会有nohup命令呢?没有点啥重要作用也对不起这个名字呀!
其实呢,产生这个疑问的主要就是因为问题5我们仅仅从表面现象就得出了结论,而没有进行本质上的剖析,所以如果只看到问题5的哥们儿可能要被误导了...
当一个终端关闭或者ssh等远程连接退出的时候,系统会向session管理的所有进程发送一个SIGHUP信号,这个信号就是挂断的意思,效果就是进程中断,理论上问题5中 ping www.baidu.com 这个后台进程也应该能够收到,但是,在session要断开这种情况是否给属于session的后台进程发送SIGHUP信号是受系统一个配置参数控制的——huponexit ,一般情况下,这个参数的缺省是off,在这种配置下,关闭终端后台进程不会收到SIGHUP信号。
shopt | grep huponexit
可以看到,在当前系统中,该参数为off,所以才会出现终端关闭或者ssh等远程连接断开的时候,后台进程能够继续以这个session运行
此时再说 nohup 应该就很清晰了,nohup其实就是忽略SIGHUP信号,这样保证我们的程序在后台平稳执行
7. tmux 后台执行的效果更好,tmux的底层原理是什么呢?
还是使用两个终端来进行
ctrl b+d
tmux ls
我们使用另一个终端观察一下:
可以看到,其实tmux创建了一个守护进程,进程PID=1348,之后通过守护进程创建 bash,之后通过bash执行ping,创建ping www.baidu.com
为了更加严谨证实这个观点,我们再创建一个tmux任务
现在是ping百度和新浪同时跑着,再观察一下
中间STAT为Zs的进程是因为我忘了截图,就退出了重新来导致的,不用关注
可以看到的是,对于每一个任务,tmux都会创建一个新的session、进程组、进程,这样实现多个进程之间互不影响
至此,关于Linux的进程相关知识应该将明白了,如果想从更加底层去分析,就去学习学习C和汇编吧!
参考文章
https://www.cnblogs.com/lvyahui/p/7389554.html
https://wudaijun.com/2016/08/linux-job-control/
https://zhuanlan.zhihu.com/p/80439267
http://www.ruanyifeng.com/blog/2016/02/linux-daemon.html
https://blog.csdn.net/weicao1990/article/details/78639549
http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html
https://segmentfault.com/a/1190000022770900
https://segmentfault.com/q/1010000000310278
https://blog.csdn.net/hust_sheng/article/details/50766752
https://segmentfault.com/a/1190000022097240
https://ytlee.cn/2020/05/the-difference-between-daemon-and-background-process/
https://www.cnblogs.com/lvyahui/p/7389554.html
https://www.jianshu.com/p/eed75164334d
https://www.lujun9972.win/blog/2019/08/26/%E5%A6%82%E4%BD%95kill%E6%95%B4%E4%B8%80%E4%B8%AA%E8%BF%9B%E7%A8%8B%E7%BB%84%E6%88%96%E4%BC%9A%E8%AF%9D/index.html
有态度,不苟同