Linux进程间关系和守护进程

进程间关系和守护进程

  • 进程组。

       每个进程除了有一个进程ID外,还属于一个进程组。进程组是一个或多个进程的集合。通常,他们与同一个作业相关联,他们可以接受来自于同一个终端的信号。每个进程组有一个唯一的组ID,每个进程组都可以有一个组长进程,进程组ID就是组长的ID。组长进程可以创建一个进程组,创建该组中的进程,然后终止。只要在某个进程组中一个进程存在,该进程组就存在,这与组长进程是否终止无关。

在这里我们可以通过一段代码来理解一下。


‘&’:表示将进程放在后台执行。

首先我们创建出一个进程组


从这里可以看出进程2641是组长进程,因为其余两个进程都是它创建出来的。接下来我们将组长进程杀掉,看一下会怎样。


这里我们将组长进程杀死后查看,发现进程组并没有被影响到,这也印证了我们上面的说法,组长进程存在与否不影响进程组。

  • 作业

Shell分前后台来控制的不是进程而是zuoye(Job)或者进程组(Process Group)。一个前台作业可以由多个进程组成,一个后台也可以由多个进程组成,Shell可以运行一个前台作业和多个后台作业,这称为作业控制。

作业与进程组的区别:如果作业中的某个进程创建出了一个子进程,则子进程不属于作业。

一旦作业运行结束,Shell就把自己提到前台(子进程还在,但它不属于作业),如果原来的前台进程还存在(如果这个子进程还没终止),它自己变为后台进程组。

接下来我们仍然用代码展示:

#include<stdio.h>
#include<unistd.h>

int main()
{
	pid_t id = fork();
	if(id<0)
		perror("fork\n");
	else if(id==0)
	{	while(1){
		printf("I am child(%d),i am running...\n",getpid());
		sleep(4);}}
	else
	{
		int i = 5;
		printf("I am father(%d),i am going to dead...%d\n",getpid(),i--);
		sleep(4);
	}
	return 0;
}

当这段代码运行起来后,我们可以发现5s之内shell无法接受任何指令,说明此时的前台作业不是shell,当父进程退出后,子进程还在运行,但此时的shell可以接受指令了,这说明shell变成了前台作业。

  • 会话

会话(Session)是一个或多个进程组的集合。一个会话可以有一个控制终端,这通常是登录其上的终端设备或伪终端设备。建立与控制终端连接的会话首进程被称为控制进程。一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组。所以一个会话中,应该包括控制进程(会话首进程),一个前台进程组和任意多个后台进程组。

通过上图我们可以看出来,我们在后台运行了一个进程组,发现它们的SID,也就是会话ID都一样,经过查找我们发现,这三个进程的父进程都是bash,而且bash也是会话首进程。

  • 作业控制

Session与进程组“Shell可以同时运行一个前台进程和任意多个后台进程”其实是不全面的,现在我们来研究更复杂的情况。事实上,Shell分前台来控制进程的不是进程而是作业或者进程组,一个前台作业可以由多个进程组成,一个后台作业同样也可以由多个进程组成,Shell可以同时运行一个前台作业和任意多个后台作业,这称为作业控制。

接下来我们仍由一段命令来了解它

在上图中,我们创建了两个作业,刚开始一号作业是在前台运行,而二号作业是在后台运行的,但我们通过fg命令和Ctrl+Z的命令当一号进程变成后台运行的,当我们重新查看后,两个作业运行位置的确变了。

通过上图能看出,我们通过Ctrl+C命令将一号作业杀掉了,当我们再次查看时,它已经不存在了。

  • 作业控制有关的信号

我们通过直接实验来理解与作业有关的信号。

这里我们也了解一下cat指令:

从图中我们能看出,cat是从标准输入中读入数据的,而后台进程是不能读终端输入的,因此内核发SIGTTIN信号给进程,该信号的默认处理动作是使进程停止。

在这里我们将cat后台运行,利用jobs查看当前作业,用fg将该作业调至前台运行,如果该作业处于停止状态,则给进程组的每个进程发送SIGCONT信号使它们继续运行。当cat提到前台运行后,它将挂起等待终端输入,当输入hehe后它也打印出同样的数据。这时如果输入Ctrl+Z,则向所有前台进程发送SIGTSTP信号,该信号的默认动作是使进程停止,cat继续以后台的形式存在。

用kill命令给一个停止的进程发送SIGTERM(15)信号,这个信号并不会立刻处理,而要等进程准备继续运行之前处理,默认动作是终止进程。

  • 守护进程

守护进程也称精灵进程(Daemon),是运行在后台的一种特殊进程。它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件,守护进程是一种很有用的进程。Linux大多数服务器就是用守护进程实现的。比如:ftp服务器,ssh服务器,Web服务器httpd等。同时,守护进程完成很多系统任务,比如,作业规划进程crond等。

Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。其它进程都是在用户登录或运行时创建的,在用户结束或注销时终止,但系统服务进程不受用户登录的影响,它一直运行着,这种进程被称为守护进程。

下面我们用ps axj命令查看系统中的命令。

  1. 凡是TPGID一栏为-1的都是没有终端控制的进程,也就是守护进程。
  2. 在COMMAND一栏用[]括起来的表示内核线程,这些线程在内核里创建,没有用户空间代码,因此没有程序文件名和命令行,通常采用以k开头的名字,表示Kernel。
  3. 可以看出守护进程通常采用以d结尾的名字,表示Daemon。

  • 创建守护进程

创建守护进程最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader。

pid_t setsid(void);

该函数调用成功时返回新创建的Session的ID,出错返回-1,调用此函数之前,当前进程不允许是进程组的Leader。为了保证函数不出错,我们利用fork函数创建一个子进程,在子进程中调用它就可以了。

成功调用该函数后:

  1. 创建一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。
  2. 创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。
  3. 如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。

接下来我们来尝试创建出一个守护进程:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/stat.h>

void mydaemon()
{
	umask(0);
	pid_t n = fork();
	if(n<0)
		perror("fork\n");
	else if(n>0)
		exit(1);

	pid_t m = setsid();
	printf("sid = %d\n",m);     //打印出守护进程的ID
	chdir("/");          //重定向到根目录
	close(0);            //关闭不需要的文件描述符
	close(1);
	close(2);
	signal(SIGCHLD,SIG_IGN);
}
int main()
{
	mydaemon();
	while(1){}
	return 0;
}

由运行结果可以看出,的确产生了一个进程,且满足上面所说的守护进程的定义。

在这里我们也可以直接调用daemon函数来创建一个守护进程。

int daemon(int nochdir,int noclose);

当nochdir为0时,当前目录变为根目录,否则不变。

当noclose为0时,标准输入输出重定向到/dev/null,也就是不输出任何信息,否则照样输出。

返回值:因为daemon函数调用了fork()函数,成功返回0,失败返回-1。

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>

int main()
{
	daemon(0,0);
	while(1);
	return 0;
}

当我们运行程序后,发现的确创建出了一个守护进程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值