进程间关系分为进程组、作业、会话。
1.进程组
概念:进程组是一个或多个进程的集合
特点:
- 每个进程除了有自己的进程ID之外,还属于一个进程组
- 进程组与同一个作业相关联,可以接收来自同一终端的各种信号。
- 每个进程组都有一个唯一的进程组ID,每个进程都可以有一个组长ID
- 组长进程ID等于进程组ID,组长进程可以创建一个进程组,创建一个进程组中的进程。
- 只要该进程组中还有一个进程,那么这个进程组就还存在,哪怕进程组长终止。
eg:
&
:表示将进程放入后台5574,5575,5576,5577都是进程ID,它们同属于一个5574进程组,组长为5574
2.作业
特点:
Shell分前后台,它控制的不是进程而是作业或者进程组
- 一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成
- Shell可以运行一个前台作业和多个后台作业,这叫做进程控制
- 作业和进程组的区别:如果作业中的某个进程创建了子进程,那么子进程不属于作业
- 一旦作业运行结束,Shell就自己提到前台来,但是子进程还在,子进程又不属于作业。如果原来的前台进程还存在,这个子进程还没有终止,它自动变为后台进程组
- 前台新起作业,Shell是无法运行的,因为它被提到了后台,只有前台作业终止了,Shell才会被提到前台,可以接受用户输入。
eg:
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id == 0)
{ //子进程
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;
}
- 当运行该程序时,你会发现前台新起了一个作业,为父进程,父进程先在前台运行5s,5s之内前台不接受任何指令,因为shell被提到后台去了
- 但是5s结束后,父进程退出,此时shell被提到其前台,此时输入命令,shell可以处理。因为父进程退出,子进程还在但是子进程不属于父进程那个作业,所以它被提到后台
我们发现子进程所属的进程组还在,组长还是父进程,并且还在不断的while循环打消息,还不能用Ctul+c终止,只可以用
kill -9 PID
杀死3.会话
会话是一个或多个进程组的集合。
- 一个会话可以有一个控制终端,这通常是登录到其上的的终端设备(在终端登录情况下)或伪终端设备(在网络登录情况下)
- 建立与控制终端连接的会话首进程被称为控制进程,
- 一个会话中的进程组可被分为一个前台进程组或一个或多个后台进程组(前台进程组只有一个)
所以一个会话中要包括一个控制进程,一个前台进程组或和任意多个后台进程组
其中SI会话id:4120,四个进程同属于一个进程组,一个会话,那么4120又属于谁呢?
可以看出,4120属于bash,bash就是我们说的会话首进程
4.守护进程
4.1:概念及作用
守护进程也叫精灵进程,是一种运行在后台的特殊进程
- 特点:
- 独立于控制终端并且周期性运行某种任务或等待处理某些发生的事件
- 因为没有控制终端所以无法和用户交互
- 不受用户登录和注销的影响
- 作用
- 守护进程是一种很有用的进程,满足于特殊的进程需求
比如:Linux大多数服务器就是守护进程
4.2:查看守护进程
ps axj | more
查看守护进程- 参数a代表列出所有用户进程
- 参数x代表列出有无控制终端的进程
参数j代表列出与作业控制相关的信息
TPGID一栏写着-1的表示没有控制终端的进程,即守护进程。
- COMMAND一列⽤用[]括起来的名字表⽰示内核线程,这些线程在内核⾥里创建,没有⽤用户空间代码,因此没有程序⽂文件名和命令⾏行, 通常采⽤用以k开头的名字,表⽰示Kernel。
可以看出守护进程通常采⽤用以d结尾的名字,表⽰示Daemon。
4.3:创建守护进程
4.3.1:setsid函数
#include<unistd.h>
pid_t setsid(void);
函数调用成功返回新创建的守护进程id,失败返回-1
4.3.2:注意事项
- 调用setsid函数之前,当前进程不允许是进程组的组长,否则返回-1
- 要保证当前进程不是进程组组长可以fork一个子进程,在子进程中调用setsid函数
- 调用成功函数的结果:
- 创建一个新的守护进程,当前进程成为守护进程,当前进程id为守护进程id
- 创建新的进程组,组长为当前进程,组长id为当前进程id
- 当前进程脱离当前控制终端
4.3.3:模拟是实现daemon函数的守护进程
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/stat.h>
void mydaemon()
{
int fd;
pid_t pid = fork();
struct sigaction sa;
umask(0);//调用umask将文件,将文件权限屏蔽字设置为0
//因为子进程会继承父进程文件权限屏蔽字,使得子进程可以自由的修改文件权限屏蔽字
if(pid < 0)
{
perror("fork");
exit(1);
}
else if(pid > 0)
{
exit(2);
}
else//子进程
{
setsid();//调用setsid函数,使得当前进程脱离原有的控制终端,
//但是当前进程依然是进程组长,仍然可以打开控制终端,所以再fork一次
//让孙子进程执行,避免失误而打开控制终端
if((pid = fork()) < 0)
{
perror("fork child");
exit(3);
}
else if(pid == 0)
{
umask(0);
if(chdir("/") < 0)//将当前目录设置为根目录,因为守护进程会保护当前目录中文件和目录不被删除或卸载
//如果有卸载或者删除操作,就会无法执行,形成保护措施,
//如果删除一些文件就可能会导致守护进程失败
{
perror("chdir");
exit(4);
}
int i = 0;
for(i = 0;i < 3;++i)
{
close(i);//关闭守护进程文件描述符0(标准输入)、1(标准输出)、
//2(标准出错),因为子进程继承父进程文件描述符表,但是因为守护进程
//脱离了控制终端,一般不需要这三个文件描述符,为了避免资源浪费,所以需要关闭
}
}
else
{
exit(7);
}
}
}
int main()
{
mydaemon();
while(1)
{
sleep(1);
}
}
查看守护进程:TPGID一栏为-1时表示为守护进程
kill -9 pid
杀死守护进程
4.3.4:创建守护进程函数daemon
#include<unistd.h>
int daemon(int nochdir,in noclose)
参数介绍:
nochdir:为0时,表示当前目录为根目录
noclose;为0时,标准输入、标准输出、标准出错重导向到为dev/null。
返回值:
如果daemon()调用fork(),调用成功,父进程就调用_exit(2)退出
此时看到错误信息全部是子进程的。
如果调用成功返回0,否则返回-1,并设置error
代码:
#include<stdio.h>
int main()
{
daemon(0,0);
while(1)
{
sleep(1);
}
return 0;
}