进程:
每一个进程不仅有一个进程ID(保存在PCB中),还属于一个进程组,进程组是由一个或多个进程组成,通常和一个作业相关联,可以接受来自统一终端的各种信号,每个进程组有一个唯一的进程组ID.
每个进程组都有一个组长进程,组长进程ID 等于组的ID.
组长进程可以创建一个进程组,这个进程组中 只要有一个进程 存在,这个进程组就存在。
作业:
当命令行执行一个程序的时候,就形成了一个作业,这个作业可以有一个进程,也可以有多个进程。
shell只能运行一个前台作业,当前台有作业时,就不会运行其他作业,shell可以一次运行一个前台作业和多个后台作业。(作业控制)
为什么shell只能运行一个前台作业?
当我们运行一个作业时,shell就被提到了后台,这时的shell不能接受我们的指令并解析执行了,但是如果前台作业退出了,shell就会被提到前台。
作业和进程组的区别:
在一个作业中创建的子进程,属于这个进程组,但是不属于这个作业,如果一旦作业完成,而且这个作业中创建的子进程还存在,那么这个子进程将被提到后台运行,因为只要进程组的一个进程存在,那么这个进程组就存在,所以也可以说,原先的进程组被提到后台。
下面是一个代码例子
1 #include<stdio.h>
2 #include<unistd.h>
3 int main ()
4 {
5 pid_t ret=fork();
6 if(ret==0)
7 {
8 //child
9 while(1)
10 {
11 printf("child=%d\n",getpid());
12 sleep(1);
13 }
14 }
15 else if(ret>0)
16 {
17 int i=4;
18 while(i--)
19 {
20 printf("father=%d\n",getpid());
21 sleep(2);
22 }
23 printf("i am father,i exit done!");
24 }
25 else
26 {
27 perror("fork");
28 return 1;
29 }
30
31
32 return 0;
33 }
会话:
会话是一个或多个进程的集合,一个会话有一个控制终端,建立与控制终端连接的会话首进程叫做控制进程,一个会话中的所有进程可以被分为一个前台进程和若干个后台进程,所以一个会话中应该包括一个会话首进程(控制进程),一个前台进程组(通常为bush),和若干个后台进程组
首先在命令行窗口敲下 sleep 2000 | sleep 3000 &
会出现如下效果
然后我们多打开几个终端,每打开一个就会创建一个会话
每个方框都是不同终端下新建的会话
守护进程
守护进程进程是运行在后台的一种特殊的进程,它独立于控制终端并周期性的执行某种任务或等待。
守护进程必须与其先前运行的环境隔离开来,这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩模。这些环境通常都是守护进程从他的父进程(一般是shell)继承过来的。
最后,守护进程的启动有它的特殊之处,它可以在linux系统启动时从启动脚本/etc/rc.d中启动。
除此之外,守护进程与普通进程并没有什么区别,因此,编写守护进程实际就是按照上述守护进程的特性,把普通进程改造成守护进程。
setsid()函数:
调用setsid的进程如果不是一个进程组的组长,那么这个函数将创建一个新的会话期。
(1)此进程变成会话的首进程
(2)此进程变成一个进程组的组长进程
(3)此进程没有控制终端,如果再调用setsid前,该进程有控制终端,那么之后将与控制终端断开连接。
(4)如果这个进程是组长进程,那么进程会返回错误。为了保证这一点,我们调用fork(),然后使父进程exit()。这样就剩下了子进程,而同时也保证了子进程不是进程组组长。
下面是守护进程的编写代码
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<signal.h>
4 #include<sys/param.h>
5 #include<sys/types.h>
6 #include<time.h>
7 #include<sys/stat.h>
8 #include<stdlib.h>
9 void init_daemon ()
10 {
11 int i=0;
12 pid_t ret=fork();
13 if(ret>0)//父进程,直接退出
14 {
15 exit(0);
16 }
17 else if(ret<0)//fork失败,退出
18 {
19 perror("fork");
20 }
20 }
21 //是第一子进程
22 setsid();//第一子进程成为会话组长和进程组组长,与原终端隔离分开
23 pid_t ret1= fork();//为了防止这个无终端会话组长进程以会话组长身份重新申请控制终端
24 //我们可以重新fork第二子进程,并结束掉第一子进程,这个第二子进程将不会是会话组长
25 //使第二子进程没有申请控制终端的权限
26 if(ret1>0)//第一子进程,结束掉
27 {
28 exit(0);
29 }
30 else if(ret1<0)
31 {
32 perror("fork");
33 }
34 //第二子进程
35 //第二子进程不在是会话组组长
36 for( i=0;i<NOFILE;i++)//关闭打开的文件描述符
37 {
38 close(i);
39 }
40 chdir("/tmp");//改变工作目录
41 umask(0);//修改文件掩模
42
43 return ;
44 }
45
46 int main ()
47 {
48 FILE *fp;
49 time_t t;
50 init_daemon();
51 while(1)
52 {
53 sleep(1);
54 if((fp=fopen("test.log","a")) >=0)
55 {
56 t=time(0);
57 fprintf(fp,"Im here at %sn",asctime(localtime(&t)) );
58 fclose(fp);
59 }
60 }
61 return 0;
62 }
当我们关掉一个原终端后,该进程变成了一个守护进程,在其他的终端下也可以打开,这就是守护进程的特点
当然系统已经为我们提供了将普通进程变成守护进程的方法
#include <unistd.h> int daemon(int nochdir, int noclose);