每一个进程除了有一个进程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. Neon Intrinsics各函数介绍

    #ifndef __ARM_NEON__ #error You must enable NEON instructions (e.g. -mfloat-abi=softfp -mfpu=neon) t ...

  2. Sql中常用的创建表 约束 主外键 增删改查的语句

    创建数据库 USE master; GO --日记数据库 create database DiaryBase on ( name=DiaryBase_Dat,--逻辑名称 FILENAME='c:\D ...

  3. Eclipse中设置格式化jsp自动换行

    JSP代码换行:Window->Preferences->Web->JSP Files->Editor->Line width

  4. 关系型数据的分布式处理系统MyCAT(转载)

      ——概述和基本使用教程 日期:2014/12/24 文:阿蜜果 1.   MyCAT概述 1.1 背景 随着传统的数据库技术日趋成熟.计算机网络技术的飞速发展和应用范围的扩充,数据库应用已经普遍建 ...

  5. Spring获取HttpServletRequest

    ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest()

  6. App功能测试的7大注意点

    转载于:https://mp.weixin.qq.com/s/27DZ1EQVpl-gb4S7n-He4g 01 运行 1)App安装完成后的试运行,可正常打开软件. 2)App打开测试,是否有加载状 ...

  7. java检测http请求的ip地址 Java问题通用解决代码

    以后再用到的话,至少能起个参考作用 java实现,struts2的Action中,依赖HttpServletRequest     package net.dookoo.web.action;   i ...

  8. Servlet的API(一)

    Servlet的API有很多,这里只谈谈两个Servlet对象:ServletConfig对象和ServletContext对象. 1. ServletConfig对象 在Servlet的配置文件中, ...

  9. Android NDK开发篇(四):Java与原生代码通信(原生方法声明与定义与数据类型)

    Java与原生代码通信涉及到原生方法声明与定义.数据类型.引用数据类型操作.NIO操作.訪问域.异常处理.原生线程 1.原生方法声明与定义 关于原生方法的声明与定义在上一篇已经讲一点了,这次具体分析一 ...

  10. slam command tool

    cd imu_ws source devel/setup.bash ls -l /dev |grep ttyUSB sudo chmod /dev/ttyUSB0 rosrun imu_pb imu ...