进程防杀笔记

一、Linux的信号量(signal)

信号是进程之间相互传递消息的一种方法,信号被称为软中断信号,是进程控制的一部分,用来通知进程发生了异步事件, 进程之间可以互相通过系统调用 kill 发送软中断信号。

信号只是用来通知某进程发生了什么,并不给进程传递数据。

signal-01signal-02

  • 接收信号的进程对各种信号有三种不同的处理方式:

    • 对需要处理的信号,进程指定特定的函数进行处理

    • 忽略某个信号

    • 对信号的处理保留系统默认值,大部分信号的处理是终止程序

  • 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系统调用);
    • 内核映像转储是指将进程数据在内存的映像和进程在内核结构中存储的部分内容以一定格式转储到文件系统,并且进程退出执行,这样做的好处是为程序员提供了方便,使得他们可以得到进程当时执行时的数据值,允许他们确定转储的原因,并且可以调试他们的程序。
  • 注意 :

    • 信号SIGKILLSIGSTOP既不能被捕捉,也不能被忽略。
    • 信号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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
int main(int argc ,char *argv[])
{
pid_t pid;

pid = fork();
if(pid<0){
perror("fork");
return -1;
}else if(pid==0){
printf("I am a child\n");
exit(0);
}else{
while(1){
sleep(100);
}
}
return 0;
}
1
2
$gcc a.c
$./a.out
  • 查看进程信息:ps -aux | grep “Z” ,尝试杀掉当前进程;

  • 查看进程详细信息:cat /proc/4385/status , 若杀不掉,则查看进程详细信息,尝试杀死其父进程。

  • 还不行,就重启吧!!!!

三、redis 进程

  • 安装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)herehere

    • Linux系统中常见的守护进程有:
      • cron 进程定期执行crontab设置的定时任务。
      • kswap守护进程定期将物理脏页写回磁盘来回收页面。
      • rsyslogd记录日志信息。
      • 还有一些常见的服务器程序,例如Redis、Nginx、MySQL等等。

实现一个进程交给Systemd去管理,继续以前面守护进程打开/tmp/log并且写入Hello World为例: here

  • Systemd配置文件内容:here01here02

    推荐参考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
    40
    Type=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
2
3
4
5
6
7
8
9
10
11
12
UID         PID   PPID   PGID    SID  C STIME TTY          TIME CMD                                                                                                       
root 2 0 0 0 0 14:53 ? 00:00:00 [kthreadd]
root 4 2 0 0 0 14:53 ? 00:00:00 [kworker/0:0H]
root 6 2 0 0 0 14:53 ? 00:00:00 [mm_percpu_wq]
root 7 2 0 0 0 14:53 ? 00:00:02 [ksoftirqd/0]
root 8 2 0 0 0 14:53 ? 00:00:02 [rcu_sched]
root 9 2 0 0 0 14:53 ? 00:00:00 [rcu_bh]
root 10 2 0 0 0 14:53 ? 00:00:00 [migration/0]
root 11 2 0 0 0 14:53 ? 00:00:00 [watchdog/0]
root 12 2 0 0 0 14:53 ? 00:00:00 [cpuhp/0]
root 13 2 0 0 0 14:53 ? 00:00:00 [cpuhp/1]
......省略部分输出

需要注意的是,用户层守护进程的父进程是 init进程(进程ID为1),从上面的输出PPID一列也可以看出,内核守护进程的父进程并非是 init进程。

对于用户层守护进程, 因为它真正的父进程在 fork 出子进程后就先于子进程 exit 退出了,所以它是一个由 init 继承的孤儿进程。

4.1 创建守护进程的过程:

  1. 调用fork创建子进程。父进程终止,让子进程在后台继续执行。
  2. 子进程调用setsid产生新会话期并失去控制终端调用setsid()使子进程进程成为新会话组长和新的进程组长,同时失去控制终端。
  3. 忽略SIGHUP信号。会话组长进程终止会向其他进程发该信号,造成其他进程终止。
  4. 调用fork再创建子进程。子进程终止,子子进程继续执行,由于子子进程不再是会话组长,从而禁止进程重新打开控制终端。
  5. 改变当前工作目录为根目录。一般将工作目录改变到根目录,这样进程的启动目录也可以被卸掉。
  6. 关闭打开的文件描述符,打开一个空设备,并复制到标准输出和标准错误上。 避免调用的一些库函数依然向屏幕输出信息。
  7. 重设文件创建掩码清除从父进程那里继承来的文件创建掩码,设为0。
  8. 用openlog函数建立与syslogd的连接。

