守护进程编程

守护进程编程

守护进程的含义

定义

守护进程(Daemon Process)是在后台运行的进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程,它在系统后台运行,为系统或其他进程提供服务,而用户通常不会直接与它交互。

特点

(1)独立于终端

守护进程没有控制终端。它不会因为终端的关闭而停止运行。例如,一个网络服务器守护进程,它在后台监听网络请求,无论用户是否登录终端,它都能正常工作。这使得守护进程能够持续运行,不受用户登录状态的限制。

(2)生命周期长

守护进程通常在系统启动时开始运行,并且会一直运行,直到系统关闭。它们的生命周期与系统的运行时间紧密相关。比如,系统日志守护进程(如 syslogd)从系统启动开始就记录日志,直到系统关闭,它一直都在后台工作,记录各种系统事件的日志信息。

(3)提供服务

守护进程的主要功能是为系统或其他进程提供服务。这些服务可以是网络服务(如 Web 服务器守护进程 Apache)、打印服务(如 CUPS 打印守护进程)、定时任务(如 cron 守护进程)等。它们在后台默默运行,确保系统功能的正常实现。

编程实现一个守护进程的主要过程

1. 创建子进程并退出父进程

  • 使用 fork() 创建一个子进程。
  • 父进程退出,确保守护进程与终端分离。

2. 创建新的会话

  • 调用 setsid() 创建一个新的会话,使守护进程成为会话的首进程。
  • 这一步可以确保守护进程与终端完全分离。

3. 改变工作目录

  • 将工作目录改为根目录(/),避免守护进程依赖于特定的用户目录。
  • 防止守护进程在用户注销时被意外终止。
  1. 关闭所有文件描述符
  • 关闭所有打开的文件描述符,避免资源泄漏。
  • 防止守护进程意外地向终端输出信息。
  1. 设置文件权限掩码
  • 设置文件权限掩码(umask),确保守护进程创建的文件具有合适的权限。
  1. 打开日志文件
  • 打开日志文件,用于记录守护进程的运行状态。
  1. 进入主循环
  • 守护进程进入主循环,执行其主要功能。
  • 例如,定期记录当前时间到日志文件。

创建一个守护进程一般有 nohup命令、fork()函数和 daemon()函数三种方法,请分别在阿里云服务器、树莓派上用这三种方式创建一个守护进程。

阿里云

nohup命令

我们以这个简单的python脚本为例,它每隔10秒钟记录一次当前时间到日志文件中


import time
def main():while True:print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - 守护进程正在运行")time.sleep(10)
if __name__ == "__main__":main()

使用以下命令运行程序


nohup python3 demo.py &python3 demo.py

在这里插入图片描述

fork()
使用 fork() 创建守护进程的步骤

第一次 fork():
创建一个子进程,父进程退出。
这样可以确保子进程与终端分离。
创建新的会话:
调用 setsid() 创建一个新的会话,使子进程成为会话的首进程。
这一步可以确保子进程与终端完全分离。
第二次 fork():
再次创建一个子进程,确保守护进程不能重新打开控制终端。
父进程退出,子进程继续运行。
改变工作目录:
将工作目录改为根目录(/),避免守护进程依赖于特定的用户目录。
关闭所有文件描述符:
关闭所有打开的文件描述符,避免资源泄漏。
设置文件权限掩码:
设置文件权限掩码(umask),确保守护进程创建的文件具有合适的权限。
进入主循环:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#include <errno.h>#define LOG_FILE "/tmp/shouhufork.txt"void log_message_to_file(const char *message) {int log_fd = open(LOG_FILE, O_WRONLY | O_APPEND | O_CREAT, 0644);if (log_fd < 0) {perror("Failed to open log file");return;}write(log_fd, message, strlen(message));write(log_fd, "\n", 1);close(log_fd);
}int main() {pid_t pid;// Step 1: First forkpid = fork();if (pid < 0) {perror("First fork failed");exit(EXIT_FAILURE);}if (pid > 0) {// Parent process exitsexit(EXIT_SUCCESS);}// Step 2: Create a new sessionif (setsid() < 0) {perror("Setsid failed");exit(EXIT_FAILURE);}// Step 3: Second forkpid = fork();if (pid < 0) {perror("Second fork failed");exit(EXIT_FAILURE);}if (pid > 0) {// Parent process exitsexit(EXIT_SUCCESS);}// Step 4: Change the working directory to rootif (chdir("/") < 0) {perror("Chdir failed");exit(EXIT_FAILURE);}// Step 5: Close all file descriptorsfor (int i = 0; i < sysconf(_SC_OPEN_MAX); i++) {close(i);}// Step 6: Set the file permission maskumask(0);// Step 7: Open the log filelog_message_to_file("Daemon started successfully");// Step 8: Enter the main loopwhile (1) {time_t now = time(NULL);char *time_str = ctime(&now);char log_message[128];snprintf(log_message, sizeof(log_message), "Current time: %s", time_str);log_message_to_file(log_message);sleep(60); // Sleep for 60 seconds}return 0;
}

