参考

  1. 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;
}

http://git.savannah.gnu.org/cgit/bash.git/tree/jobs.c

后台进程组

一个会话可以有多个后台进程组,就如我们在命令行后跟一个&就使得命令在后台执行一样。

Unix环境高级编程:守护进程的更多相关文章

  1. Unix环境高级编程——守护进程记录总结(从基础到实现)

    一.概念及其特征 守护进程是系统中生存期较长的一种进程,常常在系统引导装入时启动,在系统关闭时终止,没有控制终端,在后台运行.守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程 ...

  2. UNIX环境高级编程——守护进程

    一.守护进程简介 守护进程,也就是通常说的Daemon进程,是Linux中的后台服务进程.它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.守护进程常常在系 ...

  3. UNIX环境高级编程——守护进程列表

    amd:自动安装NFS(网络文件系统)守侯进程apmd:高级电源治理Arpwatch:记录日志并构建一个在LAN接口上看到的以太网地址和ip地址对数据库Autofs:自动安装治理进程automount ...

  4. Unix环境高级编程(八)进程关系

    本章看后给人似懂非懂的感觉,主要是不知道实际当中如何去使用.通过前面几章的学习,每个进程都有一个父进程,当子进程终止时,父进程得到通知并取得子进程的退出状态.先将本章基本的知识点总结如下,日后再看时候 ...

  5. UNIX环境高级编程--8. 进程控制

    进程控制进程标识:    每一个进程都有一个非负整型表示的唯一进程ID.虽然唯一,但是ID可以复用.当一个进程结束后,其进程ID会被延迟复用.    ID=0的进程通常是调度进程,常被称作交换进程(s ...

  6. Unix环境高级编程(六)进程控制

    本章介绍Unix的进程控制,包括进程创建,执行程序和进程终止,进程的属性,exec函数系列,system函数,进程会计机制. 1.进程标识符 每一个进程都有一个非负整数标识的唯一进程ID.ID为0表示 ...

  7. Unix环境高级编程(五)进程环境

    本章主要介绍了Unix进程环境,包含main函数是如何被调用的,命令行参数如何传递,存储方式布局,分配存储空间,环境变量,进程终止方法,全局跳转longjmp和setjmp函数及进程的资源限制. ma ...

  8. UNIX环境高级编程--9. 进程控制

    进程关系    当子进程终止时,父进程得到通知并能取得子进程的退出状态. 终端登录:    早起UNIX系统通过哑终端登录,本地的终端 or 远程的终端 .主机上链接的终端设备是固定的,所以同时登录数 ...

  9. UNIX环境高级编程——Linux进程地址空间和虚拟内存

    一.虚拟内存 分段机制:即分成代码段,数据段,堆栈段.每个内存段都与一个特权级相关联,即0~3,0具有最高特权级(内核),3则是最低特权级(用户),每当程序试图访问(权限又分为可读.可写和可执行)一个 ...

  10. (七) 一起学 Unix 环境高级编程(APUE) 之 进程关系 和 守护进程

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

随机推荐

  1. JavaScript基础(2)-DOM

    一.伪数组arguments arguments代表的是实参,有个讲究的地方是:arguments只在函数中使用. 1.返回函数实参的个数:arguments.length,例如: fn(2,4); ...

  2. C# byte array 跟 string 互转

    用 System.Text.Encoding.Default.GetString() 转换时,byte array 中大于 127 的数据转 string 时会出问题. 把这里的 Default 换成 ...

  3. [HTML] SCSS 备忘录

    Sass是成熟.稳定.强大的CSS预处理器,而SCSS是Sass3版本当中引入的新语法特性,完全兼容CSS3的同时继承了Sass强大的动态功能. 特性概览 CSS书写代码规模较大的Web应用时,容易造 ...

  4. java打包jar后,使之一直在linux上运行,不随终端退出而关闭

      nohup java -jar xxx.jar&

  5. SQLServer 在Visual Studio的2种连接方法

    一.Sql Server 在Visual Studio的连接有两种方法: (1)本地计算机连接; string s = "Data Source=计算机名称;initial Catalog= ...

  6. (转载)es进行聚合操作时提示Fielddata is disabled on text fields by default

    原文地址:http://blog.csdn.net/u011403655/article/details/71107415 根据es官网的文档执行 GET /megacorp/employee/_se ...

  7. vue实现城市列表选择

    成果展示 最后的成果就是下面所展示的内容,因为gif图没有做,只能截图所展示,接下来,会带着大家一步一步的完成下面功能,脚手架搭建和node安装在本次案例不会讲解,如果了解,可以在我的博客园找到有详细 ...

  8. Jenkins持久化集成使用

    1.概述 Jenkins是基于Java开发的一种持续集成工具,用于监控持续重复的工作,功能包括: 持续的软件版本发布/测试项目 监控外部调用执行的工作 2.搭建 2.1环境准备 首先我们要准备搭建的环 ...

  9. 使用Gitlab一键安装包后的日常备份恢复与迁移

    Gitlab 创建备份 使用Gitlab一键安装包安装Gitlab非常简单, 同样的备份恢复与迁移也非常简单. 使用一条命令即可创建完整的Gitlab备份: gitlab-rake gitlab:ba ...

  10. postgreSql 常用查询总结

    1. 日期格式转化(参考) select beg_time, end_time, extract(epoch from to_timestamp(end_time,'yyyy-mm-dd-HH24-M ...