Linux学习之悟空派上实现OLED的无线网IP及CPU温度显示【守护进程】

news2024/12/26 10:38:43

起因

最近各种网购平台似乎都在推送99元的悟空派全志H3的开发板,出于好奇就买了一块来试试水,由于这块板子基本上和orangepi-Zero的硬件结构一模一样,所以设备树、boot这些就用orangepi现成的部件了。
因为本人比较喜欢使用SSH操作,但是在不同环境下使用连接的WiFi不一样,所以对应的IP地址也就不一样,所以我给悟空派弄了一个0.98寸的OLED,用来显示CPU温度和当前IP地址,并5秒会刷新一次数据内容,为我连接SSH提供了一定的帮助,但是由于该任务是开机启动的前台任务,就导致我的串口Shell始终处于显示该线程打印数据内容的状态下,导致我无法在新环境连接新WiFi,所以考虑如何解决该问题。

处理方案

由于该任务我希望从设备启动时运行到设备关机时关闭,不希望其与终端产生关联,故在linux环境中找到较为合适的处理方案便为将守护进程。

守护进程(Daemon)

代蒙(希腊文:δαίμων、拉丁文:Dæmon、英文:Daemon)是希腊神话中的一种介于神与人之间的精灵或妖魔。它们与神祇的区别在于精灵并不具有人的外貌,而是一种善恶并存的超自然存在。在罗马神话中,代蒙称为格尼烏斯(Genius)。

代蒙无处不在,伴随着人的一生。 根据古希腊唯心主义哲学派的解释,人一生下来一直到死亡都有代蒙伴随并支配他的一切行动。

从这个英文单词的意义可以得知,daemon在linux系统中就是伴随linux运行一生并支配其行动的东西,这个东西善恶并存,是否就可以理解成daemon的好坏取决于编写daemon的我们,且无论好坏都将伴随linux的整个运行周期。

下面是标准理解,此处借鉴了大佬 JMW1407文章中 【Linux】守护进程( Daemon)的定义,作用,创建流程_daemon的作用-CSDN博客

1、定义

守护进程是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或循环等待处理某些事件的发生;它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。
守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机才随之一起停止运行;
守护进程一般都以root用户权限运行,因为要使用某些特殊的端口(1-1024)或者资源;
守护进程的父进程一般都是init进程,因为它真正的父进程在fork出守护进程后就直接退出了,所以守护进程都是孤儿进程,由init接管;
守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。
守护进程的名称通常以d结尾,比如sshd、xinetd、crond等

2、作用

  1. 守护进程是一个生存周期较长的进程,通常独立于控制终端并且周期性的执行某种任务或者等待处理某些待发生的事件
  2. 大多数服务都是通过守护进程实现的
  3. 关闭终端,相应的进程都会被关闭,而守护进程却能够突破这种限制

功能实现

基本源码

首先大家可以看一下我是如何在悟空派使用OLED显示IP地址和温度的,源代码如下

#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <syslog.h>

#define MAXFILE 65535

#include <netinet/in.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>

#include "oled.h"
#include "font.h"

#define TEMP_PATH "/sys/class/thermal/thermal_zone0/temp"
#define MAX_SIZE 32

#define ETH_NAME "wlan0"

double temp_get(void){
    int fd;
    double temp = 0;
    char buf[MAX_SIZE];
    // 开/sys/class/thermal/thermal_zoneo/temp
    fd = open(TEMP_PATH,O_RDONLY);
    if (fd < 0) {
        fprintf(stderr,"failed to open thermal_zoneo/tempin");
        return -1;
    }
    //读取内容
    if(read(fd,buf,MAX_SIZE) < 0) {
        fprintf(stderr,"fatled to read tempin");
        return -1;
    }
    // 转换为浮点数打印
    temp = atoi(buf);
        temp /= 1000;
    // printf("temp: %.2f\n", temp);
    //关闭文件
    close(fd);
        return temp;
}

//获取当前IP地址
char* get_local_ip()
{
        int sock;
        struct sockaddr_in sin;
        struct ifreq ifr;
        sock = socket(AF_INET, SOCK_DGRAM, 0);
        if (sock == -1) {
                perror("socket");
                return NULL;
        }
        strncpy(ifr.ifr_name, ETH_NAME, IFNAMSIZ);
        ifr.ifr_name[IFNAMSIZ - 1] = 0;
        if (ioctl(sock, SIOCGIFADDR, &ifr) < 0) {
                perror("ioctl");
                return NULL;
        }

        memcpy(&sin, &ifr.ifr_addr, sizeof(sin));
        return inet_ntoa(sin.sin_addr);
}

