环境实时监控系统的开发日志

大概是18年8月份时做的嵌入式小项目的简单的日志

day01

搭建A9应用层线程关系框架

基础知识

线程
  1. 线程是参与内核调度最小基本单位,进程是拥有资源的最小基本单位
  2. 进程间相互独立,而同一个进程内的线程间共享进程内所有的资源
  3. 多线程间通信简单,但是需要对临界资源进行互斥与同步操作,多进程间通信较难
  4. 多线程安全性差,因为其中一个线程崩溃可能会对其他线程造成影响,多进程间相互独立,安全性高。

线程相关函数是由第三方库支持,所以编译时需要加上 -lpthread选项。

线程创建最常见的3个函数

void *pthread_func(void *arg)
{
	//线程退出,返回给主线程的参数(静态整型a)
	pthread_exit(&a);
}
int main(int argc, const char *argv[])
{
	pthread_t tid;
	//创建线程需要:ID,线程属性,线程要做的事,传给线程的参数(数组名msg)
	pthread_create(&tid,NULL,pthread_func,msg);
	//等待线程退出,捕获线程退出后的参数(整型指针p)
	pthread_join(tid,(void **)&p);
}
信号量

用于保护互斥资源

//头文件
#include <sys/types.h>
#include <semaphore.h>
#include <pthread.h>

//首先声明信号量全局变量
sem_t sem;


//要用信号量时先初始化
if(sem_init(&sem,0,0) != 0)
{
	perror("call sem_init fail");
	exit(-1);
}

//等待信号,会阻塞
sem_wait(&sem);

//释放信号
sem_post(&sem);
消息队列

消息队列是消息的链接表,存放在内核中。

其实质上就是一个内核链表,消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。

//从消息队列msgid中,获取类型type为100的信息到msg_rear中
//未获得则阻塞
msgrcv(msgid,&msg_rear,sizeof(msg_rear),100,0)

//消息队列没有消失时,给msgid线程发送停止信号
msgctl(msgid,IPC_RMID,NULL);

消息队列是生命周期是随内核的
命令行删除消息队列 ipcrm -q key号

同步与异步的概念

同步消息:进程A向进程B发送一个消息后,要等到进程B处理完由A发送来的 消息以后进程A才可以往下执行。否则进程A将一直等待不会往下执行。
异步消息:进程A向进程B发送一个消息后,进程A不管进程B处理完消息与否都往下执行。

进程

是操作系统对运行程序资源分配的基本单位,而线程是程序逻辑,调用的基本单位。在多线程的程序中,多个线程共享临界区资源,那么就会有问题,需要互斥锁解决。

每个临界资源都由一个互斥锁来保护,任何时刻最多只能有一个线程能访问该资源。

互斥锁

3个语句搞定,定义全局变量锁-开锁-临界资源-解锁:

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);

int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr)
功能:初始化一把锁
参数:mutex:需要初始化的锁名称
attr:锁的属性,通常设为NULL
返回值:成功返回0,失败返回-1

int pthread_mutex_lock(pthread_mutex_t *mutex)
功能:获取锁
参数:获取哪一把锁
返回值:成功返回0,失败返回errno

int pthread_mutex_unlock(pthread_mutex_t *mutex)
功能:将锁释放
参数:释放的锁
返回值:成功返回0,失败返回errno

int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:销毁一把锁
参数:销毁的锁
返回值:成功返回0,失败返回errno

进阶知识

条件变量

条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。

条件变量使我们可以睡眠等待某种条件出现,是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。

pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex)

函数中的第二个参数是一个互斥量,用于对条件变量保护。该函数会将调用线程阻塞在cond这个条件变量上,将它放在等待条件的线程列表中,同时对互斥锁解锁,是的其它线程可以访问资源,在得到其它线程对于cond所发出的信号后,该函数立马返回,并再次加锁。

pthread_cond_signal(pthread_cond_t *restrict cond)

这个函数就是用来给阻塞的线程发送信号来唤醒等待cond条件的线程。

通过这两个函数便实现了线程间的同步。而这两个函数的实际功能就是一个阻塞线程,另一个唤醒线程。

对照这上面的生产者消费者模型就是例如当生产者生产过剩,使得仓库满,此时生产者线程阻塞在g_full这个条件变量上,而消费者线程消费后改变了条件状态,通过发送信号来唤醒等待g_full条件的线程,此时生产者又可以继续生产。


在printf中%d用于int或者比int小的整数类型。比int小的类型被转型成int。

%ld用于long类型,%lld用于long long类型。

%x标识的数会被当成int进行读取,所以long long类型的数如果超过int的范围会被截断,得不到正确的结果。而且因为它多占了4个字节,还会影响后面的其它标识符的输出。

另外%f标识的数会被当成double读取,即取出8个字节读取。


day02

完善A9应用层线程间关系框架


