linux系统的进程组和守护进程

进程组

概念和特性

进程组,也称之为作业。BSD于1980年前后向Unix中增加的一个新特性。代表一个或多个进程的集合。每个进程都属于一个进程组。在waitpid函数和kill函数的参数中都曾使用到。操作系统设计的进程组的概念,是为了简化对多个进程的管理。
当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。进程组ID==第一个进程ID(组长进程)。所以,组长进程标识:其进程组ID==其进程ID 
可以使用kill -SIGKILL -进程组ID(负的)来将整个进程组内的进程全部杀死。			【kill_multprocess.c】
组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关。
进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组)。
一个进程可以为自己或子进程设置进程组ID

进程组操作函数

getpgrp函数

获取当前进程的进程组ID
	pid_t getpgrp(void); 总是返回调用者的进程组ID

getpgid函数

获取指定进程的进程组ID
	pid_t getpgid(pid_t pid);	 成功:0;失败:-1,设置errno
如果pid = 0,那么该函数作用和getpgrp一样。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int main(void)
{
    pid_t pid;
 
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    } else if (pid == 0) {
        printf("child process PID == %d\n", getpid());                  //子进程ID
        printf("child Group ID == %d\n", getpgrp());                    //子进程所在组ID
        printf("child Group ID == %d\n", getpgid(0));                   //传0表当前进程
        printf("child Group ID == %d\n", getpgid(getpid()));
        exit(0);
    }
 
    sleep(3);
 
    printf("parent process PID == %d\n", getpid());
    printf("parent Group ID is %d\n", getpgrp());
 
    return 0;
}

setpgid函数

改变进程默认所属的进程组。通常可用来加入一个现有的进程组或创建一个新进程组。
	int setpgid(pid_t pid, pid_t pgid); 	成功:0;失败:-1,设置errno
将参1对应的进程,加入参2对应的进程组中。
	注意: 
1. 如改变子进程为新的组,应fork后,exec前。 
2. 权级问题。非root进程只能改变自己创建的子进程,或有权限操作的进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int main(void)
{
    pid_t pid;
 
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    } else if (pid == 0) {
        printf("child PID == %d\n",getpid());
        printf("child Group ID == %d\n",getpgid(0)); // 返回组id
        //printf("child Group ID == %d\n",getpgrp()); // 返回组id
        sleep(7);
        printf("----Group ID of child is changed to %d\n",getpgid(0));
        exit(0);
 
    } else if (pid > 0) {
        sleep(1);
        setpgid(pid,pid);           //让子进程自立门户,成为进程组组长,以它的pid为进程组id
 
        sleep(13);
        printf("\n");
        printf("parent PID == %d\n", getpid());
        printf("parent's parent process PID == %d\n", getppid());
        printf("parent Group ID == %d\n", getpgid(0));
 
        sleep(5);
        setpgid(getpid(),getppid()); // 改变父进程的组id为父进程的父进程
        printf("\n----Group ID of parent is changed to %d\n",getpgid(0));
 
        while(1);
    }
 
    return 0;
}

创建会话

创建一个会话需要注意以下6点注意事项:
1.	调用进程不能是进程组组长,该进程变成新会话首进程(session header)
2.	该进程成为一个新进程组的组长进程。
3.	需有root权限(ubuntu不需要)
4.	新会话丢弃原有的控制终端,该会话没有控制终端
5.	该调用进程是组长进程,则出错返回
6.	建立新会话时,先调用fork, 父进程终止,子进程调用setsid

getsid函数

获取进程所属的会话ID
	pid_t getsid(pid_t pid); 成功:返回调用进程的会话ID;失败:-1,设置errno