//OLED显示的基本框架
int oled_demo(struct display_info *disp) {
        int i = 0;
        char buf[100];
        double temp = 0;
    	//从系统获取并在OLED上显示温度
        temp = temp_get();
        disp->font = font3;
        sprintf(buf, "CPU-Temp: %.2f C", temp);
        oled_putstrto(disp, 0, 0, buf);
    	//从系统中获取并在OLED显示当前IP地址
        char* local_ip = get_local_ip();
        sprintf(buf, "WIP:%s", local_ip);
        oled_putstrto(disp, 0, 10, buf);
    	//显示博主本人ID
        disp->font = font1;
        oled_putstrto(disp, 0, 20, "----ASWaterbenben----");
    	//打印分隔符
        disp->font = font2;
        for(i=0;i<128;i++)
                oled_putpixel(disp, i, 30, 1);
        oled_putstrto(disp, 0, 32, " ");
        oled_putstrto(disp, 0, 37, "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ ");
    	//打印结束标记
        disp->font = font1;
        oled_putstrto(disp, 0, 54, "*********************");
        oled_send_buffer(disp);
		return 0;
}

//故障输出暂不启用
void show_error(int err, int add) {
        //const gchar* errmsg;
        //errmsg = g_strerror(errno);
        // printf("\nERROR: %i, %i\n\n", err, add);
        //printf("\nERROR\n");
}

//用户输出暂不启用
void show_usage(char *progname) {
        // printf("\nUsage:\n%s <I2C bus device node >\n", progname);
}

int main(int argc, char **argv) {
        int i;
        int e;
        char filename[32];
        struct display_info disp;

        if (argc < 2) {
                show_usage(argv[0]);
                return -1;
        }
		//清空显示句柄
        memset(&disp, 0, sizeof(disp));
        sprintf(filename, "%s", argv[1]);
        disp.address = OLED_I2C_ADDR;
        disp.font = font2;
		//打开OLED的设备驱动
        e = oled_open(&disp, filename);

        char *buf = "OLED fresh\r\n";
        int len = strlen(buf);

        if (e < 0) {
                show_error(1, e);
        }
        else {
            	//OLED设备初始化
                e = oled_init(&disp);
                if (e < 0) {
                        show_error(2, e);
                }
                else {
                    	//循环每隔5秒显示一次DEMO
                        while(1)
                        {
                                oled_demo(&disp);
                                sleep(5);
                        }
                }
        }
        return 0;
}

显示效果

效果如下图所示
在这里插入图片描述

但是在终端启动该功能后,将终端关闭则该进程也就同时关闭,并未达到预期效果,故考虑使用守护进程的处理方法进行修改;

守护进程实现逻辑和源码

脱离终端控制

由于在终端中启动的进程寿命与终端一致。终端被关闭则进程也被关闭,故首先需要脱离终端控制;

使用umask(0);为当前进程获取最大访问权限
使用fork();函数创建子进程,使用exit(0);退出父进程

完成上述操作后,终端认为父进程已退出,此时可继续进行终端操作。

独立进程

父进程退出后虽然终端可以继续操作,但是新建的子进程依旧归属于父进程所在的进程组、会话期、控制终端。故需要是因setsid();函数将子进程从父进程组中独立出来。

由于此时没有终端作为进程状态输出界面,故需要以其他形式将当前进程反馈的信息打印并保存,便于后续检查线程的运行情况,故使用openlog(“daemon”, LOG_PID, LOG_DAEMON);打开日志服务;
进程中调用 chdir() 函数,让根目录 ”/” 成为当前进程的工作目录 ,防止线程源文件所在位置是可卸载的存储介质,若介质被移除导致进程终端的问题。

进程垃圾处理

清除父进程连带可能产生的已启动文件,由于父进程已经关闭,但是当前进程会继承父进程已经打开的文件,这些文件当前进程并未使用,则将所有继承下来已打开的进程完全关闭。

加入功能代码

/*
 * Copyright (c) 2015, Vladimir Komendantskiy
 * MIT License
 *
 * SSD1306 demo of block and font drawing.
 */

//
// fixed for OrangePiZero by HypHop
//

#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <syslog.h>

#define MAXFILE 65535

#include <netinet/in.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>

#include "oled.h"
#include "font.h"

#define TEMP_PATH "/sys/class/thermal/thermal_zone0/temp"
#define MAX_SIZE 32

#define ETH_NAME "wlan0"