问题清单

可以用宏PTHREAD_COND_INITIALIZER来初始化静态定义的条件变量,使其具有缺省属性。

全局变量互斥锁不能在声明时调用函数来初始化,用宏(包裹着函数)初始化好像又可以。

条件变量与互斥锁、信号量的区别
   1. 互斥锁必须总是由给它上锁的线程解锁,信号量的挂出即不必由执行过它的等待操作的同一进程执行。一个线程可以等待某个给定信号灯,而另一个线程可以挂出该信号灯。

   2. 互斥锁要么锁住,要么被解开(二值状态,类型二值信号量)。

   3. 由于信号量有一个与之关联的状态(它的计数值),信号量挂出操作总是被记住。然而当向一个条件变量发送信号时,如果没有线程等待在该条件变量上,那么该信号将丢失。

   4. 互斥锁是为了上锁而设计的,条件变量是为了等待而设计的,信号灯即可用于上锁,也可用于等待,因而可能导致更多的开销和更高的复杂性。

问题1:

主线程发消息到消息队列,可以被子线程接收消息处理;
但是,CGI线程发消息到消息队列,子进程接收到空消息;

原因1:

两个进程之间的消息队列是不一样的!

解决办法1:

CGI进程与主进程通过系统自带的消息队列snd,rcv通信

主进程把rcv到的消息入队(自定义的消息队列),由client_quest子线程鉴定分配给其他子线程执行

结果1:

失败-----CGI进程msgsnd执行出错

解决办法2:

在另一个文件里写CGI进程,让这个CGI发消息到消息队列。

结果2:

貌似成功。疑问:唤醒的线程执行完后,有循环到等待唤醒语句,就要等待再次被唤醒?

中文调试信息在linux下打印出来是乱码。

解决方法3:

继续尝试把CGI和主进程放在一个文件里,不放在一起


按照功能设计纲要,从前端用户交互,往MCU接收命令执行操作,做好框架:

  1. CGI发消息,要登录1+账号+密码->client——request唤醒数据库线程,挂一个要登录的任务链表->数据库操作线程处理任务链表->登录结果发回给CGI

问题1:

CGI只发了一条消息,然后主进程将这条消息重复入队

解决1:

仔细检查代码

问题2:

用while(1)把等待唤醒的函数pthread_cond_wait括起来,出现故障

解决2:

直接写pthread_cond_wait(&cond_sqlite,&mutex_sqlite);

  1. CGI(已登录)发消息,要查看货物信息->

目前进展:刚打通CGI命令和client_request

day03

  1. CGI发消息,要登录1+账号+密码->client——request唤醒数据库线程,挂一个要登录的任务链表->数据库操作线程处理任务链表->登录结果发回给CGI

数据库,打开,创建表

sqlite3 *db;char *errmsg;if(sqlite3_open("EmployeeManagement.db",&db) != SQLITE_OK){	printf("error :%s \n",sqlite3_errmsg(db));	exit(-1);}printf("success open\n");if(sqlite3_exec(db,"create table if not exists user (name VARCHAR(20),password VARCHAR(20),addr VARCHAR(100),\				age INT,level INT,no INT,phone VARCHAR(20),salary INT)",NULL,NULL,&errmsg) !=SQLITE_OK){	printf("%s\n",errmsg);	exit(-1);}if(sqlite3_exec(db,"create table if not exists master (name VARCHAR(20),password VARCHAR(20))",NULL,NULL,&errmsg) !=SQLITE_OK){	printf("%s\n",errmsg);	exit(-1);}

反复阅读设计参考资料

  1. 共享内存模块

基础回顾:

同步(synchronization)指的是多个任务(线程)按照约定的顺序相互配合完成一件事情。由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等 。

信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制。信号灯包括posix有名信号灯、 posix基于内存的信号灯(无名信号灯)和System V信号灯(IPC对象)

day04

共享内存,CGI进程与主进程实现结构体(仓库实时信息)共享

共享内存的特点:
(1)共享内存就是允许两个不想关的进程访问同一个内存
(2)共享内存是两个正在运行的进程之间共享和传递数据的最有效的方式
(3)不同进程之间共享的内存通常安排为同一段物理内存
(4)共享内存不提供任何互斥和同步机制,一般用信号量对临界资源进行保护。
(5)接口简单

共享内存

遇到问题:段错误

错误排除
第一个字段必须为long类型,即表明此发送消息的类型,msgrcv根据此接收消息。msgp定义的参照格式如下:struct s_msg{ /*msgp定义的参照格式*/ long type; /* 必须大于0,消息类型 */       char mtext[256]; /*消息正文,可以是其他任何类型*/} msgp;

unsigned char 0-255

day05

共享内存问题解决

key=ftok(".",'m');shmget(key,sizeof(env_info_client_addr),IPC_CREAT|0660);改为手动输入key值shmget(0x1233,sizeof(env_info_client_addr),IPC_CREAT|0660);

