守护进程编程以及ssh反向代理
目录
- 一、守护进程概述
- 1.守护进程的编程实现过程
- 2、代码实现示例
- 二、创建守护进程
- 使用 nohup 命令
- 1. 创建测试脚本 demo_nohup.sh
- 2. 赋予执行权限并启动
- 3. 验证守护进程
- 使用 fork()函数
- 1. 编写 C 程序 daemon_fork.c
- 2. 编译并运行
- 3. 验证守护进程
- 使用 daemon() 函数
- 1. 编写 C 程序 daemon_glibc.c
- 2. 编译并运行
- 3. 验证守护进程
- 验证守护进程特性
- 停止守护进程
- 三、 掌握gdb调试原理,在阿里云服务器或树莓派上用 gdb命令调试一个c程序
- GDB 调试原理
- 调试环境准备(通用步骤)
- 1. 安装 GDB
- 2. 编写测试程序
- 3. 编译带调试信息的程序
- GDB 调试实战
- 1. 启动 GDB
- 2. 常用调试命令
- 3. 完整调试流程演示
- 四、借助阿里云服务器,使用SSH反向代理,完成树莓派的外网访问
- 中转服务器设置
- 内网主机(树莓派)ssh连接中转服务器
一、守护进程概述
守护进程(Daemon)是操作系统中一类长期在后台运行的特殊进程,它独立于控制终端,主要用于执行系统级服务或周期性任务(如日志管理、网络服务等)。其核心特征包括:脱离用户终端控制、生命周期与系统运行周期同步、资源消耗低且通常以特定权限运行。
正常情况下,当我们运行一个前台或后台进程时,一旦离开当前会话(终端),那该会话中的所有前后台进程也随即结束,当你重新打开会话时,已经“物是人非,难遇故人”了。而守护进程就可以不受会话的限制,可在前后台一直运行直至结束的进程。
守护进程的实现有两种方式:自编和利用现有程序伪装
1.守护进程的编程实现过程
在类Unix系统中,实现守护进程需遵循以下标准化步骤,这些步骤旨在确保进程与环境的完全隔离:
-
创建子进程并终止父进程
通过fork()
系统调用生成子进程后,父进程立即退出,使子进程成为“孤儿进程”并由init
进程接管。这一操作使守护进程形式上脱离终端控制,并消除与Shell终端的直接关联pid_t pid = fork(); if (pid > 0) exit(0); // 父进程退出
-
创建新会话组
子进程调用setsid()
创建新的会话组并成为组长,切断与原终端、进程组和控制终端的联系,这是实现完全后台运行的关键if (setsid() < 0) exit(1); // 失败则终止
-
改变工作目录
将当前目录切换至根目录(/
)或其他安全路径,避免因挂载点未卸载导致的系统问题chdir("/"); // 切换到根目录
-
重设文件权限掩码
调用umask(0)
重置文件创建掩码,确保后续生成文件的权限不受父进程限制 -
关闭继承的文件描述符
关闭从父进程继承的标准输入、输出和错误描述符(通常为0/1/2),或将其重定向至/dev/null
,防止意外占用终端资源close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); open("/dev/null", O_RDONLY); // 重定向标准输入
6.守护进程功能实现(无限循环)
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;
}
二、创建守护进程
创建一个守护进程一般有 nohup命令、fork()函数和 daemon()函数三种方法
使用 nohup 命令
1. 创建测试脚本 demo_nohup.sh
nano demo_nohup.sh
#!/bin/bash
while true; doecho "$(date) >> 守护进程运行中..." >> /tmp/nohup_demo.logsleep 5
done
2. 赋予执行权限并启动
chmod +x demo_nohup.sh
nohup ./demo_nohup.sh > /dev/null 2>&1 &
3. 验证守护进程
ps aux | grep demo_nohup
tail -f /tmp/nohup_demo.log # 查看日志输出
使用 fork()函数
1. 编写 C 程序 daemon_fork.c
nano daemon_fork.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>void create_daemon() {pid_t pid = fork();if (pid < 0) exit(1);if (pid > 0) exit(0); // 父进程退出setsid(); // 创建新会话chdir("/"); // 切换工作目录到根umask(0); // 重置文件权限掩码// 关闭所有打开的文件描述符for (int fd = sysconf(_SC_OPEN_MAX); fd >= 0; fd--) {close(fd);}// 重定向标准流到 /dev/nullopen("/dev/null", O_RDWR); // stdindup(0); // stdoutdup(0); // stderr
}int main() {create_daemon();// 守护进程主逻辑while (1) {int fd = open("/tmp/fork_demo.log", O_WRONLY | O_CREAT | O_APPEND, 0644);if (fd != -1) {dprintf(fd, "%ld >> 守护进程运行中...\n", time(NULL));close(fd);}sleep(5);}return 0;
}
2. 编译并运行
gcc daemon_fork.c -o daemon_fork
./daemon_fork
3. 验证守护进程
ps aux | grep daemon_fork
tail -f /tmp/fork_demo.log
使用 daemon() 函数
1. 编写 C 程序 daemon_glibc.c
nano daemon_glibc.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>int main() {if (daemon(0, 0) == -1) { // 参数:nochdir=0, noclose=0perror("daemon() 失败");exit(1);}// 守护进程主逻辑while (1) {FILE *fp = fopen("/tmp/daemon_demo.log", "a");if (fp) {fprintf(fp, "%ld >> 守护进程运行中...\n", time(NULL));fclose(fp);}sleep(5);}return 0;
}
2. 编译并运行
gcc daemon_glibc.c -o daemon_glibc
./daemon_glibc
3. 验证守护进程
ps aux | grep daemon_glibc
tail -f /tmp/daemon_demo.log
验证守护进程特性
所有方法创建的守护进程应满足以下特征:
-
无控制终端:
ps -eo pid,tty,cmd | grep -E 'nohup|daemon_fork|daemon_glibc'
输出中的
TTY
列应为?
-
父进程为 init (PID 1):
ps -o ppid= -p <守护进程PID>
停止守护进程
# 查找 PID
ps aux | grep -E 'nohup|daemon_fork|daemon_glibc'# 杀死进程
sudo kill <PID>
方法 | 优点 | 缺点 |
---|---|---|
nohup | 无需编程,简单快速 | 功能有限,依赖 shell 环境 |
fork() | 完全控制守护进程行为 | 代码复杂度高 |
daemon() | glibc 封装,代码简洁 | 部分系统标记为弃用,灵活性低 |
三、 掌握gdb调试原理,在阿里云服务器或树莓派上用 gdb命令调试一个c程序
GDB 调试原理
GDB 基于以下核心机制实现调试功能:
- 进程控制:
- 通过
ptrace
系统调用监控目标进程。 - 控制被调试进程的执行(启动、暂停、单步执行)。
- 通过
- 断点机制:
- 在代码地址插入
int 3
指令(0xCC),触发中断信号 SIGTRAP。 - 断点命中后恢复原指令内容。
- 在代码地址插入
- 符号解析:
- 依赖编译时生成的调试信息(
-g
选项)。 - 映射机器指令到源代码行号、变量名等。
- 依赖编译时生成的调试信息(
- 内存访问:
- 直接读写被调试进程的内存空间。
- 支持查看寄存器、堆栈数据。
调试环境准备(通用步骤)
1. 安装 GDB
-
树莓派(Raspberry Pi OS):
sudo apt update && sudo apt install gdb
2. 编写测试程序
创建 test.c
:
nano test.C
输入以下内容:
#include <stdio.h>int sum(int a, int b) {return a + b;
}int main() {int x = 5, y = 3;int result = sum(x, y);printf("Result: %d\n", result);return 0;
}
3. 编译带调试信息的程序
gcc -g test.c -o test
关键点:必须使用 -g
选项保留调试符号
GDB 调试实战
1. 启动 GDB
gdb ./test
2. 常用调试命令
命令 | 作用 | 示例 |
---|---|---|
break [位置] | 设置断点 | break main |
run [参数] | 启动程序 | run |
next / n | 单步执行(跳过函数) | next |
step / s | 单步进入函数 | step |
print [变量] / p | 打印变量值 | print x |
info locals | 查看当前栈帧的局部变量 | info locals |
backtrace / bt | 查看函数调用栈 | backtrace |
continue / c | 继续运行到下一个断点 | continue |
quit | 退出 GDB | quit |
3. 完整调试流程演示
# 启动 GDB
(gdb) break main # 在 main 函数设置断点
(gdb) run # 启动程序# 程序会在 main 函数开头暂停(gdb) next # 执行 int x = 5, y = 3;
(gdb) print x # 输出 x 的值(应为 5)
(gdb) step # 进入 sum 函数
(gdb) info locals # 查看 a 和 b 的值
(gdb) next # 执行 return a + b;
(gdb) print $rax # 查看返回值(x86_64 架构)
(gdb) continue # 继续执行到程序结束
(gdb) quit #退出gdb
四、借助阿里云服务器,使用SSH反向代理,完成树莓派的外网访问
目标:让其他人可以从任何地方用笔记本电脑,通过访问阿里云服务器的端口(穿透)登录到你小组树莓派系统。
中转服务器设置
1.关闭对应端口的防火墙
sudo ufw allow 9623
2.设置ssh配置文件:
3.重启ssh
systemctl restart ssh
内网主机(树莓派)ssh连接中转服务器
ssh -p 22 -qngfNTR [端口号]:localhost:22 服务器账号@[服务器地址]
阿里云服务器查看监听端口:
ss -ntl
在另外一台电脑上输入,以下命令连接树莓派
ssh -p [你绑定的端口号] [树莓派用户]@云服务器地址
参考链接:
Linux守护进程的编写及使用方法
inux系统编程之进程(八)守护进程详解及创建,daemon()使用
一招教会你基于阿里云ECS服务器实现【内网穿透SSH访问家庭树莓派】
Ubuntu用autossh实现内网穿透(反向隧道)
GDB调试入门指南
原来gdb的底层调试原理这么简单
linux下如何自定义或编写一个守护进程