当前位置:IT培训 > 华清远见教育科技集团 > 嵌入式学习 > 讲师博文 > 守护进程解析
守护进程解析
时间:2018-03-09作者:华清远见

一:守护进程的简介我们常用的进程一般分为三类:<1>交互进程 <2>批处理进程<3>守护进程。守护进程(Daemon)是一种运行在后台的一种特殊的进程,它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。守护进程也叫精灵进程。由于在linux中,每个系统与用户进行交流的界面成为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端被称为这些进程的控制终端,当控制终端被关闭的时候,相应的进程都会自动关闭。但是守护进程却能突破这种限制,它脱离于终端并且在后台运行,并且它脱离终端的目的是为了避免进程在运行的过程中的信息在任何终端中显示并且进程也不会被任何终端所产生的终端信息所打断。它从被执行的时候开始运转,知道整个系统关闭才退出(当然可以认为的杀死相应的守护进程)。如果想让某个进程不因为用户或中断或其他变化而影响,那么就必须把这个进程变成一个守护进程。

二:相关概念

查看守护进程第一行的信息。ps axj | head -1

ubuntu@farsight:/etc/profile.d$ ps axj | head -1

PPID PID PGID SID TTY TPGID STAT UID TIME

COMMAND

父进程ID 进程ID 进程组ID 会话期ID 终端ID 终端进程组ID 状态 用户 运行时间

指令

1:进程组

每运行一个程序或是命令就会产生一个进程组,而每一个进程组有一个组长进程。

进程组由进程组号(GID)标识,进程组号(GID)为组长进程PID,一般进程组的第一个进程是组长进程。

注:进程组中的这些进程之间不是孤立的,他们彼此之间或者存在者父子、兄弟关系,或者在功能有相近的联系。

那linux为什么要有进程组呢?其实提供进程组就是方便管理这些进程。 进程组的概念有很多用途,最常见的是我们在终端上向前台执行程序发出终止信号(Ctrl-C),同时终止整个进程组的所有进程。

每个进程必定属于一个进程组,也只能属于一个进程组。

函数getpgrp可以返回调用进程的进程组ID
#include <unistd.h>

pid_t getpgrp(void); //得到进程组的ID

返回值:成功则返回进程组ID,失败返回-1。

2:会话

一次登录形成一个会话,一个会话可包含多个进程组(前台或后台), 但只能有一个前台进程组,多个进程组组成我们的会话。

setsid()可建立一个新的会话,注意进程组的组长进程不能调用,调用进程是新会话的首进程(session leader) 3:控制终端

会话的首进程(session leader)打开一个终端之后, 该终端就成为该会话的控制终端

与控制终端建立连接的会话首进程称为控制进程,一个会话只能有一个控制终端

在控制终端上产生的输入和信号将发送给会话的前台进程组中的所有进程

终端上的连接断开时 (比如网络断开或 Modem 断开), 挂起信号将发送到控制进程

守护进程

linux是一个多用户多任务的分时操作系统,必须要支持多个用户同时登陆同一个操作系统,当一个用户登陆一次终端时就会产生一个会话。

 

每个会话有一个会话首进程,即创建会话的进程,建立与终端连接的就是这个会话首进程,也被称为控制进程。
一个会话可以包括多个进程组,这些进程组可被分为一个前台进程组和一个或多个后台进程组。

