●进程间关系:
一、进程组
1.进程组是一个或多个进程的集合。
2.每个进程除了有一个进程id外,还属于一个进程组。
3.通常,进程组与同一作业相关联,可以接受来自同一终端的信号。
4.和线程组相似,每个进程组有唯一的进程组ID,每个进程组有一个组长进程(组长进程ID等于进程组ID)。
5.组长进程可以创建一个进程组,然后终止。
6.和线程组不同的是,线程组组长终止,那么线程组就终止了,但是进程组中,只要有一个进程存在,该进程组就存在。
实例:

二、作业
1.广义来说,进程组其实就是作业,shell分前后台控制的是作业或者进程组。一个作业可以由一个或多个进程组成。
2.作业控制:Shell可以运行一个前台作业和任意多个后台作业,称为作业控制。
3.作业和进程组的区别:作业中的进程新创建子进程,子进程不属于作业,而是属于进程组。
4.前台创建新作业,shell就无法运行,当前台作业运行结束后,Shell自动提到前台,如果原来的进程没有终止,它自动变成进程组。
实例:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 int main()
5 {
6 pid_t pid;
7 pid=fork();
8 if(pid<0)
9 {
10 perror("fork error");
11 exit(0);
12 }
13 else if(pid==0)
14 {
15 //child
16 while(1)
17 {
18 printf("I am child:%d\n",getpid());
19 sleep(1);
20 }
21 }
22 else//father
23 {
24 int i=5;
25 while(i--)
26 {
27 printf("I am father:%d\n",getpid());
28 sleep(1);
29 }
30 }
31 return 0;
32 }
结果:

事实上,后面的讨论并不详细区分进程组和作业,可以认为他们是一样的。
三、会话
1.会话是一个或多个进程组的集合。
2.一个会话可以有一个控制终端。通常是登陆到其上的终端设备(终端登陆情况下)或伪终端设备(网络登陆情况下)。
3.建立与控制终端连接的会话首进程称为控制进程,一个会话的几个进程组可被分为一个前台进程组和一个或多个后台进程组。
会话=控制进程+一个前台进程组+任意个后台进程组
如上述第一例中,三个进程属于同一个进程组,同一个会话。
示例:

●守护进程
一、什么是守护进程
1.守护进程也叫做精灵进程,是运行在后台的一种特殊的进程。它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。
2.Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有终端控制,不能和用户直接交互。其他进程都是在用户登录或运行程序时创建,在运行结束或用户注销时销毁。但系统服务进程一直运行,这样的进程叫守护进程。(ps axj命令可以查看系统中进程)

1.凡是TPGID一栏写着-1的都是没有控制终端的进程,也就是守护进程。
2.在COMMAND一列用[]括起来的名字表示内核线程,这些线程在内核里创建,没有用户空间代码,因此没有程序文件名和命令行,通常采用以k开头的名字,表示Kernel。
3.init进程我们已经很熟悉了,udevd负责维护/dev目录下的设备文件,acpid负责电源管理,syslogd负责维护/var/log下的日志文件可以看出,守护进程通常采用以d结尾的名字,表示Daemon。
3.注销,重新登陆实际是重新建立会话。
二、创建守护进程
1.创建守护进程最重要的是调用setsid函数创建一个新的Session,并成为Leader。
#include <unistd.h>
id_t setsid(void);
// 该函数调用成功时返回新创建的Session的id(其实也就是当前进程的id),出错返回-1。
//成功调用该函数的结果是:
//当前进程成为会话首进程(控制进程),当前进程的 id 就是会话的id。
//创建一个新的进程组,当前进程成为进程组的组长,当前进程的 id 就是进程组的 id。
//如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。
//所谓失去控制终端是指,原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通的打开文件而不是控制终端了。
注意:如果调用这个函数的进程是进程组的组长,那么 setsid() 就会出错,返回 -1。 要保证当前进程不是进程组的组长也很容易,只要先fork再调用setsid就行了。fork创建的子进程和父进程在同一个进程组中,进程组的组长必然是该组的第一个进程, 所以子进程不可能是该组的第一个进程,也就不可能会是组长,在子进程中调用setsid就不会有问题了。
2.实现:
1 #include <stdio.h>
2 #include <signal.h>
3 #include <unistd.h>
4 #include <stdlib.h>
5 #include <fcntl.h>
6 #include <sys/stat.h>
7
8
9
10 void mydaemon()
11 {
12 int i;
13 int fd0;
14 pid_t pid;
15 struct sigaction sa;
16
17 umask(0);//1.调用umask将文件模式创建屏蔽字为0,想创建什么权限就是什么权限,因为子进程复制父进程的文件权限屏蔽字,此时你创建的文件权限依然没有某一写权限
18
19 //2. 调用fork,创建子进程,让父进程退出
20 //此时保证子进程不是一个进程组的组长进程
21
22 if((pid = fork()) < 0){
23 perror("fork");
24 }else if(pid > 0){//创建出子进程,父进程退出
25 exit(0);
26 }
27
28
29 setsid();// 3.调用setsid创建一个新会话
30
31 //维护守护进程
32 sa.sa_handler = SIG_IGN;//4.忽略子进程的信号
33 sigemptyset(&sa.sa_mask);
34 sa.sa_flags = 0;
35
36
37 if(sigaction(SIGCHLD,&sa, NULL) < 0){//注册子进程退出忽略信号
38 return;
39 }
40
41 //注意,再次fork,终止父进程,
42 //让孙子进程去维护这个守护进程,此时孙子进程一定不是话首进程,保证后续不会在和其他终端失联
43 if((pid = fork() < 0)){
44 perror("fork");
45 return;
46 }
47 else if(pid != 0){
48 exit(0);
49 }
50
51 //5.将当前工作目录改为根目录
52 if(chdir("/") < 0){
53 printf("chdir dir error\n");
54 return;
55 }
56
57 close(0);//守护进程没有终端,不支持从标准输入读取和输出
58 fd0 = open("/dev/null", O_RDWR);
59 dup2(fd0,1);
60 dup2(fd0,2);
61 }
62
63 int main()
64 {
65 mydaemon();
66 while(1){
67 sleep(1);
68 }
69 return 0;
70 }
71
3.daemon函数
#include <unistd.h>
int daemon(int nochdir, int noclose);
参数: 当 nochdir为 0 时,当前目录变为根目录,否则不变; 当 noclose为 0 时,标准输入、标准输出和错误输出重定向为/dev/null,也就是不输出任何信息,否则照样输出。
返回值: 如果成功函数返回0,否则返回-1并设置errno。
#include<stdio.h>
#include<unistd.h>
int main()
{
daemon(0,0);
while(1);
}
|