(七) 一起学 Unix 环境高级编程(APUE) 之 进程关系 和 守护进程

.

.

.

.

.

目录

(一) 一起学 Unix 环境高级编程(APUE) 之 标准IO

(二) 一起学 Unix 环境高级编程(APUE) 之 文件 IO

(三) 一起学 Unix 环境高级编程(APUE) 之 文件和目录

(四) 一起学 Unix 环境高级编程(APUE) 之 系统数据文件和信息

(五) 一起学 Unix 环境高级编程(APUE) 之 进程环境

(六) 一起学 Unix 环境高级编程(APUE) 之 进程控制

(七) 一起学 Unix 环境高级编程(APUE) 之 进程关系 和 守护进程

进程关系是《APUE》第三版的第九章,这章的内容对我们来说通常意义不大,因为它终篇贯穿着一个概念,叫做“终端”,而现在已经几乎无法见到真正的终端了。

但是不了解这章就无法解释第13章(守护进程)的内容,大家要了解终端和进程之间关系才能了解守护进程是个什么东西。

进程关系与守护进程这两章内容不多,而且有诸多关联,所以我们把这两章的内容放到一起讨论。

先来看看几个概念。

1.终端

真正意义上的终端是“笨设备”,只能接收命令的输入并返回结果。你问它 1+1=? 它也不知道,它只能把你的问题传给计算机,再把计算机返回的结果显示给你。

它出现在计算机既昂贵又庞大的年代。那时候的计算机昂贵到了只有一部分公司买得起、另一部分公司买不起,而且有些公司只能买一台,买第二台就要破产了的程度。

所以这么昂贵的设备如果只能给一个人使用太浪费了,于是为了让计算机可以被多人使用,就出现了终端这种设备。

2.会话(Session)

一次成功的终端登录就是一个会话。现在一次 shell 的成功登录,相当于那时候终端的成功登录。会话相当于是进程组的容器,它能承载一个或多个进程组。

3.进程组

进程组用来承载进程,一个进程组中有一个或多个进程,它是一个或多个进程的集合(也可以看作是容器)。一个进程不但拥有唯一的 PID,同时也属于一个进程组。

如何产生一个进程组呢?很简单:

1 # 使用管道可以用一条命令产生一个进程组。
2 ls | more

进程组分为前台进程组和后台进程组。

一个会话中只能有一个前台进程组,也可以没有前台进程组。

终端设备(如键盘)只能与前台进程通讯,不能与后台进程通讯,根据约定,如果终端设备与一个后台进程关联,就会杀掉这个后台进程。

什么是前台进程组呢?比如你正在使用 tar 命令进行打包的时候是无法再输入其它命令的。如果 tar 命令执行的时间很长,我们就会在命令后面添加一个 & 参数,把它放到后台去运行。

ps(1) 命令的 SID(Session ID)列 就是程序运行的会话 ID。

进程是先出现的,后来人们发现进程可以拆分为多个小任务分别执行,于是便出现了线程的概念,这个到后面线程的章节会详细讨论。

如今进程已经退化为容器了,它的存在就是为了承载线程。PID 看似是进程号,实际上是线程在消耗它。

进程和线程只是我们的说法,内核中只能看到线程,内核所谓的进程管理其实就是线程管理,内核永远以线程为单位执行任务。

总结来说:会话用来承载进程组,进程组用来承载进程,进程用来承载线程。

第九章了解这几个概念就差不多了,还记得我们前面提到的 myshell 吗,用 fork(2) + exec(3) + wait(2) 来实现一个可以执行外部命令的 shell。如果你想实现一个支持内部命令的 shell 那么可以仔细学习一下第九章的内容,shell 内部命令处理的主要知识点都在第九章。我们这里就不对第九章讨论得那么详细了,感兴趣的小伙伴可以自己看看书,有什么疑问可以加入博客上方标注的邮件列表讨论。

4.setsid(2)

1 setsid - create session and set process group ID
2
3 #include <unistd.h>
4
5 pid_t setsid(void);

创建一个会话并设置进程组的ID。这个函数是我们在第 9 章最有价值的函数,没有这个函数,我们后面就无法创建守护进程。

调用者不能是进程组组长,调用者在调用之后自动变为新进程组组长,并且脱离控制终端,进程 ID 将被设为进程组 ID 和会话 ID,所以守护进程通常 PID、PGID、SID 是相同的。通常的用法是父进程 fork(2) 一个子进程,然后子进程调用 setsid(2) 将自己变成守护进程,父进程退出即可。

5.守护进程

