一、Linux的信号量(signal)
信号是进程之间相互传递消息的一种方法,信号被称为软中断信号,是进程控制的一部分,用来通知进程发生了异步事件, 进程之间可以互相通过系统调用
kill
发送软中断信号。信号只是用来通知某进程发生了什么,并不给进程传递数据。
接收信号的进程对各种信号有三种不同的处理方式:
对需要处理的信号,进程指定特定的函数进行处理
忽略某个信号
对信号的处理保留系统默认值,大部分信号的处理是终止程序
Linux系统的两大信号:
- POSIX标准的规则信号(regular signal 1-31 编号)
- 实时信号(real-time signal 32-63)
规则信号表
信号编号 名称 默认动作 说明 1 SIGHUP 终止 终止控制终端或进程 2 SIGINT 终止 由键盘引起的终端(Ctrl-c) 3 SIGQUIT dump 控制终端发送给进程的信号, 键盘产生的退出(Ctrl-), 4 GIGILL dusmp 非法指令引起 5 SIGTRAP dump debug中断 6 SIGABRT/SIGIOT dump 异常中止 7 SIGBUS/SIGEMT dump 总线异常/EMT指令 8 SIGFPE dump 浮点运算溢出 9 SIGKILL 终止 强制杀死进程(大招, 进程不可捕获) 10 SIGUSR1 终止 用户信号, 进程可自定义用途 11 SIGSEGV dump 非法内存地址引起 12 SIGUSR2 终止 用户信号, 进程可自定义用途 13 SIGPIPE 终止 向某个没有读取的管道中写入数据 14 SIGALRM 终止 时钟中断(闹钟) 15 SIGTERM 终止 进程终止(进程可捕获) 16 SIGSTKFLT 终止 协处理器栈错误 17 SIGCHLD 忽略 子进程退出或中断 18 SIGCONT 继续 如进程停止状态则开始运行 19 SIGSTOP 停止 停止进程运行 20 SIGSTP 停止 键盘产生的停止 21 SIGTTIN 停止 后台进程请求输入 22 SIGTTOU 停止 后台进程请求输出 23 SIGURG 忽略 socket发送紧急情况 24 SIGXCPU dump CPU时间限制被打破 25 SIGXFSZ dump 文件大小限制被打破 26 SIGVTALRM 终止 虚拟定时时钟 27 SIGPROF 终止 profile timer clock 28 SIGWINCH 忽略 窗口尺寸调整 29 SIGIO/SIGPOLL 终止 I/O可用 30 SIGPWR 终止 电源异常 31 SIGSYS/SYSUNUSED dump 系统调用异常 各种默认处理动作的含义是:
- 终止程序是指进程退出;
- 忽略该信号是将该信号丢弃,不做处理;
- 停止程序是指程序挂起,进入停止状况以后还能重新进行下去,一般是在调试的过程中(例如ptrace系统调用);
- 内核映像转储是指将进程数据在内存的映像和进程在内核结构中存储的部分内容以一定格式转储到文件系统,并且进程退出执行,这样做的好处是为程序员提供了方便,使得他们可以得到进程当时执行时的数据值,允许他们确定转储的原因,并且可以调试他们的程序。
注意 :
- 信号
SIGKILL
和SIGSTOP
既不能被捕捉,也不能被忽略。 - 信号SIGIOT与SIGABRT是一个信号,可以看出,同一个信号在不同的系统中值可能不一样,所以建议最好使用为信号定义的名字,而不要直接使用信号的值。
- 信号
python处理信号量:
- demo测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26import os
import sys
import time
import atexit
import signal
import traceback
# 一个是捕捉kill信号,另一个是注册atexit函数
def term_sig_handler(signum, frame):
print('catched singal: %d' % signum)
sys.exit()
@atexit.register
def atexit_fun():
print('i am exit, stack track:')
exc_type, exc_value, exc_tb = sys.exc_info()
traceback.print_exception(exc_type, exc_value, exc_tb)
if __name__ == '__main__':
# catch term signal
signal.signal(signal.SIGTERM, term_sig_handler)
signal.signal(signal.SIGINT, term_sig_handler)
while True:
print 'hello'
time.sleep(3)- 监听的是 SIGTERM 信号:kill pid
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
当我们运行该程序时因为 while True 所以会持续的运行.
这里监听的是 SIGTERM 信号, 所以当我们在终端输入 kill pid (linux kill
默认是发送SIGTERM)时,
程序就会输出: 收到信号 15 <frame object at 0x7ff695738050> 0
当超过3次时就强制把自己杀死.
所以 SIGTERM 很适合用来做一些清理的工作
"""
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
import time
import os
import signal
receive_times = 0
def handler(signalnum, frame):
global receive_times
print u"收到信号", signalnum, frame, receive_times
receive_times += 1
if receive_times > 3:
exit(0) # 自己走
def main():
print "pid:", os.getpid()
signal.signal(signal.SIGTERM, handler)
while True:
pass
if __name__ == '__main__':
main()- 刚才我们说过SIGKILL不能被监听.
1
2
3
signal.signal(signal.SIGKILL, handler)
# 这里系统会直接跑错 AttributeError: 'module' object has no attribute 'SIGKILL'总结:
可以通过监控信号量来防止除
SIGKILL
以外的信号杀死进程。
二、linux kill -9 杀不掉的进程
kill -9 发送SIGKILL信号给进程,将其终止,但对于以下两种情况不适用
该进程是僵尸进程(STAT z),此时进程已经释放所有的资源,但是没有被父进程释放。僵尸进程要等到父进程结束,或者重启系统才可以被释放。
进程处于“核心态”,并且在等待不可获得的资源,处于“核心态 ”的资源默认忽略所有信号。只能重启系统。
kill 只能杀死用户态的进程
案例:
1 |
|
1 |
|
查看进程信息:ps -aux | grep “Z” ,尝试杀掉当前进程;
查看进程详细信息:cat /proc/4385/status , 若杀不掉,则查看进程详细信息,尝试杀死其父进程。
还不行,就重启吧!!!!
三、redis 进程
-
1
$ sudo apt-get install redis-server
查看进程:
ps -aux|grep redis
可以看到redis
服务所在用户组和PID查看服务状态:
netstat -nlt|grep 6379
使用kill -9 杀死 redis:
发现杀死后会重新启动服务
总结:
redis
会在系统创建用户和用户组:redis
redis
无法完全杀死,可以使用命令:/etc/init.d/redis-server stop
停止服务。/etc/init.d/redis-server restart
重启服务。/etc/init.d/redis-server start
停止服务。
Linux 守护进程原理及实例(Redis、Nginx):here、here
- Linux系统中常见的守护进程有:
- cron 进程定期执行
crontab
设置的定时任务。 - kswap守护进程定期将物理脏页写回磁盘来回收页面。
- rsyslogd记录日志信息。
- 还有一些常见的服务器程序,例如Redis、Nginx、MySQL等等。
- cron 进程定期执行
- Linux系统中常见的守护进程有:
实现一个进程交给Systemd去管理,继续以前面守护进程打开
/tmp/log
并且写入Hello World
为例: here
-
推荐参考here02
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40Type=forking
ExecStart=/usr/bin/redis-server /etc/redis/redis.conf # 启动命令
ExecStop=/bin/kill -s TERM $MAINPID # 退出命令
PIDFile=/var/run/redis/redis-server.pid # 进程id
TimeoutStopSec=0
Restart=always # 允许重启
User=redis # 所属用户
Group=redis # 所属用户组
RuntimeDirectory=redis # 运行时目录
RuntimeDirectoryMode=2755
UMask=007
PrivateTmp=yes
LimitNOFILE=65535
PrivateDevices=yes
ProtectHome=yes
ReadOnlyDirectories=/
ReadWriteDirectories=-/var/lib/redis
ReadWriteDirectories=-/var/log/redis
ReadWriteDirectories=-/var/run/redis
NoNewPrivileges=true
CapabilityBoundingSet=CAP_SETGID CAP_SETUID CAP_SYS_RESOURCE
MemoryDenyWriteExecute=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictRealtime=true
RestrictNamespaces=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
# redis-server can write to its own config file when in cluster mode so we
# permit writing there by default. If you are not using this feature, it is
# recommended that you replace the following lines with "ProtectSystem=full".
ProtectSystem=true
ReadWriteDirectories=-/etc/redis
[Install]
WantedBy=multi-user.target
Alias=redis.service # 设置别名
四、守护进程
守护进程(daemon)是生存期长的一种进程,没有控制终端。它们常常在系统引导装入时启动,仅在系统关闭时才终止。UNIX系统有很多守护进程,守护进程程序的名称通常以字母“d”结尾:例如,syslogd 就是指管理系统日志的守护进程。
通过ps进程查看器
ps -efj
的输出实例,内核守护进程的名字出现在方括号中,大致输出如下:
1 |
|
需要注意的是,用户层守护进程的父进程是 init进程(进程ID为1),从上面的输出
PPID
一列也可以看出,内核守护进程的父进程并非是 init进程。对于用户层守护进程, 因为它真正的父进程在 fork 出子进程后就先于子进程 exit 退出了,所以它是一个由 init 继承的孤儿进程。
4.1 创建守护进程的过程:
- 调用fork创建子进程。父进程终止,让子进程在后台继续执行。
- 子进程调用setsid产生新会话期并失去控制终端调用setsid()使子进程进程成为新会话组长和新的进程组长,同时失去控制终端。
- 忽略SIGHUP信号。会话组长进程终止会向其他进程发该信号,造成其他进程终止。
- 调用fork再创建子进程。子进程终止,子子进程继续执行,由于子子进程不再是会话组长,从而禁止进程重新打开控制终端。
- 改变当前工作目录为根目录。一般将工作目录改变到根目录,这样进程的启动目录也可以被卸掉。
- 关闭打开的文件描述符,打开一个空设备,并复制到标准输出和标准错误上。 避免调用的一些库函数依然向屏幕输出信息。
- 重设文件创建掩码清除从父进程那里继承来的文件创建掩码,设为0。
- 用openlog函数建立与syslogd的连接。
4.2 创建示例:
通过python的daemon库进行创建
1 |
|
1 |
|
4.3 实战结果
两个守护进程:v1、v2
一个服务进程:signum
v1、v2相互守护,v1还守护signum
代码实现:
4.3.1 Daemon_v1.py
1 |
|
4.3.2 Daemon_v2.py
1 |
|
4.3.3 signum.py
1 |
|
五、可选的进程防杀方案
- 使用redis的方案,在系统Systemd系统层面守护,检测不存在重新启动脚本。
- 创建程序级别的守护进程,进行守护,检测不存在重新启动脚本。两层守护,第二层守护加入信号量(signal)拦截。
- 创建定时任务,crotab持续扫描进程状态,不存在就重启。