多任务和cpu时间片:
? ?
不过,cpu如何选择下一个要执行的进程,这是一件非常复杂的事情。在Linux上,决定下一个要运行的进程是通过"调度类"(调度程序)
来实现的。程序何时运行,由进程的优先级决定。除此之外,优先级还影响分配给进程的时间片长短。在Linux中,改变进程的nice值,
可以影响某类进程的优先级值
? ?
有些进程比较重要,要让其尽快完成,有些进程则比较次要,早点或晚点完成不会有太大影响,所以操作系统要能够知道哪些进程比较
重要,哪些进程比较次要。比较重要的进程,应该多给它分配一些cpu的执行时间,让其尽快完成
下图是cpu时间片的概念(其中任务1优先级最高,任务2次之,任务3最低)
? ?
? ?
① 任务2和任务3都处于就绪态,任务1在等待一个信号量,优先级中的任务2获得CPU使用权
② 任务2的时间片用完,优先级低的任务3获得CPU使用权
③ 任务3的时间片用完,任务2重新获得CPU的使用权
④ 任务2的时间片还没用完时中断来临,中断服务程序获得CPU使用权
⑤ 中断服务程序发送了一个任务1等待的信号量,中断服务完成后优先级高的任务1获得CPU使用权
⑥ 任务1的时间片用完,任务2继续运行
⑦ 任务2的时间片用完,任务3获得CPU使用权
⑧ 任务3的时间片用完,重新分配时间片,新一轮调度开始
? ?
由此可以知道,所有的进程都有机会运行,但重要的进程总是会获得更多的cpu时间,这种方式是"抢占式多任务处理":内核可以强制
在时间片耗尽的情况下收回cpu使用权,并将cpu交给调度类选中的进程,此外,在某些情况下也可以直接抢占当前运行的进程。随
着时间的流逝,分配给进程的时间也会被逐渐消耗,当分配时间消耗完毕时,内核收回此进程的控制权,并让下一个进程运行。但因为
前面的进程还没有完成,在未来某个时候调度类还是会选中它,所以内核应该将每个进程临时停止时的运行时环境(寄存器中的内容和
页表)保存下来(保存位置为内核占用的内存),这称为保护现场,在下次进程恢复运行时,将原来的运行时环境加载到cpu上,这称为
恢复现场,这样cpu可以在当初的运行时环境下继续执行
? ?
在Linux的内核处理过程中,每一个进程默认会有一个固定的时间片来执行命令(默认为1/100秒),这段时间内进程被分配到CPU,
然后独占使用
? ?
父子进程及创建进程的方式:
? ?
在Linux,父子进程以树型结构的方式存在,父进程创建出来的多个子进程之间称为兄弟进程。CentOS 6上,init进程是所有进程的
父进程,CentOS 7上则为systemd
? ?
Linux上创建子进程的方式有三种(极其重要的概念):一种是fork出来的进程,一种是exec出来的进程,一种是clone出来的进程
? ?
(1). fork是复制进程,它会复制当前进程的副本(不考虑写时复制的模式),以适当的方式将这些资源交给子进程。所
以子进程掌握的资源和父进程是一样的,包括内存中的内容,所以也包括环境变量和变量。但父子进程是完全独立的,
它们是一个程序的两个实例
? ?
(2). exec是加载另一个应用程序,替代当前运行的进程,也就是说在不创建新进程的情况下加载一个新程序。exec还
有一个动作,在进程执行完毕后,退出exec所在环境(实际上是进程直接跳转到exec上,执行完exec就直接退出。
而exec加载程序的方式是:父进程睡眠,然后执行子进程,执行完后回到父进程,所以不会立即退出当前环境)。
所以为了保证进程安全,若要形成新的且独立的子进程,都会先fork一份当前进程,然后在fork出来的子进程上
调用exec来加载新程序替代该子进程。例如在bash下执行cp命令,会先fork出一个bash,然后再exec加载
cp程序覆盖子bash进程变成cp进程。但要注意,fork进程时会复制所有内存页,但使用exec加载新程序时会
初始化地址空间,意味着复制动作完全是多余的操作,当然,有了写时复制技术不用过多考虑这个问题
? ?
(3). clone用于实现线程。clone的工作原理和fork相同,但clone出来的新进程不独立于父进程,它只会和父进程共
享某些资源,在clone进程的时候,可以指定要共享的是哪些资源
? ?
题外知识:如何创建一个子进程? 每次fork一个进程的时候,虽然调用一次fork(),会分别为两个进程返回两个值:对子进程的返回值为0,对父进程的返回 值是子进程的pid。所以,可以使用下面的shell伪代码来描述运行一个ls命令时的过程:
假设上面是在shell脚本中执行ls命令,那么fork的是shell脚本进程。fork后,父进程将继续执行,且if语句判断失败, 于是执行wait;而子进程执行时将检测到fpid=0,于是执行exec(ls),当ls执行结束,子进程因为exec的原因将退出。于 是父进程的wait等待完成,继续执行后面的代码 如果在这个shell脚本中某个位置,执行exec命令(exec命令调用的其实就是exec家族函数),shell脚本进程直接切换到 exec命令上,执行完exec命令,就表示进程终止,于是exec命令后面的所有命令都不会再执行 |
? ?
一般情况下,兄弟进程之间是相互独立、互不可见的,但有时候通过特殊手段,它们会实现进程间通信。例如管道协调了两边的进程,
两边的进程属于同一个进程组,它们的PPID是一样的,管道使得它们可以以"管道"的方式传递数据
? ?
进程是有所有者的,也就是它的发起者,某个用户如果它非进程发起者、非父进程发起者、非root用户,那么它无法杀死进程。
且杀死父进程(非终端进程),会导致子进程变成孤儿进程,孤儿进程的父进程总是init/systemd
? ?
进程的状态:
? ?
进程并非总是处于运行中,至少cpu没运行在它身上时它就是非运行的。进程有几种状态,不同的状态之间可以实现状态切换。
下图是非常经典的进程状态描述图
? ?
? ?
? ?
新的(new):进程正在创建
运行态(running):进程正在运行,也即是cpu正在它身上
就绪(等待)态(wait/ready):进程可以运行,已经处于等待队列中,也就是说调度类下次可能会选中它
睡眠(阻塞)态(sleeped):进程睡眠了,不可运行
可中断:interruptable
不可中断:uninterruptable
停止态/终止态(stopped):暂停于内存中,但不会被调度,除非手动启动之
僵死态(zombie)
? ?
睡眠态是一个非常宽泛的概念,分为可中断睡眠和不可中断睡眠。可中断睡眠是允许接收外界信号和内核信号而被唤醒的睡眠,
绝大多数睡眠都是可中断睡眠,能ps或top捕捉到的睡眠也几乎总是可中断睡眠;不可中断睡眠只能由内核发起信号来唤醒,
外界无法通过信号来唤醒,主要表现在和硬件交互的时候。例如cat一个文件时,从硬盘上加载数据到内存中,在和硬件交互的
那一小段时间一定是不可中断的,否则在加载数据的时候突然被人为发送的信号手动唤醒,而被唤醒时和硬件交互的过程又还没完成,
所以即使唤醒了也没法将cpu交给它运行,所以cat一个文件的时候不可能只显示一部分内容。而且,不可中断睡眠若能被人为唤醒
,更严重的后果是硬件崩溃。由此可知,不可中断睡眠是为了保护某些重要进程,也是为了让cpu不被浪费
? ?
其实只要发现进程存在,且非僵尸态进程,还不占用cpu资源,那么它就是睡眠的。包括后文中出现的暂停态、追踪态,
它们也都是睡眠态
? ?
举例分析进程状态转换过程:
? ?
进程间状态的转换情况可能很复杂,这里举一个例子,尽可能详细地描述它们
? ?
以在bash下执行cp命令为例。在当前bash环境下,处于可运行状态(即就绪态)时,当执行cp命令时,首先fork出一个bash子进程,
然后在子bash上exec加载cp程序,cp子进程进入等待队列,由于在命令行下敲的命令,所以优先级较高,调度类很快选中它。
在cp这个子进程执行过程中,父进程bash会进入睡眠状态(不仅是因为cpu只有一颗的情况下一次只能执行一个进程,还因为进程等待),
并等待被唤醒,此刻bash无法和人类交互。当cp命令执行完毕,它将自己的退出状态码告知父进程,此次复制是成功还是失败,
然后cp进程自己消逝掉,父进程bash被唤醒再次进入等待队列,并且此时bash已经获得了cp退出状态码。根据状态码这个"信号",
父进程bash知道了子进程已经终止,所以通告给内核,内核收到通知后将进程列表中的cp进程项删除。至此,整个cp进程正常完成
? ?
假如cp这个子进程复制的是一个大文件,一个cpu时间片无法完成复制,那么在一个cpu时间片消耗尽的时候它将进入睡眠态,等待 IO
? ?
假如cp这个子进程复制文件时,目标位置已经有了同名文件,那么默认会询问是否覆盖,发出询问时它等待yes或no的信号,所以它
进入了睡眠状态(可中断睡眠),当在键盘上敲入yes或no信号给cp的时候,cp收到信号,从睡眠态转入就绪态,等待调度类选中它
完成cp进程
? ?
在cp复制时,它需要和磁盘交互,在和硬件交互的短暂过程中,cp将处于不可中断睡眠
? ?
假如cp进程结束了,但是结束的过程出现了某种意外,使得bash这个父进程不知道它已经结束了(此例中是不可能出现这种情况的),
那么bash就不会通知内核回收进程列表中的cp表项,cp此时就成了僵尸进程
? ?
进程结构和子shell:
? ?
-
前台进程:一般命令(如cp命令)在执行时都会fork子进程来执行,在子进程执行过程中,父进程会进入睡眠,这类是前台进
程。前台进程执行时,其父进程睡眠,因为cpu只有一颗,即使是多颗cpu,也会因为执行流(进程等待)的原因而只能执行一个
进程,要想实现真正的多任务,应该使用进程内多线程实现多个执行流
-
后台进程:若在执行命令时,在命令的结尾加上符号"&",它会进入后台。将命令放入后台,会立即返回父进程,并返回该
后台进程的的jobid和pid,所以后台进程的父进程不会进入睡眠。当后台进程出错,或者执行完成,总之后台进程终止时,
父进程会收到信号。所以,通过在命令后加上"&",再在"&"后给定另一个要执行的命令,可以实现"伪并行"执行的方式,
例如"cp /etc/fstab /tmp & cat /etc/fstab"
-
bash内置命令:bash内置命令是非常特殊的,父进程不会创建子进程来执行这些命令,而是直接在当前bash进程中执
行。但如果将内置命令放在管道后,则此内置命令将和管道左边的进程同属于一个进程组,所以仍然会创建子进程
? ?
-
说到这了,应该解释下子shell,这个特殊的子进程
? ?
一般fork出来的子进程,内容和父进程是一样的,包括变量,例如执行cp命令时也能获取到父进程的变量。但是cp命令
是在哪里执行的呢?在子shell中。执行cp命令敲入回车后,当前的bash进程fork出一个子bash,然后子bash通过exec加
载cp程序替代子bash。请不要在此纠结子bash和子shell,如果搞不清它们的关系,就当它是同一种东西好了
? ?
那是否可以理解为所有命令、脚本其运行环境都是在子shell中呢?显然,上面所说的bash内置命令不是在子shell中运行的。
其他的所有方式,都是在子shell中完成,只不过方式不尽相同
? ?
分为几种情况(只列出几种比较能说明问题的例子,还有其它很多种会进入子shell的情况):
? ?
-
.执行bash内置命令:bash内置命令是非常特殊的,父进程不会创建子进程来执行这些命令,而是直接在当前
bash进程中执行。但如果将内置命令放在管道后,则此内置命令将和管道左边的进程同属于一个进程组,所以仍然
会创建子进程,但却不一定是子shell。请先阅读完下面的几种情况再来考虑此项
-
.显然它会进入子shell环境,它的绝大多数环境都是新配置的,因为会加载一些环境配置文件。事实上fork出
来的bash子进程内容完全继承父shell,但因重新加载了环境配置项,所以子shell没有继承普通变量,更准确的
说是覆盖了从父shell中继承的变量。不妨试试在/etc/bashrc文件中定义一个变量,再在父shell中export名称
相同值却不同的环境变量,然后到子shell中看看该变量的值为何?
其实执行bash命令,既可以认为进入了子shell,也可以认为没有进入子shell。在执行bash命令后从变量
$BASH_SUBSHELL的值为0可以认为它没有进入子shell。但从执行bash命令后进入了新的shell环境来看,
它有其父bash进程,且$BASHPID值和父shell不同,所以它算是进入了子shell
执行bash命令更应该被认为是进入了一个完全独立的、全新的shell环境,而不应该认为是进入了片面的子shell环境
只执行 bash 命令进入子 shell 中时,其实子 shell 是非正式登陆,回去读取 ~/.bashrc --> /etc/bashrc -->
/etc/profile.d/*.sh 这些文件,所以更准确的说是覆盖了从父shell中继承的变量
-
.执行shell脚本:因为脚本中第一行总是"#!/bin/bash"或者直接"bash xyz.sh",所以这和上面的执行bash进
入子shell其实是一回事,都是使用bash命令进入子shell。只不过此时的bash命令和情况②中直接执行bash命令
所隐含的选项不一样,所以继承和加载的shell环境也不一样。事实也确实如此,shell脚本只会继承父shell的一项
属性:父进程所存储的各命令的路径
另外,执行shell脚本有一个动作:命令执行完毕后自动退出子shell
-
.执行非bash内置命令:例如执行cp命令、grep命令等,它们直接fork一份bash进程,然后使用exec加载程
序替代该子bash。此类子进程会继承所有父bash的环境。但严格地说,这已经不是子shell,因为exec加载的
程序已经把子bash进程替换掉了,这意味着丢失了很多bash环境
-
.非内置命令的命令替换:当命令行中包含了命令替换部分时,将开启一个子shell先执行这部分内容,再将执
行结果返回给当前命令。因为这次的子shell不是通过bash命令进入的子shell,所以它会继承父shell的所有变量内容。
这也就解释了"$(echo $$)"中"$$"的结果是当前bash的pid号,而不是子shell的pid号,因为它不是使用bash命令
进入的子shell
⑥.使用括号()组合一系列命令:例如(ls;date;echo haha),独立的括号将会开启一个子shell来执行括号内的命
令。这种情况等同于情况⑤
? ?
最后需要说明的是,子shell的环境设置不会粘滞到父shell环境,也就是说子shell的变量等不会影响父shell
? ?
-
-
还有两种特殊的脚本调用方式:exec和source
? ?
-
exec:exec是加载程序替换当前进程,所以它不开启子shell,而是直接在当前shell中执行命令或脚本,执行完exec
后直接退出exec所在的shell。这就解释了为何bash下执行cp命令时,cp执行完毕后会自动退出cp所在的子shell
-
source:source一般用来加载环境配置类脚本。它也不会开启子shell,直接在当前shell中执行调用脚本且执行脚本
后不退出当前shell,所以脚本会继承当前已有的变量,且脚本执行完毕后加载的环境变量会粘滞给当前shell,
在当前shell生效
-
? ?
job任务:
? ?
大部分进程都能将其放入后台,这时它就是一个后台任务,所以常称为job,每个开启的shell会维护一个job table,后台中的
每个job都在job table中对应一个Job项
? ?
-
手动将命令或脚本放入后台运行的方式是在命令行后加上"&"符号。例如:
? ?
[root@node2 ~]# sleep 10 & [1] 98490 [root@node2 ~]# jobs [1]+ Running sleep 10 & |
? ?
将进程放入后台后,会立即返回其父进程,一般对于手动放入后台的进程都是在bash下进行的,所以立即返回bash环境。
在返回父进程的同时,还会返回给父进程其jobid和pid。未来要引用jobid,都应该在jobid前加上百分号"%",
其中"%%"表示当前job,例如"kill -9 %1"表示杀掉jobid为1的后台进程,如果不加百分号,完了,把Init进程给杀了
(但该进程特殊,不会受影响)
? ?
Note:使用 & 放入后台的进程在执行完毕后会将要输出的信息输出到标准输出上
? ?
-
通过jobs命令可以查看后台job信息:
? ?
jobs [-lrs] [jobid]
? ?
-l:jobs默认不会列出后台工作的PID,加上-l会列出进程的PID
-r:显示后台工作处于run状态的jobs
-s:显示后台工作处于stopped状态的jobs
-p:只显示后台任务的 pid
? ?
通过"&"放入后台的任务,在后台中仍会处于运行中。当然,对于那种交互式如vim类的命令,将转入暂停运行状态
一定要注意,此处看到的是running和ps或top显示的R状态,它们并不总是表示正在运行,处于等待队列的进程也属于running。
它们都属于task_running标识
? ?
另一种手动加入后台的方式是按下CTRL+Z键,这可以将正在运行中的进程加入到后台,但这样加入后台的进程会在后台暂停运行
[root@node2 ~]# sleep 10 ^Z [1]+ Stopped sleep 10 |
? ?
从jobs信息也看到了在每个jobid的后面有个"+"号,还有"-",或者不带符号
? ?
[root@node2 ~]# jobs -l [1] 98371 Running sleep 60 & [2] 98372 Running sleep 50 & [3] 98373 Running sleep 40 & [4] 98374 Running sleep 30 & [5]- 98375 Running sleep 20 & [6]+ 98376 Running sleep 10 & |
? ?
+ 表示 job tables 中最后一个进程,可以使用 %+ 表示引用 最后一个的 jogid
- 表示 job tables 中倒数第二个进程,可以使用 %- 表示引用 倒数第二个的 jobid
? ?
fg和bg命令分别是foreground和background的缩写,也就是放入前台和放入后台,严格的说,是以运行状态放入前台和后台,
即使原来任务是stopped状态的
? ?
fg {%jobid} 将后台运行的进程切换到前台运行
bg {%jobid ……} 将后台处于 Stopped 的进程唤醒变为 Running 但依然处于后台
? ?
disown命令可以从job table中直接移除一个job,仅仅只是移出job table,并非是结束任务。而且移出job table后,作业将脱离
shell管理,不再依赖于终端,当终端断开会立即挂在init/systemd进程之下。所以,disown命令提供了让进程脱离终端的另一种方式
? ?
disown [-ar] [-h] [%jobid ...]
? ?
-h:给定该选项,将不从job table中移除job,而是将其设置为不接受shell发送的sighup信号
-a:如果没有给定jobid,该选项表示针对Job table中的所有job进行操作
-r:如果没有给定jobid,该选项严格限定为只对running状态的job进行操作
? ?
如果不给定任何选项,该shell中所有的job都会被移除,移除是disown的默认操作,如果也没给定jobid,而且也没给定-a或-r,
则表示只针对当前任务即带有"+"号的任务项
? ?
终端和进程的关系:
? ?
使用pstree命令查看下当前的进程,不难发现在某个终端执行的进程其父进程或上几个级别的父进程总是会是终端的连接程序
? ?
例如下面筛选出了两个终端下的父子进程关系,第一个行是tty终端(即直接在虚拟机中)中执行的进程情况
[root@node2 ~]# pstree -c | grep bash |-sshd---sshd---bash-+-grep |
? ?
正常情况下杀死父进程会导致子进程变为孤儿进程,即其PPID改变,但是杀掉终端这种特殊的进程,会导致该终端上的所有进程
都被杀掉,因为当控制终端退出时,会向该终端中的进程发送sighup信号。这在很多执行长时间任务的时候是很不方便的。比如
要下班了,但是你连接的终端上还在执行数据库备份脚本,这可能会花掉很长时间,如果直接退出终端,备份就终止了。所以应该
保证一种安全的退出方法
? ?
一般的方法也是最简单的方法是使用nohup命令带上要执行的命令或脚本放入后台,这样任务就脱离了终端的关联。当终端退出时,
该任务将自动挂到init(或systemd)进程下执行。如:
? ?
[root@node2 ~]# nohup tar rf a.tar.gz /tmp/*.txt & |
? ?
Note:nohup 后要执行的命令的输出信息会输入到用户家目录下的 nohup.out 文件中
? ?
另一种方法是使用screen这个工具,该工具可以模拟多个物理终端,虽然模拟后screen进程仍然挂在其所在的终端上的,
但同nohup一样,当其所在终端退出后将自动挂到init/systemd进程下继续存在,只要screen进程仍存在,其所模拟的
物理终端就会一直存在,这样就保证了模拟终端中的进程继续执行。它的实现方式其实和nohup差不多,只不过它花样更多,
管理方式也更多。一般对于简单的后台持续运行进程,使用nohup足以
? ?
另外,在子shell中的后台进程在终端被关闭时也会脱离终端,因此也不受shell和终端的控制。例如shell脚本中的后台进程,
再如"(sleep 10 &)"
? ?
可能你已经发现了,很多进程是和终端无关的,也就是不依赖于终端,这类进程一般是内核类进程/线程以及daemon类进程,
若它们也依赖于终端,则终端一被终止,这类进程也立即被终止,这是绝对不允许的
? ?
信号:
? ?
信号在操作系统中控制着进程的绝大多数动作,信号可以让进程知道某个事件发生了,也指示着进程下一步要做出什么动作。
信号的来源可以是硬件信号(如按下键盘或其他硬件故障),也可以是软件信号(如kill信号,还有内核发送的信号)。不过,
很多可以感受到的信号都是从进程所在的控制终端发送出去的
? ?
需知道的信号:
? ?
Linux中支持非常多种信号,它们都以SIG字符串开头,SIG字符串后的才是真正的信号名称,信号还有对应的数值,其实数值才是操
作系统真正认识的信号。但由于不少信号在不同架构的计算机上数值不同(例如CTRL+Z发送的SIGSTP信号就有三种值18,20,24),
所以在不确定信号数值是否唯一的时候,最好指定其字符名称
? ?
以下是需要了解的信号:
? ?
Signal Value Comment
─────────────────────────────
SIGHUP 1 终端退出时,此终端内的进程都将被终止
SIGINT 2 中断进程,可被捕捉和忽略,几乎等同于sigterm,所以也会尽可能的释放执行clean-up,释放资源,
保存状态等(CTRL+C)
SIGQUIT 3 从键盘发出杀死(终止)进程的信号 (CTRL+\)
SIGKILL 9 强制杀死进程,该信号不可被捕捉和忽略,进程收到该信号后不会执行任何clean-up行为,所以资源不
会释放,状态不会保存
SIGTERM 15 杀死(终止)进程,可被捕捉和忽略,几乎等同于sigint信号,会尽可能的释放执行clean-up,释放资源,
保存状态等
SIGCHLD 17 当子进程中断或退出时,发送该信号告知父进程自己已完成,父进程收到信号将告知内核清理进程列表。
所以该信号可以解除僵尸进程,也可以让非正常退出的进程工作得以正常的clean-up,释放资源,保存状态等
SIGSTOP 19 该信号是不可被捕捉和忽略的进程停止信息,收到信号后会进入stopped状态
SIGTSTP 20 该信号是可被忽略的进程停止信号(CTRL+Z)
SIGCONT 18 发送此信号使得stopped进程进入running,该信号主要用于jobs,例如bg & fg 都会发送该信号。
可以直接发送此信号给stopped进程使其运行起来
SIGUSR1 10 用户自定义信号1
SIGUSR2 12 用户自定义信号2?
? ?
除了这些信号外,还需要知道一个特殊信号:代码为0的信号。此信号为EXIT信号,表示直接退出。如果kill发送的信号是0(即kill -0)
则表示不做任何处理直接退出,但执行错误检查:当检查发现给定的pid进程存在,则返回0,否则返回1。也就是说,0信号可以用来
检测进程是否存在,可以代替 ps aux | grep proc_name 。(man kill中的原文为:If sig is 0, then no signal is sent, but error
checking is still performed。而man bash的trap小节中有如下描述:If a sigspec is EXIT (0),这说明0信号就是EXIT信号)
? ?
以上所列的信号中,只有SIGKILL和SIGSTOP这两个信号是不可被捕捉且不可被忽略的信号,其他所有信号都可以通过trap或
其他编程手段捕捉到或忽略掉
? ?
此外,经常看到有些服务程序(如httpd/nginx)的启动脚本中使用WINCH和USR1这两个信号,发送这两个信号时它们分别表示
graceful stop和graceful restart。所谓的graceful,译为优雅,不过使用这两个字去描述这种环境实在有点不伦不类。它对于
后台服务程序而言,传达了几个意思:(1)当前已经运行的进程不再接受新请求(2)给当前正在运行的进程足够多的时间去完成正在
处理的事情(3)允许启动新进程接受新请求(4)可能还有日志文件是否应该滚动、pid文件是否修改的可能,这要看服务程序对信号的
具体实现
? ?
再来说说,为什么后台服务程序可以使用这两个信号。以httpd的为例,在其头文件mpm_common.h中有如下几行代码:
/* Signal used to gracefully restart */ /* Signal used to gracefully stop */ |
? ?
这说明注册了对应信号的处理函数,它们分别表示将接收到信号时,执行对应的GRACEFUL函数
? ?
注意,SIGWINCH是窗口程序的尺寸改变时发送改信号,如vim的窗口改变了就会发送该信号。但是对于后台服务程序,它们根本
就没有窗口,所以WINCH信号对它们来说是没有任何作用的。因此,大概是约定俗成的,大家都喜欢用它来作为后台服务程序的
GRACEFUL信号。但注意,WINCH信号对前台程序可能是有影响的,不要乱发这种信号。同理,USR1和USR2也是一样的,
如果源代码中明确为这两个信号注册了对应函数,那么发送这两个信号就可以实现对应的功能,反之,如果没有注册,则这两个信号
对进程来说是错误信号
? ?
SIGHUP:
? ?
-
(1).当控制终端退出时,会向该终端中的进程发送sighup信号,因此该终端上运行的shell进程、其他普通进程以及任务都
会收到sighup而导致进程终止
? ?
多种方式可以改变因终端中断发送sighup而导致子进程也被结束的行为,这里仅介绍比较常见的三种:一是使用nohup命令
启动进程,它会忽略所有的sighup信号,使得该进程不会随着终端退出而结束;二是将待执行命令放入子shell中并放入
后台运行,例如"(sleep 10 &)";三是使用disown,将任务列表中的任务移除出job table或者直接使用disown -h的功能
设置其不接收终端发送的sighup信号。但不管是何种实现方式,终端退出后未被终止的进程将只能挂靠在init/systemd下
? ?
(2).对于daemon类的程序(即服务性进程),这类程序不依赖于终端(它们的父进程都是init或systemd),它们收到sighup
信号时会重读配置文件并重新打开日志文件,使得服务程序可以不用重启就可以加载配置文件
? ?
僵尸进程和SIGCHLD:
? ?
一个编程完善的程序,在子进程终止、退出的时候,内核会发送SIGCHLD信号给其父进程,父进程收到信号就会对该子进程进行善后
(接收子进程的退出状态、释放未关闭的资源),同时内核也会进行一些善后操作(比如清理进程表项、关闭打开的文件等)
? ?
在子进程死亡的那一刹那,子进程的状态就是僵尸进程,但因为发出了SIGCHLD信号给父进程,父进程只要收到该信号,子进程就会
被清理也就不再是僵尸进程。所以正常情况下,所有终止的进程都会有一小段时间处于僵尸态(发送SIGCHLD信号到父进程收到该信
号之间),只不过这种僵尸进程存在时间极短(倒霉的僵尸),几乎是不可被ps或top这类的程序捕捉到的
? ?
如果在特殊情况下,子进程终止了,但父进程没收到SIGCHLD信号,没收到这信号的原因可能是多种的,不管如何,此时子进程已经
成了永存的僵尸,能轻易的被ps或top捕捉到。僵尸不倒霉,人类就要倒霉,但是僵尸爸爸并不知道它儿子已经变成了僵尸,因为有
僵尸爸爸的掩护,僵尸道长即内核见不到小僵尸,所以也没法收尸。悲催的是,人类能力不足,直接发送信号(如kill)给僵尸进程是无
效的,因为僵尸进程本就是终结了的进程,它收不到信号,只有内核从进程列表中将僵尸进程表项移除才算完成收尸
? ?
要解决掉永存的僵尸有几种方法:
? ?
(1).杀死僵尸进程的父进程。没有了僵尸爸爸的掩护,小僵尸就暴露给了僵尸道长的直系弟子init/systemd,
init/systemd会定期清理它下面的各种僵尸进程。所以这种方法有点不讲道理,僵尸爸爸是正常的啊,不过如果僵尸爸爸
下面有很多僵尸儿子,这僵尸爸爸肯定是有问题的,比如编程不完善,杀掉是应该的
? ?
(2).手动发送SIGCHLD信号给僵尸进程的父进程。僵尸道长找不到僵尸,但被僵尸*的人类能发现僵尸,所以人类
主动通知僵尸爸爸,让僵尸爸爸知道自己的儿子死而不僵,然后通知内核来收尸
? ?
当然,第二种手动发送SIGCHLD信号的方法要求父进程能收到信号,而SIGCHLD信号默认是被忽略的,所以应该显式地在
程序中加上获取信号的代码。也就是人类主动通知僵尸爸爸的时候,默认僵尸爸爸是不搭理人类的,所以要强制让僵尸爸爸
收到通知。不过一般daemon类的程序在编程上都是很完善的,发送SIGCHLD总是会收到,不用担心
? ?
手动发送信号(kill命令):
? ?
kill [-s signal] pid...
kill [-signal] pid...
kill -l
? ?
使用kill -l可以列出Linux中支持的信号,有64种之多,但绝大多数非编程人员都用不上
? ?
pkill和killall:
? ?
两个命令都可以直接指定进程名来发送信号,不指定信号时,默认信号都是TERM
? ?
-
pkill:
? ?
pkill和pgrep命令是同族命令,都是先通过给定的匹配模式搜索到指定的进程,然后发送信号(pkill)或列出匹配的进程(pgrep),
pgrep就不介绍了
? ?
pkill能够指定模式匹配,所以可以使用进程名来删除,想要删除指定pid的进程,反而还要使用"-s"选项来指定。默认发送的
信号是SIGTERM即数值为15的信号
? ?
pkill [-signal] [-v] [-P ppid,...] [-s pid,...][-U uid,...] [-t term,...] [pattern]
pgrep [-signal] [-v] [-P ppid,...] [-s pid,...][-U uid,...] [-t term,...] [pattern]
? ?
-P ppid,... :匹配PPID为指定值的进程
-s pid,... :匹配PID为指定值的进程和子进程
-U uid,... :匹配UID为指定值的进程,可以使用数值UID,也可以使用用户名称
-t term,... :匹配给定终端,终端名称不能带上"/dev/"前缀,其实"w"命令获得终端名就满足此处条件了,
所以pkill可以直接杀掉整个终端
-v:反向匹配
-signal:指定发送的信号,发送信号的方式可以是-HUP或-SIGHUP,或数值的"-1"
-f:默认情况下,pgrep/pkill只会匹配进程名。使用-f 将匹配命令行
? ?
在CentOS 7上,还有两个好用的新功能选项
-F, --pidfile file:匹配进程时,读取进程的pid文件从中获取进程的pid值。这样就不用去写获取进程pid命令的
匹配模式
-L, --logpidfile :如果"-F"选项读取的pid文件未加锁,则pkill或pgrep将匹配失败
? ?
-
killall:kill processes by name
? ?
killall主要用于杀死一批进程,例如杀死整个进程组。其强大之处还体现在可以通过指定文件来搜索哪个进程打开了该文件,
然后对该进程发送信号,在这一点上,fuser和lsof命令也一样能实现
? ?
killall [-r,--regexp] [-s,--signal signal] [-u,--user user] [-v,--verbose] [-w,--wait] [-I,--ignore-case] [--]
[-y, --younger-than TIME] [-o, --older-than TIME] name ...
? ?
-i:交互询问
-l(小L):List all known signal names.
-I:匹配时不区分大小写
-r:使用扩展正则表达式进行模式匹配
-s, --signal:发送信号的方式可以是-HUP或-SIGHUP,或数值的"-1",或使用"-s"选项指定,默认SIGTERM
-u, --user:匹配该用户的进程
-v:给出详细信息
-w, --wait:等待直到该杀的进程完全死透了才返回。默认killall每秒检查一次该杀的进程是否还存在,只有不
存在了才会给出退出状态码
-y, --younger-than TIME:只匹配较年轻(在指定时间后启动)的进程。The units are s,m,h,d,w,M,y
-o, --older-than TIME:只匹配指定时间较早(在指定时间之前启动)的进程。The units are s,m,h,d,w,M,y
? ?
?
如果一个进程忽略了发送的信号、信号未产生效果、或者是僵尸进程将永久等待下去
? ?
fuser和lsof:
? ?
fuser可以查看文件或目录所属进程的pid,即由此知道该文件或目录被哪个进程使用。例如,umount的时候提示the device busy
可以判断出来哪个进程在使用。而 lsof 则反过来,它是通过进程来查看进程打开了哪些文件,但要注意的是,一切皆文件,包括普通文件、
目录、链接文件、块设备、字符设备、套接字文件、管道文件,所以lsof出来的结果可能会非常多
? ?
fuser:
? ?
-
fuser [-ki] [-signal] file/dir
? ?
-k:找出文件或目录的pid,并试图kill掉该pid。发送的信号是SIGKILL
-i:一般和-k一起使用,指的是在kill掉pid之前询问。
-signal:发送信号,如-1 -15,如果不写,默认-9,即kill -9
不加选项:直接显示出文件或目录的pid
? ?
-
在不加选项时,显示结果中文件或目录的pid后会带上一个修饰符:
? ?
c: 在当前目录下
e: 可被执行的
f: 是一个被开启的文件或目录
F: 被打开且正在写入的文件或目录
r: 代表root directory
m:表示模块
? ?
-
例如:
? ?
[root@xuexi ~]# fuser /usr/sbin/crond
/usr/sbin/crond: 1425e
表示/usr/sbin/crond被1425这个进程打开了,后面的修饰符e表示该文件是一个可执行文件
? ?
lsof:
? ?
[root@node2 ~]# lsof -u root | grep bash bash 98336 root cwd DIR 253,0 4096 100663361 /root bash 98336 root rtd DIR 253,0 255 64 / bash 98336 root txt REG 253,0 960472 15328 /usr/bin/bash bash 98336 root mem REG 253,0 106075056 35259288 /usr/lib/locale/locale-archive bash 98336 root mem REG 253,0 61624 35259251 /usr/lib64/libnss_files-2.17.so bash 98336 root mem REG 253,0 2156160 33797342 /usr/lib64/libc-2.17.so bash 98336 root mem REG 253,0 19288 33797349 /usr/lib64/libdl-2.17.so bash 98336 root mem REG 253,0 174520 33797429 /usr/lib64/libtinfo.so.5.9 bash 98336 root mem REG 253,0 163400 33554509 /usr/lib64/ld-2.17.so bash 98336 root mem REG 253,0 26254 67178142 /usr/lib64/gconv/gconv-modules.cache bash 98336 root 0u CHR 136,0 0t0 3 /dev/pts/0 bash 98336 root 1u CHR 136,0 0t0 3 /dev/pts/0 bash 98336 root 2u CHR 136,0 0t0 3 /dev/pts/0 bash 98336 root 255u CHR 136,0 0t0 3 /dev/pts/0 |
? ?
输出信息中各列意义:
? ?
COMMAND:进程的名称
PID:进程标识符
USER:进程所有者
FD:文件描述符,应用程序通过文件描述符识别该文件。如cwd、txt等
TYPE:文件类型,如DIR、REG等
DEVICE:指定磁盘的名称
SIZE/OFF:文件的大小或文件的偏移量(单位kb)(size and offset)
NODE:索引节点(文件在磁盘上的标识)
NAME:打开文件的确切名称
? ?
lsof的各种用法:
? ?
lsof /path/to/somefile:显示打开指定文件的所有进程之列表;建议配合grep使用
lsof -c string:显示其COMMAND列中包含指定字符(string)的进程所有打开的文件;可多次使用该选项
lsof -p PID:查看该进程打开了哪些文件
lsof -U:列出套接字类型的文件。一般和其他条件一起使用。如lsof -u root -a -U
lsof -u uid/name:显示指定用户的进程打开的文件;可使用脱字符"^"取反,如"lsof -u ^root"将显示非root用户
打开的所有文件
lsof +d /DIR/:显示指定目录下被进程打开的文件
lsof +D /DIR/:基本功能同上,但lsof会对指定目录进行递归查找,注意这个参数要比grep版本慢
lsof -a:按"与"组合多个条件,如lsof -a -c apache -u apache
lsof -N:列出所有NFS(网络文件系统)文件
lsof -n:不反解IP至HOSTNAME
lsof -i:用以显示符合条件的进程情况
lsof -i[46] [protocol][@host][:service|port]
46:IPv4或IPv6
protocol:TCP or UDP
host:host name或ip地址,表示搜索哪台主机上的进程信息
service:服务名称(可以不只一个)
port:端口号 (可以不只一个)
? ?
大概"-i"是使用最多的了,而"-i"中使用最多的又是服务名或端口了。
? ?
[root@www ~]# lsof -i :22
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sshd 1390 root 3u IPv4 13050 0t0 TCP *:ssh (LISTEN)
sshd 1390 root 4u IPv6 13056 0t0 TCP *:ssh (LISTEN)
sshd 36454 root 3r IPv4 94352 0t0 TCP xuexi:ssh->172.16.0.1:50018 (ESTABLISHED)