每一个进程除了有一个进程ID外,还属于一个进程组。  进程组是一个或多个进程的集合,通常情况下,他们是在同一作业中结合起来的,同一进程组的个进程接受来自同一终端的各种信号。 每一个进程组有一个唯一的进程ID。

组长进程

每个进程组都有一个组长进程,组长进程的进程组ID等于其进程ID。 进程组组长可以创建一个进程组,创建进程组中的进程然后种植。只要进程组中还有任意一个进程存在,那么这个进程组就存在。 从进程组的创建到最后一个进程离开的时间去成为进程组的生命周期

函数getpgrp返回调用进程的进程组ID。

#include<unistd.h>

pid_t getpgrp(void);

下面的代码来验证下进程组

void pgroup_func(){

pid_t pid;

pid=fork();

if (pid == 0){;

printf("child id is:%d, the group id is :%d\n",getpid(),getpgrp());

sleep(1);

}

else{

printf("parent id is:%d,the group id is:%d\n",getpid(),getpgrp());

sleep(1);

}

}

从下面的运行结果可以看出,子进程和父进程的进程组ID是一样的。说明子进程和父进程同属一个进程组。父进程是这个进程组的组长。

那么如何修改进程组呢,setpgid函数将pid进程的进程组ID设置为pgid. 如果这两个参数相等,则由pid指定的进程变成进程组组长。如果pid是0,则使用调用者的进程ID。如果pgid=0,则由pid指定的进程ID用做进程组ID。但是如果子进程一旦执行exec,父进程就无法调用setpgid函数来设置子进程的进程组ID了

代码增加一个setpgid

void pgroup_func(){

pid_t pid;

pid=fork();

if (pid == 0){

setpgid(pid,0);

printf("child id is:%d, the group id is :%d\n",getpid(),getpgrp());

sleep(1);

}

else{

printf("parent id is:%d,the group id is:%d\n",getpid(),getpgrp());

sleep(1);

}

}

可以看到父进程和子进程的进程组ID不一样了。

有了创建进程组的接口,新创建的进程组就不必继承父进程的进程组ID了。最常见的创建进程组的场景就是在shell中执行管道命令,代码如下:cmd1 | cmd2 | cmd3

下面用一个最简单的命令来说明,其进程之间的关系如下所示。

ps ax|grep nfsd

ps进程和grep进程都是bash创建的子进程,两者通过管道协同完成一项工作,它们隶属于同一个进程组,其中ps进程是进程组的组长。

进程组的概念并不难理解,可以将人与人之间的关系做类比。一起工作的同事,自然比毫不相干的路人更加亲近。shell中协同工作的进程属于同一个进程组,就如同协同工作的人属于同一个部门一样。

引入了进程组的概念,可以更方便地管理这一组进程了。比如这项工作放弃了,不必向每个进程一一发送信号,可以直接将信号发送给进程组,进程组内的所有进程都会收到该信号。

前面提到过,子进程一旦执行exec,父进程就无法调用setpgid函数来设置子进程的进程组ID了,这条规则会影响shell的作业控制。出于保险的考虑,一般父进程在调用fork创建子进程后,会调用setpgid函数设置子进程的进程组ID,同时子进程也要调用setpgid函数来设置自身的进程组ID。这两次调用有一次是多余的,但是这样做能够保证无论是父进程先执行,还是子进程先执行,子进程一定已经进入了指定的进程组中。由于fork之后,父子进程的执行顺序是不确定的,因此如果不这样做,就会造成在一定的时间窗口内,无法确定子进程是否进入了相应的进程组。

会话

会话是一个或多个进程组的集合。进程调用setsid函数建立一个新会话。

如果调用此函数的进程不是一个进程组的组长,则此函数就会创建一个新会话,该进餐变成会话的首进程,然后该进程成为一个新进程组的组长进程,该进程没有控制终端。因为会话首进程是具有唯一进程ID的单个进程,所以可以将会话首进程的进程ID视为会话Id。

#include <unistd.h>

pid_t setsid(void);

pid_t getsid(pid_t pid);

来看下面的2个例子:

