什么是守护进程?
Daemon(精灵)进程是Linux中后台服务进程,独立于控制终端并且周期性地执行某种任务或等待处理某些发生事件,一般采用以d结尾的名字。
守护进程就是通常讲Daemon进程,是linux后台执行的一种服务进程,特点是独立于控制终端、周期性地执行某种任务或等待处理某些发生事件,不会随终端关闭而停止,直到接受停止信息才会结束,且一般采用以d结尾的名字。
为什么需要守护进程?
举例有哪些是守护进程?
linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互,不受用户登录、注销的影响,一直在运行着,他们都是守护进程,如:预读入缓输出机制的实现;ftp服务器(借助vsftpd实现),nfs服务器。
怎么杀死守护进程?
守护进程涉及内容:
终端、进程组、会话、创建守护进程。
类UNIX系统中,用户通过终端登录系统后得到一个Shell进程,该终端称为Shell进程的控制终端(Controling Terminal),进程中,控制终端是保存在PCB(进程描述符)中的信息,而fork会复制PCB中的信息,因此由Shell进程启动的其他进程的控制终端也是这个终端。
默认情况下(没有重定向),每个进程的标准输入,标准输出和标准错误输出都指向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写就是输出到显示器上。
信号中涉及,在控制终端输入一些特殊的控制键可以向前台进程发信号,如Ctrl-C表示SIGINT,Ctrl-\表示SIGQUIT(输入输出设备总称)
字符终端:ALt+Ctrl+F1、F2、F3、F4、F5、F6
伪终端:pts(pseudo terminal slave)
图像终端:Alt+F7
网络终端:SSH、Telnet
文件与IO中涉及,每个进程都可以通过一个特殊的设备文件/dev/tty提供一个通用的接口,一个进程要访问它的控制终端既可以通过/dev/tty也可以通关该终端设备所对应的设备文件来访问。ttyname函数可以由文件描述符查出对应的文件名,该文件描述符必须指向一个终端设备而不能是任意文件。
简单来说一个linux系统启动,大致经历如下的步骤:
init-》fork-->exec-->getty-->用户输入账号-->login-->输入密码-->exec-->bash
硬件驱动程序负责读写实际的硬件设备,如从键盘读入字符和把字符输出到显示器。
线路规程像一个过滤器,对于某些特殊字符并不是让它直接通过,而是做特殊处理(拦截特殊字符并做处理),如在键盘上按下Ctrl-z,对应字符并不会被用户程序的read读到,而是被线路规程截获,解释成SIGTSTP信号发给前台进程,通常会使该进程停止。线路规程应该过滤那些字符和做那些特殊处理是可以配置的。
line discipline:线路规程,用来过滤键盘输入的内容;
ttyname函数
用于由文件描述符查出对应的文件名
char *ttyname(int fd);//成功:终端名;失败:NULL,设置errno
借助ttyname函数,通过实验看下各种不同的终端所对应的设备文件名。
//ttyname.c
#include
#include
int main(void){
printf("fd 0:%s\n",ttyname(0));
printf("fd 1:%s\n",ttyname(1));
printf("fd 2:%s\n",ttyname(2));
return 0;
}
网络终端
虚拟终端或串口终端是数目是有限的,虚拟终端(字符终端)一般就是六个:/dev/tty1-/dev/tty6,串口终端的数目不超过串口数目。而网络终端或图形终端窗口的数目却是不受限制,这是通过伪终端(Pesudo TTY)实现的。一套伪终端由一个主设备(PTY Master)和一个从设备(PTY Slave)组成。主设备相当于键盘和显示器,它不是一个“真正”的硬件而是一个内核模块,操作它的不是用户而是另外一个进程。从设备类似于/dev/tty1终端设备模块,从设备的底层驱动不是访问硬件而是访问主设备。
网络终端或图形终端窗口的Shell进程以及它启动的其他进程都会认为自己的控制终端是伪终端从设备,如/dev/pts/0、/dev/pts/1等。下面以Telnet为例说明网络登录和使用伪终端的过程。
TCP/IP协议栈:在数据包上添加报头。
如果Telnet客户端和服务器之间的网络延迟较大,我们会观察到按下一个键之后要过几秒才能回显到屏幕上。这过程中涉及:当按一个键Telnet客户端都会立刻把该字符发送给服务器,然后这个字符经过伪终端主设备和从设备之后被Shell进程读取,同时回显到伪终端从设备,回显的字符串再经过伪终端主设备、Telnetd服务器和网络发回给telnet客户端,显示该用户看。
进程组又称为作业,代表一个或多个进程的集合。每个进程都属于一个进程组,这简化了对多个进程的管理;
当父进程创建子进程时,默认子进程与父进程属于同一个进程组。进程组ID==第一个进程ID(组长进程);
杀死进程组内所有进程:kill -SIGKILL -进程组ID(负值);
组长进程可创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关;
进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组);
一个进程可为自己或子进程设置进程组ID。
进程组操作函数
getpgrp函数
用于获取当前进程的进程组ID
pid_t getpgrp(void);//返回调用者的进程组ID
getpgid函数
用于获取指定进程的进程组ID
pid_t getpgid(pid_t pid);//成功:0;失败:-1
setpgid函数
改变进程默认所属的进程组,通常用于加入一个现有的进程组或创建一个新进程组。
//将进程pid加入到进程组pgid中
int setpgid(pid_t pid,pid_t pgid);//成功:0;失败:-1,设置errno
会话
创建一个会话注意事项:
getsid函数
获取进程所属的会话ID
pid_t getsid(pid_t pid);//成功:返回调用进程的会话ID;失败:-1,设置errno
//pid=0:查看当前进程session ID
ps ajx 命令查看系统中的进程,
参数a:列出当前用户的进程以及其他用户的进程;
x:列出有控制终端进程以及无控制终端进程;
j:列出与作业控制相关的信息。
组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。
setsid函数
创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID
//调用了setsid函数的进程,既是新的会长,也是新的组长
pid_t setsid(void);//成功:返回调用进程的会话ID;失败:-1,设置errno
练习:fork一个子进程,创建一个新会话,查看进程组ID,会话ID前后变化
//session.c
#include
#include
int main(){
pid_t pid,sid;
pid = fork();
if(pid < 0){
perror("fork erro");
exit(1);
}
else if(pid == 0){
printf("child process pid: %d\n",getpid());
printf("Group ID of child pgid: %d\n",getpgid(pid));//获取进程组ID
printf("Session ID of child is:%d\n",getsid(pid));//获取会话ID
sleep(5);
sid = setsid();//创建一个会话,将自己ID设置为进程组ID,即新会话ID
printf("child process pid is %d\n",getpid());
printf("Session Id of child is:%d\n",sid);
printf("Group ID of child is: %d\n",getpgid(pid));
//sleep(5);
//exit(0);
}
return 0;
}
使用setsid函数改变当前进程组及会话ID,此时将进程id设置为了进程组ID,但为什么会话ID和进程组ID一样?
创建守护进程模型
1、创建子进程,父进程退出
(所有工作在紫禁城中进行,形式上脱离了控制终端)
2、在值进程中创建新会话
(使用setsid函数使子进程完全独立出来,脱离控制)
3、改变当前目录为根目录
(防止占用可卸载的文件系统,也可换成其他路径)
4、重设文件权限掩码
防止继承的文件创建屏蔽字拒绝某些权限
增加守护进程灵活性
5、关闭文件描述符
继承的打开文件不会用到,浪费系统资源,无法卸载
6、开始执行守护进程核心工作
7、守护进程退出处理程序模型
练习:创建一个守护进程
#include
#include
#include
#include
int main(){
pid_t pid,sid;
pid = fork();//step1:创建子进程
if(pid < 0){
perror("fork erro");
exit(1);
}
else if(pid == 0){
printf("child process pid: %d\n",getpid());
printf("Group ID of child pgid: %d\n",getpgid(pid));
printf("Session ID of child is:%d\n",getsid(pid));
// sleep(5);
sid = setsid();//step2:在子进程中创建会话
printf("child process pid is %d\n",getpid());
printf("Session Id of child is:%d\n",sid);
printf("Group ID of child is: %d\n",getpgid(pid));
//step3:改变当前目录为根目录
if(chdir("/") < 0){
perror("chdir error");
exit(1);
}
//step4:重设文件权限掩码
umask(0);
//关闭文件描述符
close(0);
open("/dev/null",O_RDWR);//?
dup2(0,1);//使用重定向函数将?
dup2(0,2);
}
while(1);
return 0;
}