【Linux】日志与守护进程

目录

一、预备知识

二、打印日志

三、守护进程

1、前置知识

2、守护进程


一、预备知识

日志是有等级的,表明该条日志的重要程度,一般分为以下几个级别:

#define DEBUG 0 //调试信息
#define INFO  1 //正常运行
#define WARNING 2 //报警
#define ERROR 3 //正常错误
#define FATAL 4 //严重错误

在打印日志时,通常需要用到可变参数列表。对于可变参数列表的读取,可以使用以下几个宏:

  • va_list

  • va_arg

  • va_start

  • va_end;

    void logMessage(int level, char* format, ...)
    {
    va_list p; //char* 类型指针
    va_start(p, format); //把指针p指向可变参数部分的起始地址
    int a = va_arg(p. int); //根据指定的类型提取参数
    va_end(p); //p = NULL
    }

二、打印日志

在打印日志时,一般都会有固定的格式,把日志格式放到一个缓冲区里,日志内容放到另一个缓冲区里。

例如:现在我们想打印日志的格式是 日志等级 时间 进程pid 消息体。那么就可以把 日志等级 时间 进程pid 放到 logleft 缓冲区中,消息体放到 logright 缓冲区中。

把可变参数列表元素打印到文件的函数:

int vsnprintf(char *str, size_t size, const char *format, va_list ap);

获取当前时间函数:

time_t time(time_t *tloc);

将时间转换成对应结构体的函数:

struct tm *localtime(const time_t *timep);

完整代码:

#pragma once

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdarg>
#include <sys/types.h>
#include <unistd.h>
#include <ctime>
using namespace std;

// 日志是有日志等级的
enum
{
    DEBUG = 0,
    INFO,
    WARNING,
    ERROR,
    FATAL,
    UKNOWN
};

string filename = "logfile";

static string toLevelString(int level)
{
    switch (level)
    {
        case DEBUG: return "DEBUG";
        case INFO: return "INFO";
        case WARNING: return "WARNING";
        case ERROR: return "ERROR";
        case FATAL: return "FATAL";
        default: return "UKNOWN";
    }
}

string getTime()
{
    time_t curr = time(nullptr);
    struct tm* tmp = localtime(&curr);

    char buffer[128];
    snprintf(buffer, sizeof(buffer), "[%d-%d-%d %d:%d:%d]", tmp->tm_year + 1900, tmp->tm_mon, tmp->tm_mday,
                                                          tmp->tm_hour, tmp->tm_min, tmp->tm_sec);

    return buffer;
}

//日志格式:左半部分:日志等级 时间 pid 
//         右半部分:消息体
void logMessage(int level, const char* format, ...)
{
    char logLeft[1024];
    string level_string = toLevelString(level);
    string curr_time = getTime();
    snprintf(logLeft, sizeof(logLeft), "[%s %s %d]", level_string.c_str(), curr_time.c_str(), getpid());

    char logRight[1024];
    va_list p;
    va_start(p, format);
    vsnprintf(logRight, sizeof(logRight), format, p);
    va_end(p);

    //打印日志
    //printf("%s %s\n", logLeft, logRight);

    //保存到文件中
    FILE* fp = fopen(filename.c_str(), "a");
    if(fp == nullptr) return;

    fprintf(fp, "%s%s\n", logLeft, logRight);
    fflush(fp);

    fclose(fp);
}

三、守护进程

1、前置知识

首先后台创建几个进程:

查看刚刚创建的进程:

进程的属性中除了我们熟知的PPID与PID外, PGID 是进程组。 SID 是会话编号。 TTY 是终端文件。

进程组的组长,都是多个进程中的第一个,进程组的编号就是第一个进程的PID。

当我们从本地或远端登录Linux时,Linux会给用户分配一个命令行解释器,即bash进程。basn进程自己成立进程组,自己成立一个会话,会话编号就是bash进程的PID。未来所起的所有进程与进程组都属于这个会话。

