当前位置: 首页 > news >正文

守护进程编程

目录

一、守护进程

1.1 守护进程概述

1.2  守护进程的功能及特点

1.2.1 守护进程的功能

1.2.2 守护进程的特点

1.3 主要过程

1.4 阿里云服务器编程实现守护进程

1.4.1 daemon 命令

1.4.2 nohup命令

1.4.3 fork()编程实现

1.5 在树莓派中通过三种方式创建守护进程

 1.5.1 nohup命令创建

 1.5.2 fork()函数创建

1.5.3 daemon()函数创建

二、gdb调试

三、SSH反向代理,完成树莓派外网访问

 3.1 阿里云服务器准备

3.2在树莓派上设置SSH反向隧道

3.3 验证反向隧道是否成功

3.4验证外网访问树莓派


一、守护进程

1.1 守护进程概述

守护进程是一种特殊的进程,它在后台运行,不与用户直接交互,并且不受会话的限制,也就是说即使用户退出了终端或关闭了会话,守护进程仍然可以继续运行,直到它被明确终止。

正常情况下,当我们运行一个前台或后台进程时,一旦离开当前会话(终端),那该会话中的所有前后台进程也随即结束,当你重新打开会话时,已经“物是人非,难遇故人”了。而守护进程就可以不受会话的限制,可在前后台一直运行直至结束的进程。

守护进程的实现有两种方式:自编和利用现有程序伪装。

1.2  守护进程的功能及特点

1.2.1 守护进程的功能

1)系统服务管理:守护进程可以管理系统的各种服务,如 Web 服务、邮件服务、数据库服务等。这些服务通常在后台运行,确保系统的正常运行。

2)网络服务:守护进程可以处理网络请求,如 HTTP 请求、FTP 请求等。例如,Apache 和 Nginx 等 Web 服务器都是以守护进程的形式运行的。

3)日志记录:守护进程可以负责记录系统的日志信息,如系统日志、应用程序日志等。例如,syslogd 守护进程用于记录系统日志。

4)任务调度:守护进程可以定期执行某些任务,如定时备份数据、定时清理临时文件等。例如,cron 守护进程用于定时任务调度。

5)硬件设备管理:守护进程可以管理硬件设备,如打印机、扫描仪等。例如,cups 守护进程用于管理打印服务。

1.2.2 守护进程的特点

1)没有用户界面:守护进程通常没有用户界面,它们在后台运行,不与用户直接交互。

2)高优先级:守护进程通常具有较高的优先级,以确保它们能够及时响应系统事件。

3)自动重启:守护进程通常设计为在崩溃或失败时自动重启,以确保服务的连续性。

4)独立运行:守护进程独立于其他进程运行,它们通常在系统启动时自动启动,并在系统关闭时终止。

5)提供系统级服务:守护进程通常提供系统级的服务,如网络服务、日志记录、任务调度等。

1.3 主要过程

1.4 阿里云服务器编程实现守护进程

1.4.1 daemon 命令

(1)创建一个新的文件 daemon.c

(2)编写守护进程代码

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/wait.h>  
#include <sys/types.h>  
#include <fcntl.h>  int main() 
{    pid_t pid;  int i, fd, len;  char *buf = "守护进程运行中.\n";  len = strlen(buf)+1;pid = fork();	//1.1 创建子进程if (pid < 0) {  printf("fork error!");  exit(1);  }if (pid>0) 		// 1.2父进程退出  exit(0);  setsid(); 		// 2.在子进程中创建新会话。  chdir("/"); 	// 3.设置工作目录为根目录  umask(0); 		// 4.设置权限掩码  for(i=0; i<getdtablesize(); i++) //5.关闭用不到的文件描述符  close(i);//6.守护进程功能实现while(1) {			// 死循环表征它将一直运行fd = open("/var/log/daemon.log",O_CREAT|O_WRONLY|O_APPEND,0600);if(fd < 0) {printf("Open file failed!\n");exit(1);  }  write(fd, buf, len);  // 将buf写到fd中  close(fd);  sleep(10);  printf("error: Never run here!\n");return 1;}  return 0;  
}

(3)编译程序

ggc -o daemon daemon.c

(4) 准备日志文件

sudo touch /var/log/daemon.log
sudo chmod 666 /var/log/daemon.log

(5)运行守护进程

./mydaemon

 

(6)验证守护进程运行

 a.查看进程是否存在

ps aux | grep mydaemon

b.检查日志内容

tail -f /var/log/daemon.log

日志内容每隔一段时间会更新一次

(7)停止守护进程