将代码保存为shouhufork.c
使用一下命令编译代码:

gcc -o shouhufork shouhufork.c

使用以下命令

./shouhufork

守护进程进入主循环,执行其主要功能。
使用命令

nohup ./shouhufork &
cat nohup.out

在这里插入图片描述

daemon函数
使用 daemon() 函数创建守护进程的步骤

调用 daemon() 函数:
daemon() 函数会自动完成以下操作:

  • 创建一个子进程,父进程退出。
  • 创建一个新的会话,使子进程成为会话的首进程。
  • 改变工作目录到根目录(/)。
  • 关闭所有文件描述符。
  • 设置文件权限掩码(umask)。

进入主循环:
守护进程进入主循环,执行其主要功能。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#include <errno.h>#define LOG_FILE "/tmp/daemon_log.txt"void log_message_to_file(const char *message) {int log_fd = open(LOG_FILE, O_WRONLY | O_APPEND | O_CREAT, 0644);if (log_fd < 0) {perror("Failed to open log file");return;}write(log_fd, message, strlen(message));write(log_fd, "\n", 1);close(log_fd);
}int main() {// Step 1: Call daemon() functionif (daemon(0, 0) == -1) {perror("Failed to start daemon");exit(EXIT_FAILURE);}// Step 2: Open the log filelog_message_to_file("Daemon started successfully");// Step 3: Enter the main loopwhile (1) {time_t now = time(NULL);char *time_str = ctime(&now);char log_message[128];snprintf(log_message, sizeof(log_message), "Current time: %s", time_str);log_message_to_file(log_message);sleep(60); // Sleep for 60 seconds}return 0;
}

保存为daemon.c,使用以下命令编译代码

gcc -o daemon daemon.c

使用以下命令运行

./daemon

在这里插入图片描述

GDB调试

用法介绍

程序暂停与断点
断点(Breakpoint):

  • GDB 允许用户在程序的特定位置设置断点。当程序运行到断点时,它会自动暂停。
  • 断点可以设置在函数入口、特定行号或特定地址。
    示例
(gdb) break main
(gdb) break file.c:42

暂停程序:

  • 当程序运行到断点时,GDB 会暂停程序的执行,允许用户检查程序的状态。
  • 用户可以查看变量的值、调用栈、寄存器内容等。
    示例
(gdb) step  # 进入函数内部
(gdb) next  # 不进入函数内部
(gdb) continue  # 继续运行到下一个断点

单步执行
单步执行(Step):

  • GDB 提供了单步执行功能,允许用户逐行或逐指令执行程序。
  • 单步执行可以帮助用户观察程序的执行流程,检查变量的变化。
    示例
(gdb) step  # 进入函数内部
(gdb) next  # 不进入函数内部
(gdb) continue  # 继续运行到下一个断点

查看变量和内存
查看变量:
GDB 允许用户查看和修改程序中的变量值。
示例

(gdb) print x
(gdb) print *ptr
(gdb) set x = 10

查看内存:
GDB 可以查看和修改内存中的内容。
示例:

