一、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
 26- import 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持续扫描进程状态,不存在就重启。