进程间关系与守护进程

  • Post author:
  • Post category:其他



进程组

  • 每个进程除了有⼀个进程ID之外,还属于⼀个进程组。进程组是⼀个或多个进程的集合。
  • 通常,它们与同⼀作业相关联,可以接收来⾃同⼀终端的各种信号。
  • 每个进程组有⼀个唯⼀的进程组ID。每个进程组都可以有⼀个组⻓进程。组⻓进程的标识是,其进程组ID等于其进程ID。 组⻓进程可以创建⼀个进程组,创建该组中的进程,然后终⽌。
  • 只要在某个进程组中⼀个进程存在,则该进程组就存在,这与其组⻓进程是否终⽌⽆关。实例如下
[lhwei@localhost Daemon]$ sleep 20 | sleep 30 | sleep 20 &
[1] 19517
[lhwei@localhost Daemon]$ ps axj | head -n1; ps axj | grep sleep | grep -v grep
  PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
 19406  19515  19515  19309 pts/1     19520 S     1001   0:00 sleep 20
 19406  19516  19515  19309 pts/1     19520 S     1001   0:00 sleep 30
 19406  19517  19515  19309 pts/1     19520 S     1001   0:00 sleep 20

&:表示将进程组放到后台运行

进程:19515 19516 19517

组长:19515,进程组中的第一个进程

kill -9 杀死进程组组长,进程组还在

ps 选项:

a: 不仅显示当前用户的进程,也列出所有其他用户的进程

x: 表示不仅列有控制端的进程,也列出所有无控制终端的进程

j: 表示列出与作业控制相关的信息


作业

shell分前后台来控制的不是进程而是作业(job)或者进程组(Process Group)。一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,shell可以运行一个前台作业和任意多个后台作业,这成为作业控制。

作业与进程组的区别:如果作业中的某个进程有创建了子进程,则子进程不属于作业。

作业运行结束,shell就把自己提到前台(子进程还在,但是子进程不属于作业),如果原来的前台进程还存在(如果这个子进程还没终止),它自动变成后台进程。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>

int main()
{
    pid_t id = fork();
    if(id == 0) {
        while(1) {
            printf("I am child, id:%d\n", getpid());
            sleep(1);
        }
    } else if (id > 0) {
        sleep(5);
        printf("father exit, id:%d\n", getpid());
    } else {
        exit(1);
    }

    return 0;
}


实验结果(

放在前台运行):


放在前运行,前5秒,父进程在前台,shell被调到后台,这时输命令并没有什么作用,因为shell接收不到,5秒后父进程退出,shell被提到前台,子进程被自动提到后台,子进程和父进程并不在同一个作业,所以父进程退出后,子进程还在后台继续打印消息,这时输入:kill -9 20241,回车之后,子进程被杀掉,停止打印。

查看进程信息


与属于同一作业的进程相比,他们的PPID不相同。


会话

会话(Session)是⼀个或多个进程组的集合。 ⼀个会话可以有⼀个控制终端。这通常是登陆到其上的终端设备(在终端登陆情况下)或伪终端设备(在⺴络登陆情况下)。建⽴与控制终端连接的会话⾸进程被称为控制进程。⼀个会话中的⼏个进程组可被分为⼀个前台进程组以及⼀个或多个后台进程组。所以⼀个会话中,应该包括控制进程(会话⾸进程),⼀个前台进程组和任意后台进程组,新打开⼀个终端:


一个作业由前台被ctrl+z放在后台,他的

ctrl+c 杀掉的不是一个进程,而是一个作业

关系示意图如下:




⼀个前台作业可以由多个进程组成,⼀个后台作业也可以由多个进程组成,Shell可以同时运⾏⼀个前台作业和任意多个

后台作业,这称为

作业控制(Job Control)


作业控制有关的信号

jobs:命令可以查看当前有哪些作业

fg:命令可以将某个作业提⾄前台运⾏,如果该作业的进程组正在后台运⾏则提⾄前台运⾏,如果该作业处于停⽌状态,则给进程组的每个进程发SIGCONT信号使它继续运⾏

Ctrl-Z:则向所有前台进程发SIGTSTP信号,该信号的默认动作是使进程停⽌

