进程关系

进程组

进程组是一个或多个进程的集合。通常,它们是在同一作业中结合起来的,同一进程组中的各进程接收来自同一终端的各种信号,每个进程组有一个唯一的进程组ID。每个进程组有一个组长进程,该组长进程的ID等于进程组ID。从进程组创建开始到最后一个进程离开为止的时间称为进程组的生命周期

#include <unistd.h>

pid_t getpgrp(void);

返回值:调用进程的进程组ID

int setpgid(pid_t pid, pid_t pgid);

返回值:成功,返回0;失败,返回-1

说明:

setpgid用于添加进程到一个现有的进程组,或者创建一个新的进程组。

函数将进程ID为pid的进程加入ID为pgid的进程组中。

如果pid == pgid,则pid指定的进程变为进程组长;

如果pid == 0,则使用调用者的进程ID;

如果pgid == 0,则将pid用作进程组ID。

会话

会话是一个或者多个进程组的集合。进程组通常是由shell管道编制在一起的,如下:

proc1 | proc2 &

proc3 | proc4 | proc5

进程调用setsid函数创建一个新的会话:

#include <unistd.h>

pid_t setsid(void);

返回值:成功,返回进程组ID;失败,返回-1

说明:

如果调用此函的进程不是一个进程组长,则此函数创建一个新的会话,具体如下:

(1)   该进程变为新会话的会话首进程。

(2)   该进程成为一个新进程组的组成进程,新进程组ID是该进程ID。

(3)   该进程没有控制终端。

pid_t getsid(pid_t pid);

返回值:成功,返回会话首进程的进程组ID;失败,返回-1

说明:

getsid(0)返回调用进程的会话首进程的进程组ID,如果pid不属于调用者所在的会话,则不返回。

控制终端

一个会话可以有一个控制终端,通常是终端设备或伪终端设备。建立与控制终端连接的会话首进程被称为控制进程。一个会话中的进程组可被分为一个前台进程组和一个后台进程组。需要有一种方法通知内核哪一个进程组是前台进程组,这样便于终端设备驱动程序知道将终端输入和终端产生的信号发送到何处:

#include <unistd.h>

pid_t tcgetpgrp(int fd);

返回值:成功,返回前台进程组ID;失败,返回-1

int tcsetpgrp(int fd, pid_pgrpid);

返回值:成功,返回0;失败,返回-1

说明:

其中,fd为相关联的打开终端。大多数应用程序并不直接调用这两个函数,而是由作业控制shell调用。

需要管理控制终端的应用程序可以调用tcgetsid函数获得控制终端的会话首进程的进程组ID:

#include <termios.h>

pid_t tcgetsid(int fd);

返回值:成功,返回会话首进程的进程组ID;失败,返回-1

作业控制

作业控制允许在一个终端上启动多个作业(进程组),哪一个作业可以访问该终端以及哪些作业在后台运行。从shell使用作业控制的角度看,用户可以在前台或者后台启动一个作业,例如:

vi main.c在前台启动只有一个进程组成的作业,而make all &在后台启动只有一个进程组成的作业。

我们可以键入3个特殊字符使得终端程序产生信号,并将它们发送到前台进程组:

中断字符(Ctrl + C)产生SIGINT信号;

退出字符(Ctrl + \)产生SIGQUIT信号;

挂起字符(Ctrl + Z)产生SIGTSTP信号;

只有前台作业才可以接收终端上输入的字符,如果后台作业试图都终端,那么终端驱动程序向后台作业发送特定信号SIGTTIN,该信号将停止此后台作业,而shell则向用户发送通知,然后用户就可以利用shell命令fg将此作业转为前台作业运行。但是如果后台作业输出到控制终端又将发生什么呢?我们可以通过stty命令禁止这种情况。此时,终端驱动程序向后台作业发送SIGTTOU信号,使其进程阻塞,当然此时我们也可以利用fg将其移到前台运行。

守护进程

守护进程(daemon)常常在系统引导装入时启动,在系统关闭时终止。由于守护进程没有控制终端(其终端名设置为?),因此,其在后台运行。大多守护进程都以超级用户权限执行。

编程规则

