Linux多进程开发(三)进程创建之守护进程的学习 - _Liang_Happy_Life__Dream - 51CTO技术博客 - Google Chrome (2013/10/11 16:48:27)
2013-07-04 17:25:35
之前发过一篇守护进程的文章,但是解析的不够详细,这次,详细来解释守护进程的一些概念和特性。
概念:
后台运行、没有控制端与之相连的进程。独立于控制终端,通常周期性的执行某种任务。
Why survival:
Linux的大多数服务器是用守护进程的方式实现,例如:Internet服务器的inted,Web服务器的http等。Linux守护进程类似Windowns的系统服务。
daemon特性:
1.让进程在后台执行,方法是fork产生一个子进程,然后父进程退出。
2.调用setsid创建一个新对话期。
控制端、登录会话和进程组通常是从父进程继承来的。
守护进程要摆脱它们,不受它们的影响,其方法是调用setid使进程成长为一个会话组长。
注:当进程是会话组长时,调用setid会失败,但第一点已经保证进程不是会话组长。
setid调用成功后,进程成为新的会话组长和进程组长,并与原来的登陆会话和进程组脱离,由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
3.禁止进程重新打开控制终端。
以上完成后,进程已经成为一个无终端的会话组长,但是它可以重新申请打开一个终端,为了避免这种情况的发生,可以通过使进程不再是会话组长来实现,再一次fork创建新的进程,使调用fork的进程退出。
4.关闭不再需要的文件描述符。
创建的新子进程从父进程继承打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的结果,先得到最高文件描述值,然后用一个循环程序,关闭0到最高文件描述符值的所有文件描述符。
5.将当前目录更改为根目录。
当守护进程当前工作目录在一个装配文件系统时,改文件系统不能被拆卸。一般需要将工作目录改为根目录。
6.将文件创建时使用的屏蔽字设置为0。
进程从创建它的父进程那里继承的文件创建屏蔽字可能会拒绝某些许可权。为防止这一点,使用umask(0)将屏蔽字清零。
7.处理SIGCHLD信号。
这一点不是必须的,但是对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求,如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie),从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。
在Linux下可以简单的将SIGCHLD信号的操作设为SIG_IGN,这样子进程结束时就不会产生僵尸进程。
以上为文字叙述,下面我们用代码来解释:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
# include <unistd.h> # include <sys/types.h> # include <signal.h> # include <sys/param.h> # include <sys/stat.h> # include <time.h> # include <syslog.h> # include <stdio.h> int init_daemon( void )
{ int pid;
int i;
/*忽略终端IO信号,STOP信号*/
signal (SIGTTOU, SIG_IGN);
signal (SIGTTIN, SIG_IGN);
signal (SIGTSTP, SIG_IGN);
signal (SIGHUP, SIG_IGN);
pid = fork();
if (pid > 0)
{ /*结束父进程,使子进程成为后台进程*/
exit (0);
}
else if (pid < 0)
{
return -1;
}
/*建立一个新的进程组,在这个新的进程组中,子进程成为这个进程组的首进程,以使该进程
脱离所有终端*/
setsid();
/*再次新建一个子进程,退出父进程,保证该进程不是进程组长,同时让该进程无法再打开一个
新的终端*/
pid = fork();
if (pid > 0)
{
exit (0);
}
else if (pid < 0)
{
return -1;
}
/*关闭所有父进程继承的不再需要的文件描述符*/
for (i = 0; i < NOFILE; close(i++))
{
;
}
/*改变工作目录,使得进程不与任何文件系统联系*/
chdir( "/" );
/*将文件屏蔽字设置为0*/
umask(0);
/*忽略SIGCHLD信号*/
signal (SIGCHLD, SIG_IGN);
return 0;
} int main( void )
{ time_t now;
init_daemon();
syslog(LOG_USER | LOG_INFO, "Test daemon \n" );
while (1)
{
sleep(8);
time (&now);
syslog(LOG_USER | LOG_INFO, "system time : \t%s\t\t\n" , ctime (&now));
}
return 0;
} |
但是要对Linux进行一些配置:
使用syslog()函数前,首先配置/etc/syslog.conf,在该文件最后末尾加上:
user.* /var/log/test.log
然后重启syslog服务。
/etc/init.d/syslog stop
/etc/init.d/syslog start
让程序在后台运行,关闭终端不会使程序退出的方式 (2013/10/11 15:43:53)
使用nohup或将程序改成守护进程。
linux的进程类型分:交互进程,批处理进程和守护进程;只有守护进程可以做到用终端打开后,关闭终端而程序不关闭。
终端(控制终端)由两部分组成,控制进程(一般为shell进程)和终端设备(TTY或pts),一个终端打开的所有进程(包括控制进程)同属一个会话,但守护进程在打开后,会新建会话,脱离原会话的影响,同时托管于init进程,因此与原终端再没有关系。
通过nohup命令启动的程序会忽略所有SIGHUP信号。在关闭控制终端时,系统会向该控制终端会话组的所有进程发SIGHUP信号,该信号
默认会让进程终止。显然忽略此信号后进程就不会终止。
用nohup命令让程序在后台执行(Unix/Linux) - 非黑白的生活 - 色彩源于心态,所以,我很花心。 - Google Chrome (2013/10/11 15:41:26)
在主机上跑程序,挂后台以后发现有时程序会退出,有时又不会。很奇怪,查了一下资料,整理如下。
& 只是放在后台运行,但是没有脱离控制终端,当终端断开的时候,进程将收到SIGHUP信号,这个信号默认动作是结束进程。用nohup将使进程脱离控制终端,进程无法进行终端操作,所有输出到终端的会被重定向。而 & 可以进行终端操作,如果发生读写终端,进程被挂起,用fg命令可以使进程获得终端控制权并继续运行。
Unix/Linux下一般想让某个程序在后台运行,很多都是使用 & 在程序结尾来让程序自动运行。比如我们要运行mysql在后台: /usr/local/mysql/bin/mysqld_safe –user=mysql &
但是我们很多程序并不象mysqld一样可以做成守护进程,可能我们的程序只是普通程序而已,一般这种程序即使使用 & 结尾,如果终端关闭,那么程序也会被关闭。为了能够后台运行,我们需要使用nohup这个命令,比如我们有个start.sh需要在后台运行,并且希望在后台能够一直运行,那么就使用nohup: nohup /root/start.sh & 在shell中回车后提示: [~]$ appending output to nohup.out
原程序的的标准输出被自动改向到当前目录下的nohup.out文件,起到了log的作用。但是有时候在这一步会有问题,当把终端关闭后,进程会自动被关闭,察看nohup.out可以看到在关闭终端瞬间服务自动关闭。咨询红旗Linux工程师后,他也不得其解,在我的终端上执行后,他启动的进程竟然在关闭终端后依然运行。在第二遍给我演示时,我才发现我和他操作终端时的一个细节不同:他是在当shell中提示了nohup成功后还需要按终端上键盘任意键退回到shell输入命令窗口,然后通过在shell中输入exit来退出终端;而我是每次在nohup执行成功后直接点关闭程序按钮关闭终端.。所以这时候会断掉该命令所对应的session,导致nohup对应的进程被通知需要一起shutdown。
这个细节有人和我一样没注意到,所以在这儿记录一下了。
附:nohup命令参考 nohup 命令
用途:不挂断地运行命令。
语法:nohup Command [ Arg ... ] [ & ]
描述:nohup 命令运行由 Command 参数和任何相关的 Arg 参数指定的命令,忽略所有挂断(SIGHUP)信号。在注销后使用 nohup 命令运行后台中的程序。要运行后台中的 nohup 命令,添加 & ( 表示”and”的符号)到命令的尾部。
无论是否将 nohup 命令的输出重定向到终端,输出都将附加到当前目录的 nohup.out 文件中。如果当前目录的 nohup.out 文件不可写,输出重定向到 $HOME/nohup.out 文件中。如果没有文件能创建或打开以用于追加,那么 Command 参数指定的命令不可调用。如果标准错误是一个终端,那么把指定的命令写给标准错误的所有输出作为标准输出重定向到相同的文件描述符。
退出状态:该命令返回下列出口值:
126 可以查找但不能调用 Command 参数指定的命令。
127 nohup 命令发生错误或不能查找由 Command 参数指定的命令。
否则,nohup 命令的退出状态是 Command 参数指定命令的退出状态。
nohup命令及其输出文件
nohup命令:如果你正在运行一个进程,而且你觉得在退出帐户时该进程还不会结束,那么可以使用nohup命令。该命令可以在你退出帐户/关闭终端之后继续运行相应的进程。nohup就是不挂起的意思( n ohang up)。
该命令的一般形式为:nohup command &
使用nohup命令提交作业
如果使用nohup命令提交作业,那么在缺省情况下该作业的所有输出都被重定向到一个名为nohup.out的文件中,除非另外指定了输出文件:
nohup command > myout.file 2>&1 &
在上面的例子中,输出被重定向到myout.file文件中。
刚才又试了一下,这样也可以:
nohup command >/dev/null 2>/dev/null &
或者
nohup command &>/dev/null
使用 jobs 查看任务。
使用 fg %n 关闭。
另外有两个常用的ftp工具ncftpget和ncftpput,可以实现后台的ftp上传和下载,这样就可以利用这些命令在后台上传和下载文件了。
使用nohup让程序在远程主机后台运行-月光博客 - Google Chrome (2013/10/11 15:38:15)
因为我购买的一个国外主机居然开放了Telnet权限,因此我也使用Telnet登录上去玩玩Linux,但发现一关闭窗口就自动和主机断开了,和Windows的终端不一样,所以就上网找啊找,找到了一个从后台一直运行某个程序的方法。
Unix/Linux下一般比如想让某个程序在后台运行,很多都是使用 & 在程序结尾来让程序自动运行。比如我们要运行mysql在后台:
/usr/local/mysql/bin/mysqld_safe --user=mysql &
但是加入我们很多程序并不象mysqld一样做成守护进程,可能我们的程序只是普通程序而已,一般这种程序使用 & 结尾,但是如果终端关闭,那么程序也会被关闭。但是为了能够后台运行,那么我们就可以使用nohup这个命令,比如我们有个test.php需要在后台运行,并且希望在后台能够定期运行,那么就使用nohup:
nohup /root/test.php &
提示:
[~]$ appending output to nohup.out
嗯,证明运行成功,同时把程序运行的输出信息放到当前目录的 nohup.out 文件中去。
附:nohup命令参考
nohup 命令
用途:不挂断地运行命令。
语法:nohup Command [ Arg ... ] [ & ]
描述:nohup 命令运行由 Command 参数和任何相关的 Arg 参数指定的命令,忽略所有挂断(SIGHUP)信号。在注销后使用 nohup 命令运行后台中的程序。要运行后台中的 nohup 命令,添加 & ( 表示“and”的符号)到命令的尾部。
无论是否将 nohup 命令的输出重定向到终端,输出都将附加到当前目录的 nohup.out 文件中。如果当前目录的 nohup.out 文件不可写,输出重定向到 $HOME/nohup.out 文件中。如果没有文件能创建或打开以用于追加,那么 Command 参数指定的命令不可调用。如果标准错误是一个终端,那么把指定的命令写给标准错误的所有输出作为标准输出重定向到相同的文件描述符。
退出状态:该命令返回下列出口值:
126 可以查找但不能调用 Command 参数指定的命令。
127 nohup 命令发生错误或不能查找由 Command 参数指定的命令。
否则,nohup 命令的退出状态是 Command 参数指定命令的退出状态。
nohup命令及其输出文件
nohup命令:如果你正在运行一个进程,而且你觉得在退出帐户时该进程还不会结束,那么可以使用nohup命令。该命令可以在你退出帐户/关闭终端之后继续运行相应的进程。nohup就是不挂起的意思( n ohang up)。
该命令的一般形式为:nohup command &
使用nohup命令提交作业
如果使用nohup命令提交作业,那么在缺省情况下该作业的所有输出都被重定向到一个名为nohup.out的文件中,除非另外指定了输出文件:
nohup command > myout.file 2>&1 &
在上面的例子中,输出被重定向到myout.file文件中。
使用 jobs 查看任务。
使用 fg %n 关闭。
另外有两个常用的ftp工具ncftpget和ncftpput,可以实现后台的ftp上传和下载,这样我就可以利用这些命令在后台上传和下载文件了。
Linux守护进程的编程实现 - hairetz的专栏 - 博客频道 - CSDN.NET - Google Chrome (2013/10/11 10:12:39)
Linux 守护进程的编程方法
守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。Linux的大多数服务器就是用守护进程实现的。比如,Internet服务器inetd,Web服务器httpd等。同时,守护进程完成许多系统任务。比如,作业规划进程crond,打印进程lpd等。
守护进程的编程本身并不复杂,复杂的是各种版本的Unix的实现机制不尽相同,造成不同Unix环境下守护进程的编程规则并不一致。这需要读者注意,照搬某些书上的规则(特别是BSD4.3和低版本的System V)到Linux会出现错误的。下面将全面介绍Linux下守护进程的编程要点并给出详细实例。
一. 守护进程及其特性
守护进程最重要的特性是后台运行。在这一点上DOS下的常驻内存程序TSR与之相似。其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩模等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。最后,守护进程的启动方式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户终端(通常是shell)执行。
总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别。因此,编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。如果读者对进程有比较深入的认识就更容易理解和编程了。
二. 守护进程的编程要点
前面讲过,不同Unix环境下守护进程的编程规则并不一致。所幸的是守护进程的编程原则其实都一样,区别在于具体的实现细节不同。这个原则就是要满足守护进程的特性。同时,Linux是基于Syetem V的SVR4并遵循Posix标准,实现起来与BSD4相比更方便。编程要点如下;
1. 在后台运行。
为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。
if(pid=fork())
exit(0);//是父进程,结束父进程,子进程继续
2. 脱离控制终端,登录会话和进程组
有必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。
控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长:
setsid();
说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
3. 禁止进程重新打开控制终端
现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:
if(pid=fork())
exit(0);//结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
4. 关闭打开的文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们:
for(i=0;i 关闭打开的文件描述符close(i);>
5. 改变当前工作目录
进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如/tmpchdir("/")
6. 重设文件创建掩模
进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0);
7. 处理SIGCHLD信号
处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN。
signal(SIGCHLD,SIG_IGN);
这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。
三. 守护进程实例
守护进程实例包括两部分:主程序test.c和初始化程序init.c。主程序每隔一分钟向/tmp目录中的日志test.log报告运行状态。初始化程序中的init_daemon函数负责生成守护进程。读者可以利用init_daemon函数生成自己的守护进程。
1. init.c清单
#include < unistd.h >
#include < signal.h >
#include < sys/param.h >
#include < sys/types.h >
#include < sys/stat.h >
void init_daemon(void)
{
int pid;
int i;
if(pid=fork())
exit(0);//是父进程,结束父进程
else if(pid< 0)
exit(1);//fork失败,退出
//是第一子进程,后台继续执行
setsid();//第一子进程成为新的会话组长和进程组长
//并与控制终端分离
if(pid=fork())
exit(0);//是第一子进程,结束第一子进程
else if(pid< 0)
exit(1);//fork失败,退出
//是第二子进程,继续
//第二子进程不再是会话组长
for(i=0;i< NOFILE;++i)//关闭打开的文件描述符
close(i);
chdir("/tmp");//改变工作目录到/tmp
umask(0);//重设文件创建掩模
return;
}
2. test.c清单
#include < stdio.h >
#include < time.h >
void init_daemon(void);//守护进程初始化函数
main()
{
FILE *fp;
time_t t;
init_daemon();//初始化为Daemon
while(1)//每隔一分钟向test.log报告运行状态
{
sleep(60);//睡眠一分钟
if((fp=fopen("test.log","a")) >=0)
{
t=time(0);
fprintf(fp,"Im here at %s/n",asctime(localtime(&t)) );
fclose(fp);
}
}
}
以上程序在RedHat Linux6.0下编译通过。步骤如下:
编译:gcc -g -o test init.c test.c
执行:./test
查看进程:ps -ef
从输出可以发现test守护进程的各种特性满足上面的要求。
3. 守护进程 - Google Chrome (2013/10/9 11:23:05)
3. 守护进程 请点评
Linux系统启动时会启动很多系统服务进程,例如第 1.3 节 “网络登录过程”讲的inetd
,这些系统服务进程没有控制终端,不能直接和用户交互。其它进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程不受用户登录注销的影响,它们一直在运行着。这种进程有一个名称叫守护进程(Daemon)。
下面我们用ps axj
命令查看系统中的进程。参数a
表示不仅列当前用户的进程,也列出所有其他用户的进程,参数x
表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数j
表示列出与作业控制相关的信息。
$ ps axj
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
0 1 1 1 ? -1 Ss 0 0:01 /sbin/init
0 2 0 0 ? -1 S< 0 0:00 [kthreadd]
2 3 0 0 ? -1 S< 0 0:00 [migration/0]
2 4 0 0 ? -1 S< 0 0:00 [ksoftirqd/0]
...
1 2373 2373 2373 ? -1 S<s 0 0:00 /sbin/udevd --daemon
...
1 4680 4680 4680 ? -1 Ss 0 0:00 /usr/sbin/acpid -c /etc
...
1 4808 4808 4808 ? -1 Ss 102 0:00 /sbin/syslogd -u syslog
...
凡是TPGID
一栏写着-1的都是没有控制终端的进程,也就是守护进程。在COMMAND
一列用[]
括起来的名字表示内核线程,这些线程在内核里创建,没有用户空间代码,因此没有程序文件名和命令行,通常采用以k
开头的名字,表示Kernel。init
进程我们已经很熟悉了,udevd
负责维护/dev
目录下的设备文件,acpid
负责电源管理,syslogd
负责维护/var/log
下的日志文件,可以看出,守护进程通常采用以d
结尾的名字,表示Daemon。
创建守护进程最关键的一步是调用setsid
函数创建一个新的Session,并成为Session Leader。
#include <unistd.h> pid_t setsid(void);
该函数调用成功时返回新创建的Session的id(其实也就是当前进程的id),出错返回-1。注意,调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。要保证当前进程不是进程组的Leader也很容易,只要先fork
再调用setsid
就行了。fork
创建的子进程和父进程在同一个进程组中,进程组的Leader必然是该组的第一个进程,所以子进程不可能是该组的第一个进程,在子进程中调用setsid
就不会有问题了。
成功调用该函数的结果是:
创建一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。
创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。
如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。所谓失去控制终端是指,原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通的打开文件而不是控制终端了。
例 34.2. 创建守护进程
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h> void daemonize(void)
{
pid_t pid; /*
* Become a session leader to lose controlling TTY.
*/
if ((pid = fork()) < 0) {
perror("fork");
exit(1);
} else if (pid != 0) /* parent */
exit(0);
setsid(); /*
* Change the current working directory to the root.
*/
if (chdir("/") < 0) {
perror("chdir");
exit(1);
} /*
* Attach file descriptors 0, 1, and 2 to /dev/null.
*/
close(0);
open("/dev/null", O_RDWR);
dup2(0, 1);
dup2(0, 2);
} int main(void)
{
daemonize();
while(1);
}
为了确保调用setsid
的进程不是进程组的Leader,首先fork
出一个子进程,父进程退出,然后子进程调用setsid
创建新的Session,成为守护进程。按照守护进程的惯例,通常将当前工作目录切换到根目录,将文件描述符0、1、2重定向到/dev/null
。Linux也提供了一个库函数daemon(3)
实现我们的daemonize
函数的功能,它带两个参数指示要不要切换工作目录到根目录,以及要不要把文件描述符0、1、2重定向到/dev/null
。
$ ./a.out
$ ps
PID TTY TIME CMD
11494 pts/0 00:00:00 bash
13271 pts/0 00:00:00 ps
$ ps xj | grep a.out
1 13270 13270 13270 ? -1 Rs 1000 0:05 ./a.out
11494 13273 13272 11494 pts/0 13272 S+ 1000 0:00 grep a.out
(关闭终端窗口重新打开,或者注销重新登录)
$ ps xj | grep a.out
1 13270 13270 13270 ? -1 Rs 1000 0:21 ./a.out
13282 13338 13337 13282 pts/1 13337 S+ 1000 0:00 grep a.out
$ kill 13270
运行这个程序,它变成一个守护进程,不再和当前终端关联。用ps
命令看不到,必须运行带x
参数的ps
命令才能看到。另外还可以看到,用户关闭终端窗口或注销也不会影响守护进程的运行。