本篇文章是阅读《UNIX网络编程卷1》的第13章的学习笔记,主要介绍了守护进程以及inetd超级服务器,本篇学习笔记还结合了《UNIX环境高级编程》中第9章和第13章的内容,从一些概念开始例如进程组、会话等等,接着给出创建守护进程的步骤以及代码示例、另外还会介绍LInux的日志系统、inetd守护进程作用流程。
终端登录流程
控制终端指的是键盘、显示器等等,当我们使用控制终端登录系统的时候,终端登录流程如下:
- 当系统启动时,内核创建进程ID为1的进程也就是init进程,init进程使系统进入多用户状态。init进程根据配置文件/etc/inittab确定需要打开哪些终端,对每一个允许登录的终端设备,init调用一次fork,它所生成的子进程则执行getty(exec)程序
- getty为终端设备调用open函数,以读写方式将终端打开,然后getty输出“longin:”之类的信息,并等待用户键入用户名
- 当用户键入用户名后,getty工作完成。然后调用login程序:execle(“/bin/login”,”login”,”-p”,username,(char *)0,envp)
- 如果用户正确登录,login将会完成如下工作:
- 将当前工作目录更改为该用户的起始目录
- 调用chowm更改该终端的所有权,使登录用户成为他的所有者
- 将对该终端设备的访问权限更改为用户读和写
- 调用setgid及initgrops设置进程的组ID
- 用login得到的所有信息初始化环境,起始目录,shell、用户名
- login进程更改为用户登录的用户ID,并调用该用的登录shell
进程组、会话
进程组
每个进程除了有一个进程id之外,还属于一个进程组,进程组是一个会或多个机进程的集合,同一个进程组的各进程接受来自同一个终端的各种信号,每一个进程组有一个唯一的进程组ID,函数getpgrp返回调用者进程的进程组ID
#include <unistd.h>
pid_t getpgrp(void);
每个进程组都有一个组长进程,组长进程的进程组ID等于该进程ID,只要该进程组中有一个进程存在,则该进程组就存在,与其组长进程是否终止无关。
进程调用setpgid函数可以加入一个现有的进程组或者创建一个新的进程组
#include <unistd.h>
int setpdid(pid_d pid, pid_t pgid);
- setpdid函数将pid进程的进程组ID设置为pgid
- 如果这两个参数相等,则有pid指定的进程变成进程组组长,如果pid=0,则使用调用者的进程ID;pgid=0,则由pid指定的进程ID用作进程组ID
- 一个进程只能为它自己或者它的子进程设置进程组ID
会话
会话是一个或多个进程组的集合,关系如图所示:
进程调用setsid创建一个新的会话
#include <unistd.h>
pid_t setsid(void)
如果调用该函数的进程不是一个进程组的组长,则此函数会创建一个新的会话:
- 该进程变成新会话的会话首进程
- 该进程成为一个新进程组的组长
- 该进程没有控制终端,如果在调用setsid之前该进程有控制终端,那么这种联系将会被切断
守护进程
守护进程是生存期长的一种进程,通常在系统装入时启动,仅在系统关闭时才终止,因为它们没有控制终端,所以它们是在后台运行的,使用ps -axj
打印系统总各个进程的状态,-x表示显示没有控制终端的进程状态
在ps的输出中,内核守护进程的名字出现在方括号内,该版本的Linux使用一个名为kthreadd的特殊进程来创建其他内核进程,守护进程的特点就是在后台运行,并且没有控制终端,并且大多数守护进程都以超级用户特权运行
编程规则
创建一个守护进程需要一下几个步骤:
- 执行一个fork(),之后父进程退出,子进程继续执行,这样做的结果就是demaon成为了init的子进程,原因有两点:一是加入deamon是从命令行的一条简单的shell启动的,那么让父进程退出可以让shell认为这条命令已经执行结束,二是虽然子进程继承了父进程的进程组ID,但是子进程不是一个进程组的组长,而调用setsid的先决条件就是不是一个进程组的组长
- setsid(),创建一个新的会话,结果有三个,(a)成为新会话的首进程,(b)成为一个新进程组的组长进程,©没有控制终端
- 调用umask将文件模式创建屏蔽字设置为一个已知的值,通常是0,如果守护进程要创建文件,那么它可能要设置特定的权限
- 将当前工作目录更改为根目录,从父进程继承过来的当前工作目录可能挂载在一个文件系统中,因为守护进程在系统关闭之前是一直存在的,如果守护进程的当前目录挂载在文件系统中,会导致该文件系统不能被卸载
- 关闭不在需要的文件描述符,这让守护进程不在持有从其父进程继承而来的任何文件描述符
- 调用openlog,使用syslogd来处理错误
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <syslog.h>
void start_daemon() {
pid_t pid;
const char *pname = "daemon";
if((pid = fork()) < 0) {
printf("Error: Fork");
return;
}else if(pid) {
exit(0);
}
if(setsid() < 0){
printf("Error: setsid");
return;
}
if((pid = fork()) < 0) {
printf("Error: Fork");
return;
}else if(pid){
exit(0);
}
umask(0);
chdir("/");
//关闭文件描述符
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
openlog(pname, LOG_PID, LOG_USER);
//执行核心任务
while(1){
printf("daemon start");
sleep(1);
}
return;
}
int main(void) {
start_daemon();
}
syslog函数
syslogd守护进程是Linux用来记录设备日志的进程,由于守护进程没有控制终端,就不能将消息print到stderr上,所以从守护进程中登记消息的常用技巧就是使用syslog函数
#include <syslog.h>
void syslog(int priority, const char *message)
其中priority是级别(level)和设施(facility)两者的结合,level的值如下:
facility的值如下:
参考链接
《Unix网络编程》
《Unix环境编程》