Linux(十五)进程间关系和守护进程

进程组/作业/会话

1.进程组
每个进程除了进程ID之外,还属于一个进程组。进程组是一个或多个进程的集合。通常,他们与一个作业相关联,可以接受来自同一终端的各种信号。每个进程组有一个唯一的进程组ID。每个进程组都可以有一个组长进程。组长进程的标识是,其进程组ID等于其进程ID。组长进程可以创建一个进程组,创建该组中的进程,然后终止。只要某个进程组中一个进程存在,则该进程组就存在,这与其组长进程是否终止无关
这里写图片描述
& :表示将进程组放在后台执行
进程:81017 81018 81019
组长:81017,该组的第一个进程,其ID与进程组ID一样
ps选项:
a:不仅列当前用户的进程,也列出其他用户的进程
x:表示不仅列有控制终端的进程,也列出所有无控制终端的进程
j:表示列出与作业控制相关的信息

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

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

一旦作业运行结束,shell就把自己提到前台(子进程还在,可是子进程不属于作业),如果原来的前台进程还存在(如果这个子进程还没终止),它就会自动变为后台进程组。
我们重新理解一下,在前台新起作业,shell是无法运行,因为他被提到了后台。但是如果前台进程退出,shell就又被提到了前台,所以可以继续接受用户输入

下面我们来看一个例子

#include <stdio.h>
#include <unistd.h>
int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return 1;
    }
    else if(id == 0)
    {//child
        while(1)
        {
            printf("child(%d)# i am running!\n",getpid());
            sleep(1);
        }

    }
    else
    {
        int i = 5;
        while(i)
        {
            printf("parent(%d)# i am going to dead...%d\n",getpid(),i--);
            sleep(1);
        }
    }
    return 0;
}

这里写图片描述
我们可以发现,程序跑起来之后,在前台新起了一个作业,包含父子两个进程,五秒之内shell是无法接受任何命令的,说明此事的前台作业不是shell,当五秒后,父进程退出,子进程不属于作业,此时shell就把自己提到了前台,我们发现shell是可以处理命令的。
这就是前面说的新起的作业退出了,但是子进程还在,所以就被提到后台

3.会话
会话是一个或多个进程组的集合。一个会话可以有一个控制终端。这通常是登录到其上的终端设备(终端登录情况)或伪终端设备(网络登录情况)。建立与控制终端连接的会话首进程称为控制进程。一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组。所以一个会话中,应该包括控制进程(会话首进程)一个前台进程组和任意后台进程组
这里写图片描述
我们可以看到会话id SID:80974
我们通过ps aux | grep -E 80974 | grep -v grep
可以看到会话首进程就是bash,而且三个进程父进程都是bash。

每打开一个终端,就新建了一个会话

作业控制

Session与进程组“Shell可同时运行一个前台进程和任意多个后台进程”其实是不全面的,现在我们来研究更复杂的情况,事实上,Shell分前后台来控制的不是进程,而是作业或者进程组,一个前台作业可以由多个进城组成,一个后台作业也可以由多个进程组成,Shell可以同时运行一个前台作业和任意多个后台作业,这称为作业控制

作业控制有关的信号

将cat放到后台运行,由于cat需要读标准输入,而后台进程是不能读标准输入的,因此内核发SIGTTIN信号给进程,该信号的默认处理动作是使进程停止
jobs命令可以查看当前有哪些作业。fg命令可以将某个作业提至前台运行,如果该作业的进程组正在后台运行,则提至前台运行,如果该作业处于停止状态,则给进程组的每个进程发SIGCONT信号使它继续运行。参数%1表示将第一个作业提至前台运行。cat提到前台运行后,挂起等待终端输入,当输入hello并回车后,cat打印出同样的一行,然后继续挂起等待输入。

守护进程

认识守护进程
守护进程也称精灵进程(Daemon),是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事情。守护进程是一种很有用的进程。Linux的大多数服务器就是用守护进程实现的。比如ftp服务器,Web服务器等。同时守护进程完成许多系统任务。比如,作为规划进程crond等
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。其他进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程(守护进程)不收用户登录注销影响,他们一直在运行着,这中近程叫做守护进程

我们可以用

ps axj | more

查看系统中的进程
凡是TPGID一栏写着-1 的都是没有控制终端的进程,也就是守护进程
在COMMAND一列用【】括起来的名字表示内核线程,这些线程在内核里创建,没有用户空间代码,因此没有程序文件名和命令行,通常以k开头的名字,表示Kernel。
守护进程通常采用以d结尾的名字,表示Daemon。

创建守护进程
创建守护进程最关键的一步是调用setsid函数创建一个新的Session,并成功为Session Leader。

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

需要注意的是,调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。要保证当前进程不是进程组的Leader很容易,我们先fork再调用setsid就可以了。fork创建的子进程和父进程在同一个进程组中,进程组的Leader必然是进程组的第一个进程,也就是父进程。

成功调用该函数的结果是:
*创建一个新的Session,当前进程成为Session Leader,当前进程id就是Session的id
*创建一个新的进程组,当前进程成为进程组的Leader,当前进程id就是进程组的id
如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。所谓失去控制终端就是原来的终端仍然是打开的,礽贪可以读写,但是只是一个普通的打开文件,而不是控制终端了

下面是具体的代码实现

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

void mydaemon(void)
{
    int i;
    int fd0;
    pid_t pid;
    struct sigaction sa;
    umask(0);//1.调用umask将文件模式创建屏蔽字设置为0.
    //2.调用fork,父进程退出(exit)
    //如果该守护进程是作为一条简单的shell命令启动的
    //,那么父进程终止使得shell认为该命令已经执行完毕
    //保证子进程不是一个进程组的组长进程
    if((pid = fork()) < 0)
    {
        perror("fork");
    }
    else if(pid > 0)
    {
        exit(0);
    }
    setsid();//3.调用setsid创建一个新会话

    sa.sa_handler = SIG_IGN;//忽略SIGCHLD信号
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    if(sigaction(SIGCHLD,&sa,NULL) < 0)
    {
        return;
    }
    //注意!再次fork,终止父进程,保持子进程不是话首进程,从而保证后续不会再和其他终端关联!
    //这部分不是必须的
    if((pid = fork()) < 0)
    {
        printf("fork error!\n");
        return;
    }
    else if(pid != 0)
    {
        exit(0);
    }
    if(chdir("/") < 0)
    {
        //5.将当前工作目录更改为根目录
        printf("child dir error\n");
        return;
    }
    //关闭不再需要的文件描述符,或者重定向到 /dev/null
    close(0);
    fd0 = open("/dev/null",O_RDWR);
    dup2(fd0,1);
    dup2(fd0,2);
}
int main()
{
    mydaemon();
    while(1)
    {
        sleep(1);
    }
}

这里写图片描述

猜你喜欢

转载自blog.csdn.net/mignatian/article/details/80367928