目录
[1.1 守护进程概述](#1.1 守护进程概述)
[1.2 守护进程的功能及特点](#1.2 守护进程的功能及特点)
[1.2.1 守护进程的功能](#1.2.1 守护进程的功能)
[1.2.2 守护进程的特点](#1.2.2 守护进程的特点)
[1.3 主要过程](#1.3 主要过程)
[1.4 阿里云服务器编程实现守护进程](#1.4 阿里云服务器编程实现守护进程)
[1.4.1 daemon 命令](#1.4.1 daemon 命令)
[1.4.2 nohup命令](#1.4.2 nohup命令)
[1.4.3 fork()编程实现](#1.4.3 fork()编程实现)
[1.5 在树莓派中通过三种方式创建守护进程](#1.5 在树莓派中通过三种方式创建守护进程)
[1.5.1 nohup命令创建](#1.5.1 nohup命令创建)
[1.5.2 fork()函数创建](#1.5.2 fork()函数创建)
[1.5.3 daemon()函数创建](#1.5.3 daemon()函数创建)
[3.1 阿里云服务器准备](#3.1 阿里云服务器准备)
[3.3 验证反向隧道是否成功](#3.3 验证反向隧道是否成功)
一、守护进程
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);
// 关闭标准IO
close(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命令创建
-
创建测试脚本
nano ~/test_daemon.sh
输入下面内容:
#!/bin/bash
while true; do
echo "$(date): Daemon is running on Raspberry Pi" >> /tmp/daemon.log
sleep 5
done
-
设置权限并运行
chmod +x ~/test_daemon.sh
-
检查运行情况
查看进程
ps aux | grep test_daemon

1.5.2 fork()函数创建
-
安装编译工具(如果尚未安装)
sudo apt update
-
创建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;
}
-
编译运行
gcc ~/fork_daemon.c -o ~/fork_daemon
-
检查运行情况
ps aux | grep fork_daemon

1.5.3 daemon()函数创建
-
创建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;
}
-
编译运行
gcc ~/daemon_func.c -o ~/daemon_func
-
检查运行情况
查看进程
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反向代理的实现过程让我认识到了网络穿透的实用价值,尤其是在远程访问树莓派等设备的时候。
参考文章: