守护进程编程

目录

一、守护进程

[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()函数创建)

二、gdb调试

三、SSH反向代理,完成树莓派外网访问

[3.1 阿里云服务器准备](#3.1 阿里云服务器准备)

3.2在树莓派上设置SSH反向隧道

[3.3 验证反向隧道是否成功](#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);
    
    // 关闭标准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命令创建

  1. 创建测试脚本

    nano ~/test_daemon.sh

输入下面内容:

复制代码
#!/bin/bash

while true; do
    echo "$(date): Daemon is running on Raspberry Pi" >> /tmp/daemon.log
    sleep 5
done
  1. 设置权限并运行

    chmod +x ~/test_daemon.sh nohup ~/test_daemon.sh > /dev/null 2>&1 &

  2. 检查运行情况

查看进程

复制代码
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;
}
  1. 编译运行

    gcc ~/fork_daemon.c -o ~/fork_daemon ~/fork_daemon

  2. 检查运行情况

    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;
}
  1. 编译运行

    gcc ~/daemon_func.c -o ~/daemon_func ~/daemon_func

  2. 检查运行情况

查看进程

复制代码
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 - 博客园

相关推荐
路上^_^27 分钟前
CSS核心笔记002
css·笔记·html
The_SkyUzi1 小时前
数据可视化笔记:柱状图
笔记·信息可视化
njsgcs2 小时前
chili3d调试笔记5 直接加入js和大模型对话 trae
笔记
大白的编程日记.3 小时前
【Linux学习笔记】Linux的环境变量和命令行参数
linux·笔记·学习
汐汐咯4 小时前
神经网络与模型训练过程笔记
笔记
zyhhsss4 小时前
大模型应用开发自学笔记
网络·人工智能·笔记·神经网络·学习·自然语言处理
谦川4 小时前
蓝桥杯 二进制问题 刷题笔记
笔记·职场和发展·蓝桥杯
Magnetic_h5 小时前
【iOS】alloc & init & new底层原理
笔记·学习·ios·objective-c
pumpkin845145 小时前
学习笔记十九——Rust多态
笔记·学习·rust