守护进程的栗子我就不写了,因为《APUE》第三版 P375 图13-1 已经有详细的代码了,我针对书上的栗子总结一下。

守护进程的特点:

1)脱离控制终端,ps(1) axj tty 为问号(?)。

2)是进程组的 leader,也就是 PID 和 PGID 相同。

3)通常没有父进程,由 1 号  init 接管。

4)创建了一个新会话,是 session 的 leader,所以 PID 与 SID 相同。

使用 ps(1) axj 命令查看,PID、PGID、SID 相同的进程就是守护进程。

守护进程也可以使用标准输出,但是不符合常理了,因为守护进程没有控制终端,所以守护进程一般会关闭或重定向标准输入输出流。

写守护进程的时候我们会切换工作路径,把它切换到一个一定会存在的路径,比如 /。因为假设你的守护进程是在一个可卸载设备(如U盘)上被启动的,如果不修改工作路径,该设备无法被卸载。

调用 umask(2) 是为了将文件模式创建掩码设置为一个已知值,因为通过继承得来的掩码可能会被设置为拒绝某些权限,如果守护进程中需要这些权限则要设置它。

对于书上的栗子,有两点要吐槽:

1)SIGHUP 信号用于通知服务进程软重启,比如修改了某服务的配置文件之后可以通过给服务进程发 SIGHUP 信号使它重新读取配置文件,所以如果没有特殊要求不必忽略该信号。

2)如果没有特殊要求,不必关闭所有的文件描述符,仅关闭标准输入、标准输出和标注错误即可。

6.系统日志

守护进程不应使用标准输出,那么当守护进程需要记录一些事件或者是错误的时候怎么办呢?那就要采用系统日志了。

系统日志一般保存在 /var/log/ 目录下,但是这个目录下的日志文件权限几乎都是只有 root 才能读写,那么普通用户的日志如何写入呢?这就需要借助系统日志函数来写日志了。

root 用户授权给 syslogd 服务专门写日志,然后其它程序都需要通过封装好的一系列函数调用 syslogd 服务来记录日志。这样就提高了日志的安全性了,可以防止日志文件被非法篡改。

1 closelog, openlog, syslog - send messages to the system logger
2
3 #include <syslog.h>
4
5 void openlog(const char *ident, int option, int facility);
6 void syslog(int priority, const char *format, ...);
7 void closelog(void);

openlog(3) 函数并非是打开日志文件,而是与 syslogd 服务建立链接,表示当前进程要写日志。

参数列表:

  ident:表明自己的身份,由程序员自行指定,写什么都行。

  option:要在日志中附加什么内容,多个选项用按位或链接。LOG_PID 是附加 PID,这个是最常用的。

  facility:消息来源。一般只能指定一个。

消息来源 含义
LOG_CRON 消息来自定时任务
LOG_DAEMON 消息来自守护进程
LOG_FTP 消息来自 FTP 服务
LOG_KERN 消息来自内核
LOG_USER 默认,常规用户级别消息

表1 facility 参数的常见需选项

syslog(3) 函数用于提交日志内容,参数列表:

  priority:优先级。详见下表:

级别 含义
LOG_EMERG 「严重」导致系统不可用的问题
LOG_ALERT 「严重」必须立即处理的情况
LOG_CRIT 「严重」临界条件
LOG_ERR 「严重」错误
LOG_WARNING 警告
LOG_NOTICE 正常
LOG_INFO 信息
LOG_DEBUG 调试

表2 日志优先级

以 LOG_ERR 为分界线,如果遇到了程序无法继续运行的问题,要报 LOG_ERR 以上的级别(包括 LOG_ERR)。

如果遇到的问题不会影响程序继续运行,报 LOG_ERR 以下级别的就可以了。

日志太多肯定对磁盘空间的要求就比较高,而且无用的日志太多会影响日志审计。日志文件中会记录哪些级别的日志是在配置文件中配置的,默认的情况是 LOG_DEBUG 以上级别的日志都会被记录。

  format:类似于 printf(3) 函数的格式化字符串。注意不要使用转义字符 \n,否则日志中会记录一个字符串"\n"而不是记录一个换行符。

  ...:format 中占位符的参数。

closelog(3) 表示日志写入结束。

7.单实例守护进程

有些守护进程需要在同一时间只能有一个实例在运行,它们称为单实例守护进程。它们在启动的时候会在 /var/run 下面创建一个进程锁文件,守护进程启动的时候会先判断这个锁文件是否存在,如果已存在就报错并退出,如果不存在就继续运行并创建一个锁文件,退出的时候再删除它。

