Linux 进程(二):进程关系及其守护进程

进程关系

进程组

进程组是一个或多个进程的集合。通常,它们是在同一作业中结合起来的,同一进程组中的各进程接收来自同一终端的各种信号,每个进程组有一个唯一的进程组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,其终端名为 ?。

[[email protected] 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(0);

        /*
 *       * Get maximum number of file descriptors.
 *               */
        if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
        {
                printf("%s: can‘t get file limit\n", cmd);
                return ;
        }

        /*
 *       * Become a session leader to lose controlling TTY.
 *               */
        if ((pid = fork()) < 0)
        {
                printf("%s: can‘t fork\n", cmd);
                return ;
        }
        else if (pid != 0) /* parent */
                exit(0);
        setsid();

        /*
 *       * Ensure future opens won‘t allocate controlling TTYs.
 *               */
        sa.sa_handler = SIG_IGN;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        if (sigaction(SIGHUP, &sa, NULL) < 0)
        {
                printf("%s: can‘t ignore SIGHUP\n", cmd);
                return ;
        }
        if ((pid = fork()) < 0)
        {
                printf("%s: can‘t fork\n", cmd);
                return ;
        }
        else if (pid != 0) /* parent */
                exit(0);

        /*
 *       * Change the current working directory to the root so
 *               * we won‘t prevent file systems from being unmounted.
 *                       */
        if (chdir("/") < 0)
        {
                printf("%s: can‘t change directory to \n", cmd);
        }

        /*
 *       * Close all open file descriptors.
 *               */
        if (rl.rlim_max == RLIM_INFINITY)
                rl.rlim_max = 1024;
        for (i = 0; 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(0);
        fd2 = dup(0);

        /*
 *       * Initialize the log file.
 *               */
        openlog(cmd, LOG_CONS, LOG_DAEMON);
        if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
                syslog(LOG_ERR, "unexpected file descriptors %d %d %d",
                  fd0, fd1, fd2);
                exit(1);
        }
}

int main(void)
{
        daemonize("ps");
        sleep(30);
        return 0;
}

[[email protected] process]# ./init
[[email protected] process]# ps -efj
UID        PID  PPID  PGID   SID  C STIME TTY          TIME CMD
root         1     0     1     1  0 19:35 ?        00:00:03 /sbin/init
root      2198  2174  2198  2057  0 19:38 pts/0    00:00:00 vim init.c
root      2237     1  2236  2236  0 19:45 ?        00:00:00 ./init
root      2238  2188  2238  2100  0 19:45 pts/1    00:00:00 ps -efj
时间: 2024-10-19 19:36:39

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

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

. . . . . 目录 (一) 一起学 Unix 环境高级编程(APUE) 之 标准IO (二) 一起学 Unix 环境高级编程(APUE) 之 文件 IO (三) 一起学 Unix 环境高级编程(APUE) 之 文件和目录 (四) 一起学 Unix 环境高级编程(APUE) 之 系统数据文件和信息 (五) 一起学 Unix 环境高级编程(APUE) 之 进程环境 (六) 一起学 Unix 环境高级编程(APUE) 之 进程控制 (七) 一起学 Unix 环境高级编程(APUE) 之 进程关系

2进程之间的关系:进程组,会话,守护进程

 1进程组 一个或过个进程的集合,进程组ID是一个正整数.用来获得当前进程组ID的函数. pid_t getpgid(pid_t pid) pid_t getpgrp(void) 获得父子进程进程组 运行结果: 组长进程标识:其进程组ID=其进程ID 组长进程可以创建一个进程组,创建该进程组中的进程,然后终止,只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关. 进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组) 一个进程可以为自己或子进程设置进程组ID i

进程、join方法、守护进程、互斥锁

