【C语言】守护进程(daemon)的输出到一个文本文件

一、常用的守护进程函数

void daemonize ()
{
    //deamonize
    pid_t pid = fork();
    if( pid > 0 )
    {       
        //parent exit
        exit(0);
    }

    //child continue
    setsid();
    chdir("/");
    close(0);
    open("/dev/null", O_RDWR);
    //no env debug
    if(!getenv("debug"))
    {
        close(1);
        close(2);
        dup(0);
        dup(0);
    }
}

这段代码的目的是让一个程序在后台以守护进程(daemon)的形式运行。让我们逐步了解每一行代码的作用:

pid_t pid = fork();

这行代码创建了一个新的进程,这是通过`fork()`系统调用实现的。`fork()`会创建一个和当前进程几乎完全相同的子进程。`fork()`调用会在父进程中返回新创建的子进程的进程ID,在子进程中则返回0。如果返回值大于0,那么代码运行于父进程;如果是0,则表示在子进程中。

if( pid > 0 )
{       
    //parent exit
    exit(0);
}

如果`fork()`的返回值大于0,表示当前代码段在父进程中运行。因为我们的目的是让程序在后台运行,我们不希望保留父进程。所以父进程会调用`exit(0)`正常退出。

//child continue
setsid();

接下来的部分是在子进程中继续执行的。`setsid()`会创建一个新的会话,并设置子进程为这个新会话的领头进程。一个会话可以包含一个或多个进程组;由`setsid`创建的新会话有一个新的进程组,且子进程是这个进程组的领头进程,并且没有控制终端。

chdir("/");

通过`chdir("/")`将当前工作目录更改为根目录("/")。这是因为守护进程通常应该不与任何特定目录关联,尤其是不应该继续驻留在启动它们的目录中,可能会妨碍卸载文件系统等操作。

close(0);
open("/dev/null", O_RDWR);

这两行代码关闭了标准输入(文件描述符0),然后打开`/dev/null`设备用于读写。所有写入到`/dev/null`的数据都会被丢弃,读取`/dev/null`会立即返回文件结束。

if(!getenv("debug"))
{
    close(1);
    close(2);
    dup(0);
    dup(0);
}

最后这部分首先检查是否存在名为"debug"的环境变量。如果没有(`getenv("debug")`返回NULL),则进行以下操作:
- 关闭标准输出(文件描述符1)。
- 关闭标准错误(文件描述符2)。
接下来,调用`dup(0)`复制文件描述符0(也就是之前打开的`/dev/null`),因为在文件描述符1和2被关闭之后,`dup`调用会使用最低的、未被使用的文件描述符号,也就是先是1然后是2,因此这一步相当于重新定向了进程的标准输出到`/dev/null`,然后又将标准错误也重定向到了`/dev/null`。
所以,这段代码的整体作用是生成一个子进程,让它脱离终端和工作目录,且默认情况下把所有的输入、输出重定向到`/dev/null`,使之成为一个后台运行的守护进程。如果设置了“debug”环境变量,则标准输出和错误不会被重定向。

二、守护进程(daemon)的输出到一个文本文件

将守护进程的输出重定向到一个文本文件,需要在代码中用`open`系统调用打开希望输出到的文件,并且用`dup2`或`dup`系统调用将标准输出(文件描述符1)和/或标准错误(文件描述符2)重定向到这个文件描述符上。
以下是将输出重定向到指定的日志文件函数:

#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

void daemonize ()
{
    // Daemonize
    pid_t pid = fork();
    if (pid > 0) 
    {       
        // Parent exits
        exit(0);
    }
    
    // Child (daemon) continues
    setsid();
    chdir("/");
    
    // Redirect standard file descriptors to /dev/null or a log file
    close(STDIN_FILENO);
    open("/dev/null", O_RDWR); // STDIN

    // Redirect STDOUT and STDERR to a log file
    const char* logFilePath = "/var/log/daemon.log";
    int logFile = open(logFilePath, O_RDWR | O_CREAT | O_APPEND, 0600);

    if (logFile == -1) {
        // Handle error, e.g., exit or print an error message
    } else {
        if (!getenv("debug")) {
            close(STDOUT_FILENO); // Close the standard output
            close(STDERR_FILENO); // Close the standard error
            dup2(logFile, STDOUT_FILENO); // Redirect standard output to the log file
            dup2(logFile, STDERR_FILENO); // Redirect standard error to the log file
        }
        // At this point, whether debugging or not, STDOUT and STDERR go to the log file
    }
    
    if (logFile != STDOUT_FILENO && logFile != STDERR_FILENO) {
        close(logFile); // We don't need this anymore
    }
}