(gdb) x/10gx 0x10000000  # 查看从地址 0x10000000 开始的 10 个 8 字节数据
(gdb) set {int}0x10000000 = 42  # 修改内存中的值

调用栈
查看调用栈(Backtrace):
GDB 可以显示当前程序的调用栈,帮助用户了解程序的执行路径。
示例:

(gdb) backtrace

切换栈帧:
用户可以切换到不同的栈帧,查看不同函数中的变量。
示例

(gdb) frame 2  # 切换到第 2 个栈帧

信号处理
信号(Signal):

  • GDB 可以捕获和处理程序中的信号(如 SIGSEGV、SIGABRT 等)。
  • 用户可以设置信号的处理方式,或者在信号发生时暂停程序。

示例:

(gdb) handle SIGSEGV stop
(gdb) handle SIGSEGV nostop

多线程支持
多线程调试:
GDB 支持多线程程序的调试,可以查看和切换不同的线程。
示例:

(gdb) info threads  # 查看所有线程
(gdb) thread 2  # 切换到第 2 个线程

远程调试
远程调试:
GDB 支持远程调试,可以通过网络连接到运行在其他机器上的程序。
示例:

(gdb) target remote :1234  # 连接到本地端口 1234
工作原理

启动 GDB:
用户启动 GDB 并加载要调试的程序:

gdb ./my_program

设置断点:
用户在程序的特定位置设置断点:

(gdb) break main

运行程序

(gdb) run

暂停程序:
当程序运行到断点时,GDB 暂停程序的执行。
检查程序状态:
用户可以查看变量、调用栈、内存等信息:

(gdb) print x
(gdb) backtrace

单步执行:
用户可以逐行或逐指令执行程序:

(gdb) step
(gdb) next

继续运行

用户可以继续运行程序到下一个断点

(gdb) continue

退出 GDB:
用户可以退出 GDB:

(gdb) quit
使用gdb调试一个程序

(1)创建 test.c

#include <stdio.h>int multiply(int x, int y) {return x * y;}int divide(int x, int y) {if (y == 0) {fprintf(stderr, "Error: Division by zero\n");return 0;}return x / y;}int main() {int a = 10, b = 0, c = 20, d;d = multiply(a, c);printf("Multiply result: %d\n", d);d = divide(a, b);printf("Divide result: %d\n", d);return 0;

在 main 函数中,变量 a 被初始化为 10,b 被初始化为 0,c 被初始化为 20,d 未初始化
调用 multiply(a, c) 计算 a 和 c 的乘积,即 10 * 20,结果为 200。这个结果被赋值给 d,所以此时 d 的值为 200。接下来打印 Multiply result: 200。然后调用 divide(a, b) 计算 a 和 b 的商,即 10 / 0。由于 b 的值为 0,这将导致除以零的错误。divide 函数会打印错误信息 “Error: Division by zero” 并返回
0。这个结果被赋值给 d,所以此时d的值变为0.

(2)编译带调试信息

gcc -g test.c -o test # -g选项生成调试符号

在这里插入图片描述

(3)启动 gdb 调试

gdb ./test
在这里插入图片描述

(4)设置断点

break multiply

break divide

在这里插入图片描述

(5)运行程序

run

在这里插入图片描述

(6)单步执行

next

在这里插入图片描述

一直next,直到出现divide函数,执行step命令进入到divide含糊内部进行单步调试

step

在这里插入图片描述

使用print命令来检查传入 divide函数的参数想和y的值,确保他们的预期值

print x

print y

在这里插入图片描述

(7)单步执行
step
在这里插入图片描述

 

程序已经执行了 divide 函数中的 if (y == 0) 条件检查。由于 y 的值是 20(不等于0) 程序将继续执行 if 语句块之外的代码,继续单步执行step

step?
在这里插入图片描述

程序已经执行到了 fprintf(stderr, “Error: Division by zero\n”); 这一行,因为在 divide 函数中检测到了除以零的情况,GDB 显示了 fprintf 函数的调用信息

(8)检查d的输出值(d在main函数里面,要检查d的值就要退出divide函数并返回到调用点,使用finish命令)

finish
在这里插入图片描述

