该系列文章为《Linux/Unix系统编程手册》的学习笔记,由于该书太过冗长,属于工具书的类别,这里对书中的一些核心内容加以提炼和整理。 书中的编程练习这里不做展示和说明。
概念
- 进程组:一组相关进程的集合。作业即进程组。
- 进程组ID:PGID,创建进程组的进程为进程组首进程,这个进程的PID为进程组ID,子进程会继承父进程的进程组ID。
- 会话:一组进程组的集合。
- 会话ID:SID,创建会话的进程为会话首进程,其PID为会话ID,子进程会继承父进程的会话ID。
- 控制终端:会话首进程首次打开一个的终端设备,一个终端设备最多可以成为一个一个会话的控制终端,拥有控制终端的进程可以通过一个特殊的设备文件
/dev/tty
获取控制终端的文件描述符。 - 前台进程组:在任意时刻,会话中的一个进程组会成为终端的前台进程组,其余进程组为后台进程组。只有前台进程组中的进程才能从终端中读取输入,在控制中端中键入字符产生的信号也只会发给前台进程组中的进程。
- 终端控制进程:建立控制终端后,会话的首进程成为该终端的控制进程。
- shell:具有特殊用途的程序,用于读取用户输入的命令以执行相应的程序响应命令,又称为命令解释器。
- 登陆shell:用户登陆系统时,由系统创建用于运行shell的进程。
当登陆一台Linux服务器后,登陆shell
成为登陆会话
的首进程和终端的控制进程,后续执行的每一个命令都会导致一个或多个进程创建,shell会把这些进程放在一个新进程组中,这些进程组都是进程会话的一部分。
与作业控制相关的信号:
- SIGINT:终端中断,键入CTRL+C字符生成。
- SIGQUIT:终端退出,键入CTRL+\生成。
- SIGSTP:中断挂起,键入CTRL+Z生成。
- SIGHUP:当控制进程断开与终端的连接时内核会向控制进程发送此信号。
- SIGTTIN:后台进程组尝试从终端读取字符串时会受到此信号,默认停止作业。
- SIGTTOU:后台进程组尝试向设置了TOSTOP(终端输出停止)标识的终端输出内容时收到此信号,默认停止作业。
进程组与会话行为
进程组
int setpgid(pid_t pid, pid_t pgid);
setpgid
系统调用用于将进程id为pid
的进程的进程组id变更为pgid
。
- pid若为0则会将该值替换为当前进程的pid。
- pgid若为0会以pid的值创建新的进程组,并将pid进程设置为进程组首进程。
改变进程组id存在限制:
- pid仅可以指定当前进程及其子进程。
- pid指定的进程不能使会话首进程。
- 子进程如果已经执行了
exec
,则不能被修改进程组id了。 - pgid仅可以指定相同会话内的pgid,创建的新进程组也属于当前会话。
会话
pid_t setsid(void);
setsid
系统调用将会创建新会话:
- 调用进程将成为会话首进程以及会话中新进程组的首进程。
- 调用进程没有控制终端。之前与控制终端的连接会被断开。
限制:
- 进程组首进程不能创建新会话。避免该限制的方法为fork出一个子进程并让父进程终止,之后由子进程调用
setsid
,由于子进程会继承父进程的PGID,因此子进程不可能是进程组首进程。
只有会话首进程能够打开终端设备并建立控制终端,之后次进程成为终端的控制进程,若控制进程终止:
- 会话中的所有进程会失去与终端的关联关系。
- 终端可以被其他会话的首进程控制。
- 内核会向前台进程组的所有成员发送SIGHUP信号。
大多数会话的首进程都是shell,shell为SIGHUP信号设置了信号处理程序,会关闭shell并向由这个shell创建的所有进程组成员发送SIGHUP信号。
作业控制
作业控制是shell的特性,允许在一个shell上同时执行多个命令(即作业/进程组),其中一个命令在前台运行,其余命令在后台运行。作业可以被停止或恢复运行以及在前后台之间来回切换。
➜ ~ sleep 3 &
[1] 10428
在命令后加&
可使进程组在后台运行,同时shell上会显示两个数字,方框内的数字为shell赋予每个作业的作业号num,后面的数字为执行命令的进程id或管道内最后一个进程的进程id。
作业控制命令
在shell的作业控制中用%num
来引用某个作业
jobs
:列出所有的后台作业fg %num
:将某个后台作业移动到前台Type Ctrl+Z
:键入Ctrl+Z挂起字符会向前台进程组发送SIGTSTP信号停止前台进程组。bg %num
:向某个作业发送SIGCONT信号恢复执行stty tostop
:设置TOSTOP(终端输出停止)标识
Daemon进程
概述
Deamon进程应具备以下特征:
- 生命周期很长:一般会在系统启动时创建并一直运行到系统关闭。
- 在后台运行并且不拥有控制终端:终端缺失保证内核永远无法为Deamon进程生成终端相关信号,如SIGINT、SIGTSTP和SIGHUP。
Linux常用Deamon进程:
- cron:定时任务。
- sshd:安全shell,允许远程主机使用安全通信协议登录系统。
- httpd:http服务器deamon。
- inetd:Internet超级服务器deamon,监听指定TCP/IP端口上的网络连接并启动响应服务器程序处理这些链接。
创建流程
- 执行
fork()
,之后父进程退出,子进程继续运行。(防止进程为进程组首进程,确保其可以创建会话) - 调用
setsid()
开启新会话。(使进程断开所有控制终端) - 第二次调用
fork()
,父进程退出,子进程继续运行。(防止进程为会话首进程,确保其不会控制任何终端) - 清除进程的umask。 (确保拥有创建文件和目录的权限)
- 将进程的工作目录修改为根目录(/)。(确保当前工作目录无法被卸载)
- 关闭从父进程继承的打开的文件描述符,关闭指向终端的0、1、2文件描述符。
- 打开
/dev/null
,并使用dup2
将0、1、2文件描述符指向这个设备。(确保在0、1、2上的io库函数不会失败,也防止使用这些文件描述符打开其他的文件)
重新初始化Deamon
由于Deamon永远不会建立控制终端,因此不会收到SIGHUP信号,Deamon可以选择为SIGHUP信号建立信号处理函数来重新初始化自身。
syslog
Deamon程序编写的一个问题是如何显示错误信息,Linux提供了一个集中日志工具,系统中的所有应用程序都可以使用这个工具来记录日志信息。syslog包含两个组件:
- syslogd deamon:从两个不同的源接收日志,一个UNIX domain socket
/dev/log
,保存本地产生的消息;另一个Internet domain socket(UNP端口514),保存通过TCP/IP发送的消息。 - syslog(3) 函数:供进程调用来记录消息,会使用传入的参数以标准格式构建一条消息,将其写入
/dev/log
socket供syslogd读取。
由syslogd处理的消息包括两个特性:
- facility:指定了消息产生的程序类型
- level:指定了消息的严重程度(优先级)
syslogd根据这两个特性,以及配置文件/etc/syslog.conf
中的指令将消息传递到几个目录中的一个或多个:
- 终端或虚拟控制台
- 磁盘文件
- FIFO
- 一个或多个登陆过的用户
- 位于另一个系统上的通过TCP/IP网络连接的进程
API
void openlog(const char *ident, int log_options, int facility);
openlog()
会建立一个到系统日志的连接,调用是可选的,如果没有建立连接,在调用syslog()
时会使用默认设置建立连接。
ident
为一个指向字符串的指针,syslog()
输出的每个消息都会包含这个字符串。facility
的默认值为LOG_USER
,表示用户进程生成的消息。
void syslog(int priority, const char *format, ...);
syslog()
会记录一条日志消息。
priority
是facility
和level
的OR值,如果省略这个参数,会采用前一个openlog()
调用中指定的facility
值,如果没有调用或没有facility
参数时会设置为LOG_USER
。format
参数是一个格式字符串。在调用syslog
时如果使用参数传入字符串需要在前面先指定%s
参数,如syslog(priority, %s, user_string)
,而不应该是syslog(priority, user_string)
。
void closelog(void);
closelog()
会关闭到系统日志的连接并释放分配给/dev/log
socket的文件描述符。deamon进程通常缺失这个调用。
int setlogmask(int mask_priority);
setlogmask()
会设置一个掩码,所有level不在这个掩码中的消息都会被过滤。有两个宏可以将level替换为mask_priority
参数,分别是LOG_MASK
与LOG_UPTO
。
配置文件
/etc/syslog.conf
配置文件控制了syslogd deamon的行为,配置规则如下
facility.level action
下面是一个例子,表示所有level为err或者更高级别的消息被发送到/dev/tty10
控制台设备上。
*.err /dev/tty10
在修改配置文件后需要发送SIGHUP信号来初始化syslogd。
$ killall -HUP syslogd
(End)