这里我们使用函数`open`创建或打开日志文件。文件被设置为可读写(`O_RDWR`),如果未存在则创建它(`O_CREAT`),并以追加模式打开(`O_APPEND`)。文件权限被设置为0600,即只有拥有者可以读写。
此外,我们会先关闭已打开的标准输出和错误文件描述符,然后使用`dup2`,把 logFile 的文件描述符复制到标准输出和错误的文件描述符上。如果文件的打开操作失败,可能需要适当地处理这个错误,比如打印一个错误信息或者退出程序。
现在,守护进程的标准输出和错误都会被写入到`/var/log/daemon.log`日志文件中。确保程序具有创建和写入该日志文件的权限,尤其是当程序以特权用户(如`root`)运行时。

使用`dup`或`dup2`系统调用只能复制一个现有的文件描述符到另一个文件描述符,不能直接打开新的文件。 如果想重定向守护进程的输出到 /var/log/daemon.log ,需要使用`open`系统调用首先打开这个文件,然后才能用`dup`或`dup2`复制相应的文件描述符。
以下是如何修改守护进程的代码以重定向输出到 /var/log/daemon.log 的示例:

if(!getenv("debug"))
{
    close(1);
    close(2);

    // 打开或创建一个文件用来写入日志
    int log_file = open("/var/log/daemon.log", O_RDWR | O_CREAT | O_APPEND, 0600);

    if (log_file < 0) {
        // 无法打开日志文件,可能是因为权限问题或其他问题
        exit(1);
    }

    // 重定向标准输出到日志文件
    if (dup2(log_file, 1) < 0) {
        // 无法重定向标准输出
        exit(1);
    }

    // 重定向标准错误到日志文件
    if (dup2(log_file, 2) < 0) {
        // 无法重定向标准错误
        exit(1);
    }

    // 现在我们已经复制了日志文件到标准输出和标准错误,关闭原始的日志文件描述符
    if (log_file > 2) {
        close(log_file);
    }
}

在上面的代码中,`open`系统调用用于打开(或创建,如果还不存在的话) /var/log/daemon.log 文件。`O_RDWR` 表示文件是以读写模式打开的,`O_CREAT` 表示如果文件不存在,就创建它。`O_APPEND` 保证所有写操作都是追加到文件末尾,而`0600`表示创建的文件权限(只有拥有者有读写权限)。
dup2系统调用用于把日志文件的文件描述符复制到标准输出(1)和标准错误(2)。如果`dup2`调用成功,它会关闭旧的文件描述符,并将它替换为新的文件描述符。注意,如果发生错误(例如,文件打开失败或`dup2`调用失败),则程序会退出。
最后,检查一下原始的日志文件描述符是否大于2,因为0、1和2是标凑输入、输出和错误。如果原始的描述符大于这些,我们就关闭它,因为标准输出和错误已经被重定向到我们的日志文件。
使用这种方式,守护进程的标准输出和错误都会被记录到 /var/log/daemon.log 文件中。 注意,在生产环境中,直接把日志输出到文件可能是不够的,可能还需使用日志旋转等措施管理日志文件的增长。 

三、dup和dup2

dup 和 dup2 是 Unix 和类 Unix 系统 (如 Linux) 的系统调用,用于复制(duplicate)文件描述符。

dup 系统调用会复制一个旧的文件描述符,返回一个新的文件描述符。新文件描述符指向旧文件描述符所指向的文件。新描述符会是当前未使用的最小值的文件描述符。比如:

int new_fd = dup(old_fd);

在这段代码中,`old_fd` 是旧的文件描述符,`new_fd` 是新创建的文件描述符。新的 new_fd 指向和 old_fd 相同的文件,拥有相同的文件偏移量和访问模式(比如读、写)。

dup2 函数和 dup 类似,也是用来复制文件描述符。不过,`dup2` 允许指定新文件描述符的值。如果指定的值已经是一个打开的文件描述符,`dup2` 会先关闭它,然后再复制。这个操作是原子性的,即系统保证这两步(关闭和复制)是连续执行的,没有其他的调用会插入其中。例如:

dup2(old_fd, TARGET_FD);

这将复制 old_fd,并确保新的文件描述符的值是 TARGET_FD。

在一个守护进程中重定向输出到 /dev/null 使用 dup 是因为它会自动复制到当前未使用的最低的文件描述符上。因为在之前的代码中,标准输入、输出和错误(文件描述符0、1、2)已经关闭,所以调用 dup(0) 将会分别把 /dev/null 分配给标准输出和标准错误 (文件描述符1和2)。
重定向输出到一个具体的文件,如 /var/log/daemon.log,使用 dup2 是因为需要更精确地控制使用哪个文件描述符。比如,想确保标准输出和错误分别被送到文件描述符1和2:

int fd = open("/var/log/daemon.log", O_RDWR | O_CREAT | O_APPEND, 0600);
if (fd < 0) {
    // Error handling
}
dup2(fd, STDOUT_FILENO); // STDOUT_FILENO typically is 1
dup2(fd, STDERR_FILENO); // STDERR_FILENO typically is 2