bg:命令可以让某个停⽌的作业在后台继续运⾏,也需要给该作业的进程组的每个进程发SIGCONT信号

给⼀个停⽌的进程发SIGTERM(15)信号,这个信号并不会⽴刻处理,⽽要等进程准备继续运⾏之前处理,默认动作是终⽌进程

给⼀个停⽌的进程发SIGKILL信号就会直接终止该进程,不管进程的状态如何


守护进程

守护进程也称精灵进程(Daemon),是运⾏在后台的⼀种特殊进程。它独⽴于控制终端并且周期性地执⾏某种任务或等待处某些发⽣的事件,Linux的⼤多数服务器就是⽤守护进程实现的

普通进程都是在用户登录或者运行程序时创建的,在程序运行结束或用户注销时终止,守护进程没有控制终端,不能直接和用户交互,不受登陆注销的影响,在后台一直运行,提供周期性服务,直到机器停止


守护进程的特点:

  • 自成会话
  • 自成进程组且为组长(Leader)
  • 脱离终端(视终端为普通文件)
  • 后台运行

ps axj命令查看系统中的进程。

  • a表⽰不仅列当前⽤户的进程,也列出所有其他⽤ 户的进程
  • x表⽰不仅列有控制终端的进程,也列出所有⽆控制终端的进程
  • j表⽰列出与 作业控制相关的信息


创建守护进程

#include <unistd.h>
pid_t setsid(void);
//该函数调⽤成功时返回新创建的Session的id(其实也就是当前进程的id),出错返回-1。


注意:

调⽤这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1

要保证当前进程不是进 程组的Leader也很容易,只要先fork再调⽤setsid

代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>

void myDeamon(){
    umask(0);

    pid_t pid;
    int fd0;
    struct sigaction sa;
    
    pid = fork();
    if(pid > 0) {
        exit(0);
    } else if(pid < 0){
        printf("fork1 error\n");
        exit(1);
    }

    //调用setsid创建一个新的会话
    //经过fork之后,直接使父进程退出,保证了当前进程不是进程组的组长
    setsid(); 

    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    
    if(sigaction(SIGCHLD, &sa, NULL) < 0) { //注册子进程退出忽略信号
        return;
    }

    //再次调用fork,让父进程退出,保持子进程不是话首进程,从而不正之后不会与其他进程关联
    //这部分不是必须的
    pid = fork();
    if(pid > 0) {
        //father
        exit(0);
    } else if(pid < 0) {
        printf("fork2 error\n");
        exit(2);
    }
    
    if(chdir("/") < 0) {    //修改当前工作目录到根目录
        printf("chdir error\n");
        return;
    }

    //关闭不许要的文件描述符 或 重定向
    close(0);
    fd0 = open("/dev/null", O_RDWR);
    dup2(fd0, 1);
    dup2(fd0, 2);
}


int main()
{
    myDeamon();
    while(1) {
        sleep(1);
    }
    return 0;
}

实验结果:


查看默认路径和文件描述符:

1、进入/proc目录,在进入16175目录查看线程属性


2、查看更改后的工作目录和文件描述符



上面的代码可以清楚地看到守护进程一步步的创建过程,也有一种非常简单的方法来创建守护进程,省去了中间繁琐的步骤,线面介绍这个函数:

daemon:

头文件及函数原型:

#include <unistd.h>  
int daemon(int nochdir, int noclose); 

参数:

当 nochdir为零时,当前目录变为根目录,否则不变;

当 noclose为零时,标准输入、标准输出和错误输出重导向为/dev/null,也就是不输出任何信息,否则照样输出。

返回值:

deamon()调用了fork(),如果fork成功,那么父进程就调用_exit(2)退出,所以看到的错误信息 全部是子进程产生的。如果成功函数返回0,否则返回-1并设置errno。 示例:

代码如下:

#include<stdio.h>
#include<unistd.h>
int main() 
{
    daemon(0,0);
    while(1){
        sleep(1);
    }
}

实验结果:


查看进程的工作目录和文件描述符



注意,调⽤这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1

要保证当前进程不是进 程组的Leader也很容易,只要先fork再调⽤setsid



版权声明:本文为luhaowei0066原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。