为什么要这么分呢?前台进程组是指需要与终端进行交互的进程组(只能有一个比如有些进程是需要完成IO操作的,那么这个进程就会被设置为前台进程组.当我们键入终端的中断键和退出键时,就会将信号发送到前台进程组中的所有进程。而后台进程组是指不需要与终端进程交互的进程组,比如:一些进程不需要完成IO 操作,或者一些守护进程就会 被设置为后台进程组(可以有多个)。

如果终端接口检测到网络已经断开连接,则会将挂断信号发送给会话首进程。

三:创建守护进程

创建一个守护进程的步骤如下:

<1>创建子进程,父进程退出

这是创建守护进程的第一步。由于守护进程是脱离控制终端的,因此,完成第一步后就会在Shell终端里造成一程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离。在Linux 中父进程先于子进程退出会造成子进程成为孤儿进程,而每当系统发现一个孤儿进程是,就会自动由1号进程(init)收养它,这样,原先的子进程就会变成init进程的子进程。

<2>在子进程中创建的新会话 [脱离控制终端]

Linux是一个多用户多任务系统,每个进程都有一个进程ID,同时每个进程还都属于某一个进程组,而每个进程组都有一个组长进程,组长进程的标识ID等于进程组的ID,且该进程组ID不会因组长进程的退出而受到影响。会话期是一个或多个进程组的集合,通常,一个会话开始与用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。我们这里要用到setsid()函数。 setsid()函数的作用:创建一个新的会话,并且担任该会话组的组长。具体作用包括:让一个进程摆脱原会话的控制,让进程摆脱原进程的控制,让进程摆脱原控制终端的控制。

pid_t setsid(void);

(1)此进程变成该对话期的首进程。

(2)此进程变成一个新进程组的组长进程。

(3)此进程没有控制终端,如果在调用setsid前,该进程有控制终端,那么与该终端的联系被解除。

(4)这个系统调用,只能由非组长进程来调用。组长进程不能调用。

思考:有了前边两个步骤,有没有人有这样的疑问,为什么要创建个子进程然后父进程退出,然后setsid()脱离当前会话?直接就在之前的进程中setsid()把当前进程脱离会话不可以么?

答案:组长进程,一般是这个程序执行的第一个进程,那么我们的父进程一般就是这组进程的组长,而setsid()这个函数的使用,是不能由组长进程来调用的。所以必须先创建一个非组长进程来调用它。

<3>改变进程的工作目录到"/"

使用fork创建的子进程继承了父进程的当前工作目录。

守护进程不应当使用父进程的工作目录,应该设置自己的工作目录,通常可以通过 chdir()来完成,一般可以将其设置为根目录。 chdir("/");

<4>重设文件掩码 umask(0)

守护进程从父进程继承来的文件创建方式掩码可能会拒绝设置某些许可权限,文件权限掩码是指屏蔽掉文件权限中的对应位。 umask(0);

<5>关掉所有不需要的文件描述符号

如果创建它的进程之前打开了某个文件,然后创建这个守护进程,这样子进程就继承了 fd,如果守护进程不关闭这个fd,一个是会占用资源,二个与改变工作目录一样,如果这个文件是位于挂载目录,那么就无法umount了。

这里可以关闭所有当前系统的文件描述符。怎么获取当前进程最大的可以打开的文件描述符个数呢?sysconf(_SC_OPEN_MAX);

<6>重新处理一下[0,1,2]==>标准输入、标准输出、标准出错。

这里需要把0,1,2重新定位一下,定位到/dev/null。这样后边再使用open的时候,获得的文件描述符还可以从3开始。不然就是从0开始的,而如果我们程序中,有从0中获取标准输入,从1中做输出,那么就成了对那个文件的读写了,这显然是错误的了。

把/dev/null看作"黑洞"。 它非常等价于一个只写文件, 所有写入它的内容都会永远丢失,而尝试从它那儿读取内容则什么也读不到.。
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>

int main(int argc, const char *argv[])

{ pid_t pid = fork();

if (pid ==-­1)

{

perror("fork()");

exit(-­1);

}

else if (0 == pid)

{

//当前是子进程

//1.重新设置会话

if (setsid() < 0)

{

perror("setsid()");

exit(­-1); }

//2.把工作目录设置到根目录

chdir("/");

//3.重新设置文件权限掩码 umask(0);

//4.关闭所有文件描述符 int i;

for (i = 1; i < sysconf(_SC_OPEN_MAX); i++)

close(i);

//5. 特殊处理0,1,2文件描述符

open("/dev/null", O_RDWR);

open("/dev/null", O_RDWR);

open("/dev/null", O_RDWR);

}

else { /

/当前是父进程,直接退出

return 0;

}

return 0;

}

拓展:守护进程创建之后,那么中间的调试信息不能通过屏幕输出出来了,那么我们看不到中间的过程,怎么来调试呢?可以通过syslog来将调试信息写到日志中。

头文件:#include<syslog.h>

函数原型:void openlog(const char *ident,int option,int facil-ity);

函数功能:打开日志文件。

函数参数:@ ident:对哪个进程进行日志记录,为进程名,如果不指定,则默认也是进程名。

@ option常用选项:

LOG_CONS:如果当前写日志失败了,就将信息输出到终端控制台。

LOG_PID:打印的每一条日志信息包含当前进程的PID

@ facil-ity常用选项:

LOG_USER:打印的每一条日志信息包含当前用户的等级信息

函数原型:void syslog(int priority, const char *format, ...);

函数功能:向日志文件中写日志信息。

函数参数:@ format:输出日志信息的参数列表,用法类同printf

@ priority:输出日志的等级信息

LOG_EMERG system is unusable

LOG_ALERT action must be taken immediately

LOG_CRIT critical conditions

LOG_ERR error conditions

LOG_WARNING warning conditions

LOG_NOTICE normal, but significant, condition

LOG_INFO informational message

LOG_DEBUG debug-level message

注意:写完的日志信息在哪里呢?一般是在/var/log目录下,不同的发行版对应的具体的文件不一样,ubuntu中对应的是/var/log/syslog文件中。

函数原型:void closelog(void);

函数功能:关闭日志文件。

一般就是在进程启动的时候打开日志,在进程结束的时候去关闭日志,这样中间就不同的记录就可以了,不需要每次记录都打开关闭,甚至打开和关闭也可以不需要,系统在第一次调用syslog记录日志的时候,如果发现日志是没有打开的,就按照自己默认的方式帮我们先打开日志文件,但是默认的记录的内容相对简单,如果我们自己需要控制日志中都输出什么信息,我们最好按照自己需要的方式打开一下。


发表评论

全国咨询电话:400-611-6270,双休日及节假日请致电值班手机:15010390966

在线咨询: 曹老师QQ(3337544669), 徐老师QQ(1462495461), 刘老师 QQ(3108687497)

企业培训洽谈专线:010-82600901,院校合作洽谈专线:010-82600350,在线咨询:QQ(248856300)

Copyright 2004-2018 华清远见教育科技集团 版权所有 ,京ICP备16055225号,京公海网安备11010802025203号

有位老师想和您聊一聊