4.2 创建示例:

通过python的daemon库进行创建

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
40
41
42
43
44
45
46
47
48
49
# -*- coding: UTF-8 -*-
import os
import sys
import time
import atexit
import signal
import traceback

#为当前进程重命名
import setproctitle
setproctitle.setproctitle("signum")

# 保存进程id
PIDFile = "/var/run/signum/signum-server.pid"

if not os.path.exists(os.path.dirname(PIDFile)):
print(os.path.dirname(PIDFile))
os.makedirs(os.path.dirname(PIDFile))
try:
with open(PIDFile,"w") as f:
pid=os.getpid()
f.write(str(pid)+"\n")
f.close()
except:
os.remove(PIDFile)

def term_sig_handler(signum, frame):
print("catched singal: %d " % signum)


@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__":
# 设置信号量的处理函数
signal.signal(signal.SIG_IGN, term_sig_handler)
signal.signal(signal.SIGTERM, term_sig_handler)
signal.signal(signal.SIGINT, term_sig_handler)

while True:
print("hello")
# sys.exit(0)
time.sleep(1)

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
40
41
42
43
44
45
46
47
48
49
50
51
52
# -*- coding: UTF-8 -*-
import os
import time
import daemon
from logzero import logger
import psutil

"""

"""
# 启动命令
ExecStart = "python /home/k/PycharmProjects/pythonProject/signum.py "
# 停止命令
ExecStop = ""
# 进程id文件
PIDFile = "/var/run/signum/signum-server.pid"


def start():
# 首先进行一次fork,防止对原有进程产生干扰
pid = os.fork()
if pid == 0:
# 对于fork出的子进程,进入Daemon模式
with daemon.DaemonContext():
# 如果文件存在
if os.path.exists(PIDFile):
with open(PIDFile, "r") as f:
pid = int(f.read())
f.close()
try:
while True:
# 如果进程id不存在
if not psutil.pid_exists(pid):
# 执行启动命令重启
os.system(ExecStart)
# 重新设置PID
with open(PIDFile, "r") as f:
pid = int(f.read())
f.close()

except ImportError as e:

logger.warning("setproctitle module not found")
time.sleep(3600)
else:
# 对于父进程(原始进程),保留原有逻辑执行
exit(0)


if __name__ == "__main__":
start()

4.3 实战结果

  • 两个守护进程:v1、v2

  • 一个服务进程:signum

  • v1、v2相互守护,v1还守护signum

  • 代码实现:

4.3.1 Daemon_v1.py

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# -*- coding: UTF-8 -*-
import json
import os
import signal
import sys
import time

import psutil
import requests

"""BASE_CONFIG"""
SERVER_IP = "192.168.121.152"
SERVER_PORT = "32768"
ROOT_URL = f"{SERVER_IP}:{SERVER_PORT}"

# 服务名称列表
SERVER_NAMES = ["signum", "daemon-v2-server"]
# 启动命令字典
EXEC_STARTS = {
"daemon-v2-server": f"/Anaconda/ls/envs/Daemon/bin/python /home/klein/WorkSpace/Daemon/testv1/Daemon_v2.py",
"signum": f"/Anaconda/ls/envs/Daemon/bin/python /home/klein/WorkSpace/Daemon/testv1/signum.py ",
}
# 停止命令字典
EXEC_STOPS = {

}
# PID文件存放根目录
PID_ROOT_PATH = "PID文件存放的根目录"
# PID文件路径字典
PIDFILES = {
"signum": f"signum PID文件全路径",
"daemon-v2-server": f"daemon-v2-server PID文件全路径",
}
# PID值字典
PID_VALUES = {
"signum": 0,
"daemon-v2-server":0
}


# 保存 daemon 进程id
D_PIDFILE = "当前进程的pid文件存放路径"


def put_log(description: str = None):
"""
推送日志信息
:param description:
:return:
"""
url = f"http://{SERVER_IP}:32769/log"
header = {
"Content-Type": "application/json"
}
if description:
try:
description = f"[time:{time.strftime('%Y-%m-%d %X', time.localtime())}] : " + description
requestData = {"username": "daemon", "description": description, "status": 2, "log_type": 4}
requestData = json.dumps(requestData)
res = requests.put(url, data=requestData, headers=header, timeout=5)
if res.status_code == 200:
return True
else:
return False
except Exception as e:
return False
return False


