进程组/作业/会话
1.进程组
每个进程除了有一个进程ID之外,还属于一个进程组.进程组是一个或多个进程的集合.通常,它们与同一作业相关联,可以接收来自同一终端的各种信号.
每一个进程组有一个唯一的进程组ID,每一个进程组都可以有一个组长进程.组长进程的标识是进程ID与进程组ID相同.组长进程可以创建一个进程组,这个进程组中 只要有一个进程 存在,这个进程组就存在。与组长进程是否存在无关.
2.作业
sheel分前后台来控制的不是进程而是作业,或者进程组.
当命令行执行一个程序的时候,就形成了一个作业,这个作业可以有一个进程,也可以有多个进程。
一个前台作业可以有多个进程组成,一个后台作业也可以有多个进程组成,
sheel可以运行一个前台作业或者多个后台作业.------作业控制
为什么shell只能运行一个前台作业?
当我们运行一个作业时,shell就被提到了后台,这时的shell不能接受我们的指令并解析执行了,但是如果后台作业退出了,shell就会被提到前台
@@@@作业与进程组的区别@@@@--------如果作业中某个进程又创建了子进程,则子进程不属于作业.属于进程组,一旦作业运行结束,sheel就把自己提到前台(子进程还在,可是子进程不属于作业),如果原来的前台进程还在,(如果这个子进程还没终止),它就会自动变为后台进程组.
1 #include<stdio.h>
2 #include<unistd.h>
3 int main(){
4 pid_t id = fork();
5 if(id<0){
6 perror("fork");
7 return 1;
8 }
9 else if(id==0){
10 //子进程
11 while(1){
12 printf("child(%d)# i am run!\n",getpid());
13 sleep(1);
14 }
15 }
16 else{
17 //父进程
18 int i=5;
19 while(i){
20 printf("parent(%d)# i am going to dead .... %d\n",getpid(),i--);
21 sleep(1);
22 }
23 }
24 return 0;
25 }
一旦作业运行结束,shlle就 被提到前台(子进程还在,可是子进程不属于作业),如果原来的前台进程还在(如果这个子进程还存在),它自动变为后台的进程.
在前台新起作业,shell是无法运行的,因为他被提到了后台,但是如果前台进程退出,shell就又被提到前台,所以就可以继续输入.
jobs命令可以查看当前有哪些作业,fg命令可以把作业提到前台运行 ,如果该作业处于正在运行状态,则提至前台运行,如果该作业处于停止状态,则给进程组的每一个进程发SIGCONT信号使它继续运行,ctrl+z则向所有前台进程(前台作业)发SIGTSTR信号,该信号默认动作是使进程停止,该作业继续以后台形式存在.ctrl+c则杀掉整个作业
3.会话
会话(Session)是一个或者多个进程组的集合,一个会话可以有一个控制终端,建立与终端连接的会话首进程称为控制进程,一个会话中的几个进程可被分为一个前台进程组以及多个后台进程组,所以一个会话中应该包括控制进程(会话首进程),一个前台进程组和任意后台进程组.
守护进程
守护进程也称精灵进程(Daemon),是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。Linux的大多数服务器就是用守护进程实现的。比如,ftp服务器,ssh服务器,Web服务器httpd等。同时,守护进程完成许多系统任务。比如,作业规划进程crond等。
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。其它进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程(守护进程)不受用户登录注销的影响,它们一直在运行着。这种进程有一个名称叫守护进程(Daemon)。
.......还有很多
凡是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。
注意,调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。要保证当前进程不是进 程组的Leader也很容易,只要先fork再调用setsid就行了。fork创建的子进程和父进程在同一个进程组中,进程组的Leader必然是该组的第一个进程,所以子进程不可能是该组的第一个进程,在子进程中调用setsid就不会有问题了。
成功调用该函数的结果是:
创建一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。
创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。
如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。所谓失去控制终端是指,原来的控制终端仍然是打开的,
代码:
1 #include<unistd.h>
2 #include<signal.h>
3 #include<stdlib.h>
4 #include<stdio.h>
5 #include<fcntl.h>
6 #include<sys/stat.h>
7 void mydaemon(void){
8 int i;
9 int fd0;
10 pid_t pid;
11 struct sigaction sa;
12 //Linux中信号相关的一个结构体struct sigaction主要在sigaction信号安装和sigqueue信号发送时会用到
13 //该结构位于/usr/include/bits/sigaction.h
14 umask(0);//调用umask将命令模式创建屏蔽字设置0
15 if(pid=fork()<0){
16 perror("fork");
17 }
18 else if(pid>0){
19 exit(0);//父进程,终止进程
20 }
21 //子进程
22 setsid();//创建一个会话
23 sa.sa_handler = SIG_IGN;//忽略SIGCHLD信号
24 sigemptyset(&sa.sa_mask);
25 sa.sa_flags = 0;
26 if(sigaction(SIGCHLD,&sa,NULL)<0){// 注册子进程退出忽略信号
27 return;
28 }
29 if((pid=fork())<0){
30 printf("fork error!\n");
31 return;
32 }
33 else if(pid!=0){
34 exit(0);
35 }
36 if(chdir("/")<0){
37 printf("child dir error\n");
38 return;
39 }
40 close(0);
41 fd0 = open("/dev/null",O_RDWR);
42 dup2(fd0,1);
43 dup2(fd0,2);
44 }
45 int main(){
46 mydaemon();
47 while(1)
48 {
49 sleep(1);
50 }
51}
当我们关掉一个原终端后,该进程变成了一个守护进程,在其他的终端下也可以打开,这就是守护进程的特点
当然系统已经为我们提供了将普通进程变成守护进程的方法
1 #include<stdio.h>
2 #include<unistd.h>
3 int main(){
4 daemon(0,0);
5 while(1);
6 }