守护进程编程

目录

一、守护进程

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);
    
    // 关闭标准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

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 - 博客园

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值