参考

  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. 不同的最小割(cqoi2016,bzoj4519)(最小割树)

    学过图论的同学都知道最小割的概念:对于一个图,某个对图中结点的划分将图中所有结点分成 两个部分,如果结点\(s,t\)不在同一个部分中,则称这个划分是关于\(s,t\)的割.对于带权图来说,将 所有顶 ...

  2. 初探日志框架Logback

    一. 背景 最近因为学习项目时需要使用logback日志框架来打印日志, 使用过程中碰到很多的疑惑, 而且需要在控制台打印mybatis执行的sql语句, 于是决定沉下心来 研究一下logback的使 ...

  3. 【重要通知】本人所有技术文章转移至https://zzqcn.github.io

    本人所有技术文章转移至 https://zzqcn.github.io

  4. linux防火墙(三)—— iptables语法之匹配条件

    一.iptables规则的匹配条件类型有三类 1.通用匹配:可直接使用,不依赖于其他条件或扩展,包括网络协议.IP地址.网络接口等条件 2.隐含匹配:要求以特定的协议匹配作为前提,包括端口.TCP标记 ...

  5. Linux - 更改软件源

    镜像源 网易镜像源 在网易开源镜像页面,点击对应镜像名的使用帮助,可以查看到更新源的方法,按步骤操作即可. 阿里云镜像源 在阿里云开源镜像页面,点击对应Mirror分类的help标签,可以查看到更新源 ...

  6. day 39 jq 学习入门2

    ---恢复内容开始--- 前情提要: jq 是用来降低js 的工作的一个组件 一:利用jq 实现动画效果 <!DOCTYPE html> <html lang="en&qu ...

  7. 副本集mongodb 无缘无故 cpu异常

    mondb 服务器故障 主从复制集 主:   192.168.1.106从:   192.168.1.100仲裁:192.168.1.102 os版本:CentOS Linux release 7.3 ...

  8. webgl之五彩光源

    一.Three.js中有哪些光源? 在Three.js中,光源有一个基类THREE.Light(hex),这个hex接受16进制颜色作为参数而初始化光源的颜色,比如我们要定义一种绿色的光源,可以这样来 ...

  9. Git for Windows之分支管理、分支合并、解决分支冲突

    分支是在稳定版本出现bug的情况下,通过分支技术,在保证稳定版本稳定的情况,修改稳定版本的(差异下载的,速度极快,不同于SVN等技术,会将所有的代码下载到本地)副本,通过合并,解决冲突,最后回归到稳定 ...

  10. Decorator装饰者模式(结构型模式)

    1.需求 假设让我们去设计FCL中的Stream类,该类具有流类的基本功能,除了有各种不同类型的流外(如内存流.文件流.网络流等等),但是在不同的业务场景下,如处理银行业务,需要给相关的内存流进行加密 ...