double temp_get(void){
    int fd;
    double temp = 0;
    char buf[MAX_SIZE];
    // 开/sys/class/thermal/thermal_zoneo/temp
    fd = open(TEMP_PATH,O_RDONLY);
    if (fd < 0) {
        fprintf(stderr,"failed to open thermal_zoneo/tempin");
        return -1;
    }
    //读取内容
    if(read(fd,buf,MAX_SIZE) < 0) {
        fprintf(stderr,"fatled to read tempin");
        return -1;
    }
    // 转换为浮点数打印
    temp = atoi(buf);
        temp /= 1000;
    // printf("temp: %.2f\n", temp);
    //关闭文件
    close(fd);
        return temp;
}


char* get_local_ip()
{
        int sock;
        struct sockaddr_in sin;
        struct ifreq ifr;
        sock = socket(AF_INET, SOCK_DGRAM, 0);
        if (sock == -1) {
                perror("socket");
                return NULL;
        }
        strncpy(ifr.ifr_name, ETH_NAME, IFNAMSIZ);
        ifr.ifr_name[IFNAMSIZ - 1] = 0;
        if (ioctl(sock, SIOCGIFADDR, &ifr) < 0) {
                perror("ioctl");
                return NULL;
        }

        memcpy(&sin, &ifr.ifr_addr, sizeof(sin));
        return inet_ntoa(sin.sin_addr);
}


int oled_demo(struct display_info *disp) {
        int i = 0;
        char buf[100];
        double temp = 0;
        temp = temp_get();
        disp->font = font3;
        sprintf(buf, "CPU-Temp: %.2f C", temp);
        oled_putstrto(disp, 0, 0, buf);
        //putstrto(disp, 0, 0, "Spnd spd  2468 rpm");
        //      oled_putstrto(disp, 0, 9+1, "Spnd cur  0.46 A");
        char* local_ip = get_local_ip();
        sprintf(buf, "WIP:%s", local_ip);
        oled_putstrto(disp, 0, 10, buf);
        disp->font = font1;
        //      oled_putstrto(disp, 0, 18+2, "Spnd tmp    53 C");
        oled_putstrto(disp, 0, 20, "----ASWaterbenben----");
        disp->font = font2;
        //      oled_putstrto(disp, 0, 27+3, "DrvX tmp    64 C");
        for(i=0;i<128;i++)
                oled_putpixel(disp, i, 30, 1);
        oled_putstrto(disp, 0, 32, " ");
        oled_putstrto(disp, 0, 37, "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ ");
        disp->font = font1;
        //      oled_putstrto(disp, 0, 54, "Total cur  2.36 A");
        oled_putstrto(disp, 0, 54, "*********************");
        oled_send_buffer(disp);

        disp->font = font3;
        // for (i=0; i<100; i++) {
        //      sprintf(buf, "Spnd spd  %d rpm", i);
        //      oled_putstrto(disp, 0, 0, buf);
        //      oled_putstrto(disp, 135-i, 36+4, "===");
        //      oled_putstrto(disp, 100, 0+i/2, ".");
        //      oled_send_buffer(disp);
        // }
        //oled_putpixel(disp, 60, 45);
        //oled_putstr(disp, 1, "hello");

return 0;
}

void show_error(int err, int add) {
        //const gchar* errmsg;
        //errmsg = g_strerror(errno);
        // printf("\nERROR: %i, %i\n\n", err, add);
        //printf("\nERROR\n");
}

void show_usage(char *progname) {
        // printf("\nUsage:\n%s <I2C bus device node >\n", progname);
}

