1. 进程组

每个进程除了有一个进程 ID之外,还属于一个进程组。进程组是一个或多个进程的集合。

通常,它们与同一作业相关联,可以接收来自同一终端的各种信号。 每个进程组有一个唯

一的进程组ID。每个进程组都可以有一个组长进程。组长进程的标识是,其进程组 ID等于

其进程ID。

组长进程可以创建一个进程组,创建该组中的进程,然后终止。 只要在某个进程组中一个

进程存在,则该进程组就存在,这与其组长进程是否终止无关。

2.作业

Shell分前后台来控制的不是进程而是 作业(Job)或者进程组( Process Group。一个

前台作业可以由多个进程组成,一个后台也可以由多个进程组成, Shell可以运行一个前台

作业和任意多个后台作业,这称为作业控制。

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

一旦作业运行结束, Shell就把自己提到前台,如果原来的前台进程还存在(如果这个子进

程还没终止),它自动变为后台进程组。

例子:

#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    pid_t id ;
    id = fork();
    if (id  == 0){
        while (1)
        {
            printf("child\n");
            sleep(4);
        }
    }else
    {
            printf("father exit\n");
            exit(0);
    }
    return 0;
}

运行:

[bozi@localhost test_20160731]$ gcc -o work work.c
[bozi@localhost test_20160731]$ ./work
father exit
[bozi@localhost test_20160731]$ child // 父进程结束 bash调到前台 又变成后台 因为子进程还在运行
child
child
child

3.会话

会话(Session)是一个或多个进程组的集合。

一个会话可以有一个控制终端。 这通常是登陆到其上的终端设备(在终端登陆情况下)或

伪终端设备(在网络登陆情况下)。 建立与控制终端连接的会话首进程被称为控制进程。

一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组。所以一个

会话中,应该包括控制进程(会话首进程),一个前台进程组和任意后台进程组。

1 $ proc1 | proc2 &

2 $ proc3 | proc4 | proc5

其中proc1 与proc2 属于同一个后台进程组, proc3, proc4和 proc5属于同一个前台进程组,

Shell本身属于一个单独的进程组。这些进程组的控制终端相同,它们同属于一个会话,当

用户在控制终端输入特殊的控制键(如 Ctrl+C,产生 SIGINT,Ctrk+\,产生 SIGQUIT,Ctrl+Z,

产生SIGTSTP),内核发送相应的信号给前台进程组中的所有进程。


一般在进程组中 第一个进程就是组长进程

组长进程的组ID与组长的PID是一样的

其他进程的组ID与组长进程的组ID一样

进程组中只要还有一个进程存在,组就存在,与组长存在不存在没有关系。

在命令行上运行一个二进制代码,就运行起来一个作业,一个进程完成一个任务,而作业是多个进程合作完成的复杂的任务

Ctrl+c 只能终止 前台进程组

[root@localhost test_20160731]# sleep 200|more|sleep 300

用管道 创建 进程组

[bozi@localhost ~]$ ps axj|grep -ER 'sleep|more'

 3828  7640  7640  3440 pts/0     7640 S+       0   0:00 sleep 200

 3828  7641  7640  3440 pts/0     7640 S+       0   0:00 more

 3828  7642  7640  3440 pts/0     7640 S+       0   0:00 sleep 300

 7646  7684  7683  7646 pts/2     7683 S+     500   0:00 grep -ER sleep|more

[bozi@localhost ~]$ ps axj|head

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND

[bozi@localhost ~]$ ps axj|grep -ER 'sleep|more|bash'

 3438  3440  3440  3440 pts/0     7728 Ss     500   0:00 bash  最开始的用户组   进程ID 组ID SID(会话ID) 相同

 3820  3828  3828  3440 pts/0     7728 S        0   0:00 bash                【特殊 su产生的】

 3438  7118  7118  7118 pts/1     7118 Ss+    500   0:00 bash

 3438  7646  7646  7646 pts/2     7731 Ss     500   0:00 bash

 3438  7668  7668  7668 pts/3     7668 Ss+    500   0:00 bash

 3828  7728  7728  3440 pts/0     7728 S+       0   0:00 sleep 200

 3828  7729  7728  3440 pts/0     7728 S+       0   0:00 more

 3828  7730  7728  3440 pts/0     7728 S+       0   0:00 sleep 300

 7646  7732  7731  7646 pts/2     7731 S+     500   0:00 grep -ER sleep|more|bash

