您的当前位置:首页正文

【Linux】孤儿进程|守护进程|Shell脚本设置守护进程开机自启

2024-11-24 来源:个人技术集锦

Orphan孤儿进程

父进程先于子进程异常退出,子进程被托管给托管进程,子进程成为活态进程,失去管理,这种进程称为孤儿进程(Orphan Process)。

Ubuntu16.04托管进程为upstart进程,14.04版本托管进程为init进程

孤儿进程是异常进程模型的残留,会影响新进程的创建与使用。这种活态进程的危害是有弹性的,取决于孤儿进程的作业,如果孤儿进程被设置大量频繁的申请占用系统资源,那么这种孤儿进程危害极大。

下面是一个产生孤儿进程的demo程序:

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

int main()
{
    pid_t pid;
    pid=fork();
    if(pid>0){
        printf("Parent PID:%d\n",getpid());
        sleep(10);
        exit(0);
    }else if(pid==0){
        printf("Child PID:%d PPID:%d\n",getpid(),getppid());
        sleep(11);
        printf("Child PID:%d PPID:%d\n",getpid(),getppid());
    }else{
        perror("fork call failed");
        exit(0);
    }
	return 0;
}

1543号进程为upstart托管进程

这种情况下,子进程一直打印未退出,是否影响终端的命令输入?

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

int main()
{
    pid_t pid;
    pid=fork();
    if(pid>0){
        printf("Parent PID:%d\n",getpid());
        sleep(10);
        exit(0);
    }else if(pid==0){
        printf("Child PID:%d PPID:%d\n",getpid(),getppid());
        while(1){
        printf("Child PID:%d PPID:%d\n",getpid(),getppid());
	sleep(2);
	}
    }else{
        perror("fork call failed");
        exit(0);
    }
	return 0;
}

不影响,因为在终端打印是标注输出文件描述符,输入命令执行是标准输入文件描述符

如何解决孤儿进程带来的问题

守护进程Daemon Process

什么是守护进程?

守护进程是孤儿进程的一种特殊情况,开发者创建父子进程模型,让父进程退出,子进程变为守护进程。是一种人为的孤儿进程。

守护进程(Daemon Process)是在计算机系统中以后台方式运行的特殊进程,经典的后台服务进程。持续执行在后台完成特定服务,不干预前台任务。不依赖于终端或会话,它独立于终端控制并在系统级别执行特定的任务。

普通的软件进程随用户持续,生命周期较短。守护进程的生命周期较长,开机启动关机结束,持续服务于平台。

守护进程不能持续占用系统资源(CPU、内存等),长时间处于低开销模式。

守护进程的工作模式

间隔执行(sleep)、定时启动、条件触发、低销模式(大多数时间进程处于睡眠态)

后台服务进程不允许访问前台,不能使用标准输入标准输出,而标准输出需要使用,但是不能让其打印在终端,所以要使用(dup2实现重定向)。

如何查看守护进程

通过命令ps axj,可以查看系统中的所有进程。其中,参数a指定要列出所有用户的进程,而不仅仅是当前用户的进程;参数x包括列出所有无控制终端的进程,而不仅仅是有控制终端的进程;参数j则能够提供与作业控制相关的信息。

这个命令的输出包含了许多字段,包括PID、PPID、PGID、SID、TTY、STAT、TIME、COMMAND等。其中,PID是进程的唯一标识符;PPID是父进程的PID;PGID是进程所属的进程组ID;SID是进程所属的会话ID;TTY是进程所使用的终端设备;STAT是进程状态;TIME是进程运行的时间;COMMAND是进程正在执行的命令。

由于参数j还提供了作业控制相关的信息,因此此命令的输出还包括作业ID(JID)、进程对应的作业名(JOBNAME)以及作业的状态(STATE)等信息。这些信息对于理解进程和作业之间的关系以及系统运行情况非常有用。

凡是TPGID一栏写着 -1 的都是没有控制终端的进程,也就是守护进程;

守护进程的实现流程

关于什么是会话,子进程如何话脱离终端创建会话,可以看我这一期博客: 

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

void Daemon_Bussiness(){
    int fd=open("time.logg",O_RDWR,0664);
    if(fd==-1){
        perror("open failed");
    }
    char buffer[80];
    struct tm*local;
    int nLength;
    while(1){
        time_t now=time(NULL);//生成时间种子
        local=localtime(&now);
        nLength=strftime(buffer,sizeof(buffer),"%Y-%m-%d %H-%M-%S\n",local);
        write(fd,buffer,nLength);
        sleep(3);
    }
}
void Daemon_Create(){
    //1、创建子进程,父进程退出
    pid_t pid;
    pid=fork();
    if(pid>0){
        printf("Parent PID:%d is Running\n",getpid());
        sleep(2);
        printf("Parent is Exiting\n");
        exit(0);
    }
    else if(pid==0){
    //2、创建新会话,脱离终端
    printf("Child PID:%d is Running,PPID:%d,SID:%d\n",getpid(),getppid(),getsid(getpid()));
    sleep(2);
    printf("Child PID:%d,SID:%d,变为了守护进程\n",getpid(),setsid());
    //3、改变工作路径
    chdir("./");
    //4、修改进程umask
    umask(0002);
    //5、关闭无用描述符
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    //重定向标准出错符
    int fd=open("ERROR.log",O_RDWR|O_CREAT,0664);
    dup2(fd,STDERR_FILENO);
    //6、执行守护进程业务
    Daemon_Bussiness();
    //7、守护进程退出处理(由于本demo程序中没有申请内存,所以不涉及到这里)
    exit(0);
    }else{
        perror("fork call failed");
    }

}
int main()
{
    Daemon_Create();
        return 0;
}

运行结果:

运行可执行文件后,生成了守护进程。每隔3秒增加一行日志

杀死守护进程后,日志不再增加。

关闭终端后,守护进程仍在后台不停运行。

关于重定向标准出错流:

当我们尝试只读方式打开一个不存在的文件,会通过标准出错向我们报错,正常标准出错是打印到终端上的,在我们重定向之后,将错误打印到了错误日志里。

shell脚本

Shell脚本是用于自动化任务的一种编程脚本,通常运行在UNIX和Linux操作系统的Shell终端中。Shell脚本可以执行命令行中的一系列命令,以便自动化系统管理任务、批处理作业和其他重复性工作。

Shell 脚本的特点

创建和运行Shell脚本

#!/bin/bash

ls -l
date
赋予执行权限并运行脚本
sudo chmod 0775 shell_start.sh

运行脚本

./shell_start.sh

运行结果:

成功执行了ls -ldate两个命令

通过shell脚本设置守护进程开机自启动

开机自启动的实现原理

系统初始化脚本(SysV init)

sudo ln -s /etc/init.d/myscript /etc/rc.d/rc3.d/S99myscript

设置守护进程开机自启

1、将其他脚本的启动块信息复制到自定义脚本中

3、通过命令将脚本加入开机启动序列sudo update-rc.d shell start 99 2.

通过命令删除开机启动序列中的脚本sudo update-rc.d shell remove

下面将写一个脚本设置我们创建的守护进程开机自启动

将启动块信息添加到我们脚本中

通过命令将脚本加入开机启动序列sudo update-rc.d shell_start start 99 2.

重启一下查看是否设置成功。

设置成功!

 

显示全文