进程组
概念:
进程组是一个或多个进程的集合
查看命令
ps axj | grep sleep | grep -v grep
a:不仅列出当前用户进程。也列出其他所有其他用户的进程
x:不仅列出有控制端的进程,也列出所有其他所有无控制端的进程
j:列出所有与作业控制相关的信息
grep sleep:查看只含sleep的信息
grep -v grep:不看grep进程信息
当我们输入命令,“回车键一按”,一个进程组就产生了,来看例子
sleep命令执行,进程组成立。
进程:9768 9769 9770
进程组组长(PGID):9768
kill -9 杀掉进程组长,进程组依然存在
特性
- 有唯一的组ID
- 可以有一个进程组组长,一般父进程为组长
- 组长进程可以创建进程,终止进程
- 只要进程组有一个进程在,则该进程组就存在,这与其组长进程是否存在无关
- 通常他们与同一个作业相关联,可以收到来自同一个终端的信号
例子:学校里,每个班就是一个进程组,每个同学就是一个进程
作业
概念:作业是用户需要计算机完成的某项任务,是要求计算机所做工作的集合。一个作业的完成要经过作业提交、作业收容、作业执行和作业完成4个阶段。一个作业需要一个或多个进程组配合完成
简单来讲作业就是系统中一项任务
进程组执行任务即执行作业。为了完成作业,往往需要多个进程组相互协作。
Shell分前后台来控制的不是进程是作业(Job)或者进程组(Process Group)。
1个前台作业可以由多个进程组成,1个后台也可以由多个进程组成,Shell可以运1个前台作业和任意多个后台作业,这称为作业控制。
作业与进程组的区别:如果作业中的某个进程创建了子进程,则进程不属于作业。
一旦作业运行结束,Shell就把自己提到前台(子进程还在,可是子进程不属于作业),如果原来的前台进程还存在(如果这个子进程还没终止),它自动变为后台进程组。
我们在重新理解下,在前台新起作业,shell是法运行,因为他被提到了后台。 但是如果前台进程退出,shell就被提到了前台,所以可以继续接受用户输入,。 来看例子证明这句话
#include <stdio.h>
#include <unistd.h>
int main( )
{
pid_t pid=fork( );
if(pid<0)
{
perror( " fork");
return 1;
}
else if( pid==0)
{
while( 1)
{
printf("i am child!,id:%d\n",getpid( ));
sleep( 1);
}
}
else
{
int i=10;
while( i--)
{
printf("i am father,id is:%d\n",getpid( ));
sleep(1);
}
}
return 0;
}
在前台新起作业 ./a.out,shell是法运行,因为他被提到了后台。 但是如果前台进程退出,shell就被提到了前台,所以可以继续接受用户输入,
但此时子进程并没有退出,而是退到后台,继续执行,往显示器上输出内容
查看进程信息如下
此时,用kill -9杀掉即可。
这里注意:后台进程可以往显示器输出内容,但是不能从显示器读取内容。
作业控制
Shell分前后台来控制的不是进程是作业(Job)或者进程组(Process Group)。
1个前台作业可以由多个进程组成,1个后台也可以由多个进程组成,Shell可以运1个前台作业和任意多个后台作业,这称为作业控制。
查看作业命令:jobs
将作业提到后台:ctrl +z
将作业提到前台:fg (作业编号)
杀掉前台作业:ctrl+c
kill -9 前台后台作业均有效
来看下面例子:新起五个作业
先用kill -9 杀掉2976,成功杀掉
然后用ctrl +c 杀掉后台进程,失败,因为非kill信号杀不掉后信号,只有当进程被提到前台(从内核态切换到用户态时)才会有效
然后用jobs查看作业,执行fg 1,将作业1提到前台,然互用ctrl +c 杀掉,成功。
与作业控制相关的信号:
信号 | 编号 | 默认动作 | 对应事件 |
SIGTTIN | 21 | 停止知道下一个SIGCCHLD | 后台进程从终端读 |
SIGCHLD | 17 | 忽略 | 一个子进程停止或终止 |
SIGCONT | 18 | 忽略 | 继续进程如果该进程已停止 |
SIGTSTP | 20 | 停止知道下一个SIGCCHLD | 来自终端的停止信号 |
SIGKILL | 9 | 终止 | 杀死进程 |
SIGTERM | 15 | 终止 | 软件终止信号 |
来看例子
执在前台行cat命令结果
你在显示器输入什么,直接有给你显示在显示器上
现在,我们将cat命令放到后台
- jobs命令可以查看当前有哪些作业。
- fg命令可以将某个作业提到前台运行,如果该作业的进程组正在后台运行则提到前台运行,如果该作业处于停止状态,则给进程组的每个进程SIGCONT信号使它继续运行。参数%1表示将第1个作业提到前台运行。cat提到前台运行后,挂起等待终端输入,当输入hello并回车后,cat打印出同样的内容,然后继续挂起等待输入。如果输按下Ctrl-Z则向所有前台进程发SIGTSTP信号,该信号的默认动作是使进程
停止,cat继续以后台作业的形式存在。 - bg命令可以让某个停止的作业在后台继续运⾏,也需要给该作业的进程组的每个进程发SIGCONT信号。cat进程继续运行,要读终端输入,然而它在后台不能读终端输人,所以又收到SIGTTIN信号停止。
# kill -15 4872
# jobs
[1]+ Stopped cat
# fg 1
cat
Terminated
用kill命令给一个停止的进程发SIGTERM(15)信号,这个信号并不会立刻处理,而要等进程准备继续运行之前处理,默认动作是终止进程。但如果给一个停止的进程发SIGKILL信号就不同了。
总结:后台进程不能从终端下读取数据,但能写
守护进程
守护进程概念
守护进程也称精灵进程(Daemon),是运⾏在后台的⼀种特殊进程。它独⽴于控制终端并且周期性地执⾏
某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。Linux的很多数服务器就是用守护进程实现的。比如,ftp服务器,ssh服务器,Web服务器httpd等。同时,守护进程完成许多系统任务。比如,作业规划进程crond等。
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。
其它进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终至,但系统服务进程(守护进程)不受用户登录
注销的影响,它们一直在运行着。这种进程有一个名称叫守护进程(Daemon)**
下面我们用ps axj命令查看系统中的进程。参数a表示不仅列当前用户的进程,也列出所有其他用户的进程,参数x表示不仅列有控制终端的进程,也列出所有⽆控制终端的进程,参数j表示列出与 作业控制相关的信息。
- 凡是TPGID一栏写着-1的都是没有控制终端的进程,也就是守护进程。
- 在COMMAND一列用[]括起来的名字表示内核线程,这些线程在内核创建,没有用户空间代码,因此没有程序文件名和命令, 通常采用以k开头的名字,表示Kernel。
- init进程我们已经很熟悉了,udevd负责维护/dev目录下的 设备下件,acpid负责电源管理,syslogd负责维护/var/log下的⽇志⽂件
- 可以看出,守护进程通常采以d结尾的名字,表示Daemon。
创建守护进程
创建守护进程最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader。
#include <unistd.h>
pid_t setsid(void);
该函数调⽤成功时返回新创建的Session的id(其实也就是当前进程的id),出错返回-1。
成功调用该函数的结果是:
- 创建一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。
- 创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。
- 如果当前进程原本有一个控制终端,则它失去这个控制终端,成为⼀个没有控制终端的进程。所谓失去控制终端是指,原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通的打开⽂件⽽不是控制终端
注意,调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。要保证当前进程不是进 程组的Leader也很容易,只要先fork再调⽤setsid就可了。fork创建的父进程和子进程在同一个进程组中,进程组的
Leader必然是该组的第一个进程,所以子进程不可能是该组的第一个进程,在子进程中调用setsid就不会有问题了。
守护进程代码
- 手动实现
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
void mydaemon( )
{
umask( 0);
pid_t pid;
struct sigaction sa;
//调用fork,父进程退出
//保证子进程不是进程组组长
pid==fork( );
if( pid<0)
{
perror( " fork");
exit( 1);
}
else if( pid>0)
{
printf(" i am father!i will exit...\n");
sleep( 1);
exit( 0);
}
//调用setsid,创建一个新会话
setsid( );
//调用sigaction函数
//当子进程退出后自动回收子进程资源
sa.sa_handler=SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags=0;
if((sigaction(SIGCHLD,&sa,NULL))<0 )
{
return ;
}
//使当前目录更改为根目录
if(chdir("/")<0)
{
printf("child dir error\n");
return ;
}
//关闭不需要的文件描述符
close(0);
close(1);
close(2);
}
int main( )
{
mydaemon( );
while( 1)
{
sleep( 1);
}
}
- 调用库函数实现
#include <unistd.h>
int daemon(int nochdir, int noclose);
参数:0:代表关闭对应文件描述符
1:代表打开对应文件描述符
返回值
(此函数为fork,如果fork(2)成功,父类调用_exit(2),从而使
其他错误只能由孩子看到。成功守护进程()返回零。如果一个错误
发生时,守护进程()返回-1,并将errno设置为fork(2)指定的任何错误。
和setsid(2)。
代码实现
#include <unistd.h>
#include <stdio.h>
in main()
{
daemon(0,0);
while(1);
}
现在新起会话,对比守护进程和一般进程的区别
注销用户,重新登陆
验证了:
- 其它进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终至,但系统服务进程(守护进程)不受用户登录
注销的影响,它们一直在运行着。 - 守护进程自称进程组,自称作业,自称会话,不受用户注销影响