Linux系统编程——守护进程

1. 终端

输入设备和输出设备的综合称为终端
       在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端(Controlling 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… 网络终端

1.1 终端的启动流程

       文件与I/O中讲过,每个进程都可以通过一个特殊的设备文件/dev/tty访问它的控制终端。事实上每个终端设备都对应一个不同的设备文件,/dev/tty提供了一个通用的接口,一个进程要访问它的控制终端既可以通过/dev/tty也可以通过该终端设备所对应的设备文件来访问。ttyname函数可以由文件描述符查出对应的文件名,该文件描述符必须指向一个终端设备而不能是任意文件。
       简单来说,一个Linux系统启动,大致经历如下的步骤:
init --> fork --> exec --> getty --> 用户输入帐号 --> login --> 输入密码 --> exec --> bash(文字终端)
       硬件驱动程序负责读写实际的硬件设备,比如从键盘读入字符和把字符输出到显示器,线路规程像一个过滤器,对于某些特殊字符并不是让它直接通过,而是做特殊处理,比如在键盘上按下Ctrl-z,对应的字符并不会被用户程序的read读到,而是被线路规程截获,解释成SIGTSTP信号发给前台进程,通常会使该进程停止。线路规程应该过滤哪些字符和做哪些特殊处理是可以配置的。
在这里插入图片描述

line disciline: 线路规程,用来过滤键盘输入的内容。

1.2 ttyname函数

由文件描述符查出对应的文件名
char *ttyname(int fd);成功:终端名;失败:NULL,设置errno

借助ttyname函数,通过实验看一下各种不同的终端所对应的设备文件名。

#include <unistd.h>
#include <iostream>
using namespace std;
int main(void)
{
    
    
    cout << "fd 0:" << ttyname(0) << endl;
    cout << "fd 1:" << ttyname(1) << endl;
    cout << "fd 2:" << ttyname(2) << endl;
    return 0;
}	

1.3 网络终端

       虚拟终端或串口终端的数目是有限的,虚拟终端(字符控制终端)一般就是/dev/tty1∼/dev/tty6六个,串口终端的数目也不超过串口的数目。然而网络终端或图形终端窗口的数目却是不受限制的,这是通过伪终端(Pseudo TTY)实现的。

一套伪终端由一个主设备(PTY Master)和一个从设备(PTY Slave)组成。主设备在概念上相当于键盘和显示器,只不过它不是真正的硬件而是一个内核模块,操作它的也不是用户而是另外一个进程。从设备和上面介绍的/dev/tty1这样的终端设备模块类似,只不过它的底层驱动程序不是访问硬件而是访问主设备。网络终端或图形终端窗口的Shell进程以及它启动的其它进程都会认为自己的控制终端是伪终端从设备,例如/dev/pts/0、/dev/pts/1等。下面以telnet为例说明网络登录和使用伪终端的过程。
在这里插入图片描述

网络终端
       TCP/IP协议栈:在数据包上添加报头。
       如果telnet客户端和服务器之间的网络延迟较大,我们会观察到按下一个键之后要过几秒钟才能回显到屏幕上。这说明我们每按一个键telnet客户端都会立刻把该字符发送给服务器,然后这个字符经过伪终端主设备和从设备之后被Shell进程读取,同时回显到伪终端从设备,回显的字符再经过伪终端主设备、telnetd服务器和网络发回给telnet客户端,显示给用户看。也许你会觉得吃惊,但真的是这样:每按一个键都要在网络上走个来回!

2. 进程组

2.1 概念和特性

在这里插入图片描述

2.2 进程组相关函数

函数名 函数原型 说明 返回值
getpgrp函数 pid_t getpgrp(void); 获取当前进程的进程组ID 总是返回调用者的进程组ID
getpgid函数 pid_t getpgid(pid_t pid); 获取指定进程的进程组ID 成功:0;失败:-1,设置errno,getpgid(0) == getpgrp()
setpgid函数 int setpgid(pid_t pid, pid_t pgid); 改变进程默认所属的进程组。通常可用来加入一个现有的进程组或创建一个新进程组。 成功:0;失败:-1,设置errno

int setpgid(pid_t pid, pid_t pgid);

  • 将参1对应的进程,加入参2对应的进程组中。
  • 注意:
    • 如改变子进程为新的组,应fork后,exec前。
    • 权级问题。非root进程只能改变自己创建的子进程,或有权限操作的进程
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
using namespace std;

int main(){
    
    
    
    pid_t pid = fork();
    if(pid == 0){
    
    
        //子进程
        cout << "child PID is : " << getpid() << endl;
        cout << "child group ID is : " << getpgid(0) << endl;;//返回组ID,同下
        //cout << "child group ID is : " << getpgrp() << endl;//返回组ID,
        sleep(6);//休眠一下,方便父进程修改子进程用户组
        cout << "-----group id of child is changeed to : " << getpgrp() << endl;
        exit(0);
    }else if(pid > 0){
    
    //父进程
        sleep(1);//让子进程打印其pid和gpid
        //子进程自立门户,把它自己的pid当做用户组id
        setpgid(pid,pid);
        sleep(15);
        cout << endl;

        cout << "parent PID is : " << getpid() << endl;
        cout << "parent's parent PID is :" << getppid() << endl;
        cout << "parent group PID is : " << getpgid(0) << endl;
        sleep(5);

        setpgid(getpid(),getppid());//改变父进程的组id为父进程的父进程
        cout << "group id of parent is changed to :" << getpgid(0) << endl;
    }else{
    
    
        perror("fork error");
        exit(1);
    }

    return 0;
}

3. 会话

3.1 创建会话

在这里插入图片描述

3.2 getsid函数

获取进程所属的会话ID
pid_t getsid(pid_t pid);

  • 成功:返回调用进程的会话ID;失败:-1,设置errno
  • pid为0表示察看当前进程session ID

ps ajx命令查看系统中的进程。

  • 参数a表示不仅列当前用户的进程,也列出所有其他用户的进程
  • 参数x表示不仅列有控制终端的进程,也列出所有无控制终端的进程
  • 参数j表示列出与作业控制相关的信息。

组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程

3.3 setsid函数

创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID。

pid_t setsid(void);

  • 成功:返回调用进程的会话ID;失败:-1,设置errno
  • 调用了setsid函数的进程,既是新的会长,也是新的组长。

fork一个子进程,并使其创建一个新会话。查看进程组ID、会话ID前后变化

#include <iostream>
#include <unistd.h>
using namespace std;

int main(){
    
    
    pid_t pid = fork();
    if(pid == 0){
    
    
        cout << "child PID is : " << getpid() << endl;
        cout << "group PID of child is : " << getpgid(0) << endl;
        cout << "session PID of child is : " << getsid(0) << endl;
        
        sleep(10);
        setsid();//子进程非组长进程,故可以成为新回话首进程,且成为组长进程,该进程组id为会话进程

        cout << "changeed:.............." << endl;

        cout << "child PID is : " << getpid() << endl;
        cout << "group PID of child is : " << getpgid(0) << endl;
        cout << "session PID of child is : " << getsid(0) << endl;
        sleep(20);
        exit(0);

    }else if (pid < 0){
    
    
        perror("fork error");
        exit(1);
    }
    return 0;
}

4. 守护进程

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

创建守护进程模型

  1. 创建子进程,父进程退出
    所有工作在子进程中进行形式上脱离了控制终端
  2. 在子进程中创建新会话
       setsid()函数
       使子进程完全独立出来,脱离控制
  3. 改变当前目录为根目录
       chdir()函数
       防止占用可卸载的文件系统
       也可以换成其它路径
  4. 重设文件权限掩码
       umask()函数
       防止继承的文件创建屏蔽字拒绝某些权限
       增加守护进程灵活性
  5. 关闭文件描述符
       继承的打开文件不会用到,浪费系统资源,无法卸载
       将0、1、2重定向 /dev/null
       dup2()
  6. 开始执行守护进程核心工作
       周期性地执行某种任务或等待处理某些发生的事件
  7. 守护进程退出处理程序模型
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
using namespace std;

void mydaemond(){
    
    
    pid_t pid,sid;
    //1. fork()一个子进程
    pid = fork();

    //2. 创建会话
    sid = setsid();
    //
    //3. 改变工作目录为根目录
    int ret = chdir("/home/du2020/");
    if(ret == -1){
    
    
        perror("chdir error");
        exit(1);
    }
    //4. 设置掩码
    umask(0002);
    //5. 重定向文件描述符,0,1,2指向dev/null空文件
    close(STDIN_FILENO);
    open("/dev/null",O_RDWR);
    dup2(0,STDOUT_FILENO);
    dup2(0,STDERR_FILENO);

}
int main(){
    
    
    mydaemond();
    //6.守护进程
    while(1){
    
    
    	;   
    }
}

.bashrc 可配置bash

Guess you like

Origin blog.csdn.net/weixin_44515978/article/details/119381856