您的位置:首页 > 其它

终端,作业控制与守护进程

2016-07-31 23:55 281 查看
进程组
概念:一个或多个进程的集合。
每一个进程除了有一个进程ID外,还属于一个进程组,同时也只能属于一个进程组。每个进程组都有一个唯一的进程组ID,且都可以有一个组长进程。一般在进程组中,第一个进程是组长进程。
为啥要创建进程组呢?为了方便对进程进行管理。假设要完成一个任务,需要同时并发10个进程,当用户处于某种原因要终止这个任务时,如若没有进程组,就需要手动的一个一个的去杀死这10个进程,并且严格按照进行间的关系顺序,否则会打乱进程间的关系,有了进程组,就可以将这10个进程设置一个进程组,他们共有一个组号(pgrp),并且选取一个进程作为组长,(通常选取“辈分”最高的那个,通常该进程的ID就是该进程组的ID)。现在就可以通过杀死整个进程组来关闭这10个进程。组长进程可以创建进程组,创建该组中的进程,然后终止。只要在某个进程组中一个进程存在,则该组进程就存在,这与组长进程是否终止无关。

作业
shell分前后台来控制的是作业或进程组,不是进程。一个前台作业可以由多个进程组成,一个后台可以由多个进程组成。
作业控制:shell可以运行一个前台作业和任意多个后台作业。
1、 与作业控制有关的信号:
我们cat为例(把他放在后台,从终端读)
(1)由于cat需要读标准输入(也就是终端输入),而后台进程是不能读终端输入的,因此内核发SIGTTIN信号给进程, 该信号的默认处理动作是使进程停止。

[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信号而停止。



2、给一个停止的进程发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]

守护进程的特性:
1、后台运行(最重要的)
2、守护进程必须与其运行前的环境隔离开来。
3、启动方式有其特殊之处:可以在linux系统启动时从启动脚本/etc/rc.d中启动,可以在作业规划进程cround启动。还可以由用户终端(通常是shell)执行。

后台进程与守护进程的区别
1、后台进程与特定终端关联,与会话紧密相联。
2、守护进程是后台进程的一种,与终端无关

创建守护进程
1、调用umask将文件模式创建屏蔽字段设置为0
2、调用fork函数,父进程退出。
原因:1)如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止使得shell认为该命令已经执行完毕。
2)保证子进程不是一个 进程组的组长进程。
3、调用setsid创建一个新会话。
setsid会导致:
1)调用进程成为新会话的首进程。
2)调用进程成为一个进程组的组长进程 。
3)调用进程没有控制终端。(再次fork一次,保证 daemon进程,之后不会打开tty设备)
4、将当前工作目录更改为根目录。
5、关闭不在需要的文件描述符。
6、 其他:忽略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


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息