def init_signum():
"""
初始化signum服务
:return:
"""
# 等待服务启动
time.sleep(10)
return True


def init(name: str):
"""
启动进程后,根据服务名称初始化服务
:param name 服务名称
:return:
"""
if name == "signum":
res = init_signum()
if not res:
put_log(f"启动服务失败")
elif name == "name":
pass
else:
pass


def start_v3():
"""
开始守护进程 : 目前使用的版本
:return:
"""
# 首先进行一次fork,防止对原有进程产生干扰
pid = os.fork()
if pid == 0:
try:
# 为当前进程重命名
import setproctitle
setproctitle.setproctitle("[Daemon-v1]")
write_pid(D_PIDFILE)
# 屏蔽普通信号量
shield_signal()
for name in SERVER_NAMES:
# 如果文件存在
if os.path.exists(PIDFILES[name]):
with open(PIDFILES[name], "r") as f:
PID_VALUES[name] = int(f.read())
f.close()
while True:
for name in SERVER_NAMES:
# 如果进程id不存在
# put_log(f"{PID_VALUES}")
if not psutil.pid_exists(PID_VALUES[name]):
# 执行启动命令重启
os.system(EXEC_STARTS[name])
try:
put_log(f"{name} : 服务重启"
f"中......")
finally:
init(name)
# 重新设置PID
with open(PIDFILES[name], "r") as f:
PID_VALUES[name] = int(f.read())
f.close()
# else:

except Exception as e:
put_log(e.__str__())
time.sleep(3600)
else:
# 对于父进程(原始进程),保留原有逻辑执行
sys.exit(0)


def term_sig_handler(signum, frame):
"""
捕获信号量 kill signum 并推送日志处理
:param signum:
:param frame:
:return:
"""
put_log(f"Someone tried to shut down the daemon : kill {signum} [daemon-v1-server]")


def shield_signal():
"""
添加要屏蔽的信号量
:return:
"""
signal.signal(signal.SIG_IGN, term_sig_handler)
signal.signal(signal.SIGTERM, term_sig_handler)
signal.signal(signal.SIGINT, term_sig_handler)


def daemon_exist():
"""
判断当前守护进程是否存在
:return:
"""
# 文件不存在
if not os.path.exists(D_PIDFILE):
# print(os.path.dirname(D_PIDFILE))
if not os.path.exists(os.path.dirname(D_PIDFILE)):
os.makedirs(os.path.dirname(D_PIDFILE))
return False
else:
# 文件存在
with open(D_PIDFILE, "r") as f:
pid = int(f.read())
f.close()
if psutil.pid_exists(pid):
return True
else:
return False


def write_pid(daemon_pid: str):
"""
写入进程 pid
:param daemon_pid:
:return:
"""
with open(daemon_pid, "w") as f:
pid = os.getpid()
# print(pid)
f.write(str(pid) + "\n")
f.close()


if __name__ == "__main__":
if not daemon_exist():
start_v3()

4.3.2 Daemon_v2.py

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# -*- coding: UTF-8 -*-
import json
import os
import signal
import sys
import time

import psutil
import requests


"""BASE_CONFIG"""
SERVER_IP = "192.168.121.152"

# 服务名称列表
SERVER_NAMES = ["daemon-v1-server", ]
# 启动命令字典
EXEC_STARTS = {
"daemon-v1-server": f"/Anaconda/ls/envs/Daemon/bin/python /home/klein/WorkSpace/Daemon/testv1/Daemon_v1.py",
}
# 停止命令字典
EXEC_STOPS = {

}
# PID文件存放根目录
PID_ROOT_PATH = "根目录"
# PID文件路径字典
PIDFILES = {
"daemon-v1-server": f"daemon-v1-server PID文件位置",
}
# PID值字典
PID_VALUES = {
"daemon-v1-server": 0
}

# 保存 daemon-v2 进程id
D_PIDFILE = "当前进程的PID文件位置"


def put_log(description: str = None):
"""
推送日志信息
:param description:
:return:
"""
url = f"http://{SERVER_IP}:32769/log"
header = {
"Content-Type": "application/json"
}
if description:
try:
description = f"[time:{time.strftime('%Y-%m-%d %X', time.localtime())}] : " + description
requestData = {"username": "daemon", "description": description, "status": 2, "log_type": 4}
requestData = json.dumps(requestData)
res = requests.put(url, data=requestData, headers=header, timeout=5)
if res.status_code == 200:
return True
else:
return False
except Exception as e:
return False
return False

