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

【嵌入式】【阿里云服务器】【树莓派】学习守护进程编程、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博客

相关文章:

【嵌入式】【阿里云服务器】【树莓派】学习守护进程编程、gdb调试原理和内网穿透信息

目录 一. 守护进程的含义及编程实现的主要过程 1.1守护进程 1.2编程实现的主要过程 二、在树莓派中通过三种方式创建守护进程 2.1nohup命令创建 2.2fork()函数创建 2.3daemon()函数创建 三、在阿里云中通过三种方式创建守护进程 3.1nohup命令创建 3.2fork()函数创建 …...

数据结构学习笔记 :树与二叉树详解

目录 树的基本概念二叉树的定义与特性二叉树的存储结构 3.1 顺序存储 3.2 链式存储二叉树遍历特殊二叉树类型总结与应用场景 一、树的基本概念 核心定义 树&#xff1a;由根节点和若干子树构成的层次结构。叶子节点&#xff08;终端节点&#xff09;&#xff1a;没有子节点的…...

前沿篇|CAN XL 与 TSN 深度解读

引言 1. CAN XL 标准演进与设计目标 2. CAN XL 物理层与帧格式详解 3. 时间敏感网络 (TSN) 关键技术解析 4. CAN XL + TSN 在自动驾驶领域的典型应用...

七、LangChain Tool类参数对接机制解析:基于Pydantic的类型安全与流程实现

LangChain 的 Tool 类(包括 BaseTool 和 StructuredTool)通过 参数校验、输入解析、函数调用 的流程,将外部函数与 Agent 的逻辑对接。以下是其内部逻辑的详细解析: 1. 工具与函数对接的核心机制 (1) 工具的定义方式 LangChain 提供了两种主要方式定义工具: 继承 BaseTo…...

Spring-AI-alibaba 结构化输出

1、将模型响应转换为 ActorsFilms 对象实例&#xff1a; ActorsFilms package com.alibaba.cloud.ai.example.chat.openai.entity;import java.util.List;public record ActorsFilms(String actor, List<String> movies) { } GetMapping("/toBean")public Ac…...

AI大模型科普:从零开始理解AI的“超级大脑“,以及如何用好提示词?

大家好&#xff0c;小机又来分享AI了。 今天分享一些新奇的东西&#xff0c; 你有没有试过和ChatGPT聊天时&#xff0c;心里偷偷犯嘀咕&#xff1a;"这AI怎么跟真人一样对答如流&#xff1f;它真的会思考吗&#xff1f;" 或者刷到技术文章里满屏的"Token"…...

STM32单片机入门学习——第40节: [11-5] 硬件SPI读写W25Q64

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难&#xff0c;但我还是想去做&#xff01; 本文写于&#xff1a;2025.04.18 STM32开发板学习——第一节&#xff1a; [1-1]课程简介第40节: [11-5] 硬件SPI读…...

【Java学习笔记】关键字汇总

Java 关键字汇总 用于定义数据类型的关键字&#xff1a; classinterfaceenumbyteshortintlongfloatdoublecharbooleanvoid 用于定义数据值的关键字&#xff1a; truefalsenull 用于定义流程控制的关键字&#xff1a; ifelseswitchcasedefaultwhiledoforbreakcontinueretu…...

langgraph框架之初识

1.什么是langgraph&#xff1f; LangGraph 是一个用于构建可控代理的底层编排框架。在AI中&#xff0c;代理也就是执行动作的智能体&#xff0c;也就是agent。使用这个框架可以构建一个可以自由控制的智能执行体&#xff0c;它可以帮我们做许多事情&#xff0c;如下&#xff1…...

如何将 .txt 文件转换成 .md 文件

一、因为有些软件上传文件的时候需要 .md 文件&#xff0c;首先在文件所在的目录中&#xff0c;点击“查看”&#xff0c;然后勾选上“文件扩展名”&#xff0c;这个时候该目录下的所有文件都会显示其文件类型了。 二、这时直接对目标的 .txt 文件进行重命名&#xff0c;把后缀…...

pdfjs库使用记录1

import React, { useEffect, useState, useRef } from react; import * as pdfjsLib from pdfjs-dist; // 设置 worker 路径 pdfjsLib.GlobalWorkerOptions.workerSrc /pdf.worker.min.js; const PDFViewer ({ url }) > { const [pdf, setPdf] useState(null); const […...

Qt 创建QWidget的界面库(DLL)

【1】新建一个qt库项目 【2】在项目目录图标上右击&#xff0c;选择Add New... 【3】选择模版&#xff1a;Qt->Qt设计师界面类&#xff0c;选择Widget&#xff0c;填写界面类的名称、.h .cpp .ui名称 【4】创建C调用接口&#xff08;默认是创建C调用接口&#xff09; #ifnd…...

Django REST framework 并结合 `mixin` 的示例

下面为你提供一个使用 Django REST framework 并结合 mixin 的示例,该示例将实现一个简单的图书管理 API。 项目需求 我们要创建一个图书管理系统的 API,支持对图书信息的创建、读取、更新和删除操作。 实现步骤 1. 项目初始化 首先,确保你已经安装了 Django 和 Django…...

linux查看及修改用户过期时间

修改用户有效期 密码到期时间 sudo chage -E 2025-12-31 username sudo chage -M 180 username sudo chage -d $(date %F) username 查询用户密码到期时间 for user in $(cat /etc/passwd |cut -d: -f1); do echo $user; chage -l $user | grep "Password expires"; …...

Vue.directive自定义v-指令