只是普通用户 没有su的情况

 8194  9587  9587  9587 pts/1     9685 Ss     500   0:00 bash

 9587  9685  9685  9587 pts/1     9685 S+     500   0:00 sleep 200

 9587  9686  9685  9587 pts/1     9685 S+     500   0:00 more

 9587  9687  9685  9587 pts/1     9685 S+     500   0:00 sleep 300

+表示是前台进程

bash 创建 sleep more sleep 后 自己转为后台 因为只允许一个前台作业(子进程不属于作业)


终端就是文件

查看终端对应的设备

代码:

#include<stdio.h>
#include <unistd.h>
int main()
{
    printf("fd:%d -> %s \n", 0 , ttyname(0));
    printf("fd:%d -> %s \n", 1 , ttyname(1));
    printf("fd:%d -> %s \n", 2 , ttyname(2));
    return 0;
}

运行:

[bozi@localhost test_20160731]$ ./printf_ttyname
fd:0 -> /dev/pts/0
fd:1 -> /dev/pts/0
fd:2 -> /dev/pts/0


重新打开一个终端

[bozi@localhost test_20160731]$ ./printf_ttyname
fd:0 -> /dev/pts/1
fd:1 -> /dev/pts/1
fd:2 -> /dev/pts/1


[bozi@localhost ~]$ ls -al /dev/pts/

总用量 0

drwxr-xr-x.  2 root root      0 7月  31 10:09 .

drwxr-xr-x. 19 root root   3840 7月  31 02:10 ..

crw--w----.  1 bozi tty  136, 0 7月  31 10:16 0

crw--w----.  1 bozi tty  136, 1 7月  31 08:53 1

crw--w----.  1 bozi tty  136, 2 7月  31 09:57 2

c---------.  1 root root   5, 2 7月  31 10:09 ptmx

0 1 2 就是终端文件  再新建一个终端则 文件 多一个 如3 4 5 。。。

向别的终端显示 就是先别的终端文件 写信息

spacer.gifwKiom1eeHAeyF0fMAACAy9T_5Tw622.png

[bozi@localhost ce]$ ./a.out&

[1] 7984

[1]是 作业号  7984是作业的最后一个进程 的进程ID

如 进程组

[bozi@localhost test_20160731]$ sleep 100 | more&

[1] 9544

[bozi@localhost test_20160731]$

[1]+  Stopped                 sleep 100 | more

[bozi@localhost test_20160731]$ ps axj|grep -ER 'sleep |more'

 8196  9543  9543  8196 pts/0     9547 T      500   0:00 sleep 100

 8196  9544  9543  8196 pts/0     9547 T      500   0:00 more

9870就是进程组的最后一个进程的进程ID


fg 1(1是作业号)  后台变成前台

 Ctrl+z  前台变成后台(但是stop状态)

 bg 1 让后台的作业运行起来

jobs 查看有多少作业

jobs

[1]+  Done                    ./a.out


不允许后台程序 从标准输入读数据 但可以允许向标准输出 写数据

如 cat 从标准输入读入 然后输出

[bozi@localhost test_20160731]$ cat
sd
sd
sd
sd
aa
aa
cc
cc
aa
aa

将cat转到后台

[bozi@localhost test_20160731]$ cat&
[1] 8234
[bozi@localhost test_20160731]$
[1]+  Stopped                 cat

不允许读数据的 一直是Stop状态

[bozi@localhost test_20160731]$ jobs
[1]+  Stopped                 cat
[bozi@localhost test_20160731]$ bg 1
[1]+ cat &
[bozi@localhost test_20160731]$ jobs
[1]+  Stopped                 cat

