第十三章-守护进程

守护进程(daemon)是生存周期长的一种进程。常在系统导入时启动,仅在系统关闭时终止。因为它们没有控制终端,所以是在后台运行。

一、守护进程的特征

ps命令打印系统中各个进程的状态,该命令有多个选项。

ps -axj
//ps -efj

-a 显示由其他用户拥有的进程的状态
-x 显示没有终端的进程的状态
-j 显示与作业有关的信息:会话ID、进程组ID、控制终端以及终端进程组ID

系统进程依赖于操作系统实现。父进程ID为0的各进程通常是内核进程,它们作为系统引导装入过程的一部分而启动(init是个例外,它是一个由内核在引导装入时启动的用户层次的命令)。内核进程通常存在于系统的整个生命周期中,以root特权运行,无控制终端,无命令行。

对于需要在进程上下文执行工作但却不能被用户进程上下文调用的每一个内核组件,通常有它自己的内核守护进程。如,在Linux中:

  • kswapd守护进程,也称为内存换页守护进程。它支持虚拟内存子系统在经过一段时间后将脏页面慢慢写回磁盘来回收这些页面
  • flush守护进程,在可用内存达到设置的最小阈值时将脏页面冲洗至磁盘。它也定期地将脏页面冲洗回磁盘来减少在系统出现故障时发生的数据丢失
  • sync_supers守护进程,定期将文件系统元数据冲洗至磁盘
  • jbd守护进程,帮助实现ext4文件系统中的日志功能

用户级守护进程?

  • init守护进程,主要负责启动各层次特定的系统服务,这些服务通常是在它们自己拥有的守护进程的帮助下实现的
  • rsyslogd守护进程,可以被由管理员启用的将系统消息记入日志的任何程序使用
  • inetd守护进程,侦听系统网络接口,以便取得来自网络的对各种网络服务进程的请求
  • cron守护进程,在定期安排的日期和时间执行命令。许多系统管理任务是通过cron每隔一段时间就运行相关程序而得以实现的

注:大多数守护进程都以root特权运行,所有的守护进程都没有控制终端。用户层守护进程的父进程是init进程

二、编程规则

在编写守护进程程序时要遵循一些基本规则,以防止产生不必要的交互作用。
(1)调用umask将文件模式创建屏蔽字设置为一个已知值(通常是0)
(2)调用fork,然后使父进程exit。第一,如果该守护进程是作为一条简单的shell命令启动的那么父进程终止会让shell认为这条命令已经执行完毕;第二,虽然子进程继承了父进程的进程组ID,但获得了一个新的进程ID,这就保证了子进程不是一个进程组的组长进程,这是下面将要进行setsid调用的先决条件
(3)调用setsid创建一个新会话。并使调用进程:(a)成为新会话的首进程(b)成为一个新进程组的组长进程(c)没有控制终端
(4)将当前工作目录更改为根目录。因为从父进程处继承过来的当前工作目录可能在一个挂载的文件系统中,导致该文件系统不能被卸载(或者改到指定位置,在此位置完成它们的全部工作)。
(5)关闭不再需要的文件描述符。
(6)某些守护进程打开/dev/null(黑洞)使其具有文件描述符0、1、2,这样,任何一个试图读标准输入、写标准输出或标准错误的行为不会产生效果

以下函数可由一个想要初始化为守护进程的程序调用:

#include "apue.h"
#include 
#include 
#include 

void daemonize(const char* cmd)
{
     
    int i, fd0, fd1, fd2;
    pid_t pid;
    struct rlimit r1;
    struct sigaction sa;

    //清除文件创建屏蔽字
    umask(0);

    //获取文件描述符最大数量
    if(getrlimit(RLIMIT_NOFILE, &r1) < 0)
        fprintf(stderr, "can't get file limit\n");

    //成为会话首进程,丢弃控制终端
    if(pid = fork() < 0)
        fprintf(stderr, "can't fork\n");
    else if(pid != 0)
        exit(0);

    setsid();

    /*屏蔽信号,确保之后不会申请控制终端*/
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0; //设为0表示默认属性,可屏蔽正在处理的信号
    //忽略SIGHUP信号
    if(sigaction(SIGHUP, &sa, NULL) < 0)
        fprintf(stderr, "can't ignore SIGHUP\n");

    if(pid = fork() < 0)
        fprintf(stderr, "can't fork\n");
    else if(pid != 0)
        exit(0);

    //改变当前工作目录
    if(chdir("/") < 0)
        fprintf(stderr, "can't fork\n");
    //关闭所有描述符
    if(r1.rlim_max == RLIM_INFINITY)
        r1.rlim_max = 1024;
    for(int i = 0; i < r1.rlim_max; i++)
        close(i);

    //将文件描述符0,1,2绑定到/dev/null(黑洞)
    fd0 = open("/dev/null", O_RDWR);
    fd1 = dup(0);
    fd2 = dup(0);

    //初始化日志文件
    openlog(cmd, LOG_CONS, LOG_DAEMON);
    if(fd0 != 0 || fd1 != 0 || fd2 != 0){
     
        syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
        exit(1);
    }
}

三、出错记录

守护进程存在的一个问题是如何处理出错消息。因为它本来就不应有控制终端,所以不能只是简单地写到标准错误上;我们也不希望每个守护进程将它自己的出错消息写到一个单独的文件中。所以,需要一个集中的守护进程出错记录设施——syslog。
第十三章-守护进程_第1张图片
有以下3种产生日志消息的方法:
(1)内核例程可以调用log函数。任何一个用户进程都可以通过open并read设备/dev/klog来读取这些消息
(2)大多数用户进程(守护进程)调用syslog函数来产生日志消息。
(3)无论一个用户进程是在此主机上,还是在通过TCP/IP网络连接到此主机的其他主机上,都可将日志消息发送到UDP端口514。注:syslog函数不产生这些数据报,它们要求产生此日志消息的进程进行显示的网络编程。

#include 
void openlog(const char* ident, int option, int facility);
void syslog(int priority, const char* format, ...);
void closelog(void);
int setlogmask(int maskpri);

四、单实例守护进程

五、守护进程的惯例

六、客户进程-服务器进程模型

你可能感兴趣的:(#,APUE读书笔记,linux,进程)