守护进程编程
目录
一、守护进程
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 - 博客园