进程间关系和守护进程

进程组

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

[sifanchao@localhost code]$ sleep 1000 | sleep 2000 | sleep 3000 &
[1] 4203
[sifanchao@localhost code]$ ps ajx | head -1 && ps ajx | grep sleep
  PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
  4167   4201   4201   4167 pts/1      4206 S      500   0:00 sleep 1000
  4167   4202   4201   4167 pts/1      4206 S      500   0:00 sleep 2000
  4167   4203   4201   4167 pts/1      4206 S      500   0:00 sleep 3000
  4167   4207   4206   4167 pts/1      4206 S+     500   0:00 grep sleep

'&': 表示将进程组放在后台执行 
进程:4201 4202 4203 
组长:4201, 进行组当中的第一个进程 
kill -9 杀掉组长,进组还在。 
ps选项:    
    a: 不仅列当前用户的进程,也列出所有其他用户的进程    
    x: 表示不仅列有控制终端的进程,也列出所有无控制终端的进程    
    j: 表示列出与作业控制相关的信息

作业

Shell分前后台来控制的不是进程而是作业(Job)或者进程组(Process Group)。一个前台作业可以由多个进程组成,⼀=一个后台也可以由多个进程组成,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{ //parent        
        int i = 5;        
        while(i){            
            printf("parent(%d)# I am going to dead ... %d\n", getpid(), i--);            
            sleep(1);        
        }    
    }    
    return 0; 
} 

我们发现, 程序跑起来之后,在前台新起了1个作业,包涵父子两个进程。
5s之内,shell无法接受任何命令!说明此时的前台作业不是shell。
但是父进程退出之后,子进程还在运行,但此时输入的命令,shell可以处理,说明此时shell变成了前台作业。
换句话说,我们刚新起的作业退出了!但子进程还在,就自动被提到后台。

会话

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

[sifanchao@localhost code]$ sleep 1000 | sleep 2000 | sleep 3000 &
[1] 4203
[sifanchao@localhost code]$ ps ajx | head -1 && ps ajx | grep sleep
  PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
  4167   4201   4201   4167 pts/1      4206 S      500   0:00 sleep 1000
  4167   4202   4201   4167 pts/1      4206 S      500   0:00 sleep 2000
  4167   4203   4201   4167 pts/1      4206 S      500   0:00 sleep 3000
  4167   4207   4206   4167 pts/1      4206 S+     500   0:00 grep sleep

SID: 会话id,4167,三个进程都属于同一个进程组,同一个会话 

4167是谁呢?
[sifanchao@localhost code]$ ps aux | grep -E 4167 | grep -v grep
500        4167  0.0  0.1 108488  1956 pts/1    Ss   14:24   0:00 -bash

bash!也就是我们的解释器,会话首进程,而且三个进程的父进程都是bash。
多打开几个终端,对比就会发现,每打开一个终端,就新建了一个会话。

作业控制

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

[sifanchao@localhost code]$ jobs
[1]+  Running                 sleep 1000 | sleep 2000 | sleep 3000 &
[sifanchao@localhost code]$ sleep 200 &
[2] 4347
[sifanchao@localhost code]$ jobs
[1]-  Running                 sleep 1000 | sleep 2000 | sleep 3000 &
[2]+  Running                 sleep 200 &
[sifanchao@localhost code]$ sleep 300 &
[3] 4348
[sifanchao@localhost code]$ jobs
[1]   Running                 sleep 1000 | sleep 2000 | sleep 3000 &
[2]-  Running                 sleep 200 &
[3]+  Running                 sleep 300 &
[sifanchao@localhost code]$ fg 1
sleep 1000 | sleep 2000 | sleep 3000
^C
[sifanchao@localhost code]$ jobs
[2]-  Running                 sleep 200 &
[3]+  Running                 sleep 300 &
[sifanchao@localhost code]$ jobs 2
[2]-  Running                 sleep 200 &
[sifanchao@localhost code]$ fg 2
sleep 200
^C
[sifanchao@localhost code]$ fg 3
sleep 300
^Z
[3]+  Stopped                 sleep 300
[sifanchao@localhost code]$ jobs
[3]+  Stopped                 sleep 300
[sifanchao@localhost code]$ bg 3
[3]+ sleep 300 &
  1. jobs命令可以查看当前有哪些作业。
  2. fg命令可以将某个作业提至前台运行,如果该作业的进程组正在后台运行,则提至前台运行,如果该作业处于停止状态,则给进程组的每个进程发SIGCONT信号使它继续运行。
  3. 如果输入Ctrl+Z则向所有前台进程发SIGTSTP信号,该信号的默认动作是使进程停止,cat继续以后台作业的形式存在。
  4. bg命令可以让某个停止的作业在后台继续运行,也需要给该作业的进程组的每个进程发SIGCONT信号。

守护进程

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

[sifanchao@localhost code]$ ps axj | head -1 && ps axj | grep -E 'd$'
  PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
[3]+  Done                    sleep 300
     1    507    507    507 ?            -1 S<s      0   0:00 /sbin/udevd -d
     1   1500   1499   1499 ?            -1 Sl       0   0:07 /usr/sbin/vmtoolsd
     1   1871   1871   1871 ?            -1 S<sl     0   0:00 auditd
     1   1970   1970   1970 ?            -1 Ssl      0   0:00 NetworkManager --pid-file=/var/run/NetworkManager/NetworkManager.pid
     1   2001   2001   2001 ?            -1 Ss       0   0:00 /usr/sbin/wpa_supplicant -c /etc/wpa_supplicant/wpa_supplicant.conf -B -u -f /var/log/wpa_supplicant.log -P /var/run/wpa_supplicant.pid
     1   2033   2033   2033 ?            -1 Ss       0   0:00 /usr/sbin/acpid
     1   2045   2045   2045 ?            -1 Ssl     68   0:00 hald
     1   2122   2122   2122 ?            -1 Ssl      0   0:00 pcscd
     1   2218   2218   2218 ?            -1 Ss       0   0:00 /usr/sbin/sshd
     1   2367   2367   2367 ?            -1 Ss       0   0:00 /usr/sbin/abrtd
     1   2394   2394   2394 ?            -1 Ss       0   0:01 crond
     1   2429   2429   2429 ?            -1 Ss       0   0:00 /usr/sbin/atd
   507   2471    507    507 ?            -1 S<       0   0:00 /sbin/udevd -d
   507   2472    507    507 ?            -1 S<       0   0:00 /sbin/udevd -d
     1   2625   1956   1956 ?            -1 S        0   0:00 /usr/libexec/polkit-1/polkitd
  2491   2642   2454   2454 ?            -1 Sl       0   0:00 pam: gdm-password
     1   2822   2741   2741 ?            -1 S      500   0:00 /usr/libexec/gvfsd
  • 创建守护进程

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

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

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

成功调用该函数的结果是:

  1. 创建一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。
  2. 创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。
  3. 如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。所谓失 去控制终端是指,原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通的打开文件而不是控制终端了。
#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;//4. 忽略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);
} 
  • 系统创建守护进程的方法
int daemon(int nochdir, int noclose);

如果nochdir为零,守护进程()将进程当前工作目录更改为根目录(“/”);
如果noclose为零,守护进程()将标准输入、标准输出和标准错误重定向为/dev/null;

#include <stdio.h> 
#include <unistd.h> ‘
int main() 
{    
    daemon(0,0);    
    while(1); 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值