进程组linux
概念:一个或多个进程的集合。shell
每个进程除了有一个进程ID外,还属于一个进程组,同时也只能属于一个进程组。每一个进程组都有一个惟一的进程组ID,且均可以有一个组长进程。通常在进程组中,第一个进程是组长进程。服务器
为啥要建立进程组呢?为了方便对进程进行管理。假设要完成一个任务,须要同时并发10个进程,当用户处于某种缘由要终止这个任务时,如若没有进程组,就须要手动的一个一个的去杀死这10个进程,而且严格按照进行间的关系顺序,不然会打乱进程间的关系,有了进程组,就能够将这10个进程设置一个进程组,他们共有一个组号(pgrp),而且选取一个进程做为组长,(一般选取“辈分”最高的那个,一般该进程的ID就是该进程组的ID)。如今就能够经过杀死整个进程组来关闭这10个进程。组长进程能够建立进程组,建立该组中的进程,而后终止。只要在某个进程组中一个进程存在,则该组进程就存在,这与组长进程是否终止无关。并发
做业ide
shell分先后台来控制的是做业或进程组,不是进程。一个前台做业能够由多个进程组成,一个后台能够由多个进程组成。函数
做业控制:shell能够运行一个前台做业和任意多个后台做业。 测试
一、 与做业控制有关的信号:spa
咱们cat为例(把他放在后台,从终端读)线程
(1)因为cat须要读标准输入(也就是终端输入),然后台进程是不能读终端输入的,所以内核发SIGTTIN信号给进程, 该信号的默认处理动做是使进程中止。blog
[liu153@liu153 7-31_class16]$ cat &
[1] 895
[liu153@liu153 7-31_class16]$ //再嗯回车
[1]+ Stopped cat
[liu153@liu153 7-31_class16]$
(2)jobs命令:查看当前先后先后台有哪些做业
(3)fg命令:能够将某个做业提至前台运行:
a、若是该做业的进程组正在后台运行则提至前台运行;
b、若是该做业处于中止状态,则给进程组的每一个进程发SIGCONT信号使它继续行。
参数%1表示将第1个做业提至前台运行。
cat提到前台运行后,挂起等待终端输入,当 输入hello并回车后,cat打印出一样的一行,而后继续挂起等待输入。紧接着, 若是输入Ctrl-Z则向全部前 台进程发SIGTSTP 信号,该信号的默认动做是使进程中止,cat继续之后台做业的形式存在。
(4)bg命令:可让某个中止的做业在后台继续运行。也须要给该做业的进程组的每一个进程发SIGCONT信号。cat进程继续运行,又要读终端输入,然而它在后台不能读终端输入,因此又收到SIGTTIN信号而中止。
二、给一个中止的进程发SIGTERM与SIGKILL信号的区别:
(1)kill 命令给一个中止的进程发送SIGTERM信号时并不会当即被处理,而是等到合适的时候处理,默认处理动做是终止进程。
(2)SIGKILL信号既不能被阻塞也不能被忽略 ,也不能用自定义函数捕捉 ,只能按系统的默认动做马上处理(SIGSTOP 信号也与此相似)。(这样保证了无论什么样的进程都能用 SIGKILL终止或者用SIGSTOP中止, 当系统出现异 常时管理员老是有办法杀掉有问题的进程或者暂时停掉怀疑有问题的进程。)
做业与进程组的区别:
若是一个做业中的某个进程又建立了一个子进程,该子进程不属于做业。一旦做业运行结束,shell就把它提到前台,若是原来前台进程还存在,它自动变为后台进程组。
会话
概念:一个或多个进程组的集合,但只能有一个前台进程组。
控制进程:创建与控制终端链接的会话首进程。每一个会话都有一个会话首领(leader)即建立会话的进程。
sys_setsid()调用能建立一个会话。
注意:只有当前进程不是进程组的组长时,才能建立一个新的会话。
一次会话中应该包括:一个控制进程,一个前台进程和任意多个后台进程。
终端
控制终端:会话的领头进程打开一个终端,以后,该终端就成为该会话的控制终端。一个会话只能有一个控制终端。
进程属于一个进程组,进程组属于一个会话,会话可能有也可能没有控制终端。通常而言,当用户在某个终端上登陆时,一个新的会话就开始了。进程组由组中的领头进程标识,领头进程的进程标识符就是进程组的组标识符。相似地,每一个会话也对应有一个领头进程。
同一会话中的进程经过该会话的领头进程和一个终端相连,该终端做为这个会话的控制终端。一个会话只能有一个控制终端,而一个控制终端只能控制一个会话。用户经过控制终端,能够向该控制终端所控制的会话中的进程发送键盘信号。
当咱们打开多个终端窗口时,实际上就建立了多个终端会话。每一个会话都会有本身的前台工做和后台工做。
查看终端设备
测试代码:
#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;
}
终端1运行结果:
[liu153@liu153 7-31_class16]$ ./a.out
fd :0->/dev/pts/2
fd :1->/dev/pts/2
fd :2->/dev/pts/2
终端2运行结果:
[liu153@liu153 7-31_class16]$ ./a.out
fd :0->/dev/pts/0
fd :1->/dev/pts/0
fd :2->/dev/pts/0
[liu153@liu153 7-31_class16]$
终端3运行结果:
[liu153@liu153 7-31_class16]$ ./a.out
fd :0->/dev/pts/3
fd :1->/dev/pts/3
fd :2->/dev/pts/3
[liu153@liu153 7-31_class16]$
守护进程(精灵进程)
是运行在后台的一种特殊进程,独立于控制终端而且周期性的执行某种任务或等待处理某些发生的事件。守护进程是一种颇有用的进程,Linux的大多数服务器就是用守护进程实现的。 守护进程完成许多系统任务。大多数守护进程以d结尾,凡是中括号括起来的都是内核线程。
root 2 0.0 0.0 0 0 ? S Jul27 0:00 [kthreadd]
root 17 0.0 0.0 0 0 ? S Jul27 0:00 [kacpid]
root 22 0.0 0.0 0 0 ? S Jul27 0:00 [ksuspend_usbd]
root 23 0.0 0.0 0 0 ? S Jul27 0:04 [khubd]
root 24 0.0 0.0 0 0 ? S Jul27 0:00 [kseriod]
root 28 0.0 0.0 0 0 ? S Jul27 0:00 [khungtaskd]
root 30 0.0 0.0 0 0 ? SN Jul27 0:00 [ksmd]
root 38 0.0 0.0 0 0 ? S Jul27 0:00 [pciehpd]
root 40 0.0 0.0 0 0 ? S Jul27 0:00 [kpsmoused]
root 72 0.0 0.0 0 0 ? S Jul27 0:00 [kstriped]
root 1103 0.0 0.0 0 0 ? S Jul27 0:00 [kauditd]
root 2001 0.0 0.0 0 0 ? S< Jul27 0:00 [krfcommd]
守护进程的特性:
一、后台运行(最重要的)
二、守护进程必须与其运行前的环境隔离开来。
三、启动方式有其特殊之处:能够在linux系统启动时从启动脚本/etc/rc.d中启动,能够在做业规划进程cround启动。还能够由用户终端(一般是shell)执行。
后台进程与守护进程的区别
一、后台进程与特定终端关联,与会话紧密相联。
二、守护进程是后台进程的一种,与终端无关
建立守护进程
一、调用umask将文件模式建立屏蔽字段设置为0
二、调用fork函数,父进程退出。
缘由:1)若是该守护进程是做为一条简单的shell命令启动的,那么父进程终止使得shell认为该命令已经执行完毕。
2)保证子进程不是一个 进程组的组长进程。
三、调用setsid建立一个新会话。
setsid会致使:
1)调用进程成为新会话的首进程。
2)调用进程成为一个进程组的组长进程 。
3)调用进程没有控制终端。(再次fork一次,保证 daemon进程,以后不会打开tty设备)
四、将当前工做目录更改成根目录。
五、关闭不在须要的文件描述符。
六、 其余:忽略SIGCHLD信号。
建立守护进程代码:
测试1:
#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);
}
测试2;
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
void create_daemon(void)
{
int i;
int fd0;
pid_t pid;
struct sigaction sa;
umask(0);//设置文件掩码为0
if(pid = fork() < 0){
}else if(pid != 0){
exit(0);//第一次fork()终止父进程
}
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){//为什么要fork两次?->再次fork,终止子进程,保证孙子进程不是话首进程,从而保证后续不会再和其余终端关联
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()
{
create_daemon();
while(1)
{
sleep(1);
}
return 0;
}
运行监视:
关闭标准输入,重定向全部标准(输入输出错误)到/dev/null