守护进程
# 守护进程
守护进程一般运行在后台,并且没有控制终端,同时守护进程是一直运行的,通常伴随着操作的系统启动而启动,停止而停止。
我们可以使用ps -exj
来观察进程
[root@jb51 process]# ps -exj
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
0 1 1 1 ? -1 Ss 0 8:34 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
0 2 0 0 ? -1 S 0 0:02 [kthreadd]
2 4 0 0 ? -1 S< 0 0:00 [kworker/0:0H]
2 6 0 0 ? -1 S 0 0:44 [ksoftirqd/0]
2 7 0 0 ? -1 S 0 0:42 [migration/0]
2 8 0 0 ? -1 S 0 0:00 [rcu_bh]
2 9 0 0 ? -1 S 0 34:45 [rcu_sched]
2 10 0 0 ? -1 S< 0 0:00 [lru-add-drain]
2 11 0 0 ? -1 S 0 0:16 [watchdog/0]
2 12 0 0 ? -1 S 0 0:12 [watchdog/1]
2 13 0 0 ? -1 S 0 0:44 [migration/1]
2 14 0 0 ? -1 S 0 0:45 [ksoftirqd/1]
2 16 0 0 ? -1 S< 0 0:00 [kworker/1:0H]
2 18 0 0 ? -1 S 0 0:00 [kdevtmpfs]
2 19 0 0 ? -1 S< 0 0:00 [netns]
2 20 0 0 ? -1 S 0 0:02 [khungtaskd]
2 21 0 0 ? -1 S< 0 0:00 [writeback]
2 22 0 0 ? -1 S< 0 0:00 [kintegrityd]
2 23 0 0 ? -1 S< 0 0:00 [bioset]
2 24 0 0 ? -1 S< 0 0:00 [bioset]
2 25 0 0 ? -1 S< 0 0:00 [bioset]
2 26 0 0 ? -1 S< 0 0:00 [kblockd]
2 27 0 0 ? -1 S< 0 0:00 [md]
2 28 0 0 ? -1 S< 0 0:00 [edac-poller]
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
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
编写守护进程要求
设置文件创建屏蔽字:
umask
[root@jb51 process]# umask -S u=rwx,g=rx,o=rx # 用户具有读写和执行权限 组有读和执行权限 其他用户有读和执行权限
1
2
3使用
umask 077
更改权限,它的读写执行权限就会被屏蔽掉了,一般来说我们设置为 0一般父进程先
fork
一个子进程,然后父进程退出,子进程调用setsid
来创建会话;如果调用setsid
的进程是组长进程就会报错;进程调用setsid
之后会有 3 个影响:- 该进程会变成组长进程
- 该进程会变成会话首进程
- 该进程不再有控制终端
当调用
setsid
之后,一般会再创建一个子进程,让会话首进程退出。在
unix/linux
的发行版本System V
中,确保该进程不会再获得控制终端把守护进程的工作目录设置为根目录(
/proc/进程id/cwd
)把一些文件描述符关闭,一般来说是【标准输入、标准输出、标准错误】
fpoen("/dev/null")
1/dev/null
这个控设备文件,可以看做是一个黑洞文件,对该文件的任何读写错误操作都会被丢弃一般喜欢把
dev/null
代替为 0,1,2echo "hello"; // php解释器转换为 write(1, "hello")
1
2
3
4
<?php
// 第0步:
umask(0);
// 第一步:
$pid = pcntl_fork();
if ($pid > 0) {
exit(0);
}
// 第二步
// 该进程会变成组长进程、会话首进程、没有控制终端 TTY:?
if (-1 == posix_setsid()) {
fprintf(STDOUT, "setsid失败" . PHP_EOL);
}
// 第三步
// 设置工作目录
chdir("/");
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
// 当关掉以上标准输入、标准输出、标准错误文件之后,如果后面有对文件的操作,他返回的文件描述符就从0开始
file_put_contents("/data/work/php/process/demo21.log", "pid=".posix_getpid());
// 对应指令:write(0, "pid=xxx"); 可能会出错
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
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
我们使用追踪命令来查看
[root@jb51 process]# strace -f -s 65500 -o demo21.txt php demo21.php
1
主要看后面
28880 setsid() = 28880
28880 chdir("/") = 0
28880 close(0) = 0
28880 close(1) = 0
28880 close(2) = 0
28880 lstat("/data/work/php/process/demo21.log", 0x7ffc31686380) = -1 ENOENT (No such file or directory)
28880 open("/data/work/php/process/demo21.log", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 0
28880 fstat(0, {st_mode=S_IFREG|0666, st_size=0, ...}) = 0
28880 lseek(0, 0, SEEK_CUR) = 0
28880 write(0, "pid=28880", 9) = 9
28880 close(0) = 0
28880 write(1, "\nWarning: (null)(): supplied resource is not a valid stream resource in Unknown on line 0\n", 90) = -1 EBADF (Bad file descriptor)
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
上面有 3 个close
,都关掉了,下面的创建新文件就会从 0 开始,0 对应的通常是标准输入,使用file_put_contents
会打开一个文件描述符返回 0,是没有问题的,但是要写入内容,1 就会报错。
所以这里需要打开
<?php
// 第0步:
umask(0);
// 第一步:
$pid = pcntl_fork();
if ($pid > 0) {
exit(0);
}
// 第二步
// 该进程会变成组长进程、会话首进程、没有控制终端 TTY:?
if (-1 == posix_setsid()) {
fprintf(STDOUT, "setsid失败" . PHP_EOL);
}
// 第三步
// 设置工作目录
chdir("/");
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
// 对应的文件描述符正好是 0,此时下面的就是1
// 这里是用 /dev/null 来代替标准输入、标准输出、标准错误
$stdin = fopen("/dev/null", "a"); // 0
$stdout = fopen("/dev/null", "a"); // 1
$stderr = fopen("/dev/null", "a"); // 2
echo "hello.x"; // write(1, "hello.x") 丢弃
// 当关掉以上标准输入、标准输出、标准错误文件之后,如果后面有对文件的操作,他返回的文件描述符就从0开始
file_put_contents("/data/work/php/process/demo21.log", "pid=".posix_getpid());
// 对应指令:write(0, "pid=xxx"); 可能会出错
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
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
<?php
// 第0步:
umask(0);
// 第一步:
$pid = pcntl_fork();
if ($pid > 0) {
exit(0);
}
// 第二步
// 该进程会变成组长进程、会话首进程、没有控制终端 TTY:?
if (-1 == posix_setsid()) {
fprintf(STDOUT, "setsid失败" . PHP_EOL);
}
$pid = pcntl_fork();
if ($pid > 0) {
// 让会话首进程退出
exit(0);
}
// 第三步
// 设置工作目录
chdir("/");
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
// 对应的文件描述符正好是 0,此时下面的就是1
// 这里是用 /dev/null 来代替标准输入、标准输出、标准错误
$stdin = fopen("/dev/null", "a"); // 0
$stdout = fopen("/dev/null", "a"); // 1
$stderr = fopen("/dev/null", "a"); // 2
echo "hello.x"; // write(1, "hello.x") 丢弃
// 当关掉以上标准输入、标准输出、标准错误文件之后,如果后面有对文件的操作,他返回的文件描述符就从0开始
//file_put_contents("/data/work/php/process/demo21.log", "pid=".posix_getpid());
// 对应指令:write(0, "pid=xxx"); 可能会出错
$fd = fopen("/data/work/php/process/demo21.log", "a");
// 这个子进程会彻底的和控制终端断开连接,所以不能再使用标准输入、标准输出、标准错误
$pid = pcntl_fork();
if ($pid == 0) {
fprintf($fd, "pid=%d, ppid=%d, sid=%d, time=%s\n", posix_getpid(), posix_getppid(), posix_getsid(posix_getpid()), time());
// 可以添加死循环来保证守护进程不退出
while (1) {
sleep(1);
}
exit(0);
}
// 父进程回收退出的子进程
$pid = pcntl_wait($status);
if ($pid > 0) {
fprintf($fd, "pid=%d, ppid=%d, sid=%d, time=%s\n", posix_getpid(), posix_getppid(), posix_getsid(posix_getpid()), time());
fclose($fd);
exit(0);
}
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
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
此时运行php demo21.php
,就会变成一个守护进程,如果想杀掉他,就要使用信号,kill -s SINGKILL
或者kill -9 进程号
编辑 (opens new window)
上次更新: 2022/06/03, 00:07:00