解释进程接受信号 是在适合的时候

[bozi@localhost test_20160731]$ ps
  PID TTY          TIME CMD
 8196 pts/0    00:00:00 bash
 8301 pts/0    00:00:00 ps
[bozi@localhost test_20160731]$ cat&
[1] 8308
[bozi@localhost test_20160731]$
[1]+  Stopped                 cat
[bozi@localhost test_20160731]$ kill -15 8308
[bozi@localhost test_20160731]$ ps
  PID TTY          TIME CMD
 8196 pts/0    00:00:00 bash
 8308 pts/0    00:00:00 cat
 8310 pts/0    00:00:00 ps
[bozi@localhost test_20160731]$ fg 1
cat
已终止

现象:后台stop 发送kill -15 不终止 但转到前台时 就马上终止了

原因:cat转到后台后 变成stop状态 不能接受信号 只有前台进程可以接受标准输入(如kill -15)

所以在cat从后台转到前台时 他就可以接受标准输入  他的状态由后台转到前台状态 但是这个变化是由在内核中修改进程PID完成的 这样就有了从内核到用户态接受信号的时机 这样就可以接受信号了

特殊的是 9号状态 直接在内核区就终止了

[bozi@localhost test_20160731]$ cat&
[1] 8336
[bozi@localhost test_20160731]$
[1]+  Stopped                 cat
[bozi@localhost test_20160731]$ kill -9 8336
[bozi@localhost test_20160731]$
[1]+  已杀死               cat

原因:9号信号特殊 操作系统在内核检测到 就马上终止进程 不用转前台


守护进程

进程名多以d结尾  如httpd

以【】包裹的进程名为内核进程

TTY 为? 表示守护进程 是没有终端的 与控制终端脱离关联

 

[bozi@localhost test_20160731]$ ps aux | grep -ER 'd]$'
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         2  0.0  0.0      0     0 ?        S    07:16   0:00 [kthreadd]
root        17  0.0  0.0      0     0 ?        S    07:16   0:00 [kacpid]
root        22  0.0  0.0      0     0 ?        S    07:16   0:00 [ksuspend_usbd]
root        23  0.0  0.0      0     0 ?        S    07:16   0:00 [khubd]
root        24  0.0  0.0      0     0 ?        S    07:16   0:00 [kseriod]
root        28  0.0  0.0      0     0 ?        S    07:16   0:00 [khungtaskd]
root        30  0.0  0.0      0     0 ?        SN   07:16   0:00 [ksmd]
root        38  0.0  0.0      0     0 ?        S    07:16   0:00 [pciehpd]
root        40  0.0  0.0      0     0 ?        S    07:16   0:00 [kpsmoused]
root        72  0.0  0.0      0     0 ?        S    07:16   0:00 [kstriped]
root      1038  0.0  0.0      0     0 ?        S    07:17   0:00 [kauditd]

守护进程 是 后台进程的一种 只是与终端无关的 自成用户组 自成会话 与终端脱离关联 父进程为1

创建守护进程:

方法多,但核心是利用setsid函数

函数:

       pid_t setsid(void);

DESCRIPTION

       setsid() creates a new session

练习代码【自己的一个精灵程序】:

#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include<signal.h>
#include <fcntl.h>
void my_daemon()
{
    umask(0);// 设置文件掩码为0
    pid_t id = fork();
    if (id == 0)
    {
        // child
        setsid();//  设置 新会话
        chdir("/");// 更换 目录
        close(0);
        close(1);
        close(2);
        signal(SIGCHLD,SIG_IGN);// 注册子进程退出忽略信号
    }
    else
    {
        sleep(14);
        exit(0);// 终止父进程
    }
    close(0);// 关闭标准输入
    int fd0 = open("dev/null", O_RDWR);// 重定向所有标准输出、 错误到/dev/null
    dup2(fd0, 1);
    dup2(fd0, 2);
}
int main()
{
    my_daemon();
    while(1);
}

运行:

 [bozi@localhost test_20160731]$ ./my_daemon
