[UNIX环境高级编程]阅读第十三章 守护进程

1 简单了解

守护进程(daemon)是生存期长的一种进程。
常常在系统引导装入时启动,仅在系统关闭时才终止。
因为守护进程没有控制终端,所以说它们是在后台运行的。
UNIX系统有很多守护进程,它们执行日常事务活动。

2.守护进程的特征

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

UID:用户ID、进程ID、父进程ID、进程组ID、会话ID、终端名称以及命令字符串。

# ps -ajx
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    0     1     1     1 ?           -1 Ss       0  26:22 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
    0     2     0     0 ?           -1 S        0   0:00 [kthreadd]
    2     4     0     0 ?           -1 S<       0   0:00 [kworker/0:0H]
    2     6     0     0 ?           -1 S        0   4:56 [ksoftirqd/0]
    2     7     0     0 ?           -1 S        0   1:30 [migration/0]
    2     8     0     0 ?           -1 S        0   0:00 [rcu_bh]
    2     9     0     0 ?           -1 S        0  47:49 [rcu_sched]
    2    10     0     0 ?           -1 S<       0   0:00 [lru-add-drain]
    2    11     0     0 ?           -1 S        0   0:36 [watchdog/0]
    2    12     0     0 ?           -1 S        0   0:32 [watchdog/1]
    2    13     0     0 ?           -1 S        0   1:29 [migration/1]
    2    14     0     0 ?           -1 S        0   4:55 [ksoftirqd/1]
    2    16     0     0 ?           -1 S<       0   0:00 [kworker/1:0H]

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

在ps的输出实例中,内核守护进程的名字出现在方括号中。
这个版本的Linux使用一个名为kthreadd的特殊内核进程来创建其它内核进程,所以kthreadd表现为其它内核进程的父进程。

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

kswapd守护进程也称为内存换页守护进程。它支持虚拟内存子系统在经过一段时间后将脏页面慢慢地写回磁盘来回收这些页面。

进程1通常是init。它是一个系统守护进程,除了其他工作外,主要负责启动各运行层次特定的系统服务。这些服务器通常是在它们自己拥有的守护进程的帮助下实现的。

rpcbind守护进程提供将远程过程调用(Remote Procedure Call,RPC)程序号映射为网络端口号的服务。rsyslogd守护进程可以被有管理员启用的将系统消息计入日志的任何程序使用。可以在一台实际的控制台上打印这些消息,也可将它们写到一个文件中。

3 编写守护进程的规则

编写守护进程程序时需要遵循一些基本规则,以防止产生不必要的交互作用。

  1. 首先要做的是调用umask将文件模式创建屏蔽字设置为一个已知值(通常是0)
    由继承得来的文件模式创建屏蔽字可能会被设置为拒绝某些权限。
    如果守护进程要创建文件,那么它可能要设置特定的权限。例如,若守护进程要创建组可读、组可写的文件,继承的文件模式创建屏蔽字可能会屏蔽上述两种权限中的一种,而使其无法发挥作用。另一方面,如果守护进程调用的库函数创建了文件,那么将文件模式创建屏蔽字设置位一个限制性更强的值(如007)可能会更明智,因为库函数可能不允许调用者通过一个显示的库函数参数来设置权限。
  2. 调用fork,然后使父进程exit。这样做实现了下面几点。第一,如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止会让shell认为这条命令已经执行完毕。第二,虽然紫禁城继承了父进程的组ID,但获得了一个新的进程ID,这就保证了子进程不是一个进程组的组长进程。这是下面将要进行的setsid调用的先决条件。
  3. 调用setsid创建一个新会话。创建会话时经过三个阶段,(1)该进程变成新会话的会话首进程(session leader,会话首进程是创建该会话的进程)。此时,该进程是新会话中的唯一进程。(2)该进程成为一个新进程组的组长进程。新进程组ID是调用该进程的进程ID。(3)该进程没有控制终端。如果在调用setsid之前该进程有一个控制终端,那么这种联系也被切断。
  4. 将当前工作目录改为根目录。从父进程处继承过来的当前工作目录可能在一个挂载的文件系统中。因为守护进程通常在系统再引导之前是一直存在的,所以如果守护进程的当前工作目录在一个挂载文件系统中,那么该文件系统就不能被卸载。
    或者,某些守护进程还可能会把当前工作目录更改到某个指定位置,并在此位置进行它们的全部工作。例如,行式打印机假脱机守护进程就可能将其工作目录更改到它们的spool目录上。
  5. 关闭不再需要的文件描述符。这使守护进程不在持有从其父进程继承来的任何文件描述符(父进程可能是shell进程,或某个其他进程)。可以使用open_max函数或getrlimt函数来判定最高文件描述符值,并关闭直到该值的所有描述符。
  6. 某些守护进程打开/dev/null使其具有文件描述符0、1和2,这样,任何一个视图读标准输入、写标准输出或标准错误的库例程都不会产生任何效果。因为守护进程并不与终端设备相关联,所以其输出无处显示,也无从从交互式用户那里接收输入。即使守护进程是从交互式会话启动的,但是守护进程是在后台运行的,所以登录会话的终止并不影响守护进程。如果其他用户在同一终端设备上登录,我们不希望在该终端上见到守护进程的输出,用户也不期望他们在终端上的输入被守护进程读取。

阅读书中代码:

void daemonize(const char *cmd)
定义int 型变量 i,fd0,fd1,fd2
定义pid类型变量,pid

定义rlimit结构体 r1
/include/uapi/linux/resource.h(用于linux的资源控制/计算的头文件)源码中对rlimit的定义
struct rlimit {
	__kernel_ulong_t	rlim_cur;
	__kernel_ulong_t	rlim_max;
};
对的定义__kernel_ulong_t:typedef unsigned long	__kernel_ulong_t;	#本质是unsigned long类型

清除文件创建屏蔽字 umask(0),将其置为0
得到文件描述符的极限值
成为一个会话的首进程,并关闭控制终端
确保未来打开时不会被分配控制终端
改变当前工作目录到root,这样就不会阻止文件系统被加载
关闭所有的文件描述符
将文件描述符0、1和2附加到/dev/null
初始化日志文件

3.守护进程出错记录

守护进程没有控制终端,所以不能写到标准错误上
不能将所有守护进程都写到控制台设备上
不能每个守护进程写一个日志文件

需要一个集中的守护进程出错记录设施,BSD的syslog

资料:
终端、Shell、tty 和控制台(console)有什么区别?

在线Linux源码阅读网站
https://blog.csdn.net/scanf_linux/article/details/82625145

Linux rlimit 函数详解
https://blog.csdn.net/rikeyone/article/details/88798384

你可能感兴趣的:(UNIX环境高级编程)