(1)   首先调用umask将文件模式创建屏蔽字设置为一个已知数值(通常是0)。这样做是防止继承而来的屏蔽字没有某些权限,尤其是写权限。

(2)   调用fork,然后使父进程exit。

(3)   调用setsid创建一个新会话。

(4)   将当前工作目录更改为根目录。因为守护进程通常在系统引导之前就存在,如果守护进程的当前工作目录在一个需要挂载的文件系统上,那么该文件系统不能被卸载。也有某些守护进程会把当前工作目录更改到某个指定的位置,例如行式打印机假脱机守护进程就可能将其工作目录更改到它们的spool目录上。

(5)   关闭不再需要的文件描述符。可以使用open_max函数或者getrlimit函数获取最高文件描述符值,然后关闭直到该值的所有文件描述符。这样做可以避免守护进程从其父进程继承任何文件描述符。

(6)   某些守护进程将文件描述符0、1、2指向/dev/null。这样可以使得任何以恶搞试图读标准输入、写标准输出、写标准错误输出的程序不产生任何效果。由于守护进程是在后台运行的,因此登录会话的终止并不影响守护进程。如果其他用户在同一终端设备上登录,我们自然不希望在该终端上见到守护进程的输出,用户也不希望他们在终端上的输入被守护进程读取,因此上述措施是相当有用的。

如下程序可由想要初始化为守护进程的程序调用,在main函数中调用函数daemonize,然后使main进程进入休眠状态,通过ps –efj命名查看进程状态,可以发现守护进程init,其终端名为 ?。

[root@benxintuzi process]# cat init.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/resource.h> void daemonize(const char *cmd)
{
int i, fd0, fd1, fd2;
pid_t pid;
struct rlimit rl;
struct sigaction sa; /*
* * Clear file creation mask.
* */

umask(); /*
* * Get maximum number of file descriptors.
* */

if (getrlimit(RLIMIT_NOFILE, &rl) < )
{
printf("%s: can't get file limit\n", cmd);
return ;
} /*
* * Become a session leader to lose controlling TTY.
* */

if ((pid = fork()) < )
{
printf("%s: can't fork\n", cmd);
return ;
}
else if (pid != ) /* parent */
exit();
setsid(); /*
* * Ensure future opens won't allocate controlling TTYs.
* */

sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = ;
if (sigaction(SIGHUP, &sa, NULL) < )
{
printf("%s: can't ignore SIGHUP\n", cmd);
return ;
}
if ((pid = fork()) < )
{
printf("%s: can't fork\n", cmd);
return ;
}
else if (pid != ) /* parent */
exit(); /*
* * Change the current working directory to the root so
* * we won't prevent file systems from being unmounted.
* */

if (chdir("/") < )
{
printf("%s: can't change directory to \n", cmd);
} /*
* * Close all open file descriptors.
* */

if (rl.rlim_max == RLIM_INFINITY)
rl.rlim_max = ;
for (i = ; i < rl.rlim_max; i++)
close(i); /*
* * Attach file descriptors 0, 1, and 2 to /dev/null.
* */

fd0 = open("/dev/null", O_RDWR);
fd1 = dup();
fd2 = dup(); /*
* * Initialize the log file.
* */

openlog(cmd, LOG_CONS, LOG_DAEMON);
if (fd0 != || fd1 != || fd2 != ) {
syslog(LOG_ERR, "unexpected file descriptors %d %d %d",
fd0, fd1, fd2);
exit();
}
} int main(void)
{
daemonize("ps");
sleep();
return ;
} [root@benxintuzi process]# ./init
[root@benxintuzi process]# ps
-efj
UID PID PPID PGID SID C STIME TTY TIME CMD
root : ? :: /sbin/init
root : pts/ :: vim init.c
root 2237 1 2236 2236 0 19:45 ? 00:00:00 ./init
root : pts/ :: ps -efj

