Unix环境高级编程:守护进程
参考
- Unix环境高级编程,第9,13章
介绍
守护进程就是Linux中使用ps aux
那些一般以d结尾的程序,比如rsyslogd,sshd等,为daemon简称。他们是长期在后台执行的随终端关闭而关闭的程序。
一般情况下我们登陆终端,执行程序只要产生的不是守护进程,一般的fork得到的进程,它们在终端失去连接或者关闭后都会被相应的终止。平常使用中可以采用nohup
命令来执行一个不希望随终端而关闭的程序,实际上就是以守护进程的方式执行它。
原理
要使一个进程成为守护进程,那么就要将它和终端脱离关系,否则终端一旦关闭就会相应的关闭与它相关的进程。在Linux中每个终端会对应一个会话(Session)。如果我们手工新建一个会话那么就不会自动的和终端关联,也就是说脱离了原来的终端成为一个守护进程。
方法
命令行
可以使用setsid
命令将某个程序启动为守护进程。有如下程序
#include <stdio.h>
#include <unistd.h>
int main() {
for(;;) {
sleep(1);
printf(".\n");
}
return 0;
}
编译后设生成文件为a.out,则执行命令
setsid ./a.out
可以发现命令执行后并没有阻塞当前的终端,当然这种效果使用./a.out&
也可以达到,但两者是不同的。后者会在终端关闭后被关闭,而前者已经成为一个守护进程不受影响。此时可以退出当前执行setsid命令的终端,然后再次登录,查看a.out进程是否依然在运行。
ubuntu@dev00:~$ ps aux|grep a.out
ubuntu 20567 0.0 0.0 4192 356 ? Ss 13:02 0:00 ./a.out
ubuntu 20652 0.0 0.0 10460 948 pts/2 R+ 13:11 0:00 grep --color=auto a.out
可以看到a.out进程依然是在运行的。但是这里有个疑问,就是上述程序命的内容明明是每隔1秒输出一个.
并换行然而重新登录后,我们并没有看到有任何的输出。我们通过proc
文件系统来看一下究竟
ubuntu@dev00:~$ ll /proc/20567/fd
total 0
dr-x------ 2 ubuntu ubuntu 0 Jul 29 13:11 ./
dr-xr-xr-x 9 ubuntu ubuntu 0 Jul 29 13:02 ../
lrwx------ 1 ubuntu ubuntu 64 Jul 29 13:11 0 -> /dev/pts/1 (deleted)
lrwx------ 1 ubuntu ubuntu 64 Jul 29 13:11 1 -> /dev/pts/1 (deleted)
lrwx------ 1 ubuntu ubuntu 64 Jul 29 13:11 2 -> /dev/pts/1 (deleted)
可以看到进程打开文件描述符(0,1,2分别对应标准输入,标准输出,错误输出)实际指向已经被标识为已删除(deleted)。而他们的指向/dev/pts/1
其实是一个伪终端。当与主机断开连接后对应的伪终端自然就失效了。可以使用who
命令查看当前登陆用户和他们使用的终端
ubuntu@dev00:~/c$ who
ubuntu pts/0 2015-07-29 01:09 (10.214.224.50)
ubuntu pts/2 2015-07-29 13:06 (10.214.224.50)
由此我们可以知道一般的继承自环境的标准输入输出对守护进程来说是不必要的,因为这些标准输入输出一般都是与终端挂钩的,一旦终端关闭这些输入输出已经失效了,我们也就无法看到守护进程的输出了。这也是为什么所用的守护进程都采用日志形式进行日志信息输出的原因,当然他们的输入一般就是配置文件。
编程实现
与setsid
命令对应的有一个同名的setsid
系统调用,使得当前进程运行于一个全新的会话中。这个调用原型非常简单
pid_t setsid(void);
但是它有个限制就是进程主的组长是不能够调用这个的,而通过bash执行的命令或者启动的程序恰好会放到一个新进程组内并把运行的进程作为该组组长。这样我们在程序中就只能先fork一下(父进程主动退出),然后用子进程去调用setsid
。父进程主动退出那么在运行的命令行上看来命令似乎立即返回了。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t cid = fork();
if (cid > 0) {
// parent process(process group leader), exit immediately
exit(0);
}
// child process
pid_t sid = setsid();
for (;;) {
sleep(10);
printf(".\n");
}
return 0;
}
编译运行后,我们可以通过ps aux
命令查到该进程确实已经是一个守护进行了,即中断一列是一个?表示没有对应的关联终端。
ubuntu 20762 0.0 0.0 4188 88 ? Ss 13:43 0:00 ./a.out
ubuntu 20763 0.0 0.0 17164 1324 pts/2 R+ 13:43 0:00 ps aux
不过《Unix环境高级编程》中给出的建议是在上述子进程内在fork一次然后在调用setsid避免守护进程成为会话的leader这样在某些系统上是有限制的,不过linux似乎没有碰到。
概念
进程组
进程组表示一组进程,一般情况下运行的程序fork出来的子进程和父进程都是属于同一个进程组的。可以向一个进程组发送信号,然后进程组内的每个进程都会收到该信号。如果不进行额外的设置我们可以推断,系统中所有的进程都是同一个进程组的,不过显然这个假设是不成立的,虽然进程都是不断fork出来的。一个例外就是bash程序会把执行的命令程序放到一个单独的进程组中,而不是放到它自己所在的那个。进程组有一个leader,进程组ID即为该leader的pid。试着执行以下命令:
$ sleep 100 &
$ ps -o pid,pgid,ppid,sid,cmd -e
在ps输出的最后几行应该有类似如下几行:
27245 27245 27244 27245 -bash
28931 28931 27245 27245 sleep 100
28932 28932 27245 27245 ps -o pid,pgid,ppid,sid,cmd -e
其中输出的数字依次是进程ID,进程组ID,父进程ID,会话ID,命令参数。从中我们可以知道sleep命令和ps命令的父进程都是bash进程,sleep和ps命令在各自的进程组中是各自的leader。如果自己写一个如下的一个普通程序:
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main() {
fork();
sleep(100);
return 0;
}
编译并运行:
$ ./a.out &
$ ps -o pid,pgid,ppid,sid,cmd -e
28986 28986 27245 27245 ./a.out
28987 28986 28986 27245 ./a.out
28988 28988 27245 27245 ps -o pid,pgid,ppid,sid,cmd -e
可以看到a.out产生的两个进程都在自己的进程组内,不是各自独立的。
设置进程组
可以通过int setpgrp(pid_t pid, pid_t gpid)
来将一个进程设置为指定的进程组,也可以创建一个新进程组然后pid
参数指定的进程成为该组leader。这个调用只能对自己或者子进程有效。我们可以修改原来的代码:
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main() {
pid_t child = fork();
if (child > 0) {
setpgid(child, child);
}
sleep(100);
return 0;
}
此时再来通过ps命令进行检验可以得到如下类似输出
29011 29011 27245 27245 ./a.out
29012 29012 29011 27245 ./a.out
29013 29013 27245 27245 ps -o pid,pgid,ppid,sid,cmd -e
a.out程序产生的两个进程现在已经位于不同的进程组中了。在linux中可以通过一个()
让其中的命令在一个进程组中如下:
$ (sleep 10 | sleep 20)&
29020 29020 27245 27245 -bash
29021 29020 29020 27245 sleep 10
29022 29020 29020 27245 sleep 20
29023 29023 27245 27245 ps -o pid,pgid,ppid,sid,cmd -e
前台进程组
每个会话一般只有一个前台进程组,前台进程即可以在控制终端上进行交互的那些进程。可以通过tcsetpgrp
来将哪个进程组设定为前台进程组。我们在执行bg
&fg
命令时就用的了这个调用。
give_terminal_to (pgrp, force)
pid_t pgrp;
int force;
{
sigset_t set, oset;
int r, e;
r = 0;
if (job_control || force)
{
sigemptyset (&set);
sigaddset (&set, SIGTTOU);
sigaddset (&set, SIGTTIN);
sigaddset (&set, SIGTSTP);
sigaddset (&set, SIGCHLD);
sigemptyset (&oset);
sigprocmask (SIG_BLOCK, &set, &oset);
if (tcsetpgrp (shell_tty, pgrp) < 0)
{
/* Maybe we should print an error message? */
#if 0
sys_error ("tcsetpgrp(%d) failed: pid %ld to pgrp %ld",
shell_tty, (long)getpid(), (long)pgrp);
#endif
r = -1;
e = errno;
}
else
terminal_pgrp = pgrp;
sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
}
if (r == -1)
errno = e;
return r;
}
后台进程组
一个会话可以有多个后台进程组,就如我们在命令行后跟一个&
就使得命令在后台执行一样。
Unix环境高级编程:守护进程的更多相关文章
- Unix环境高级编程——守护进程记录总结(从基础到实现)
一.概念及其特征 守护进程是系统中生存期较长的一种进程,常常在系统引导装入时启动,在系统关闭时终止,没有控制终端,在后台运行.守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程 ...
- UNIX环境高级编程——守护进程
一.守护进程简介 守护进程,也就是通常说的Daemon进程,是Linux中的后台服务进程.它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.守护进程常常在系 ...
- UNIX环境高级编程——守护进程列表
amd:自动安装NFS(网络文件系统)守侯进程apmd:高级电源治理Arpwatch:记录日志并构建一个在LAN接口上看到的以太网地址和ip地址对数据库Autofs:自动安装治理进程automount ...
- Unix环境高级编程(八)进程关系
本章看后给人似懂非懂的感觉,主要是不知道实际当中如何去使用.通过前面几章的学习,每个进程都有一个父进程,当子进程终止时,父进程得到通知并取得子进程的退出状态.先将本章基本的知识点总结如下,日后再看时候 ...
- UNIX环境高级编程--8. 进程控制
进程控制进程标识: 每一个进程都有一个非负整型表示的唯一进程ID.虽然唯一,但是ID可以复用.当一个进程结束后,其进程ID会被延迟复用. ID=0的进程通常是调度进程,常被称作交换进程(s ...
- Unix环境高级编程(六)进程控制
本章介绍Unix的进程控制,包括进程创建,执行程序和进程终止,进程的属性,exec函数系列,system函数,进程会计机制. 1.进程标识符 每一个进程都有一个非负整数标识的唯一进程ID.ID为0表示 ...
- Unix环境高级编程(五)进程环境
本章主要介绍了Unix进程环境,包含main函数是如何被调用的,命令行参数如何传递,存储方式布局,分配存储空间,环境变量,进程终止方法,全局跳转longjmp和setjmp函数及进程的资源限制. ma ...
- UNIX环境高级编程--9. 进程控制
进程关系 当子进程终止时,父进程得到通知并能取得子进程的退出状态. 终端登录: 早起UNIX系统通过哑终端登录,本地的终端 or 远程的终端 .主机上链接的终端设备是固定的,所以同时登录数 ...
- UNIX环境高级编程——Linux进程地址空间和虚拟内存
一.虚拟内存 分段机制:即分成代码段,数据段,堆栈段.每个内存段都与一个特权级相关联,即0~3,0具有最高特权级(内核),3则是最低特权级(用户),每当程序试图访问(权限又分为可读.可写和可执行)一个 ...
- (七) 一起学 Unix 环境高级编程(APUE) 之 进程关系 和 守护进程
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
随机推荐
- Maximum repetition substring(POJ - 3693)(sa(后缀数组)+st表)
The repetition number of a string is defined as the maximum number \(R\) such that the string can be ...
- Python大法之告别脚本小子系列—各类URL采集器编写
本文作者:i春秋签约作家——阿甫哥哥 系列文章专辑:https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid ...
- sublime text3 -- JavaScript Completions
今天在使用sublime text3时,它 智能 的自动安装了一个插件,JavaScript Completions.一般插件都是为了提高开发效率的,于是百度搜了一下用法. 相关说明很少,packag ...
- iOS,Android,WP, .NET通用AES加密算法
这两天为移动App开发API,结果实现加密验证时碰到一大坑.这里不得不吐槽下又臭又硬的iOS,Windows Server无法解密出正确的结果,Android则可以,后来使用了通用的AES256加密算 ...
- POJ 2546
#include<iostream> #include<numeric> #include<iomanip> #include<algorithm> # ...
- 解决ubuntu安装系统默认没有创建root用户
安装ubuntu操作系统的时候,默认没有设置root账户的步骤!!! 这样在操作系统安装完成之后,就没有root用户, 一般,当前的普通用户,如果需要执行一些命令的时候,只要在命令前边加上sudo就行 ...
- SQL SERVICE 拆分字符串的表值函数
SQL代码 ALTER FUNCTION [dbo].[SplitToTable]( @SplitString nvarchar(max), @Separator nvarchar(10)=' ')R ...
- Quartz.NET在ASP.NET 中使用
Quartz.NET 项目地址 http://quartznet.sourceforge.net/ common logging是一个通用日志接口,log4net是一个具体实现 ,也可以使用NLog, ...
- 第k大的数
问题描述:输入一组数,指定一个k,输出这组数里第k大的数. 一般这种题目,第一想法是把整个数组先排序后,再选取第k位的数.但是这样做实际上浪费了大量的时间在排序上,我们只是要求第k大的数,并非要把整个 ...
- 杂谈:Windows操作系统的介绍与对Win8操作系统市场反响冷淡原因的分析
Windows操作系统,毫无疑问是操作系统市场上的霸主,也正因为Windows操作系统的诞生让电脑的操作性能变得更加平民化,深的用户的喜爱.至今身边的人也是选择windows操作系统的居多,这篇文章也 ...