a.首先找到进程ID

pgrep mydaemon

b.终止进程

kill <进程ID>

我们将696这个进程终止,再次找到进程发现696已经被终止了

利用现有程序伪装成守护进程

  • 意思就是希望某一个程序能够在当前会话被关闭后,照样能够运行,方法也很简单,就是利用nohup命令。

  • 例如,我想运行一个命令sleep 1000,正常情况下,该命令在终端执行后,如果关闭该终端,命令随之结束,而不会等到1000秒之后。

  • 通过执行:nohup sleep 1000 &命令后,查看sleep的运行情况如下:

 关闭当前会话窗口,再重新打开,执行ps axjf | grep sleep命令发现它任然在执行中。

1.4.2 nohup命令

(1)创建日志目录

mkdir -p ~/daemon_logs

(2)启动守护进程(每分钟记录时间到日志)

nohup bash -c 'while true; do date >> ~/daemon_logs/nohup.log; sleep 60; done' > /dev/null 2>&1 &

(3)验证

查看进程

ps aux | grep 'bash -c'

查看日志

tail -f ~/daemon_logs/nohup.log

1.4.3 fork()编程实现

新建一个.c代码

nano fork_daemon.c

C代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <signal.h>
#include <time.h>#define LOG_FILE "/var/log/fork_daemon.log"void daemonize() {pid_t pid = fork();if (pid < 0) exit(EXIT_FAILURE);if (pid > 0) exit(EXIT_SUCCESS); // 父进程退出setsid(); // 创建新会话chdir("/");umask(0);// 关闭标准IOclose(STDIN_FILENO);close(STDOUT_FILENO);close(STDERR_FILENO);
}void log_message(const char* msg) {FILE* log = fopen(LOG_FILE, "a");if (log) {time_t now = time(NULL);fprintf(log, "[%s] %s\n", ctime(&now), msg);fclose(log);}
}int main() {daemonize();log_message("Daemon started");while (1) {log_message("Running...");sleep(10); // 每10秒记录一次}return EXIT_SUCCESS;
}

编译

gcc fork_daemon.c -o fork_daemon

启动

sudo ./fork_daemon

确认守护进程状态

ps aux | grep fork_daemon

查看日志

tail -f /var/log/fork_daemon.log

1.5 在树莓派中通过三种方式创建守护进程

 1.5.1 nohup命令创建

1. 创建测试脚本

nano ~/test_daemon.sh

输入下面内容:

#!/bin/bashwhile true; doecho "$(date): Daemon is running on Raspberry Pi" >> /tmp/daemon.logsleep 5
done

2. 设置权限并运行

chmod +x ~/test_daemon.sh` `nohup ~/test_daemon.sh > /dev/null 2>&1 &

3. 检查运行情况

 查看进程

ps aux | grep test_daemon

 1.5.2 fork()函数创建

1. 安装编译工具(如果尚未安装)

sudo apt update` `sudo apt install build-essential -y

2. 创建C程序

nano ~/fork_daemon.c

输入下面内容:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <time.h>int main(int argc, char* argv[]) {pid_t process_id = 0;pid_t sid = 0;// 创建子进程process_id = fork();// 创建失败if (process_id < 0) {printf("fork failed!\n");exit(1);}// 父进程退出if (process_id > 0) {printf("process_id of child process %d \n", process_id);exit(0);}// 设置新的会话sid = setsid();if(sid < 0) {exit(1);}// 改变工作目录chdir("/");// 关闭标准输入、输出、错误close(STDIN_FILENO);close(STDOUT_FILENO);close(STDERR_FILENO);// 守护进程主循环while (1) {FILE *log = fopen("/tmp/fork_daemon.log", "a+");if (log != NULL) {time_t now;time(&now);fprintf(log, "[%ld] Daemon is running (fork method)\n", now);fclose(log);}sleep(5);}return 0;
}

3. 编译运行

gcc ~/fork_daemon.c -o ~/fork_daemon` `~/fork_daemon

4. 检查运行情况

ps aux | grep fork_daemon

1.5.3 daemon()函数创建

1. 创建C程序

nano ~/daemon_func.c

输入下面内容:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <time.h>int main(int argc, char* argv[]) {// 使用daemon()函数创建守护进程// 参数1: 是否改变工作目录到根目录// 参数2: 是否关闭标准输入输出错误if (daemon(1, 0) == -1) {printf("daemon creation failed\n");exit(EXIT_FAILURE);}// 守护进程主循环while (1) {FILE *log = fopen("/tmp/daemon_func.log", "a+");if (log != NULL) {time_t now;time(&now);fprintf(log, "[%ld] Daemon is running (daemon function)\n", now);fclose(log);}sleep(5);}return 0;
}

2. 编译运行

gcc ~/daemon_func.c -o ~/daemon_func` `~/daemon_func

3. 检查运行情况
 查看进程

ps aux | grep daemon_func

二、gdb调试

核心机制

ptrace系统调用:GDB通过ptrace()接管目标进程的执行权,可访问其内存和寄存器

断点实现:将指定地址的指令替换为int 3(0xCC)触发软中断

符号表加载:读取可执行文件的.symtab.debug_info节获取变量/函数信息

(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。最后打印 Divide result: 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

程序已经执行到了 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命令

三、SSH反向代理,完成树莓派外网访问

利用阿里云服务器,使用SSH反向代理,完成树莓派的外网访问。即让其他人可以从任何地方用笔记本电脑,通过访问阿里云服务器的端口(穿透)ssh登录进入你的树莓派系统。

 3.1 阿里云服务器准备

(1)开放安全组端口

通过命令 sudo ufw status 可以查看到当前激活的端口,查看是否有我们将要连接的端口。

若没有,需要通过命令

sudo ufw allow 9613/tcp 

进行端口的开放

(2)修改阿里云的SSH配置文件,允许端口转发

sudo nano /etc/ssh/sshd_config

确保文件中有如下配置:

GatewayPorts yes
AllowTcpForwarding yes

配置完成后重启SSH服务: 

sudo systemctl restart sshd

3.2 在树莓派上设置SSH反向隧道

首先通过电脑cmd命令行登录进入树莓派

进入后通过命令: ssh -fN -R 端口号:localhost:22 <用户名>@<阿里云IP地址> 在树莓派上建立反向

SSH隧道

3.3 验证反向隧道是否成功

建立成功后我们可以通过一系列命令测试反向隧道是否成功

(1)在阿里云服务器上检查监听端口:

sudo netstat -tuln | grep <端口号>

(2)通过阿里云连接树莓派:

ssh -p 6000 pi@localhost

3.4 验证外网访问树莓派

我们通过其他电脑,连接与树莓派不同的WIFI

输入命令 ssh -p <端口号> <树莓派地址>@<阿里云IP地址>

然后就可以成功通过外网访问树莓派

四、总结

这次主要学习了守护进程的相关概念、功能以及如何在不同的环境下创建守护进程,理解到了守护进程在系统管理服务中的重要性;此外,还学习了如何使用gdb调试工具进行程序调试;最后,SSH反向代理的实现过程让我认识到了网络穿透的实用价值,尤其是在远程访问树莓派等设备的时候。

参考文章:

linux系统编程之进程(八):守护进程详解及创建,daemon()使用 - mickole - 博客园

相关文章:

  • 音视频之H.265/HEVC变换编码
  • kafka jdbc connector适配kadb数据实时同步
  • Uniapp调用native.js使用经典蓝牙串口通讯方法及问题解决
  • Web 前端包管理工具深度解析:npm、yarn、pnpm 全面对比与实战建议
  • 第五章 SQLite数据库:4、SQLite 进阶用法:常见的约束、PRAGMA 配置、数据操作
  • 微信小程序怎么分包步骤(包括怎么主包跳转到分包)
  • UE5 渲染视频
  • RAG 实战|用 StarRocks + DeepSeek 构建智能问答与企业知识库
  • 力扣刷题-热题100题-第35题(c++、python)
  • 捕鱼船检测数据集VOC+YOLO格式2105张1类别
  • 【工具-Krillin AI】视频翻译、配音、语音克隆于一体的一站式视频多语言转换工具~
  • BFS DFS ----习题
  • C语言教程(十):C 语言函数详解
  • 数据结构之队列及其应用
  • Openfein实现远程调用的方法(实操)
  • 聊一聊接口测试是如何进行的?
  • Vue3如何选择传参方式
  • 虚幻基础:ue引擎的碰撞
  • HTTP/1.1 队头堵塞问题
  • 函数对象-C++
  • 特朗普的百日执政支持率与他“一税解千愁”的世界观和方法论
  • 纪录电影《中国有戏:天幕计划》启动,有望太空播放
  • 中消协发布“五一”消费提示:践行“光盘行动”,抵制餐饮浪费
  • 马上评丨机械停车库成“僵尸库”,设计不能闭门造车
  • 油电同智,安全超充!从上海车展看中国汽车产业先发优势
  • 讲座|现代女性在面对生育、事业与家庭之间的复杂抉择