[bozi@localhost ~]$ ps axj|grep -ER 'my_'
 8196 10168 10168  8196 pts/0    10168 S+     500   0:00 ./my_daemon // 父进程
10168 10169 10169 10169 ?           -1 Rs     500   0:04 ./my_daemon  // 子进程
10132 10173 10172 10132 pts/1    10172 S+     500   0:00 grep -ER my_
[bozi@localhost ~]$ ps axj|grep -ER 'my_'
 8196 10168 10168  8196 pts/0    10168 S+     500   0:00 ./my_daemon
10168 10169 10169 10169 ?           -1 Rs     500   0:05 ./my_daemon
10132 10175 10174 10132 pts/1    10174 S+     500   0:00 grep -ER my_
[bozi@localhost ~]$ ps axj|grep -ER 'my_'
 8196 10168 10168  8196 pts/0    10168 S+     500   0:00 ./my_daemon
10168 10169 10169 10169 ?           -1 Rs     500   0:11 ./my_daemon
10132 10178 10177 10132 pts/1    10177 S+     500   0:00 grep -ER my_
[bozi@localhost ~]$ ps axj|grep -ER 'my_'
    1 10169 10169 10169 ?           -1 Rs     500   0:15 ./my_daemon
10132 10180 10179 10132 pts/1    10179 S+     500   0:00 grep -ER my_
[bozi@localhost ~]$ ps axj|head -n1
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND

改进:{创建两次子进程 让孙子进程当守护进程}

#include<stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
void creat_daemon()
{
    int i;
    int fd0;
    pid_t pid;
    struct sigaction sa;
    umask(0);// 设置文件 掩码为0
    if ((pid = fork()) < 0)
    {
    }
    else if (pid != 0)
    {
        exit(0); // 终止父进程
    }
    setsid(); // 设置新会话
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if (sigaction(SIGCHLD, &sa, NULL) < 0)//注册子进程退出忽略信号
    {
        return;
    }
    if ((pid = fork()) < 0)
    {
        printf("fork error\n");
        return ;
    }
    else if (pid != 0) // 终止父进程 保证子进程 不是话首 进程 从而保证此后不会再和其他终端关联
    {
        exit(0);
    }
    if (chdir("/") < 0)
    {
        // 更改工作目录到根
        printf("child dir error\n");
        return ;
    }
    close(0); // 关闭标准输入
    fd0 = open("/dev/null", O_RDWR);// 重定向标准输出 标准错误到/dev/null
    dup2(fd0, 1);
    dup2(fd0, 2);
}
int main()
{
    creat_daemon();
    while (1)
    {
        sleep(1);
    }
}

运行监视:

[bozi@localhost dev]$ ps -axj | grep -ER 'my_'

Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.8/FAQ

    1 10942 10941 10941 ?           -1 S      500   0:00 ./my_daemon_op

10132 10945 10944 10132 pts/1    10944 S+     500   0:00 grep -ER my_

[bozi@localhost dev]$ ps -axj | head -n1

Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.8/FAQ

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND

【fork两次的原因】

fork第二次主要目的是。防止进程再次打开一个控制终端。因为打开一个控制终端的前台条件是该进程必须是会话组长。再fork一次,孙子进程ID != sid(sid是进程子进程的sid)。所以也无法打开新的控制终端。

终止子进程 保证孙子进程 不是话首 进程 从而保证此后不会再和其他终端关联

直接调用系统函数:

       int daemon(int nochdir, int noclose);

代码:

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

运行:

[bozi@localhost dev]$ ps -axj | grep -ER 'my_'
Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.8/FAQ
    1 11147 11147 11147 ?           -1 Rs     500   0:03 ./my_daemon_sys

一些缩写

PID = 进程ID (由内核根据延迟重用算法生成)
PPID = 父进程ID(只能由内核修改)
PGID = 进程组ID(子进程、父进程都能修改)
SID = 会话ID(进程自身可以修改,但有限制,详见下文)

TPGID= 控制终端进程组ID(由控制终端修改,用于指示当前前台进程组)