pid为0表示察看当前进程session ID
ps ajx命令查看系统中的进程。参数a表示不仅列当前用户的进程,也列出所有其他用户的进程,参数x表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数j表示列出与作业控制相关的信息。
组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。
setsid函数
创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID。
	pid_t setsid(void);  成功:返回调用进程的会话ID;失败:-1,设置errno
	调用了setsid函数的进程,既是新的会长,也是新的组长。	
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
	pid_t pid;
	if((pid = fork())<0){
		perror(“fork”);
		exit(1);
	}
else if(pid == 0)
{
printf(“child process PID is %d\n”,getpid());
printf(“Group id of child is %d\n”,getpgid(0));
printf(“session id of child is %d\n”,getsid(0));
sleep(10);
setsid();
printf(“changed :\n”);
printf(“child process PID is %d\n”,getpid());
printf(“Group id of child is %d\n”,getpgid(0));
printf(“session id of child is %d\n”,getsid(0));
sleep(20);
exit(0);
}
return 0;
}

守护进程

Daemon(精灵)进程,是Linux中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字。
Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互。不受用户登录、注销的影响,一直在运行着,他们都是守护进程。如:预读入缓输出机制的实现;ftp服务器;nfs服务器等。
创建守护进程,最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader。
创建守护进程模型

1fork()创建子进程,父进程exit()退出
这是创建守护进程的第一步。由于守护进程是脱离控制终端的,因此,完成第一步后就会在Shell终端里造成程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离,在后台工作。
2)在子进程中调用 setsid() 函数创建新的会话
在调用了fork()函数后,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,因此,这还不是真正意义上的独立开来,而 setsid() 函数能够使进程完全独立出来。
3)再次 fork() 一个子进程并让父进程退出
现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端,可以通过 fork() 一个子进程,该子进程不是会话首进程,该进程将不能重新打开控制终端。退出父进程。
4)在子进程中调用 chdir() 函数,让根目录 ”/” 成为子进程的工作目录
这一步也是必要的步骤。使用fork创建的子进程继承了父进程的当前工作目录。由于在进程运行中,当前目录所在的文件系统(如“/mnt/usb”)是不能卸载的,这对以后的使用会造成诸多的麻烦(比如系统由于某种原因要进入单用户模式)。因此,通常的做法是让"/"作为守护进程的当前工作目录,这样就可以避免上述的问题,当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如/tmp。改变工作目录的常见函数是chdir。
5)在子进程中调用 umask() 函数,设置进程的文件权限掩码为0
文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限。由于使用fork函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为0,可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是umask。在这里,通常的使用方法为umask(0)6)在子进程中关闭任何不需要的文件描述符
同文件权限码一样,用fork函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。
在上面的第二步之后,守护进程已经与所属的控制终端失去了联系。因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0123个文件(常说的输入、输出和报错)已经失去了存在的价值,也应被关闭。
7)守护进程退出处理
当用户需要外部停止守护进程运行时,往往会使用 kill 命令停止该守护进程。所以,守护进程中需要编码来实现 kill 发出的signal信号处理,达到进程的正常退出。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

int main(int argc, const char *argv[])
{
	pid_t pid;
	pid = fork();
	if( pid < 0 )
	{
		printf(" Error fork\n");
		exit(1);
	}
	else if (pid > 0)
	{
		//父进程退出 
		exit(0);
	}
	setsid();
	chdir("/tmp");
	umask(0);
	int i;
	for(i=0; i< getdtablesize(); i++)
	{
		close(i);
	}
	//这时创建完守护进程,下面开始进入守护进程工作
	char buf[64] = "This is a Daemon1\n";
	int fp;
	while(1)
	{
		if((fp = open("daemon.txt",O_WRONLY | O_CREAT | O_APPEND, 0600)) < 0 )
		{
			printf(" fail open \n");
			exit(1);
		}
		write(fp, buf, strlen(buf));
		close(fp);
		sleep(20);
	}
	exit(0);
}
发布了20 篇原创文章 · 获赞 1 · 访问量 2499

猜你喜欢

转载自blog.csdn.net/m0_46198325/article/details/104357874