进程组
在linux中,每一个进程还属于一个进程组,一个进程组有多个进程组成,通常,他们联合起来作业,可以接受从同一个终端下的各种信号。并且每一个进程组都有一个进程组ID。在Linux中叫做PGID,一个进程组由多个进程组成,进程组中有一个组长进程,组长进程的标识是他的进程ID和组ID相同。一般一个进程组中第一个创建的进程就是组长进程。一个进程组中只要还有一个进程存在,那么这个进程组就存在。
我们可以通过ps 命令,来查看进程信息。
-a 列出所有用户的进程
-x 不仅列出有终端控制的,也列出没有终端控制的进程。
-j列出与作业控制相关的信息
PID:进程ID
PPID:父进程ID
PGID:所属进程组的ID
SID:会话ID
TTY:相关控制终端
作业
Shell分前后台来控制的不是进程而是作业或者叫做进程组,Shell可以运行一个前台作业和任意多个后台作业,这就叫做作业的控制。
作业和进程组的区别:如果一个进程组中的某个进程fork出来了子进程,那么子进程属于进程组,而不属于作业。
现在我们就可以理解为什么当我们运行一个进程之后,再输入命令,就没有作用了,原因就是因为让我们启动一个作业以后,默认是放在前台的,而前台只能有一个进程,因此Shell就被提到后台了,无法接受我们的指令。
1 #include<stdio.h>
2 #include<unistd.h>
3 int main()
4 {
5 pid_t pid;
6 if(pid=fork()==0)
7 {
8 //child
9 while(1)
10 {
11 printf("hello, world\n");
12 sleep(2);
13 }
14
15 }
16 else
17 {
18 sleep(5);
19 }
20
21 return 0;
22 }
来看一下结果:
输入./a.out按下回车,就会在前台起一个作业,父进程sleep(5)秒,在这5秒期间,Shell被提到后台,无法接受我们输入的指令,五秒之后,父进程退出,那么Shell变成前台作业,可以接受我们的ls指令,子进程属于进程组但是不属于作业,因此它在后台一直向屏幕上打印hello,world。
会话
会话是由一个或者多个进程组的集合,一个会话可以有一个控制终端,这通常是登陆到其上的终端设备(终端登陆情况下)和伪终端情况(xshell网络登陆),建立与终端控制的会话首进程我们称之为控制进程,一般为bash,一个会话的进程组可以分为一个前台进程和任意多个后台进程,所以一个会话包括一个控制进程(会话首进程),一个前台进程组和任意多个后台进程组。
作业控制
事实上,Shell分前后台来控制的不是进程而是作业,Shell可以运行一个前台作业和任意多个后台作业,这就叫做作业控制。
- 在启动程序的时候,和后面跟上&选项表示把进程放在后台运行。
- jobs命令可以查看后台作业
- fg+作业编号,就可以把后台作业提到前台。
- 一个前台进程我们也可以用ctrl+z让他变成stop状态放在后台,然后再用bg命令让它运行起来。此时还是在后台
- ctrl+c只能结束掉前台作业。
下面来看一个例子,在前台新起一个作业cat,它的作用是从标准输入中读取数据,然后输出到标准输出中
当我们把cat放到后台运行时,再查看它的状态
此时cat的状态变成stopped,为什么会这样呢,原因是bash不允许后台进程从标准输入中读取数据,就会给cat发送一个SIGTTIN信号,该信号的默认处理动作是让进程停止。但是后台进程是运行写的。前面我们写的一个小程序子进程就是后台作业,一直再向屏幕上写,验证了这一点,当我们尝试给一个停止的进程发送信号的时候,并不会立即处理,而是等到进程运行起来的时候,才会去处理。但是linux中有强大的9号信号,可以插死除了僵尸进程之外的各种状态进程。
守护进程
守护进程也叫做精灵进程,是在运行期间的一种特殊的进程,它独立于控制终端并且周期性的执行某种任务或者等待某些事件的发生,这是一种很有用的进程,再linux中很多的服务都是由守护进程实现的,比如,http服务器,ssh服务器,Web服务器,守护进程完成着很多的系统任务,比如说作业规划进程crond。
linux系统启动的时候有很服务进程启动,这些进程没有控制终端,无法和用户进行交互,其他进程都是在用户登陆或者程序运行时创建,在运行结束或者用户注销的时候终止,但是守护进程不受用户登陆和注销的影响,他们一直在运行。
通过ps -ajx|more来查看守护进程
- 凡是TPGID一栏写着-1的都是没有控制终端的进程,也就是守护进程。
-在COMMAND一栏中用[]括起来的表示内核线程,没有用户态代码,因此没有程序文件名和命令行,通常以K开头的表示kernal。
-我们可以看出来,守护进程通常是以d结尾的,表示Daemon。
创建守护进程
创建守护进程用setsid函数创建一个新的Session,并成为Session Leader。
#include<unistd.h>
setsid(void);
这个函数成功调用新的Session的id,也就是当前进程的id,出错返回-1
注意:调用这个函数之前,当前进程不允许是所属进程组的组长,否则调用出错,必须要出子进程,然后再调用setsid,这是因为fork出子进程,子进程也属于这个进程组,但肯定不是组长进程,因为守护进程自成一个进程组,它的ID肯定是组长ID,如果创建它的进程是组长进程,那么再内核中就会有两个ID一样的进程中,这是错误的。
这个函数调用成功的结果是:
- 创建一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id,即自成一个会话
- 创建一个新的进程组,并且自成组长
- 如果当前进程有一个控制终端,那么它将会失去这个控制终端,没有控制终端
基于上面几点,我们来自己写一个守护进程
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<sys/stat.h>
4 #include<signal.h>
5 #include<fcnatl.h>
6 #include<stdlib.h>
7 #include<unistd.h>
8
9
10 void mydaemon()
11 {
12 umask(0);//文件创建模式的屏蔽字设为0
13 pid_t pid;
14 if(pid=fork()==0)
15 {
16 //child
17 //三个重定向
18 setsid();
19 signal(SIGCHLD,SIG_IGN);
20
21 //忽略SIGCHLD信号
22 if(chdir("./")<0)
23 {
24 perror("chdir\n");
25 exit(0);
26 }//更改当前工作目录为根目录
27 close(0);
28 int fd=open("/dev/null",O_RDWR);
29 dup2(fd,1);
30 dup2(fd,2);
31 }
32 else
33 {
34 //father
35 exit(0);
36 }
37 }
38 int main()
39 {
40
41 mydaemom();
42 while(1);
43
44
45 return 0;
46 }
看看结果:
这是我们根据守护进程的特性,自己模拟实现的一个创建守护进程的函数,当然有封装好的接口拱我们使用。
1 #include<unistd.h>
2 daemon(0,0);
daemon()函数也是必须fork出子进程才可以调用,这里两个参数默认都是0,表示更改进程默认打开的三个文件描述符,和更改进程的工作目录。