void session_func(){

pid_t pid;

pid=fork();

if(pid == 0){

printf("child id is:%d, the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));

}

else{

printf("parent id is:%d,the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));

}

}

在子进程中创建会话

void session_func(){

pid_t pid;

pid=fork();

if(pid == 0){

setsid();

printf("child id is:%d, the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));

}

else{

printf("parent id is:%d,the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));

}

}

}

我们可以看到在子进程没用setsid函数建立一个会话之前,子进程是和父进程在同一会话里的,当子进程用setsid函数建立一个会话,会话的首进程ID就是子进程ID也就是会话ID。

一个会话可以有一个控制终端。这通常是登陆到其上的终端设备(在终端登陆情况下)或伪终端设备(在网络登陆情况下)。建立与控制终端连接的会话首进程被称为控制进程。

一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组。所以一个会话中,应该包括控制进程(会话首进程),一个前台进程组和任意后台进程组。

无论何时键入终端的终端键,都会将中断信号发送到前台进程组的所有进程

还是来看之前的例子,

void session_func(){

pid_t pid;

pid_t pid1;

pid=fork();

pid1=tcgetpgrp(0);

printf("The pid1 is %d\n",pid1);

if(pid == 0){

setsid();

printf("child id is:%d, the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));

}

else{

printf("parent id is:%d,the group id is:%d,the session id is:%d\n",getpid(),getpgrp(),getsid(pid));

}

}

tcgetpgrp的原型如下,通过终端的文件描述符fd返回前台进程组pid.

#include<unistd.h>

pid_t tcgetpgrp(int fd)

通过tcgetpgrp(0)得到终端1的pid,可以看到和父进程的pid是一样的。

linux c编程:进程控制(四)进程关系的更多相关文章

  1. Linux系统编程之进程控制(进程创建、终止、等待及替换)

    进程创建 在上一节讲解进程概念时,我们提到fork函数是从已经存在的进程中创建一个新进程.那么,系统是如何创建一个新进程的呢?这就需要我们更深入的剖析fork函数. 1.1 fork函数的返回值 调用 ...

  2. 【Linux程序设计】之进程控制&守护进程

    这个系列的博客贴的都是我大二的时候学习Linux系统高级编程时的一些实验程序,都挺简单的. 实验题目:Linux环境下的进程控制 实验目的:熟悉并掌握Linux环境下进程的相关函数的应用:守护进程的概 ...

  3. Linux系统编程(7)—— 进程之进程概述

    我们知道,每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体.现在我们全面了解一下其中都有哪些信息. 进程id.系统中每个进程有 ...

  4. linux c编程:进程控制(四)进程调度

    当系统中有多个进程到时候,哪个进程先执行,哪个进程后执行是由进程的优先级决定的.进程的优先级是由nice值决定的.nice值越小,优先级越高.可以看做越友好那么调度优先级越低.进程可以通过nice函数 ...

  5. Linux系统编程(9)—— 进程之进程控制函数exec系列函数

    在Linux中,并不存在exec()函数,exec指的是一组函数,一共有6个,分别是: #include <unistd.h> extern char **environ; int exe ...

  6. Linux系统编程(8)—— 进程之进程控制函数fork

    fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事. 一个进程调用fork()函数后,系统先 ...

  7. Linux Shell编程(28)——进程替换

    进程替换与命令替换很相似. 命令替换把一个命令的结果赋给一个变量,例如 dir_contents=`ls -al`或xref=$. 进程替换则是把一个进程的输出回馈给另一个进程 (换句话说,它把一个命 ...

  8. C语言 进程控制---创建进程fork()函数

    #include "sys/types.h" #include "stdio.h" #include "stdlib.h" #include ...

  9. Linux环境编程之同步(四):Posix信号量

    信号量是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语.有三种类型:Posix有名信号量,使用Posix IPC名字标识.Posix基于内存的信号量,存放在共享内存区中:System ...

随机推荐

  1. [React] Use react-rewards to add microinteractions to React app to reward users for some actions

    It's important that our users enjoy using our application or website. One way we can make it happen ...

  2. Android源代码下载

    清华大学AOSP镜像: https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/

  3. 如何安装Android SDK Emulator

    1 下载并安装JDK,可以到官方网站寻找自己的对应版本下载 http://www.oracle.com/technetwork/java/javase/downloads/jdk-7u1-downlo ...

  4. 系统封装 如何加载PE到Easyboot进行合盘

    1 直接使用别人的PE. 如何加载PE到Easyboot首先需要知道验证你下载的PE的ISO镜像能否启动 如果答案是可以的,以自由天空的MINIPE为例,虽然可以启动,但是完全没有菜单提示,我们想要作 ...

  5. UISearchBar 点击取消回到原来位置时会跳动的解决方法

    今天改动项目里面測试给提的bug.有一个bug就是当点击UISearchBar的取消buttonUISearchBar回到原来位置时会发生偏差50像素左右的跳动,细致看看前面几个程序写的那个页面,也没 ...

  6. EFFECTIVE JAVA 类和接口

    第十六条:复合优先于继承 //这是一个不好的类---执行的结果 addCount = 4(addAll的实现依赖于HashSet的add方法,InstrumentHashSet方法重写了add方法有执 ...

  7. 苹果版小黄车(ofo)app主页菜单效果

    代码地址如下:http://www.demodashi.com/demo/12823.html 前言: 最近又是公司项目上线一段时间了,又是到了程序汪整理代码的节奏了.刚好也用到了ofo主页菜单的效果 ...

  8. JQuery DataTables学习

    1.Datatables简单介绍 DataTables是一个jQuery的表格插件.这是一个高度灵活的工具,根据的基础逐步增强,这将添加先进的互动控制.支持不论什么HTML表格. 主要特点: 自己主动 ...

  9. script 标签 幼儿园级别的神坑。居然还让我踩到了。

    这样的写法,会导致页面出现问题,就类似被中断了一样,百思不得其解还以为是代码出了问题. <script src="./Components/ProcessLine/ProcessLin ...

  10. JSTL JSP页面推断某个cookie的值和读取值....

    <c:if test="${cookie['woshop'].value eq '1'}">                 <div>           ...