翻阅文章有感&#xff0c;记录学习 vue前端菜单权限控制_vue权限管理菜单思路-CSDN博客 一、定义&#xff1a;Vue.directive是Vue框架中给开发者用于注册自定义指令和返回已注册指令的API 二、基本语法&#xff1a; // 注册 Vue.directive(my-directive, {bind: function () …...

AI Agent 元年,于 2025 开启

私人博客传送门 AI Agent 元年&#xff0c;于 2025 开启 | 魔筝炼药师...

Django 自带开发服务器

$ python manage.py runserver $ python manage.py runserver 666 # 用 666 端口 $ python manage.py runserver 0.0.0.0:8000 # 让局域网内其他客户端也可访问 $ python manage.py runserver --skip-checks # 跳过检查自动检查 $ python manage.py runserver --…...

Spring 数据库编程

Spring JDBC 传统的JDBC在操作数据库时&#xff0c;需要先打开数据库连接&#xff0c;执行SQL语句&#xff0c;然后封装结果&#xff0c;最后关闭数据库连接等资源。频繁的数据库操作会产生大量的重复代码&#xff0c;造成代码冗余&#xff0c;Spring的JDBC模块负责数据库资源…...

进阶篇|CAN FD 与性能优化

引言 1. CAN vs. CAN FD 对比 2. CAN FD 帧结构详解...

CTF--各种绕过哟

一、原网页&#xff1a; 二、步骤&#xff1a; 1.源代码&#xff1a; <?php highlight_file(flag.php); $_GET[id] urldecode($_GET[id]); $flag flag{xxxxxxxxxxxxxxxxxx}; if (isset($_GET[uname]) and isset($_POST[passwd])) {if ($_GET[uname] $_POST[passwd])pr…...

【Pandas】pandas DataFrame where

Pandas2.2 DataFrame Indexing, iteration 方法描述DataFrame.head([n])用于返回 DataFrame 的前几行DataFrame.at快速访问和修改 DataFrame 中单个值的方法DataFrame.iat快速访问和修改 DataFrame 中单个值的方法DataFrame.loc用于基于标签&#xff08;行标签和列标签&#…...

嵌入式ARM RISCV toolchain工具 梳理arm-none-eabi-gcc

嵌入式TOOLchain工具 梳理 简介 本文总结和梳理一下一些toolchain的规则和原理&#xff0c;方便后续跨平台的时候&#xff0c;给大家使用toolchain做一个参考。 解释如何理解arm-none-eabi-gcc等含义&#xff0c;以及如何一看就知道该用什么编译器。 当然如果有哪里写的不是…...

OpenBMC:BmcWeb log输出

BmcWeb的log函数定义于:http\logging.hpp 说实话,个人觉得这一版的log函数有点炫技,使用起来也没有之前的版本方便,不过也还是值的参考一下。 1.如何输出log BMCWEB_LOG_ERROR("GetAll on path {} iface {} service {} failed with code {}",objectPath, inte…...

复现SCI图像增强(Toward fast, flexible, and robust low-light image enhancement.)

运行train.py报错 > File "/home/uriky/桌面/SCI-main/SCI-main/train.py", line 105, in main > train_queue torch.utils.data.DataLoader( File "/home/uriky/anaconda3/envs/AA/lib/python3.8/site-packages/torch/utils/data/dataloader.py&q…...

深入理解C++中string的深浅拷贝

目录 一、引言 二、浅拷贝与深拷贝的基本概念 2.1 浅拷贝 2.2 深拷贝 在C 中&#xff0c; string 类的深浅拷贝有着重要的区别。 浅拷贝 深拷贝 string 类中的其他构造函数及操作 resize 构造 构造&#xff08;赋值构造&#xff09; 构造&#xff08;拼接构造&#xf…...

性能测试面试题的详细解答

以下是性能测试面试题的详细解答&#xff1a; 1. 性能测试的流程是怎样的&#xff1f; 性能测试流程通常包括以下几个步骤&#xff1a; - **需求分析**&#xff1a;明确测试目标、性能指标&#xff08;如响应时间、吞吐量等&#xff09;。 - **环境搭建**&#xff1a;搭建测试环…...

第八篇:系统分析师第三遍——3、4章

目录 一、目标二、计划三、完成情况四、意外之喜(最少2点)1.计划内的明确认知和思想的提升标志2.计划外的具体事情提升内容和标志 五、总结 一、目标 通过参加考试&#xff0c;训练学习能力&#xff0c;而非单纯以拿证为目的。 1.在复习过程中&#xff0c;训练快速阅读能力、掌…...

Unity粒子特效打包后不显示

1.粒子发mesh&#xff0c;如果打包后不显示&#xff0c;尝试勾选r/w 2.如果还不行&#xff0c;mesh重做&#xff0c;目前发现ab包打出的&#xff0c;有的mesh会出问题&#xff0c;暂时原因不详。...

PFC 是什么?

现在进行液晶电视机和等离子电视机电路分析时、故障维修时&#xff0c;都经常的提到“PFC 电路”一词&#xff0c;这 在早期的电视机中是没有的&#xff0c;早期维修电视机的师傅从来没有接触过的&#xff0c;但是 PFC 电路是目前液晶电视机 和等离子电视机中不可缺少的电路。那…...

6.5 GitHub监控系统实战:双通道采集+动态调度打造高效运维体系

GitHub Sentinel Agent 定期更新功能设计与实现 关键词:GitHub API 集成、定时任务调度、Python 爬虫开发、SMTP 邮件通知、系统稳定性保障 1. GitHub 项目数据获取功能 1.1 双通道数据采集架构设计 #mermaid-svg-ZHJIMXcMAyDHVhmV {font-family:"trebuchet ms",v…...