在这段代码中,`open` 调用创建或打开日志文件,然后 dup2 函数重定向标准输出和错误到这个文件(文件描述符1和2),保证所有守护进程的输出都被写入到指定日志文件中。使用 dup2 而不是 dup 使得能精确指定要被复制的新文件描述符的数值。

对于将守护进程的输出重定向到`/var/log/daemon.log`而言,简单地使用`dup`可能是不够的。
想要将守护进程的标准输出和标准错误重定向到`/var/log/daemon.log`,可以采用如下方法:
首先打开日志文件:

int log_fd = open("/var/log/daemon.log", O_WRONLY|O_CREAT|O_APPEND, 0600);
if (log_fd < 0) {
    // Handle error if file opening fails
}

在这里,`O_WRONLY`表示以只写方式打开文件,`O_CREAT`表示如果文件不存在就创建它,`O_APPEND`表示写入时总是追加到文件的末尾。`0600`是文件的权限位,表示文件所有者可以读写,而其他用户没有任何权限。
之后将标准输出和标准错误重定向到这个文件:

dup2(log_fd, STDOUT_FILENO); // STDOUT_FILENO is 1
dup2(log_fd, STDERR_FILENO); // STDERR_FILENO is 2

在这里,`dup2`首先会关闭第二个参数指定的文件描述符(如果它已经打开),然后将第二个参数的文件描述符复制为第一个参数的文件描述符。在上述代码中,我们先将`log_fd`复制到标准输出(`STDOUT_FILENO`),然后再复制到标准错误(`STDERR_FILENO`)。
最后,既然标准输出和标准错误已被重定向,`log_fd`就不再需要了,因此应该关闭它:

if (log_fd > STDERR_FILENO) { // Check if it's not one of the standard descriptors
    close(log_fd);
}

对于标准输入,通常会继续将其重定向到`/dev/null`。
通过上述步骤,守护进程的标准输出和错误就会被写入到`/var/log/daemon.log`文件中去。记得要考虑`open`和`dup2`可能失败的情况,并进行适当的错误处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/467759.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

3.18数据结构

一、数据结构----->用来组织存储数据 一组用来保存一种或多种特定关系的数据的集合&#xff08;组织和存储数据&#xff09; 程序 数据结构 算法 MVC&#xff1a;软件设计架构 M&#xff1a;数据的管理&#xff08;数据结构&#xff09; V&#xff1a;视图&#xff0c…

​C语言-memcmp(内存块的比较)

memcmp&#xff08;内存块的比较&#xff09; 语法 memcmp函数是C语言标准库中的一个函数&#xff0c;用于比较两个内存块的内容是否相等。它定义在<string.h>头文件中。memcmp函数在比较两个字符串或者任何内存数据时非常有用&#xff0c;它不会检查字符串的长度&…

Godot 学习笔记(3):IOC容器注入,以NlogServices为例

文章目录 前言环境注意事项Ioc注入文件夹设置Service服务搭建Nlog.configNlogService配置ButtonTest1Service控制反转Program主入口ButtonTest1从Ioc中获取服务 输出生命周期问题 总结 前言 Godot.Net中使用IOC之后&#xff0c;Godot的代码将会被极大的解耦。这里不不展开说明…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单实战案例 之二 素描画风格效果

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单实战案例 之二 素描画风格效果 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单实战案例 之二 素描画风格效果 一、简单介绍 二、素描画风格效果实现原理 三、案例简单实现步骤 一、简单介绍 Python是一种跨…

MYSQL日志 redo_log更新流程 bin_log以及bin_log数据恢复

Redo_log写入策略 Redo log的Innodb_flush_log_at_trx_commit:: 这个参数有三个取值 取值为0&#xff1a;每次事务提交时&#xff0c;只是把redo_log留在 redo log buffer中&#xff0c;宕机会丢失数据&#xff1b; 取值为1&#xff08;默认值&#xff09;&#xff1a;每次事…

Zookeeper(2)常用命令,ACL权限

文章目录 前言一、zk节点和节点类型节点类型 二、常用命令1.客户端连接2.常用命令help 帮助命令创建节点getsetdeletedeleteall pathstat查看节点的状态setquota增加配额listquota /frame 查看配额delquota删除配额 三、ACL权限控制&#xff1a;1、ZooKeeper权限特性&#xff1…

从单机到分布式微服务,大文件校验上传的通用解决方案

一、先说结论 本文将结合我的工作实战经历&#xff0c;总结和提炼一种从单体架构到分布式微服务都适用的一种文件上传和校验的通用解决方案&#xff0c;形成一个完整的方法论。本文主要解决手段包括多线程、设计模式、分而治之、MapReduce等&#xff0c;虽然文中使用的编程语言…