int main(int argc, char **argv) {

        printf("pid = %d\n", getpid());
        int i;
        int fd;
        pid_t pid;

        // 第一步
        umask(0);
        // 第二步
        pid = fork();
        if(pid < 0) {
                perror("fork error!");
                exit(1);
        } else if(pid > 0) {
                exit(0);
        }
        // 打开系统日志服务
        openlog("daemon", LOG_PID, LOG_DAEMON);
        // 第三步
        setsid();

        // 第四步
        chdir("/");

        // 第五步
        for (i = 0; i < MAXFILE; ++i) {
                close(i);
        }
        signal(SIGCHLD,SIG_IGN);


        int e;
        char filename[32];
        struct display_info disp;

        if (argc < 2) {
                show_usage(argv[0]);

                return -1;
        }

        memset(&disp, 0, sizeof(disp));
        sprintf(filename, "%s", argv[1]);
        disp.address = OLED_I2C_ADDR;
        disp.font = font2;

        e = oled_open(&disp, filename);

        char *buf = "OLED fresh\r\n";
        int len = strlen(buf);

        if (e < 0) {
                show_error(1, e);
        }
        else {
                e = oled_init(&disp);
                if (e < 0) {
                        show_error(2, e);
                }
                else {
                        while(1)
                        {
                                oled_demo(&disp);
                                fd = open("/tmp/deamon.log", O_CREAT|O_WRONLY|O_
                                if (fd < 0) {
                                        syslog(LOG_ERR, "open");
                                        exit(1);
                                }
                                write(fd, buf, len+1);
                                close(fd);
                                sleep(5);
                        }
                }
        }
        closelog();
        return 0;
}

编译与执行

使用gcc命令将修改好的c文件编译为out文件,并将out文件加入到开机自启任务中

即在 /etc/rc.local中的exit(0);前添加/home/orangepi/Cprogram/temp/oled.sh(oled.sh为已经授权的sh运行脚本,运行脚本核心是将编译完成的执行文件带入硬件固有的I2C硬件接口),oled.sh内容如下:

#!/bin/bash
cd /home/orangepi/Cprogram/temp
./oled_demo /dev/i2c-0

/home/orangepi/Cprogram/temp为执行文件所在路径

oled_demo就是编译完成的执行文件,/dev/i2c-0为OLED屏幕当前使用的硬件I2C接口。

效果

效果就不太好展示了,大概口头说一下,就是我的悟空派在启动后OLED就被点亮,并在屏幕上显示当前CPU温度、当前无线网IP地址,同时串口终端也成正常终端的状态,每次屏幕内容刷新都会在/tmp/deamon.log日志文件中打印一条消息OLED fresh。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1060765.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全志ARM926 Melis2.0系统的开发指引①

全志ARM926 Melis2.0系统的开发指引① 1. 编写目的2. Melis2.0 系统概述3. Melis2.0 快速开发3.1. Melis2.0 SDK 目录结构3.2. Melis2.0 编译环境3.3. Melis2.0 固件打包3.4. Melis2.0 固件烧录3.5.串口打印信息3.6. Melis2.0 添加和调用一个模块3.6.1. 为什么划分模块&#xf…

Sentinel-微服务保护

一、初识Sentinel 1、雪崩问题及解决方案 雪崩问题 微服务调用链路中的某个服务故障&#xff0c;引起整个链路中的所有微服务都不可用&#xff0c;这就是雪崩。 解决雪崩问题的常见方式有四种&#xff1a; 1、超时处理&#xff1a;设定超时时间&#xff0c;请求超过一定时间…

【C语言】内存函数的详细教学和模拟实现

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是gugugu。希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f194;本文由 gugugu 原创 CSDN首发&#x1f412; 如需转载还请通知⚠…

全志ARM926 Melis2.0系统的开发指引②

全志ARM926 Melis2.0系统的开发指引② 编写目的4. 编译工具链使用4.1.工具链通用配置4.2.模块的工具链配置4.3.简单的 makefile 5. 固件烧录工具的安装5.1.PhoenixSuit 的安装步骤5.2.检验 USB 驱动安装5.3.使用烧录软件 PhoenixSuit -全志相关工具和资源-.1 全志固件镜像修改工…

Python:操作SQLite数据库简单示例

本文用最简单的示例演示python标准库提供的SQLite数据库进行新增、查询数据的过程。 代码文件app.py # -*- coding: UTF-8 -*- from flask import Flask import sqlite3app Flask(__name__)app.route(/) def hello_world():return Hello World!#创建数据库 app.route(/creat…

Go基础之变量和常量

Go基础之变量和常量 文章目录 Go基础之变量和常量一. 标识符、关键字、内置类型和函数1.1 标识符1.2 关键字1.3 保留字1.4 内置类型1.4.1 值类型&#xff1a;1.4.2 引用类型&#xff1a;(指针类型)1.5 内置函数1.6 内置接口error 二.Go变量命名规范2.1 采用驼峰体命名2.2 简单、…

Python无废话-办公自动化Excel图表制作

openpyxl 支持用Excel工作表中单元格的数据&#xff0c;创建条形图、折线图、散点图和饼图等。 图表制作步骤 在openpyxl模块中创建图表&#xff0c;步骤如下: ①选择一个单元格区域&#xff0c;创建Reference 对象&#xff0c;作为图形数据a)(Value)。 ②创建一个Chart对象…

阿里云ECS服务器上启动的portainer无法访问的问题

如下图&#xff0c;在阿里云ECS服务器上安装并启动了portainer&#xff0c;但是在自己电脑上访问不了远程的portainer。 最后发现是要在网络安全组里开放9000端口号&#xff0c;具体操作如下&#xff1a; 在云服务器管理控制台点击左侧菜单中的网络与安全-安全组&#xff0c;然…

阻塞队列--线程安全问题

之前的队列在很多场景下都不能很好地工作&#xff0c;例如 大部分场景要求分离向队列放入&#xff08;生产者&#xff1a;主要调用offer方法&#xff09;、从队列拿出&#xff08;消费者&#xff1a;主要调用poll方法&#xff09;两个角色、它们得由不同的线程来担当&#xff0…

uboot启动流程-uboot内存分配工作总结

一. uboot 启动流程 _main 函数中会调用 board_init_f 函数&#xff0c;本文继续简单分析一下 board_init_f 函数。 本文继续具体分析 board_init_f 函数。 本文继上一篇文章的学习&#xff0c;地址如下&#xff1a; uboot启动流程-uboot内存分配_凌肖战的博客-CSDN博客 二…

【C语言】浮点数在内存中的存储和读取——底层分析

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是gugugu。希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f194;本文由 gugugu 原创 CSDN首发&#x1f412; 如需转载还请通知⚠…

【12】c++设计模式——>单例模式练习(任务队列)

属性&#xff1a; &#xff08;1&#xff09;存储任务的容器&#xff0c;这个容器可以选择使用STL中的队列&#xff08;queue) &#xff08;2&#xff09;互斥锁&#xff0c;多线程访问的时候用于保护任务队列中的数据 方法&#xff1a;主要是对任务队列中的任务进行操作 &…

MySql运维篇---008:日志:错误日志、二进制日志、查询日志、慢查询日志,主从复制:概述 虚拟机更改ip注意事项、原理、搭建步骤

1. 日志 1.1 错误日志 错误日志是 MySQL 中最重要的日志之一&#xff0c;它记录了当 mysqld 启动和停止时&#xff0c;以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时&#xff0c;建议首先查看此日志。 该日志是默认开启的&a…

[激光原理与应用-71]:光电传感器的工作原理详解:光电效应原理、类型、光信号的光谱、电信号的频谱

目录 前言&#xff1a; 一、光电传感器组成 二、光电元件&#xff1a;光电效应的分类 2.1 外光电效应&#xff1a;逸出效应 2.2 内光电效应&#xff1a;光电导效应 2.3 内光电效应&#xff1a;光生伏特效应&#xff08;电流效应&#xff09; 2.3.1 光电转换元件PD 三、…

云安全之等级保护详解

等级保护概念 网络安全等级保护&#xff0c;是对信息系统分等级实行安全保护&#xff0c;对信息系统中使用的安全产品实行按等级管理&#xff0c;对信息系统中发生的信息安全事件分等级进行响应、处置。 网络安全等级保护的核心内容是&#xff1a;国家制定统一的政策、标准&a…

【ldt_struct】0ctf2021-kernote

前言 题目给的文件系统是 ext4&#xff0c;所以我们只需要将其挂载即可使用&#xff1a; 1、创建一个空目录 2、使用 mount 将其挂载即可 3、使用 umount 卸载即可完成打包 开启了 smap、smep、kaslr 和 kpti 保护&#xff0c;并且给了如下内核编译选项&#xff1a; Her…

SpringBoot大文件上传实现分片、断点续传

大文件上传流程 客户端计算文件的哈希值&#xff0c;客户端将哈希值发送给服务端&#xff0c;服务端检查数据库或文件系统中是否已存在相同哈希值的文件&#xff0c;如果存在相同哈希值的文件&#xff0c;则返回秒传成功结果&#xff0c;如果不存在相同哈希值的文件&#xff0…

GO 中的指针?

本文也主要聊聊在 GO 中的指针和内存&#xff0c;希望对你有点帮助 如果你学习过 C 语言&#xff0c;你就非常清楚指针的高效和重要性 使用 GO 语言也是一样&#xff0c;项目代码中&#xff0c;不知道你是否会看到函数参数中会传递各种 map&#xff0c;slice &#xff0c;自定…

使用正则表达式批量修改函数

贪心匹配&#xff0c;替换中的$1代表括号中的第一组。 使用[\s\S\r]代表所有字符&#xff0c;同时加个问号代表不贪心匹配:

【RP-RV1126】烧录固件使用记录

文章目录 烧录完整固件进入MASKROM模式固件烧录升级中&#xff1a;升级完成&#xff1a; 烧录部分进入Loader模式选择文件切换loader模式 烧录完整固件 完整固件就是update.img包含了所有的部件&#xff0c;烧录后可以直接运行。 全局编译&#xff1a;./build.sh all生成固件…