操作系统发展史 发展史1. 1946年第一台计算机诞生--20世纪50年代中期,计算机工作还在采用手工操作方式.此时还没有操作系统的概念.2. 20世纪50年代后期,出现人机矛盾:手工操作的慢速度和计算机的高速度之间形成了尖锐矛盾,手工操作方式已严重损害了系统资源的利用率(使资源利用率降为百分之几,甚至更低),不能容忍.3. ?唯一的解决办法:只有摆脱人的手工操作,实现作业的自动过渡.这样就出现了成批处理.?? 批处理联机批处理系统(即作业的输入/输出由CPU来处理,例如 通过磁带)脱机批处理系

第二十一天:进程间的通信及守护进程

进程的定义:一个其中运行着一个或者多个线程的地址空间和这些线程所需要的系统资源.通俗的说就是正在运行的程序.可以使用ps -ajx查看进程,每个进程都会被分配一个唯一的数字编号,为进程标识符(PID)父进程的描述符称为(PPID),STAT表示系统进程的运行状态,其中,S表示睡眠,R表示可运行,D表示等待,T表示停止,Z表示死进程或僵尸进程(子进程存在,父进程死亡,编写代码绝对不能出现). 使用fork函数创建进程.fork复制当前进程,在进程表中创建一个新的表项,新的表项中的许多属性与当前的进

作业、进程组、会话和守护进程

1. 进程组 每个进程除了有一个进程ID之外,还属于一个进程组.进程组是一个或多个进程的集合.通常,它们与同一作业相关联,可以接收来自同一终端的各种信号.每个进程组有一个唯一的进程组ID.每个进程组都可以有一个组长进程.组长进程的标识是,其进程组ID等于其进程ID.组长进程可以创建一个进组,创建该组中的进程,然后终止.只要在某个进程组中一个进程存在,则该进程组就存在,这与其组长进程是否终止无关. 2.作业 Shell分前后台来控制的不是进程而是作业(Job)或者进程组(Process Group

Linux系统开发7 进程关系,守护进程

[本文谢绝转载原文来自http://990487026.blog.51cto.com] <大纲> Linux系统开发7  进程关系守护进程 终端 网络终端 Linux PCB结构体信息 进程组 修改子进程.父进程的组ID 会话组 设置一个会话脱离控制终端 生成一个新的会话 守护进程 守护进程模板 获取当前系统时间  终端 在UNIX系统中用户通过终端登录系统后得到一个Shell进程这个终端成为Shell进 程的控制终端Controlling Terminal在讲进程时讲过控制终端是保存在PCB

Linux中 终端、作业控制与守护进程

1. 进程组 每个进程除了有一个进程 ID之外,还属于一个进程组.进程组是一个或多个进程的集合. 通常,它们与同一作业相关联,可以接收来自同一终端的各种信号. 每个进程组有一个唯 一的进程组ID.每个进程组都可以有一个组长进程.组长进程的标识是,其进程组 ID等于 其进程ID. 组长进程可以创建一个进程组,创建该组中的进程,然后终止. 只要在某个进程组中一个 进程存在,则该进程组就存在,这与其组长进程是否终止无关. 2.作业 Shell分前后台来控制的不是进程而是 作业(Job)或者进程组( P

Java实现Linux下服务器程序的双守护进程

一.简介 现在的服务器端程序很多都是基于Java开发,针对于Java开发的Socket程序,这样的服务器端上线后出现问题需要手动重启,万一大半夜的挂了,还是特别麻烦的. 大多数的解决方法是使用其他进程来守护服务器程序,如果服务器程序挂了,通过守护进程来启动服务器程序. 万一守护进程挂了呢?使用双守护来提高稳定性,守护A负责监控服务器程序与守护B,守护B负责监控守护A,任何一方出现问题,都能快速的启动程序,提高服务器程序的稳定性. Java的运行环境不同于C等语言开发的程序,Java程序跑在JVM

linux下Shell编程--标准的守护进程的启动脚本

一个标准的守护进程的启动脚本: #! /bin/sh WHOAMI=`whoami` PID=`ps -u $WHOAMI | gerp mydaemond | awk '{print $1}'` if (test "$1" = "") then echo "mydaemond [start][stop][version]" exit 0 fi if ( test "$1" = "status") then