守护进程

守护进程:也称为精灵进程,守护进程是一个在后台运行并且不受任何终端控制的进程。Unix操作系统有很多典型的守护进程(其数目根据需要或20—50不等),它们在后台运行,执行不同的管理任务。用户使守护进程独立于所有终端是因为,在守护进程从一个终端启动的情况下,这同一个终端可能被其他的用户使用。

有关命令

ps命令:Linux中的ps命令是Process Status的缩写。ps命令用来列出系统中当前运行的那些进程。ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,如果想要动态的显示进程信息,就可以使用top命令。
ps [options] [--help]

例子:
ps -ef显示所有进程的完整信息
-e:显示所有进程
-f:显示完整格式的进程信息
-o:用户自定义格式

grep命令:Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来。grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。

普通进程:普通进程在终端运行时,输入命令不会有效果
在运行程序时,在末尾加上&,或者在运行是按ctrl+z都可以使程序到后台运行
(1.将程序在后台运行,2.将程序暂停)
但是如果终端关闭,所以程序都将终止

守护进程:守护进程却能够突破这种限制,它从被执行开始运转,直到接收到某种信号或者整个系统关闭时才会退出。如果想让某个进程不因为用户、终端或者其他的变化而受到影响,那么就必须把这个进程变成一个守护进程。

如何创建守护进程

  1. 创建子进程,退出父进程

    为了脱离控制终端需要退出父进程,之后的工作都由子进程完成。在Linux中父进程先于子进程退出
    会造成子进程成为孤儿进程,而每当系统发现一个孤儿进程时,就会自动由1号进程(init)收养它,
    这样,原先的子进程就会变成init进程的子进程。

  2. 在子进程中创建新的会话
    pid_t setsid(void);用于创建一个新的会话,并担任该会话组的组长。
  • 让进程摆脱原会话的控制;
  • 让进程摆脱原进程组的控制;
  • 让进程摆脱原控制终端的控制;
  1. 改变当前目录为根目录

使用fork创建的子进程继承了父进程的当前的工作目录。由于在进程运行中,当前目录所在的文件系统是不能卸载的,这对以后的使用会造成诸多的麻烦。因此,通常的做法是让根目录”/”作为守护进程的当前工作目录。这样就可以避免上述的问题。如有特殊的需求,也可以把当前工作目录换成其他的路径。改变工作目录的方法是使用chdir函数。

  1. 重设文件权限掩码

由于fork函数创建的子进程继承了父进程的文件权限掩码,这就给子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为0(即,不屏蔽任何权限),可以增强该守护进程的灵活性。设置文件权限掩码的函数是umask。通常的使用方法为umask(0)。文件权限掩码:是指屏蔽掉文件权限中的对应位。

  1. 关闭文件描述符

用fork创建的子进程也会从父进程那里继承一些已经打开了的文件。在使用setsid调用之后,守护进程已经与所属的控制终端失去了联系,因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1、2(即,标准输入、标准输出、标准错误输出)的三个文件已经失去了存在的价值,也应该关闭。

文件描述符

当你打开一个存在的文件或者创建一个新文件,操作系统都会返回这个文件描述符(其实就是代表这个文件的),后续对这个文件的操作的一些函数,都会用到这个文件描述符作为参数;
linux中三个特殊的文件描述符,数字分别为0,1,2

  • 0:标准输入【键盘】,对应的符号常量叫STDIN_FILENO
  • 1:标准输出【屏幕】,对应的符号常量叫STDOUT_FILENO
  • 2:标准错误【屏幕】,对应的符号常量叫STDERR_FILENO

代码实现

```c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(){
    pid_t pid;

    //1. 创建子进程,退出父进程
    pid = fork();
    if(pid<0){
        printf("fork errno\n");
    }
    else if(pid>0){
        exit(0);
    }

    //2. 在子进程中创建新的会话
    setsid();

    //3. 改变当前目录为根目录
    chdir("/");

    //4. 重设文件权限掩码
    umask(0);

    //5. 关闭文件描述符
    close(0);
    int fd0;
    fd0=open("dev/null",O_RDWR);
    dup2(fd0,1);
    dup2(fd0,2);

    while(true){
        sleep(1);
    }
    return 0;
}
```

运行程序后,使用命令查看系统进程ps -eo pid,ppid,sid,tty,pgrp,comm,stat,cmd | grep -E 'bash|PID|mydaemon'

  PID  PPID   SID TT        PGRP COMMAND         STAT CMD
23000     1 23000 ?        23000 mydaemon        Ss   ./mydaemon

PPID父进程ID为1,说明fork子进程后,父进程退出,子进程被1号进程收养,TT=?说明已和终端脱离