一个进程组被创建出来,是为了完成一个任务,查看后台任务的指令:

jobs

每一组进程都有一个编号,称为任务编号。

把后台任务提到前台的指令:

fg [任务编号]

把前台任务放到后台的指令:

ctrl + z
bg [任务编号]

创建进程组是为了完成任务的。在用户的视角,可以把一个进程组叫做一个任务。进程组可能包含一个或多个进程。

任务分为前台任务与后台任务。如果把后台任务提到前台,老的前台任务就无法运行了。在一个会话中,任何时刻,都只能有一个前台任务在运行。这就是为什么我们在命令行启动一个进程时,bash就无法运行了的原因。

用户登录就是创建一个会话并启动bash任务,在命令行中启动进程,就是创建新的前后台任务。用户退出就是销毁会话,可能会影响会话内部的所有任务。

网络服务器为了不受到用户的登录与注销的影响,一般就会通过守护进程的方式运行。

2、守护进程

守护进程是把一个任务独立出来,自己成为一个会话,以免受到其他会话的影响。

创建守护进程的函数:

pid_t setsid(void);

谁调用这个函数,谁就把自己设置为守护进程。函数调用成功,返回调用这个函数的进程的pid。失败则返回-1,错误码被设置。

需要注意的是,一个进程组的组长,不能调用 setsid 函数

再创建守护进程时,有时会需要更改守护进程的工作路径,更改函数:

int chdir(const char *path);

当一个进程编程守护进程时,他就不应该与标准输出、标准输入、标准错误文件有交互了。我们可以接用文件黑洞 /dev/null 来处理。 /dev/null 在任何一个Linux系统里都一定存在,向这个文件中写入的所有数据都会消失,读取这个文件直接返回。

守护进程完整代码:

#pragma once

#include <unistd.h>
#include <cstdlib>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "log.hpp"
#include "error.hpp"

//守护进程是孤儿进程的一种
void Daemon()
{
    //1.忽略一些异常信号
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);

    //2.让自己不要成为组长
    if(fork() > 0) exit(0); //让父进程直接退出,这样保证子进程一定不是组长,一定可以调用setsid
                            //因为进程组的编号是父进程的pid

    //3.新建会话,自己成为会话的话首进程
    pid_t ret = setsid();
    if((int)ret == -1)
    {
        logMessage(FATAL, "deamon error, code: %d, string: %s", errno, strerror(errno));
        exit(SETSID_ERR);
    }

    //4.可以更换守护进程的工作目录
    //chdir

    //5.处理后续的对于文件描述符0, 1, 2的问题
    int fd = open("/dev/null", O_RDWR);
    if(fd < 0)
    {
        logMessage(FATAL, "open error, code: %d, string: "%s", error, strerror(errno));
        exit(OPEN_ERR);
    }

    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    close(fd);
}
相关推荐
小O_好好学19 分钟前
CentOS 7文件系统
linux·运维·centos
哲伦贼稳妥43 分钟前
一天认识一个硬件之机房地板
运维·网络·经验分享·其他
x晕x1 小时前
Linux dlsym符号查找疑惑分析
linux·运维·服务器
活跃的煤矿打工人2 小时前
【星海saul随笔】Ubuntu基础知识
linux·运维·ubuntu
hgdlip2 小时前
如何快速切换电脑的ip地址
网络·tcp/ip·电脑
fasewer2 小时前
第五章 linux实战-挖矿 二
linux·运维·服务器
楚灵魈3 小时前
[Linux]从零开始的网站搭建教程
linux·运维·服务器
小小不董3 小时前
《Linux从小白到高手》理论篇:深入理解Linux的网络管理
linux·运维·服务器·数据库·php·dba
这可就有点麻烦了3 小时前
强化学习笔记之【TD3算法】
linux·笔记·算法·机器学习
DY009J3 小时前
深度探索Kali Linux的精髓与实践应用
linux·运维·服务器