Linux 守护进程

发布时间 2023-11-06 14:43:18作者: imXuan

1.进程组

  • 进程组,也称之为作业。BSD于1980年前后向Unix中增加的一个新特性。代表一个或多个进程的集合。
  • 每个进程都属于一个进程组。在 waitpid函数 和 kill函数 的参数中都曾使用到。操作系统设计的进程组的概念,是为了简化对多个进程的管理。
  • 当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。进程组ID为第一个进程ID(组长进程)。所以,组长进程标识:其进程组ID为其进程ID
  • 可以使用kill -SIGKILL -进程组ID(负进程组ID)来将整个进程组内的进程全部杀死:

 

  • 组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关。
  • 进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组)。
  • 一个进程可以为自己或子进程设置进程组ID

2.会话

2.1 会话概念

  • 一个会话可以有一个控制终端。这通常是终端设备或伪终端设备;
  • 建立与控制终端连接的会话首进程被称为控制进程;
  • 一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组;
  • 如果一个会话有一个控制终端,则它有一个前台进程组,其它进程组为后台进程组;
  • 如果终端接口检测到断开连接,则将挂断信号发送至控制进程(会话首进程)。

2.2 创建会话注意事项

1) 调用进程不能是进程组组长,该进程变成新会话首进程(session header)

2) 该调用进程是组长进程,则出错返回

3) 该进程成为一个新进程组的组长进程

4) 需有root权限(ubuntu不需要)

5) 新会话丢弃原有的控制终端,该会话没有控制终端

6) 建立新会话时,先调用fork, 父进程终止,子进程调用setsid

3.守护进程

  守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字。

  守护进程是个特殊的孤儿进程,这种进程脱离终端,为什么要脱离终端呢?之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。由于在 Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。

  Linux 的大多数服务器就是用守护进程实现的。比如,Internet 服务器 inetd,Web 服务器 httpd 等

3.1 守护进程概念

  • 守护进程是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或循环等待处理某些事件的发生;它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。
  • 守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机才随之一起停止运行;
  • 守护进程一般都以root用户权限运行,因为要使用某些特殊的端口(1-1024)或者资源;
  • 守护进程的父进程一般都是init进程,因为它真正的父进程在fork出守护进程后就直接退出了,所以守护进程都是孤儿进程,由init接管;
  • 守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。
  • 守护进程的名称通常以d结尾,比如sshd、xinetd、crond等

3.2 守护进程模型

1) 创建子进程,父进程退出(必须)

  • 所有工作在子进程中进行形式上脱离了控制终端

2) 在子进程中创建新会话(必须)

  • setsid()函数
  • 使子进程完全独立出来,脱离控制

3) 改变当前目录为根目录(不是必须)

  • chdir()函数
  • 防止占用可卸载的文件系统
  • 也可以换成其它路径

4) 重设文件权限掩码(不是必须)

  • umask()函数
  • 防止继承的文件创建屏蔽字拒绝某些权限
  • 增加守护进程灵活性

5) 关闭文件描述符(不是必须)

  • 继承的打开文件不会用到,浪费系统资源,无法卸载

6) 开始执行守护进程核心工作(必须)

7) 守护进程退出处理程序模型

3.3 示例代码

  每隔2s获取一次系统时间, 将这个时间写入到磁盘文件:

/*
* time_t rawtime;
* time ( &rawtime  ); --- 获取时间,以秒计,从1970年1月一日起算,存于rawtime
* localtime ( &rawtime  ); //转为当地时间,tm 时间结构
* asctime() // 转为标准ASCII时间格式:
*/
void write_time(int num)
{
    time_t rawtime;
    struct tm * timeinfo;
    // 获取时间
    time(&rawtime);
#if 0
    // 转为本地时间
    timeinfo = localtime(&rawtime);
    // 转为标准ASCII时间格式
    char *cur = asctime(timeinfo);
#else
    char* cur = ctime(&rawtime);
#endif// 将得到的时间写入文件中
    int fd = open("/home/edu/timelog.txt", O_RDWR | O_CREAT | O_APPEND, 0664);
    if (fd == -1)
    {
        perror("open error");
        exit(1);
    }
    // 写文件
    int ret = write(fd, cur, strlen(cur) + 1);
    if (ret == -1)
    {
        perror("write error");
        exit(1);
    }
    // 关闭文件
    close(fd);
}
​
int main(int argc, const char* argv[])
{
    pid_t pid = fork();
    if (pid == -1)
    {
        perror("fork error");
        exit(1);
    }
​
    if (pid > 0)
    {
        // 父进程退出
        exit(1);
    }
    else if (pid == 0)
    {
        // 子进程
        // 提升为会长,同时也是新进程组的组长
        setsid();
​
        // 更改进程的执行目录
        chdir("/home/edu");
​
        // 更改掩码
        umask(0022);
​
        // 关闭文件描述符
        close(STDIN_FILENO);
        close(STDOUT_FILENO);
        close(STDERR_FILENO);
​
        // 注册信号捕捉函数
        //先注册,再定时
        struct sigaction sigact;
        sigact.sa_flags = 0;
        sigemptyset(&sigact.sa_mask);
        sigact.sa_handler = write_time;
        sigaction(SIGALRM, &sigact, NULL);
​
        // 设置定时器
        struct itimerval act;
        // 定时周期
        act.it_interval.tv_sec = 2;
        act.it_interval.tv_usec = 0;
        // 设置第一次触发定时器时间
        act.it_value.tv_sec = 2;
        act.it_value.tv_usec = 0;
        // 开始计时
        setitimer(ITIMER_REAL, &act, NULL);
​
        // 防止子进程退出
        while (1);
    }
​
    return 0;
}