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

一、常用的守护进程函数

void daemonize ()
{//deamonizepid_t pid = fork();if( pid > 0 ){       //parent exitexit(0);}//child continuesetsid();chdir("/");close(0);open("/dev/null", O_RDWR);//no env debugif(!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 exitexit(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 ()
{// Daemonizepid_t pid = fork();if (pid > 0) {       // Parent exitsexit(0);}// Child (daemon) continuessetsid();chdir("/");// Redirect standard file descriptors to /dev/null or a log fileclose(STDIN_FILENO);open("/dev/null", O_RDWR); // STDIN// Redirect STDOUT and STDERR to a log fileconst 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 outputclose(STDERR_FILENO); // Close the standard errordup2(logFile, STDOUT_FILENO); // Redirect standard output to the log filedup2(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 descriptorsclose(log_fd);
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring 拦截器实现请求拦截与参数处理【拦截器(Interceptor)和过滤器(Filter)的区别】

业务场景&#xff1a; 项目中需要使用请求头传输一个密文字符串&#xff0c;后端服务获取密文字符串后&#xff0c;进行解密验证&#xff0c;然后执行响应的业务&#xff0c;这里有好几个接口都用需要使用这个密文字符串&#xff0c;如果我们在每个接口中进行校验处理&#xff…

AI预测福彩3D第13弹【2024年3月20日预测--第3套算法重新开始计算第3次测试】

今天咱们继续对第3套算法进行第3次测试&#xff0c;第3套算法加入了012路的权重。废话不多说了&#xff0c;直接上结果吧~ 最终&#xff0c;经过研判分析&#xff0c;2024年3月20日福彩3D的七码预测结果如下&#xff1a; 百位&#xff1a;7 4 8 3 6 9 0&#xff08;5换0&#x…

锂电池寿命预测 | Matlab基于ALO-SVR蚁狮优化支持向量回归的锂离子电池剩余寿命预测

目录 预测效果基本介绍程序设计参考资料 预测效果 基本介绍 锂电池寿命预测 | Matlab基于ALO-SVR蚁狮优化支持向量回归的锂离子电池剩余寿命预测 基于蚁狮优化和支持向量回归的锂离子电池剩余寿命预测: 1、提取NASA数据集的电池容量&#xff0c;以历史容量作为输入&#xff0c;…

Java项目:66 ssm实验室耗材管理系统设计与实现+jsp

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 管理员管理实验材料&#xff0c;审核教师与学生对实验材料的申请信息&#xff0c;统计每学年实验材料的使用总数信息。 教师申请使用实验材料…

【探索Linux】—— 强大的命令行工具 P.28(网络编程套接字 —— 简单的UDP网络程序模拟实现)

阅读导航 引言一、UDP协议二、UDP网络程序模拟实现1. 预备代码⭕makefile文件⭕打印日志文件⭕打开指定的终端设备文件&#xff0c;并将其作为标准错误输出的目标文件描述符 2. UDP 服务器端实现&#xff08;UdpServer.hpp&#xff09;3. UDP 客户端实现&#xff08;main函数&a…

day04vue学习

day04 一、学习目标 1.组件的三大组成部分&#xff08;结构/样式/逻辑&#xff09; ​ scoped解决样式冲突/data是一个函数 2.组件通信 组件通信语法父传子子传父非父子通信&#xff08;扩展&#xff09; 3.综合案例&#xff1a;小黑记事本&#xff08;组件版&#xff09…

全量知识系统的核心-全量知识的一个“恰当组织”的构想及百度AI答问

全量知识系统的核心-全量知识的一个恰当组织 Q1. 以下是对 我刚刚完成的文档“全量知识系统的核心&#xff1a;全量知识的一个恰当组织构想”的百度AI答复。由于字数400的限制&#xff0c;内容被分成四段. 第一次回答&#xff1a;学科和科学的框架 关于技术学科、一般学科和…

DeprecationWarning: isDaemon() is deprecated, get the daemon attribute instead

报错处理 # t.setDaemon(True) # 阙辉注释 t.daemonTrue # 阙辉新增

已有TensorFlow安装包新建相应python版本的虚拟环境

已有TensorFlow安装包新建虚拟环境 新建conda虚拟环境 新建的虚拟环境默认在Anaconda安装目录D:\Anaconda3\envs&#xff08;根据自己的安装目录看&#xff09; 切换到新建的虚拟环境 在这里可以直接安装下载好的TensorFlow安装包 检查是否安装好 输入python进入python环境…

YOLOv7 | 添加GSConv,VoVGSCSP等多种卷积,有效提升目标检测效果,代码改进(超详细)

⭐欢迎大家订阅我的专栏一起学习⭐ &#x1f680;&#x1f680;&#x1f680;订阅专栏&#xff0c;更新及时查看不迷路&#x1f680;&#x1f680;&#x1f680; YOLOv5涨点专栏&#xff1a;http://t.csdnimg.cn/QdCj6 YOLOv7专栏&#xff1a; http://t.csdnimg.cn/dy…

《论文阅读》端到端情感原因对提取的有效子句间建模

《论文阅读》端到端情感原因对提取的有效子句间建模 前言简介模型架构Document EncodingInter-Clause Relationship ModelingClause Pair Representation Learning and Ranking损失函数问题前言 亲身阅读感受分享,细节画图解释,再也不用担心看不懂论文啦~ 无抄袭,无复制,纯…

浙政钉埋点:深入解析与应用实践

一、引言 在数字化时代&#xff0c;数据已经成为驱动业务发展的重要动力。对于政府机构而言&#xff0c;如何有效收集、分析和利用数据&#xff0c;以提升公共服务质量和效率&#xff0c;已成为一项重要课题。浙政钉作为浙江省政府推出的数字化协同办公平台&#xff0c;其埋点…

使用Python实现凯撒密码加密

凯撒密码&#xff08;Caesar Cipher&#xff09;是一种简单的替换式密码&#xff0c;其加密原理是将明文中的每个字符按照一定的偏移量进行替换&#xff0c;从而得到密文。在Python中&#xff0c;我们可以使用内置的字符串函数和循环结构来实现凯撒密码的加密过程。 以下是一个…

使用nginx搭建的文件服务器一直提示 404 Not Found ,是文件不存在还是没有权限?

使用nginx搭建一个简单的文件服务器&#xff0c;配置如下 user root; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid;# Load dynamic modules. See /usr/share/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf;events {wor…

mapbox 获取当前比例尺 scale

一、mapbox中提供了比例尺控件&#xff0c;在创建地图后可使用此方式显示比例尺控件 map.addControl(new mapboxgl.ScaleControl(), bottom-left) 可通过修改css改变自带比例尺样式 二、计算得出当前scale&#xff0c; 具体代码可在mapbox源码中查看&#xff0c;此处仅以单位M…

爬虫逆向sm3和sm4 加密 案例

注意&#xff01;&#xff01;&#xff01;&#xff01;某XX网站逆向实例仅作为学习案例&#xff0c;禁止其他个人以及团体做谋利用途&#xff01;&#xff01;&#xff01; 案例--aHR0cDovLzExMS41Ni4xNDIuMTM6MTgwODgvc3Vic2lkeU9wZW4 第一步&#xff1a;分析页面和请求方式 …

spark基本原理UI界面解读

这里是引用 1 八股文 1.1 基本原理 driver节点是整个应用程序的指挥所 指挥官是sparkcontext 环境&#xff1a;构建一个集群 应用程序提交 确定主节点&#xff0c;确定指挥所driver&#xff0c;确定指挥官sparkcontext sparkcontext会向资源管理器申请资源 会将作业分…

常见的几个Python技术难题

大家在日常开发中有没有遇到一些难题呢&#xff1f;计划后面出几期专题针对性的解决。大家如果有其它问题可以在评论区给出哈。 以下是几个Python技术难题的例子&#xff1a; 并发和多线程编程&#xff1a;Python的全局解释器锁&#xff08;GIL&#xff09;限制了多线程的并行…

华为OD机试 - 租车骑绿岛(Java JS Python C C++)

题目描述 部门组织绿岛骑行团建活动。租用公共双人自行车,每辆自行车最多坐两人,最大载重M。 给出部门每个人的体重,请问最多需要租用多少双人自行车。 输入描述 第一行两个数字m、n,分别代表自行车限重,部门总人数。 第二行,n个数字,代表每个人的体重,体重都小于…

CCF-CSP认证考试 202303-2 垦田计划 100分题解

更多 CSP 认证考试题目题解可以前往&#xff1a;CSP-CCF 认证考试真题题解 原题链接&#xff1a; 202303-2 垦田计划 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 512.0MB 问题描述 顿顿总共选中了 n n n 块区域准备开垦田地&#xff0c;由于各块区域大小不一&#xf…