守护进程如果想要开机自动启动,可以配置到自动启动脚本中:/etc/rc.d/rc.local

时间: 2024-10-09 23:43:14

(七) 一起学 Unix 环境高级编程(APUE) 之 进程关系 和 守护进程的相关文章

(十二) 一起学 Unix 环境高级编程 (APUE) 之 进程间通信(IPC)

. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编程 (APUE) 之 文件和目录 (四) 一起学 Unix 环境高级编程 (APUE) 之 系统数据文件和信息 (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境 (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制 (七) 一起学 Unix 环境高级编程 (APUE)

(十一) 一起学 Unix 环境高级编程 (APUE) 之 高级 IO

. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编程 (APUE) 之 文件和目录 (四) 一起学 Unix 环境高级编程 (APUE) 之 系统数据文件和信息 (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境 (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制 (七) 一起学 Unix 环境高级编程 (APUE)

(十三) [终篇] 一起学 Unix 环境高级编程 (APUE) 之 网络 IPC:套接字

. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编程 (APUE) 之 文件和目录 (四) 一起学 Unix 环境高级编程 (APUE) 之 系统数据文件和信息 (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境 (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制 (七) 一起学 Unix 环境高级编程 (APUE)

(九) 一起学 Unix 环境高级编程 (APUE) 之 线程

. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编程 (APUE) 之 文件和目录 (四) 一起学 Unix 环境高级编程 (APUE) 之 系统数据文件和信息 (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境 (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制 (七) 一起学 Unix 环境高级编程 (APUE)

(六) 一起学 Unix 环境高级编程(APUE) 之 进程控制

. . . . . 目录 (一) 一起学 Unix 环境高级编程(APUE) 之 标准IO (二) 一起学 Unix 环境高级编程(APUE) 之 文件 IO (三) 一起学 Unix 环境高级编程(APUE) 之 文件和目录 (四) 一起学 Unix 环境高级编程(APUE) 之 系统数据文件和信息 (五) 一起学 Unix 环境高级编程(APUE) 之 进程环境 (六) 一起学 Unix 环境高级编程(APUE) 之 进程控制 上一篇博文中我们讨论了进程环境,相信大家对进程已经有了初步的认识

(十) 一起学 Unix 环境高级编程 (APUE) 之 线程控制

. . . . . 之前我们在创建线程的时候都是使用的默认属性,本章主要讨论的是自定义线程的属性. 使用默认属性基本上能解决掉遇到的大部分问题,所以自定义属性在实际项目中用得比较少. 1.线程属性 <APUE>第三版 P341 表中的属性可以用来限定一个进程能创建线程的最大数量,但是限定线程数量的宏不必太当真,因为在上一篇博文中我们说过了一个线程能创建的线程的数量是受很多因素影响的,并非一定是以这几个宏值为准的. 线程属性使用 pthread_attr_t 类型表示. 1 #include &

Unix 环境高级编程 (APUE) 之 网络 IPC:套接字

一起学 Unix 环境高级编程 (APUE) 之 网络 IPC:套接字 . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编程 (APUE) 之 文件和目录 (四) 一起学 Unix 环境高级编程 (APUE) 之 系统数据文件和信息 (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境 (六) 一起学 Unix 环境高级编程 (APU

《Unix环境高级编程》读书笔记 第7章-进程环境

1. main函数 int main( int argc, char *argv[] ); argc是命令行参数的数目,包括程序名在内 argv是指向参数的各个指针所构成的数组,即指针数组 当内核执行C程序时(使用exec函数),在调用main前先调用一个特殊的启动例程.可执行程序文件将此启动例程指定为程序的起始地址——这是由连接器设置的,而连接器则是由C编译器调用.启动例程从内核取得命令行参数和环境变量值,然后按上述方式调用main函数做好安排. 2. 进程终止 有8种方式使进程终止,其中5种

《Unix环境高级编程》读书笔记 第8章-进程控制

1. 进程标识 进程ID标识符是唯一.可复用的.大多数Unix系统实现延迟复用算法,使得赋予新建进程的ID不同于最近终止所使用的ID ID为0的进程通常是调度进程,也常被称为交换进程.它是内核的一部分,是系统进程. ID为1的进程通常是init进程,在自举过程结束时由内核调用.该进程负责在内核自举后启动一个Unix系统,它决不会终止,是一个普通的用户进程,但以超级用户特权运行. ID为2的进程是页守护进程,负责支持虚拟存储器系统的分页操作. #include <unistd.h> pid_t