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 环境高级编 ...
随机推荐
- 【翻译】 Windows 内核漏洞学习—空指针解引用
Windows Kernel Exploitation – NullPointer Dereference 原文地址:https://osandamalith.com/2017/06/22/windo ...
- ie6兼容性处理
IE6下border-bottom不起作用? 在IE6下,border-bottom:1px solid #000 不起作用,但border:1px solid #000 其作用. (经过测试,对于b ...
- ThinkPHP5代码执行的简单分析
漏洞影响版本: ThinkPHP 5.0.5-5.0.22 ThinkPHP 5.1.0-5.1.30 漏洞复现: 一.mac的debug环境搭建. 一键化环境搭建工具: mamp pro ,调试工具 ...
- 解决修改css或js文件,浏览器缓存更新问题。
在搜索引擎中搜索关键字.htaccess 缓存,你可以搜索到很多关于设置网站文件缓存的教程,通过设置可以将css.js等不太经常更新的文件缓存在浏览器端,这样访客每次访问你的网站的时候,浏览器就可以从 ...
- 前端安全 -- XSS攻击
XSS漏洞是最广泛.作用最关键的web安全漏洞之一.在绝大多数网络攻击中都是把XSS作为漏洞链中的第一环,通过XSS,黑客可以得到的最直接利益就是拿到用户浏览器的cookie,从而变相盗取用户的账号密 ...
- Mahout使用(一)
1.HelloMahout.java2.DistanceTest.java3.MahoutDemo.java 1.HelloMahout.java package cn.crxy.mahout; im ...
- django中url,静态文件,POST请求的配置 分类: Python 2015-06-01 17:00 789人阅读 评论(0) 收藏
平时使用的是pycharm,所以这篇文章主要也是使用pycharm默认创建的django项目为基础进行讲解.项目目录如下图: 1.URL的配置 当创建好项目后,运行项目就可以看到django默认的页面 ...
- 02-03:springboot 整合listener
1.通过注解扫描完成Listener组件的注册 1.1 编写listener /** * Springboot 整合listener */ import javax.servlet.ServletCo ...
- linux下安装lnmp环境
安装nginx 1 检查是否安装该程序: which nginx #查看nginx是否存在 which php #查看php是否存在 which mys ...
- 前端组件化Polymer入门教程(3)——快速入门
本系列主要翻译官方的教程,因为国内目前这方面的资料太少了,但也不一定和官网的一样,反正就是自己想到哪就写到哪. 如果我没有说明,默认情况下index.html始终包含这段代码,后面将不会再贴上来. & ...