在我们学习完进程和线程之后,我们知道进程与进程之间是相互独立的,而线程与线程之间是有一定的联系的。那么进程与进程之间就真的没有关系吗?接下来就来学习一下进程间关系以及一个新的名词守护进程。
在学习进程间关系之前,我们来学习几个名词:
1、进程组/作业/会话
(1)进程组
我们在学习进程的时候就知道,每一个进程都有自己唯一的进程ID,那么今天我就要说,每一个进程除了有一个进程ID之外,还属于一个进程组,那么相应的也就会有一个组ID。进程组是一个或多个进程的集合。通常,它们与同一任务相关联,可以是接收来自同一终端的各种信号。每个进程组有一个唯一的进程组ID,每个进程组都可以有一个组长进程。那么组长进程可以创建一个进程组,创建该组中的进程,然后终止。也就是说:只要在某个进程组中有一个进程存在,那么该进程组就存在,这与组长存在与否没有关系。
在上图中,有几个命令要给大家来解释一下。
‘&’:表示将进程组放至后台执行;
PID为进程ID,PGID为进程组ID,那么进程ID和进程组ID的就是组长ID,所以组长为5183号进程,由上图也可看出,组长进程是进程组中的第一个进程;
而这个组中有三个进程:5183 5184 5185;
当kill -9 5183 即杀死组长进程之后,再查看进程发现当杀死组长进程之后,进程组仍存在;
ps选项:
a :不仅列出当前用户的进程,而且也列出其他用户的进程;
x :表示不仅列有控制终端的进程,也列出无控制终端的进程;
J :表示列出与作业控制相关的进程;
ps axj |grep sleep 列出与sleep相关的所有进程;
grep -v grep 表示不显示含有’grep’的任务;
在此要说明的是,ctrl C 终止是终止了一个前台进程组(也可以说是终止了一个前台任务)。
(2)作业
Shell分前后台来控制的不是进程而是作业或者是进程组。一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成。Shell可以运行一个前台作业和任意多个后台作业,这称为作业控制。
作业与进程组的区别:如果作业中的一个进程有创建了一个子进程,那么该子进程不属于作业,但属于进程组。
一旦作业结束,shell就把自己提到前台(子进程还在,但不属于作业),如果原来的前台进程还存在(如果这个子进程还没终止),它自动变为后台进程。
当你运行一个死循环的代码时,你就会发现,此时如果你想要输入一个命令,那么是不可能的,因为当你在进行这个作业的时候,shell被逼到了后台,所以它是无法执行你的命令的,但当你Ctrl C结束掉你得当前进程后,shell就又被提到了前台,所以就可以继续接受用户输入。
(3)会话
会话是一个或多个进程组的集合。一个会话可以有一个控制终端。这通常是登录到其上的终端设备(在终端登录情况下)或伪终端设备(在网络登录情况下),建立与终端控制连接的会话首进程被称为控制进程。一个会话中的几个进程组可被分为一个前台进程组合以及一个或多个后台进程组。所以一个会话中,应该包括控制进程(会首进程),一个前台进程组合任意后台进程组(也就是说,会话由会话首进程,控制终端,前台、后台作业组成)。
会话的默认首进程是bash。一旦会话首进程被杀,则会话依旧存在,但终端被杀,会话 。
每打开一个终端,就相当于新建了一个会话。
SID就是会话ID,也就是3939,三个进程都属于同一个进程组,同一个会话。
那么3939是谁呢?通过命令ps aux |grep -E 3939 可以看出3939是bash。
2、作业控制
会话与进程组“shell可以同时运行一个前台进程和任意多个后台进程”是不太全面的。事实上,shell分前后台来控制的不是进程而是作业(job)或者是进程组(Process Grope)。一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,shell可以同时运行一个前台作业和任意多个后台作业,这称为作业控制。
下面就来介绍几个作业控制的常见命令:
3、作业控制相关的信号
总结一下:后台进程不能从前台(终端下)读取数据,但是后台进程可以向终端写数据。
4、守护进程
(1)初始守护进程
守护进程也叫精灵进程,是运行在后台的一种特殊进程。它独立于控制终端并且周期性的执行某种任务或者等待处理某些发生的事件。Linux的大多数服务器都是由守护进程实现的。比如 ftp服务器,ssh服务器,web服务器,http等。同时,守护进程完成许多系统任务,如作业规划进程crond(定时任务)等。
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接与用户交互。其他进程都是在用户登录或者运行程序时创建,在运行结束或用户注销时终止,但系统服务进程(守护进程)不受用户注销登录的影响,它们一直运行着,着中进程有一个名字叫守护进程。
下面我们采用ps axj 命令查看系统中的进程:
ps axj |more
- 凡是TPGID一栏写着-1的都是没有控制终端的进程,也就是守护进程。
- 在COMMAND一栏用[]括起来的名字表示内核线程,这些线程在内核里创建,没有用户控件代码,因此没有程序文件名和命令行,通常采用以k开头的名字,表示kernel.
- init进程我们已经很熟悉了,udevd负责维护/dev目录下的设备文件,acpid负责电源管理, syslogd 负责维护/var/log下的日志文件。
- 可以看出,守护进程通常采用以d结尾的名字,表示Daemon。
(2)创建守护进程
创建守护进程最关键的一部是调用setsid()函数来创建一个新的会话,并且成为会话Leader。
#include<unistd.h>
pid_t setsid(void);
该函数调用成功返回新创建的会话ID(其实也就相当于当前进程的id),出错就返回-1。
注意:调用该函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。要保证当前进程不是进程组的Leader也很容易,只要先fork一下,然后再调用setsid就行了。fork创建的子进程和父进程再同一个进程组中,进程组的Leader必然也是该组的第一个进程,所以子进程不可能是改组的第一个进程,在子进程中调用setsid就不会有什么问题了。
成功调用该函数的结果是:
- 创建一个新的会话,当前进程成为会话Leader,当前进程的id就是会话id。
- 创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。
- 如果当前进程原本有一个控制终端,则它失去这个控制终端,成一个没有控制终端的进程。所谓失去控制终端是指原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通的打开文件而不是控制终端了。
接下来实现一个守护进程:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<signal.h>
#include<stdlib.h>
void mydaemon(void)
{
umask(0);//1、调用umask将文件模式创建屏蔽字设置为0
//2、调用fork,父进程退出exit
pid_t id = fork();
if(id > 0 )
{
exit(1);
}
setsid(); //3、创建新会话
signal(SIGCHLD,SIG_IGN);//4、忽略SIGCHLD信号
if(chdir("/")<0) //5、将工作目录设置为根目录
{
printf("child dir error\n");
return;
}
close(0); //6、关闭文件描述符
close(1);
close(2);
//int fd0;
//close(0);
//fd0=open("/dev/null",o_RDWR);
//dup2(fd0,1);
//dup2(fd0,2);
}
int main()
{
mydaemon();
while(1)
{
sleep(1);
}
return 0;
}
在库中,其实已经提供了一个函数,只要调用该函数,就可以自动创建一个守护进程,这个函数就是daemon()函数。
int daemon(int nochdir , int noclose);
#include<stdio.h>
#include<unistd.h>
int main()
{
daemon(0,0);//(关闭更改目录,关闭文件描述符)
while(1) ;
}
Linux向外提供网络服务以守护进程方式。