def init(name: str):
"""
启动进程后,根据服务名称初始化服务
:param name 服务名称
:return:
"""
if name == "signum":
pass
else:
time.sleep(10)
pass


def start_v3():
"""
开始守护进程 : 目前使用的版本
:return:
"""
# 首先进行一次fork,防止对原有进程产生干扰
pid = os.fork()
if pid == 0:
try:
# 为当前进程重命名
import setproctitle
setproctitle.setproctitle("[Daemon-v2]")
# 写入当前进程id
write_pid(D_PIDFILE)
# 屏蔽普通信号量
shield_signal()
for name in SERVER_NAMES:
# 如果文件存在
if os.path.exists(PIDFILES[name]):
with open(PIDFILES[name], "r") as f:
PID_VALUES[name] = int(f.read())
f.close()
while True:
for name in SERVER_NAMES:
# 如果进程id不存在
# put_log(f"{PID_VALUES}")
if not psutil.pid_exists(PID_VALUES[name]):
# 执行启动命令重启
os.system(EXEC_STARTS[name])
try:
put_log(f"{name} : 服务重启"
f"中......")
finally:
init(name)
# 重新设置PID
with open(PIDFILES[name], "r") as f:
PID_VALUES[name] = int(f.read())
f.close()
# else:

except Exception as e:
put_log(e.__str__())
time.sleep(3600)
else:
# 对于父进程(原始进程),保留原有逻辑执行
sys.exit(0)


def term_sig_handler(signum, frame):
"""
捕获信号量 kill signum 并推送日志处理
:param signum:
:param frame:
:return:
"""
put_log(f"Someone tried to shut down the daemon : kill {signum} [daemon-v2-server]")


def shield_signal():
"""
添加要屏蔽的信号量
:return:
"""
signal.signal(signal.SIG_IGN, term_sig_handler)
signal.signal(signal.SIGTERM, term_sig_handler)
signal.signal(signal.SIGINT, term_sig_handler)


def daemon_exist():
"""
判断当前守护进程是否存在
:return:
"""
# 文件不存在
if not os.path.exists(D_PIDFILE):
# print(os.path.dirname(D_PIDFILE))
if not os.path.exists(os.path.dirname(D_PIDFILE)):
os.makedirs(os.path.dirname(D_PIDFILE))
return False
else:
# 文件存在
with open(D_PIDFILE, "r") as f:
pid = int(f.read())
f.close()
if psutil.pid_exists(pid):
return True
else:
return False


def write_pid(daemon_pid: str):
"""
写入进程 pid
:param daemon_pid:
:return:
"""
with open(daemon_pid, "w") as f:
pid = os.getpid()
print(pid)
f.write(str(pid) + "\n")
f.close()


if __name__ == "__main__":
if not daemon_exist():
start_v3()

4.3.3 signum.py

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
40
41
42
43
44
45
46
47
48
49
50
# -*- coding: UTF-8 -*-
import os
import sys
import time
import atexit
import signal
import traceback

# 为当前进程重命名
import setproctitle
setproctitle.setproctitle("signum")

# 保存进程id
PIDFile = "当前进程的PID文件位置"

if not os.path.exists(os.path.dirname(PIDFile)):
# print(os.path.dirname(PIDFile))
os.makedirs(os.path.dirname(PIDFile))
try:
with open(PIDFile, "w") as f:
pid = os.getpid()
f.write(str(pid) + "\n")
f.close()
except Exception as e:
os.remove(PIDFile)


def term_sig_handler(signum, frame):
print("catched singal: %d " % signum)


@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__":
# 设置信号量的处理函数
signal.signal(signal.SIG_IGN, term_sig_handler)
signal.signal(signal.SIGTERM, term_sig_handler)
signal.signal(signal.SIGINT, term_sig_handler)

while True:
print("hello")
# sys.exit(0)
time.sleep(1)

五、可选的进程防杀方案

  • 使用redis的方案,在系统Systemd系统层面守护,检测不存在重新启动脚本。
  • 创建程序级别的守护进程,进行守护,检测不存在重新启动脚本。两层守护,第二层守护加入信号量(signal)拦截。
  • 创建定时任务,crotab持续扫描进程状态,不存在就重启。