【开发】SpringBoot 整合 Redis

目录 前言 1. Redis 的下载及安装 1.1 Redis 的下载 1.2 安装 Redis 1.3 启动 Redis 2. 创建 SpringBoot 项目整合 Redis 2.1 环境要求 2.2 SpringBoot项目构建 2.2.1 方式一 2.2.2 方式二 2.3 在 pom.xml 文件中导入依赖坐标 2.4 在 application.properties 中加…

通过docker容器安装zabbix6.4.12图文详解(监控服务器docker容器)

一、相关环境及镜像 环境&#xff1a;ubuntu 22.04&#xff0c;zabbix-server6.4&#xff0c;mysql8.0 前提&#xff1a; 1&#xff09;先安装docker环境 2&#xff09;下载相关镜像 docker pull mysql:8.0 docker pull zabbix/zabbix-java-gateway:alpine-6.4-latest docker …

25考研|北大软微会「爆炸」吗?

软微不是已经爆炸了吗&#xff1f; 大家去看看他的录取平均分就知道了&#xff0c;没有实力千万别碰&#xff0c;现在考软微已经不存在捡漏之说。 110408的复试线已经划到了465分&#xff0c;这个人真的不低了&#xff0c;因为有数学一和408两个比较难的专业课&#xff0c;复…

从零开始学习在VUE3中使用canvas(三):font(字体)

一、简介 我们可以使用font在canvas中绘制文字&#xff0c;方式如下: const ctx canvas.getContext("2d"); // 绘制文字 ctx.font "24px 黑体, 宋体"; //字体大小 首选字体 备选字体 ctx.fillText("这里是显示的字的内容", 100, 50); //文字…

力扣106---从中序和后序序列构造二叉树

题目描述&#xff1a; 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7], postorder [9,15,7,20…

Django HTML模版

一个网站的基本框架&#xff08;如页面布局、导航栏、页脚栏等&#xff09;往往是相同的。可以把这个基本框架做成一个模版&#xff0c;其它正式的HTML页面可以直接套用这个模版&#xff0c;可以大减少各HTML文件的代码量。 语法&#xff08;模版文件中&#xff09;&#xff1…

蓝桥杯练习题——健身大调查

在浏览器中预览 index.html 页面效果如下&#xff1a; 目标 完成 js/index.js 中的 formSubmit 函数&#xff0c;用户填写表单信息后&#xff0c;点击蓝色提交按钮&#xff0c;表单项隐藏&#xff0c;页面显示用户提交的表单信息&#xff08;在 id 为 result 的元素显示&#…

python统计分析——单样本分布形状和概率密度

参考资料&#xff1a;python统计分析【托马斯】 一、单样本分布的形状参数 在scipy.stats中&#xff0c;连续分布函数的特征是他们的位置和尺度。举两个例子&#xff1a;对于正态分布&#xff0c;&#xff08;位置/形状&#xff09;是由分布的&#xff08;均值/标准差&#xf…

计算地球圆盘负荷产生的位移

1.研究背景 计算受表面载荷影响的弹性体变形问题有着悠久的历史&#xff0c;涉及到许多著名的数学家和物理学家&#xff08;Boussinesq 1885&#xff1b;Lamb 1901&#xff1b;Love 1911&#xff0c;1929&#xff1b;Shida 1912&#xff1b;Terazawa 1916&#xff1b;Munk &…

B003-springcloud alibaba 服务治理 nacos discovery ribbon feign

目录 服务治理服务治理介绍什么是服务治理相关方案 nacos实战入门搭建nacos环境安装nacos启动nacos访问nacos 将商品微服务注册进nacos将订单微服务注册进nacos订单服务通过nacos调用商品服务 实现服务调用的负载均衡什么是负载均衡代码实现负载均衡增加一个服务提供者自定义实…

HTML5语义化元素

在HTML5之前&#xff0c;网站的分布层级有哪些呢&#xff1f; nav&#xff0c;header&#xff0c;main&#xff0c;footer 这样做有一个弊端 我们往往过多的使用div&#xff0c;通过ID或class来区分元素 对于浏览器来说这些元素不够语义化 对于我来说搜索引擎来说&#xff0c;不…

鸿蒙Harmony应用开发—ArkTS声明式开发(绘制组件:Line)

直线绘制组件。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 Line(value?: {width?: string | number, height?: string | number}) 从API version 9开始&#xff0c;该接…

深入理解mysql 从入门到精通

1. MySQL结构 由下图可得MySQL的体系构架划分为&#xff1a;1.网络接入层 2.服务层 3.存储引擎层 4.文件系统层 1.网络接入层 提供了应用程序接入MySQL服务的接口。客户端与服务端建立连接&#xff0c;客户端发送SQL到服务端&#xff0c;Java中通过JDBC来实现连接数据库。 …