(9)继续执行程序(程序将继续执行并打印 Divide result: 后跟 d 的值)
continue
在这里插入图片描述

 
完成调试,退出gdb时,使用quit命令

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

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

相关文章

在复杂性的迷宫里寻找路标 —— 读《人月神话》有感

初读《人月神话》时&#xff0c;正值参与的第一个大型项目陷入泥潭&#xff1a;需求像不断膨胀的气球&#xff0c;团队规模从 10 人扩充到 30 人&#xff0c;进度却像被灌了铅的钟表&#xff0c;指针越来越沉重。布鲁克斯在书中写下的 "向进度落后的项目增加人力&#xff…

SpringCloud Alibaba微服务工程搭建

前言 在讲微服务工程的搭建之前&#xff0c;我们先分析下为什么要使用微服务呢&#xff1f; 1、单体应用的痛点 维护困难&#xff1a;代码臃肿&#xff0c;牵一发而动全身。扩展性差&#xff1a;无法按需扩展特定功能&#xff0c;只能整体扩容。技术栈僵化&#xff1a;难以引…

flutter json解析增强

依赖:xxf_json 反序列化兼容特征一览表 类型\是否兼容 int double num string bool int yes yes yes yes yes double yes yes yes yes yes num yes yes yes yes yes string yes yes yes yes yes bool yes yes yes yes yes 专业词语 .g…

Neo4j初解

Neo4j 是目前应用非常广泛的一款高性能的 NoSQL 图数据库&#xff0c;其设计和实现专门用于存储、查询和遍历由节点&#xff08;实体&#xff09;、关系&#xff08;边&#xff09;以及属性&#xff08;键值对&#xff09;构成的图形数据模型。它的核心优势在于能够以一种自然且…

学习MySQL的第十天

一、MySQL的数据类型 1.MySQL的数据类型 2.常见的数据类型的属性 二、整数类型 三、浮点类型 REAL默认就是DOUBLE。如果你把SQL模式设定为启用“REAL_AS_FLOAT”,那么,MySQL就认为REAL是FLOAT。如果要启用“REAL_AS_FLOAT”,可以通过以下SQL语句实现: SET sql_mode &…

ubuntu24.04上使用qemu+buildroot+uboot+linux+tftp+nfs模拟搭建vexpress-ca9嵌入式linux开发环境

1 准备工作 1.1 安装依赖工具 sudo apt-get update && sudo apt-get install build-essential git bc flex libncurses5-dev libssl-dev device-tree-compiler1.2 安装arm交叉编译工具链 sudo apt install gcc-arm-linux-gnueabihf安装之后&#xff0c;在终端输入ar…

ubuntu 22.04 使用ssh-keygen创建ssh互信账户

现有两台ubuntu 22.04服务器&#xff0c;ip分别为192.168.66.88和192.168.88.66。需要将两台服务器创建新用户并将新用户做互信。 创建账户 adduser user1 # 如果此用户不想使用密码&#xff0c;直接一直回车就行&#xff0c;创建的用户是没法使用用户密码进行登陆的 su - …

【PCIE配置空间】

1 PCIE配置空间 1.1 软件如何知道PCIE设备是Swith&#xff0c;RC还是EP&#xff1f; –软件通过读取寄存器信息。 PCIE配置空间• PCIE寄存器&#xff1b;--PCIE配置协议规定必须实现的空间。--PCIE存在两种配置空间Type0/Type1;--Type0配置空间EP设备必须实现&#xff1b;-…

Android 热点二维码简单示例

Android 热点二维码简单示例 一、前言 Android 原生设置有热点二维码分享功能&#xff0c;有些系统应用也会有这个需求。 下面看看是如何实现的。 本文是一个比较简单的内容。 二、热点二维码生成实现 1、效果 整个应用就一个普通的Activity&#xff0c;显示一个按钮和二维…

uv:重新定义Python开发效率的下一代工具链

在Python生态系统中&#xff0c;包管理和项目工具链的复杂性一直是开发者面临的一大挑战。从依赖管理、虚拟环境创建到多版本Python切换&#xff0c;传统的工具链&#xff08;如pip、virtualenv、poetry等&#xff09;虽然功能强大&#xff0c;但操作繁琐、性能不足的问题长期存…

