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

起因

最近各种网购平台似乎都在推送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/tempfd = 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);//显示博主本人IDdisp->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秒显示一次DEMOwhile(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/tempfd = 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.hqwc.cn/news/125440.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

redis高可用(主从复制,哨兵,集群)

目录 一、主从复制&#xff1a; 1.主从复制介绍&#xff1a; 2.主从复制的作用&#xff1a; 3.主从复制流程&#xff1a; 4.搭建Redis 主从复制&#xff1a; 4.1 环境准备&#xff1a; 4.2 安装redis&#xff1a; 4.3 master节点修改 Redis 配置文件&#xff1a; 4.4 slave节点…

【数据结构与算法】树、二叉树的概念及结构(详解)

前言: &#x1f4a5;&#x1f388;个人主页:​​​​​​Dream_Chaser&#xff5e; &#x1f388;&#x1f4a5; ✨✨专栏:http://t.csdn.cn/oXkBa ⛳⛳本篇内容:c语言数据结构--树以及二叉树的概念与结构 目录 一.树概念及结构 1.树的概念 1.1树与非树 树的特点&#xff1…

【C++设计模式之装饰模式:结构型】分析及示例

装饰模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许在运行时动态地给一个对象添加额外的行为。 描述 装饰模式通过创建一个包装器&#xff08;Wrapper&#xff09;来包裹原始对象&#xff0c;并在原始对象的行为前后添加额外的功能。…

前端 | AjaxAxios模块

文章目录 1. Ajax1.1 Ajax介绍1.2 Ajax作用1.3 同步异步1.4 原生Ajax 2. Axios2.1 Axios下载2.2 Axios基本使用2.3 Axios方法 1. Ajax 1.1 Ajax介绍 Ajax: 全称&#xff08;Asynchronous JavaScript And XML&#xff09;&#xff0c;异步的JavaScript和XML。 1.2 Ajax作用 …

定时器+按键控制LED流水灯模式+定时器时钟——“51单片机”

各位CSDN的uu们好呀&#xff0c;今天&#xff0c;小雅兰的内容是51单片机中的定时器以及按键控制LED流水灯模式&定时器时钟&#xff0c;下面&#xff0c;让我们进入51单片机的世界吧&#xff01;&#xff01;&#xff01; 定时器 按键控制LED流水灯模式 定时器时钟 源代…

第一百六十四回 如何实现NumberPicker

文章目录 1.概念介绍2.使用方法2.1 NumberPicker2.2 CupertinoPicker 3.示例代码4.内容总结 我们在上一章回中介绍了"如何在任意位置显示PopupMenu"相关的内容&#xff0c;本章回中将介绍如何实现NumberPicker.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1.概…

3D孪生场景搭建:参数化模型

1、什么是参数化模型 参数化模型是指通过一组参数来定义其形状和特征的数学模型或几何模型。这些参数可以用于控制模型的大小、形状、比例、位置、旋转、曲率等属性&#xff0c;从而实现对模型进行灵活的调整和变形。 在计算机图形学和三维建模领域&#xff0c;常见的参数化模…

Android改造CardView为圆形View,Kotlin

Android改造CardView为圆形View&#xff0c;Kotlin 可以利用androidx.cardview.widget.CardView的cardCornerRadius特性&#xff0c;将CardView改造成一个圆形的View&#xff0c;技术实现的关键首先设定CardView为一个宽高相等的View&#xff08;正方形&#xff09;&#xff0c…

华为云云耀云服务器L实例评测|Elasticsearch的Docker版本的安装和参数设置 端口开放和浏览器访问

前言 最近华为云云耀云服务器L实例上新&#xff0c;也搞了一台来玩&#xff0c;期间遇到各种问题&#xff0c;在解决问题的过程中学到不少和运维相关的知识。 本篇博客介绍Elasticsearch的Docker版本的安装和参数设置&#xff0c;端口开放和浏览器访问。 其他相关的华为云云…

Day-07 修改 Nginx 配置文件

至此&#xff1a; 简单的 Docker 安装 Nginx并启动算是成功了! ps: 如何修改 Nginx的配置、更改nginx 的资源文件&#xff1f; eg&#xff1a; 1、可以将容器中的目录和本机目录做映射。 2、达到修改本机目录文件就影响到容器中的文件。 1.本机创建实例文件夹 新建目录&#x…