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


    大概是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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    信号量

    用于保护互斥资源

    //头文件
    #include 
    #include 
    #include 
    
    //首先声明信号量全局变量
    sem_t sem;
    
    
    //要用信号量时先初始化
    if(sem_init(&sem,0,0) != 0)
    {
    	perror("call sem_init fail");
    	exit(-1);
    }
    
    //等待信号,会阻塞
    sem_wait(&sem);
    
    //释放信号
    sem_post(&sem);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    消息队列

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

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

    //从消息队列msgid中,获取类型type为100的信息到msg_rear中
    //未获得则阻塞
    msgrcv(msgid,&msg_rear,sizeof(msg_rear),100,0)
    
    //消息队列没有消失时,给msgid线程发送停止信号
    msgctl(msgid,IPC_RMID,NULL);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    消息队列是生命周期是随内核的
    命令行删除消息队列 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);
    
    • 1
    • 2
    • 3

    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)
    
    • 1

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

    pthread_cond_signal(pthread_cond_t *restrict cond)
    
    • 1

    这个函数就是用来给阻塞的线程发送信号来唤醒等待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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    问题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

    反复阅读设计参考资料

    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;
    
    • 1

    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);
    
    • 1

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

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

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

    信号量的工作原理

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

    目前进度:

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

    尝试与CGI共享内存通信

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

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

    错误排除

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

    day06

    lockwait cond_unlock
    
    • 1

    为什么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:在线程里,操作互斥资源时加锁

  • 相关阅读:
    6.CF431E Chemistry Experiment 权值线段树+二分
    mongodb 入门笔记
    STM32F103通用定时器介绍(中断实验)
    代码随想录算法训练营DAY29(记录)|C++回溯算法Part.5|491.递增子序列、46.全排列、47.全排列II
    OAuth2密码模式已死,最先进的Spring Cloud认证授权方案在这里
    SpringMVC多文件上传
    常回家看看之house_of_cat
    xgboost 与 lgbm
    南卡和FIIL 哪个更好用?南卡和FIIL CC nano蓝牙耳机对比测评
    TinyWebServer学习笔记-threadpool
  • 原文地址:https://blog.csdn.net/qq_42534809/article/details/126685603