T101D加固平板电脑:无人机地面站的高效智能控制核心

随着无人机技术在应急救援、农业监测、军事侦察等领域的广泛应用&#xff0c;对地面控制设备的要求也日益提高。鲁成伟业推出的T101D加固平板电脑凭借其高性能、强防护和专业化设计&#xff0c;成为无人机地面站的核心控制终端&#xff0c;为复杂环境下的作业提供了可靠支持。 …

Datawhale AI春训营】AI + 新能源(发电功率预测)Task1

赛题链接 官网 新能源发电功率预测赛题进阶方案 下面是ai给的一些建议 新能源发电功率预测赛题进阶方案 一、时序特性深度挖掘 1. 多尺度周期特征 # 分钟级周期编码 train[15min_index] (train[hour]*4 train[minute]//15)# 周周期特征 train[weekday] pd.to_datetime…

山东科技大学深度学习考试回忆

目录 一、填空&#xff08;五个空&#xff0c;十分&#xff09; 二、选择题(五个&#xff0c;十分&#xff09; 三、判断题&#xff08;五个&#xff0c;五分&#xff09; 四、论述题&#xff08;四个&#xff0c;四十分&#xff09; 五、计算题&#xff08;二个&#xff…

Redis线上操作最佳实践有哪些?

大家好&#xff0c;我是锋哥。今天分享关于【Redis线上操作最佳实践有哪些?】面试题。希望对大家有帮助&#xff1b; Redis线上操作最佳实践有哪些? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在使用 Redis 时&#xff0c;尤其是在生产环境中&#xff0c;合理…

mac中的zip文件压缩与压缩文件中指定目录删除

问题 在使用mac的图形界面压缩文件后&#xff0c;往往那个压缩文件中带有__MACOSX文件&#xff0c;但是&#xff0c;这个文件夹又是我们不需要的目录&#xff0c;所有&#xff0c;需要对mac图形化界面压缩后的文件目录进行删除&#xff0c;改如何做&#xff1f; 检查压缩文件…

【记录】服务器用命令开启端口号

这里记录下如何在服务器上开启适用于外界访问的端口号。 方法 1 使用防火墙 1 su &#xff0c;命令 输入密码 切换到root节点 2 开启防火墙 systemctl start firewalld3 配置开放端口 firewall-cmd --zonepublic --add-port8282/tcp --permanent4 重启防火墙 firewall-cmd…

深度学习-torch,全连接神经网路

3. 数据集加载案例 通过一些数据集的加载案例&#xff0c;真正了解数据类及数据加载器。 3.1 加载csv数据集 代码参考如下 import torch from torch.utils.data import Dataset, DataLoader import pandas as pd ​ ​ class MyCsvDataset(Dataset):def __init__(self, fil…

C++/Python实现RGB和HSI相互转换

1--C版本 #include <opencv2/opencv.hpp> #include <iostream> #include <cmath>// RGB to HSI cv::Vec3f RGBtoHSI(cv::Vec3b rgb) {float B rgb[0] / 255.0f;float G rgb[1] / 255.0f;float R rgb[2] / 255.0f;float num 0.5f * ((R - G) (R - B));f…

【Linux我做主】make和makefile自动化构建

make和makefile自动化构建 make和makefile自动化构建github地址前言背景介绍为什么需要make和makefile&#xff1f; make和makefile解析什么是make和makefile依赖关系和依赖方法核心语法结构简单演示编译清理 多阶段编译示例 make时执行的顺序场景1&#xff1a;clean目标在前(特…

Qt 入门 5 之其他窗口部件

Qt 入门 5 之其他窗口部件 本文介绍的窗口部件直接或间接继承自 QWidget 类详细介绍其他部件的功能与使用方法 1. QFrame 类 QFrame类是带有边框的部件的基类。它的子类包括最常用的标签部件QLabel另外还有 QLCDNumber、QSplitter,QStackedWidget,QToolBox 和 QAbstractScrol…