wxvirus wxvirus
首页
  • Go文章

    • Go语言学习
  • Rust

    • Rust学习
  • Java

    • 《Java》
  • Python文章

    • Python
  • PHP文章

    • PHP设计模式
  • 学习笔记

    • 《Git》
  • HTML
  • CSS
  • JS
  • 技术文档
  • GitHub技巧
  • 刷题
  • 博客搭建
  • 算法学习
  • 架构设计
  • 设计模式
  • 学习
  • 面试
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

无解的lifecycle

let today = new Beginning()
首页
  • Go文章

    • Go语言学习
  • Rust

    • Rust学习
  • Java

    • 《Java》
  • Python文章

    • Python
  • PHP文章

    • PHP设计模式
  • 学习笔记

    • 《Git》
  • HTML
  • CSS
  • JS
  • 技术文档
  • GitHub技巧
  • 刷题
  • 博客搭建
  • 算法学习
  • 架构设计
  • 设计模式
  • 学习
  • 面试
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • C&C++

  • PHP

    • PHP基础或常见方法

    • Laravel

    • ThinkPHP

    • PHP多进程编程

      • 程序与进程
      • PHP解释器的执行过程
      • 进程环境
      • 解释器文件
      • 进程标识与fork
      • 进程退出
      • 进程exec
      • 进程调度
      • 多进程编写
      • SUID特权进程
      • 进程查看
      • 中断信号
      • 进程关系与守护进程
      • 进程组
      • 会话
      • 守护进程
        • 守护进程
      • 作业控制
      • 进程间通信
    • swoole

  • Python

  • Go

  • microservice

  • rust

  • Java

  • 学习笔记

  • 后端
  • PHP
  • PHP多进程编程
wxvirus
2022-06-02

守护进程

# 守护进程

守护进程一般运行在后台,并且没有控制终端,同时守护进程是一直运行的,通常伴随着操作的系统启动而启动,停止而停止。

我们可以使用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

编写守护进程要求

  1. 设置文件创建屏蔽字:umask

    [root@jb51 process]# umask -S
    u=rwx,g=rx,o=rx
    # 用户具有读写和执行权限 组有读和执行权限 其他用户有读和执行权限
    
    1
    2
    3

    使用umask 077更改权限,它的读写执行权限就会被屏蔽掉了,一般来说我们设置为 0

  2. 一般父进程先fork一个子进程,然后父进程退出,子进程调用setsid来创建会话;如果调用setsid的进程是组长进程就会报错;进程调用setsid之后会有 3 个影响:

    1. 该进程会变成组长进程
    2. 该进程会变成会话首进程
    3. 该进程不再有控制终端

    当调用setsid之后,一般会再创建一个子进程,让会话首进程退出。

    在unix/linux的发行版本System V中,确保该进程不会再获得控制终端

  3. 把守护进程的工作目录设置为根目录(/proc/进程id/cwd)

  4. 把一些文件描述符关闭,一般来说是【标准输入、标准输出、标准错误】

    fpoen("/dev/null")
    
    1

    /dev/null 这个控设备文件,可以看做是一个黑洞文件,对该文件的任何读写错误操作都会被丢弃

    一般喜欢把dev/null代替为 0,1,2

    echo "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

我们使用追踪命令来查看

[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

上面有 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
<?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

此时运行php demo21.php,就会变成一个守护进程,如果想杀掉他,就要使用信号,kill -s SINGKILL或者kill -9 进程号

编辑 (opens new window)
上次更新: 2022/06/03, 00:07:00
会话
作业控制

← 会话 作业控制→

最近更新
01
vue3配合vite初始化项目的一些配置
07-26
02
网盘系统开发学习
07-24
03
linux多进程
06-19
更多文章>
Theme by Vdoing | Copyright © 2021-2024 wxvirus 苏ICP备2021007210号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式