前言
本篇文章我们来讲解守护进程,守护进程在进程中是一个比较重要的概念,在笔试面试中也经常考到,这篇文章就带大家来学习一下什么是守护进程。
一、守护进程概念
守护进程(Daemon Process)是在后台运行的一种特殊类型的进程,独立于终端会话,并且通常没有与用户交互的界面。守护进程在操作系统启动时启动,并持续运行,提供某种服务或执行特定的任务。
以下是守护进程的一些特点和作用:
1.后台运行:守护进程在后台运行,与终端会话无关。它不会像前台进程一样与终端交互,也不会受到终端关闭的影响。
2.无用户交互:守护进程通常没有与用户直接交互的界面。它在后台默默地运行,执行特定的任务或提供服务,如网络服务、定时任务等。
3.生命周期控制:守护进程通常由操作系统启动,在操作系统运行期间一直保持活动状态。它可以在操作系统启动时自动启动,并在系统关闭时自动终止。
4.分离与父进程:守护进程通常会从父进程(比如 Shell)中分离,成为一个独立的进程组,没有与终端相关联。这样可以避免与终端的交互和输出。
5.日志和错误处理:守护进程通常会将输出记录到日志文件中,而不是直接向终端输出。这有助于跟踪和排除问题,并提供可靠的错误处理机制。
6.无控制终端:守护进程不会占用、控制或依赖于终端设备,这使得它们可以在系统无人值守时运行。它们可以在系统启动后一直执行,无需用户干预。
守护进程的设计和实现要考虑到一些关键问题,如资源管理、信号处理、错误处理、日志记录、进程间通信等。同时,为了实现守护进程的特性,Linux 中提供了一些守护进程相关的编程技术和系统调用,如 fork()、setsid()、umask()、open()、chdir()、close() 等。
创建和管理守护进程需要谨慎处理,确保守护进程正确启动、运行和终止,同时记录必要的日志信息,以便进行故障排除。通常,各个 Linux 发行版提供了一些工具和约定用于配置和管理守护进程,如 Systemd、Upstart 和 SysVinit 等。
总而言之,守护进程是在后台运行的一种特殊类型的进程,独立于终端会话,并且没有与用户交互的界面。它们提供特定的服务或执行特定的任务,通常在操作系统启动时运行,并持续提供服务。
二、空洞文件
/dev/null 是一个特殊的设备文件,也被称为空洞文件或黑洞文件。
以下是对 /dev/null 空洞文件的一些特点和用途:
数据丢弃:将数据写入 /dev/null 相当于将其永久丢弃,数据不会进行存储,也不会影响其他进程和文件系统。这在某些情况下非常有用,比如不需要关心命令的输出结果或需要忽略某些输出。
输出重定向:可以将命令的输出重定向到 /dev/null,这样输出将会被丢弃,不显示在终端或输出到其他地方。通过将标准输出或标准错误重定向到 /dev/null,可以静默执行命令或过滤掉不需要的输出。
例如,command > /dev/null 将命令的标准输出重定向到 /dev/null,从而使输出被丢弃。
同样,command 2> /dev/null 将命令的标准错误输出重定向到 /dev/null,以丢弃标准错误输出。
虚拟输入:通过从 /dev/null 读取数据,会立即得到一个文件结束符(EOF),即读操作立即返回,不会返回任何实际数据。在一些情况下,当需要一个空的输入源时,可以从 /dev/null 读取数据。
总结来说,/dev/null 空洞文件在 Unix-like 系统中用于丢弃数据、将输出静默化或提供一个空的输入源。它是一种有用的机制,可用于在处理数据和命令输出时,忽略不需要的信息。
请注意,/dev/null 是一个特殊文件,而不是目录。在文件系统中,它位于 /dev 目录下,并且常常被用于 I/O 操作的重定向。
三、创建守护进程
这段代码的守护进程创建过程如下:
父进程创建子进程。
父进程退出,子进程成为孤儿进程,由 init 进程接管。
子进程创建孙子进程。
子进程退出,孙子进程成为孤儿进程,由 init 进程接管。
孙子进程成为新会话的首进程,并失去对控制终端的控制。
孙子进程修改文件权限掩码、切换工作目录、关闭标准输入输出文件描述符、重新打开指定的文件描述符(包括将输出重定向到日志文件)。
孙子进程进入一个无限循环,在每次循环中执行一些操作。
这样创建的守护进程在后台运行,与终端无关,不接收终端输入,并且将输出写入日志文件或丢弃。
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> int main(void) { pid_t pid = 0; int i = 0; pid = fork(); if(pid > 0) { printf("pid : %d\n", getpid()); exit(0); } else if(pid == 0) { setsid(); printf("pid : %d\n", getpid()); if((pid = fork()) > 0) { printf("child = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid())); printf("groundson: %d\n", pid); exit(0); } else { umask(0); chdir("/"); close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); i += open("/dev/null", O_RDONLY); // 0 --> STDIN i += open("/home/book/linuxsystem/my.log", O_WRONLY); // 1 --> STDOUT i += open("/dev/null", O_RDWR); // 2 --> STDERR printf("child = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid())); while( 1 ) { // do something printf("i = %d\n", i++); sleep(1); fflush(stdout); } } } return 0; }
运行效果:
这里可以看出这个守护进程不属于任何一个终端,他会一直在后台运行下去,直到系统退出或者使用kill杀死进程。
总结
本篇文章就讲解到这里。