出现找不到文件的错误时,

semid=semget(key2,1,0666)) < 0改为semid=semget(key2,1,IPC_CREAT|0666)) < 0

把刷新共享内存的互斥锁和等待唤醒注释掉,出现段错误

信号量的工作原理

由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.举个例子,就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。

目前进度:

完成了与CGI的设置温度消息队列通信

尝试与CGI共享内存通信

完成了与M0的串口命令通信

把unsignal int转换成char类型,通过串口发送给M0

错误排除

共享内存里的数组在两个进程的长度不同,无法正常同步数据

day06

lockwait cond_unlock

为什么lock紧接着wait紧接着unlock

因为这里每一个线程都有各自的条件变量来判断是否唤醒它;
如果多个线程用同一个cond_来唤醒的话,才会在cond_后面接上对临界资源的操作。

day07

串口初始化的函数体写成另一个文件。

int和char之间的转换(通过指针强转*(int *))

C语言四个char型组成int型,和1个int型分成4个char型的方法 - CSDN博客

三个表:

goods:
dev_no goodsId

env:
dev_no temperatureMax temperatureMin humidityMax humidityMin illuminationMax illuminationMin

collect_env:
dev_no

开发工具:MK,UC,NotePad,VM,PDF

day08

问题1:

undefined reference to `pthread_client_request’

检查Makefile

问题2移植sqlite3的权限问题

sudo make

day09

CAREMA进程第一次唤醒后没有继续操作,紧接着第二次唤醒就会阻塞。

return;//退出当前函数
exit(-1);//退出整个程序

day10

串口接收时要循环,判断接受

day11

  1. 机器状态一口气发160个,通过zigbee发送,接收时只能接收32个字节大小的数据。
  2. 数组名----通过偏移找到其他数组原素
  3. 机器状态下,通过串口可以一次收发160字节数据

day12

测试:

  1. 设置温度上限————共享内存刷新的数据,即实时环境信息中,三种状态的最小值显示出错,前端已修复

  2. database is locked————sqlite事务和锁的机制没理解透,多线程并发执行约500条update语句后,发生冲突报错。

    解决办法1:在线程里,操作互斥资源时加锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要写一个 Java 监控系统,需要以下步骤: 1. 确定监控的目标:你想监控什么?是服务器性能?还是应用程序? 2. 选择监控工具:可以选择 Java Management Extensions (JMX) 或其他第三方工具,例如 JavaMelody、Hyperic HQ 等。 3. 编写代码:根据你选择的监控工具,编写代码实现监控功能。如果使用 JMX,可以创建一个 MBean 并通过 JMX API 访问它。 4. 配置监控:配置监控频率、数据收集方式、触发警告条件等。 5. 部署监控系统:将监控系统部署到生产环境中。 6. 监控数据分析:定期分析监控数据,以识别任何性能问题或故障。 希望这些步骤能帮助你完成写一个 Java 监控系统的任务。 ### 回答2: Java监控系统是用Java编写的一种软件系统,用于监控和管理Java应用程序的运行状态和性能指标。它可以实时地收集和分析应用程序的运行数据,提供实时监控、性能分析和故障诊断等功能。 在设计和开发Java监控系统时,需要考虑以下几个方面: 1. 数据收集:系统需要采集Java应用程序的运行数据,包括CPU使用率、内存占用、线程状态等。可以使用Java Management Extensions(JMX)来收集这些数据,并提供给监控系统进行分析。 2. 数据分析:监控系统需要对收集到的数据进行分析,以识别潜在的性能问题和故障。可以使用数据挖掘和机器学习算法来分析数据,并提供对应用程序性能的预测和优化建议。 3. 实时监控:监控系统需要提供实时的监控功能,可以通过图形界面或命令行界面展示Java应用程序的运行状态和性能指标。可以使用JavaFX或Swing等图形库来实现监控界面,并通过JMX连接到Java应用程序。 4. 告警和通知:监控系统需要能够发现Java应用程序的异常情况,并及时向管理员发送告警通知。可以设置阈值来监控关键性能指标,当指标超过阈值时,触发告警通知。 5. 日志记录:监控系统需要记录Java应用程序的运行日志,以便故障发生时进行问题定位和分析。可以使用日志框架如Log4j或SLF4J来记录日志,并存储到数据库或文件中。 6. 扩展性:监控系统应具备良好的扩展性,能够适应不同规模和复杂度的Java应用程序。可以通过模块化设计和插件机制来实现系统的可扩展性,使其能够方便地添加和删除功能模块。 综上所述,Java监控系统是一个功能强大的软件系统,通过收集和分析Java应用程序的运行数据,为管理员提供实时监控、性能分析和故障诊断等功能,帮助优化和维护Java应用程序的稳定运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值