Linux 进程(二):进程关系及其守护进程的更多相关文章

  1. linux系统编程之进程(八):守护进程详解及创建,daemon()使用

    一,守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.它不需要用户输入就能运行而且提供某种服务,不是对整个 ...

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

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

  3. Linux 的进程组、会话、守护进程

    一.进程组ID 每个进程都属于一个进程组.每个进程组有一个领头进程.进程组是一个或多个进程的集合,通常它们与一组作业相关联,可以接受来自同一终端的各种信号.每个进程组都有唯一的进程组ID(整数,也可以 ...

  4. linux第1天 fork exec 守护进程

    概念方面 文件是对I/O设备的抽象表示.虚拟存储器是对主存和磁盘I/O设备的抽象表示.进程则是对处理器.主存和I/O设备的抽象表示 中断 早期是没有进程这个概念,当出现中断技术以后才出现进程这个概念 ...

  5. Linux系统编程(26)——守护进程

    Linux系统启动时会启动很多系统服务进程,比如inetd,这些系统服务进程没有控制终端,不能直接和用户交互.其它进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程不受用户 ...

  6. linux c语言学习笔记之守护进程

    哈尔滨理工大学软件工程专业08-7李万鹏原创作品,转载请标明出处 http://blog.csdn.net/woshixingaaa/archive/2010/06/06/5651095.aspx 守 ...

  7. Python之路(第三十七篇)并发编程:进程、multiprocess模块、创建进程方式、join()、守护进程

    一.在python程序中的进程操作 之前已经了解了很多进程相关的理论知识,了解进程是什么应该不再困难了,运行中的程序就是一个进程.所有的进程都是通过它的父进程来创建的.因此,运行起来的python程序 ...

  8. 浅析linux 下shell命令执行和守护进程

    执行shell脚本有以下几种方式 1.相对路径方式,需先cd到脚本路径下 [root@banking tmp]# cd /tmp [root@banking tmp]# ./ceshi.sh 脚本执行 ...

  9. Linux+Nginx+Asp.net Core及守护进程部署

    上篇<Docker基础入门及示例>文章介绍了Docker部署,以及相关.net core 的打包示例.这篇文章我将以oss.offical.site站点为例,主要介绍下在linux机器下完 ...

随机推荐

  1. Asp.Net北大青鸟总结(五)-数据绑定控件

        在前面的博客我已经介绍了关于一个特殊控件也是我们经经常使用到的控件gridview的使用实现真假分页.这也是属于绑定控件的一种使用.那么我们接下来来介绍一下数据绑定这门技术吧!  一.数据绑定 ...

  2. python列表解析和正同表达式

    正则表达式 [i for i in ['1232','233','22'] if re.match('^233$', i)]   return  ['233']

  3. 【Java】Java_17 数组

    数组 数组是一种数据类型,属于引用类型. 1.定义数组 type[] arrayName; type arrayNmae[]; 以上2种定义数组方式的区别: type[] arrayName:语义强, ...

  4. jquery淡入淡出无延迟代码

    <!DOCTYPE html> <html> <head> <script src="jquery.js"></script& ...

  5. Python 中实现装饰器时使用 @functools.wraps 的理由

    Python 中使用装饰器对在运行期对函数进行一些外部功能的扩展.但是在使用过程中,由于装饰器的加入导致解释器认为函数本身发生了改变,在某些情况下——比如测试时——会导致一些问题.Python 通过  ...

  6. URL中的#号

    一.#的涵义 #代表网页中的一个位置.其右面的字符,就是该位置的标识符.比如, http://www.example.com/index.html#print 就代表网页index.html的prin ...

  7. php文件加载路径

    <?php require('reusable.php'); echo "相对路径加载<br/>"; /* ./ 表示当前文件所在的目录 ../ 表示当前文件所在 ...

  8. 动态加载script 和 link

    1.script EventUtil.addHandler(window, "load", function(event){ var script = document.creat ...

  9. ASP.NET MVC传递Model到视图的多种方式总结

    ASP.NET MVC传递Model到视图的多种方式总结 有多种方式可以将数据传递到视图,如下所示: ViewData ViewBag PartialView TempData ViewModel T ...

  10. Atitit.biz业务系统 面向框架  面向模式---------数据映射imp

    Atitit.biz业务系统 面向框架  面向模式---------数据映射imp 1.1. 面向变量  面向过程  面向对象 面向组件  面向框架  面向服务 面向模式1 1.2. 第2章 架构模式 ...