【嵌入式】【阿里云服务器】【树莓派】学习守护进程编程、gdb调试原理和内网穿透信息
目录
一. 守护进程的含义及编程实现的主要过程
1.1守护进程
1.2编程实现的主要过程
二、在树莓派中通过三种方式创建守护进程
2.1nohup命令创建
2.2fork()函数创建
2.3daemon()函数创建
三、在阿里云中通过三种方式创建守护进程
3.1nohup命令创建
3.2fork()函数创建
3.3daemon()函数创建
四、gdb调试
4.1核心机制
4.2用gdb命令调试一个C程序
五、SSH反向代理,完成树莓派外网访问
5.1阿里云服务器准备
5.2在树莓派上设置SSH反向隧道
5.3验证反向隧道是否成功
5.4验证外网访问树莓派
一. 守护进程的含义及编程实现的主要过程
1.1守护进程
守护进程是运行在后台的独立于终端的进程,通常用于提供系统服务(如Web服务器、数据库等)。其特点包括:
-
脱离终端控制(不与用户直接交互)。
-
生命周期长(随系统启动/关闭)。
-
以root权限或特定用户身份运行。
1.2编程实现的主要过程
(1)屏蔽部分控制终端操作的信号
为了避免终端挂起(SIGHUP)信号影响守护进程,需要在程序开始时忽略该信号。这可以通过signal()
函数实现。
#include <signal.h>
// 屏蔽SIGHUP信号
signal(SIGHUP, SIG_IGN);
(2) 在后台运行
通过fork()
创建一个子进程,父进程退出,子进程继续运行,从而实现程序在后台运行。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
pid_t pid = fork();
if (pid < 0) {perror("fork failed");exit(EXIT_FAILURE);
}
if (pid > 0) {exit(EXIT_SUCCESS); // 父进程退出
}
(3) 脱离控制终端、登录会话和进程组
调用setsid()
创建一个新的会话,使进程脱离控制终端、登录会话和进程组。
#include <unistd.h>
if (setsid() < 0) {perror("setsid failed");exit(EXIT_FAILURE);
}
(4) 禁止进程重新打开控制终端
再次忽略SIGHUP信号,确保进程不会重新打开控制终端。
#include <signal.h>
// 再次忽略SIGHUP信号
signal(SIGHUP, SIG_IGN);
(5) 关闭打开的文件描述符
关闭所有已打开的文件描述符,防止守护进程继承不必要的文件描述符。
#include <unistd.h>
#include <limits.h>
// 关闭所有打开的文件描述符
for (int fd = 0; fd < sysconf(_SC_OPEN_MAX); fd++) {close(fd);
}
(6) 改变当前工作目录
将工作目录改为根目录(/
),防止守护进程被挂载的文件系统限制。
#include <unistd.h>
// 改变工作目录到根目录
if (chdir("/") < 0) {perror("chdir failed");exit(EXIT_FAILURE);
}
(7) 重设文件创建掩模
设置文件创建掩模为0,允许守护进程创建文件时具有最大权限。
#include <sys/types.h>
#include <sys/stat.h>
// 设置文件创建掩模为0
umask(0);
(8) 处理 SIGCHLD 信号
忽略SIGCHLD信号,防止子进程退出影响父进程。
#include <signal.h>
// 忽略SIGCHLD信号
signal(SIGCHLD, SIG_IGN);
二、在树莓派中通过三种方式创建守护进程
2.1nohup命令创建
1. 创建测试脚本
nano ~/test_daemon.sh
输入下面内容:
#!/bin/bash
while 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
2.2fork()函数创建
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
2.3daemon()函数创建
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
三、在阿里云中通过三种方式创建守护进程
3.1nohup命令创建
1. 创建程序
nano nohup_daemon.sh
输入以下内容:
#!/bin/bash
while true; doecho "$(date): Daemon is running (nohup method)" >> /var/log/nohup_daemon.logsleep 10
done
2. 编译运行
sudo chmod +x nohup_daemon.sh设置执行权限nohup nohup_daemon.sh > /dev/null 2>&1 &使用nohup启动守护进程
3. 检查运行情况
ps aux | grep nohup_daemon
3.2fork()函数创建
1. 创建程序
nano ~/fork_daemon.c
输入以下内容:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h>
#define LOG_FILE "/var/log/fork_daemon.log"
void write_log(const char *message) {FILE *log = fopen(LOG_FILE, "a");if (log != NULL) {time_t now;time(&now);fprintf(log, "[%s] %s\n", ctime(&now), message);fclose(log);}
}
int main() {pid_t pid = fork();if (pid < 0) {exit(EXIT_FAILURE);}if (pid > 0) {exit(EXIT_SUCCESS);}umask(0);setsid();chdir("/");close(STDIN_FILENO);close(STDOUT_FILENO);close(STDERR_FILENO);while (1) {write_log("Daemon is running (fork method)");sleep(10);}return EXIT_SUCCESS;
}
2. 编译运行
gcc ~/fork_daemon.c -o ~/fork_daemon ~/fork_daemon
3. 检查运行情况
ps aux | grep fork_daemon
3.3daemon()函数创建
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调试
4.1核心机制
ptrace系统调用:GDB通过ptrace()接管目标进程的执行权,可访问其内存和寄存器
断点实现:将指定地址的指令替换为int 3(0xCC)触发软中断
符号表加载:读取可执行文件的.symtab和.debug_info节获取变量/函数信息
4.2用gdb命令调试一个C程序
(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 multiplybreak divide
(5)运行程序
run
(6)单步执行
next
一直next,直到出现divide函数,执行step命令进入到divide含糊内部进行单步调试
step
使用print命令来检查传入 divide函数的参数想和y的值,确保他们的预期值
print xprint 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反向代理,完成树莓派外网访问
5.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
5.2在树莓派上设置SSH反向隧道
首先通过电脑cmd命令行登录进入树莓派
进入后通过命令:ssh -fN -R 端口号:localhost:22 <用户名>@<阿里云IP地址>
在树莓派上建立反向SSH隧道
5.3验证反向隧道是否成功
建立成功后我们可以通过一系列命令测试反向隧道是否成功
(1)在阿里云服务器上检查监听端口:
sudo netstat -tuln | grep <端口号>
(2)通过阿里云连接树莓派:
ssh -p 6000 pi@localhost
5.4验证外网访问树莓派
我们通过其他电脑,连接与树莓派不同的WIFI
输入命令ssh -p <端口号> <树莓派地址>@<阿里云IP地址>
然后就可以成功通过外网访问树莓派
参考博客:
Linux系统编程——特殊进程之守护进程_linux c++ 守护进程 exec 自动重启-CSDN博客