欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 焦点 > 守护进程编程

守护进程编程

2025/4/20 10:20:04 来源:https://blog.csdn.net/Apple66666666666/article/details/147341194  浏览:    关键词:守护进程编程

守护进程编程

守护进程的含义

定义

守护进程(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命令

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com