分类目录:摩登3平台开户

摩登3咨询:_小白如何自学单片机?

很多刚开始学习单片机的小伙伴最苦恼的就是如何入门,不知道从哪一部分开始、在哪里查找学习资源、按照怎样的学习步骤进行学习,而且摸索学习步骤的过程在浪费时间的同时也会降低学习兴趣。为了帮助大家解决这种情况,小编将单片机达人的学习经验进行了整理,以文章的形式分享给大家。 一、基础理论知识 要掌握理论知识,第一步还是要通读一遍教材,这样我们才能站在巨人的肩膀上。《电工基础》、《电路分析》、《模拟电路》、《数字电路》、《电子制作》等电子技术基础知识一定要先通读。 (1)电场与磁场:库仑定律、高斯定理、环路定律、电磁感应定律。 (2)直流电路:电路基本元件、欧姆定律、基尔霍夫定律、叠加原理、戴维南定理。 (3)正弦交流电路:正弦量三要素、有效值、复阻抗、单相和三相电路计算、功率及功率因数、串联与并联谐振、安全用电常识。 (4)RC和RL电路暂态过程:三要素分析法。 (5)变压器与电动机:变压器的电压、电流和阻抗变换、三相异步电动机的使用、常用继电-接触器控制电路。 (6)半导体及二极管及整流、滤波、稳压电路。 (7)三极管及单管放大电路、信号处理电路、信号发生电路、功率放大电路、直流稳压电源等。 (8)电子产品工艺流程、电子产品的结构和装配、调试和检修。 (9)线性集成运算放大器和运算电路及理想运放组成的比例、加减和积分运算电路。 (10)数字基础及逻辑函数化简、集成逻辑门电路、组合逻辑电路和RS、D、JK触发器,时序逻辑电路。 (11)多谐振荡器、单稳态触发器、施密特触发器的结构、工作原理、参数计算和应用。 (12)数模和模数转换等相关内容。 二、常用软件 (1)Protel99se、AltiumDesigner9等PCB电路设计软件。 (2)Multisim11、Proteus7.8等电子电路原理仿真设计软件。 (3)Keil、Progisp20等单片机应用程序开发平台相关设计软件。 三、资料检索 很多时候遇到问题,要查找资料的时候却不知道去哪里找,这里小编给大家推荐三个网站:GitHub,StackOverflow,中国知网。 GitHub 程序员都会用到的一个代码托管网站,熟悉的人就不用我多说了。在上面可以搜索到很多很好的开源项目。 StackOverflow 英文网站,英文水平要好。上面可以搜索到很多技术细节上的问题,回答大多都会比较靠谱,有点类似知乎,但问题主要是IT相关的。 中国知网 如果你想做一个项目但是还不知道应该从哪里入手或遇到了技术上的阻碍,就可以在这里搜一搜,期刊和论文的一般会有转载,有助于你系统了解相关的知识。如果是在校大学生,使用校园网应该是可以免费下载文档的。如果不是,可以上某宝租账号下载。 四、实践 实践是检验真理的唯一标准。对一个学单片机的新手来说,如果按教科书式的学法,上来就是一大堆指令和名词,结果学了半天还是搞不清这些指令的作用,也许用不了几天就会觉得枯燥乏味以至于半途而废。 所以学习与实践结合是一个很好的方法,边学习、边演练,循序渐进,这样用不了几次就能将所用到的指令理解、吃透、扎根于脑海,甚至让这些指令“根深蒂固”。 -END- | 整理文章为传播相关技术,版权归原作者所有 |  | 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

摩登3娱乐登录地址_手把手教你升级Keil MDK的ARM编译器

作者 | strongerHuang 微信公众号 | strongerHuang 今天在我的技术交流群里,有朋友问了这么一个问题:怎么才能用更高的编译器呀? 这位朋友给了一张图: 从上图可以看得出来,这位朋友使用的Keil MDK并不是最新版本。目前(2020-11) 最新MDK版本为V5.32 ,默认编译器版本为V6.14.1: 如果我想使用V6.15版本编译器该怎么操作呢?那么下面就来讲讲:怎样将Keil MDK的编译器升级为最新的编译器(更换为指定版本的编译器): 下载ARM编译器 安装ARM编译器 Keil 配置编译器 额外说明 strongerHuang 1 下载ARM编译器 这里不一定是使用最新的编译,我们也可以使用老版本的,目前官方提供了历史版本供大家下载。 通过浏览器自带下载器可能比较慢,推荐使用迅雷,很多都有镜像,速度相对快点。 strongerHuang 2 安装ARM编译器 安装之前需要提醒一点,根据你Keil MDK版本不同,支持的编译器可能存在兼容问题。比如MDK是V4版本,建议下载32位版本。 我这里以ARM编译器Windows 64位为例,安装过程比较傻瓜式,基本一路“next”即可。 这里建议修改一下路径: 安装完成之后,会有相关的说明文档,可以看下: strongerHuang 3 Keil 配置编译器 Keil MDK里面有很多配置选项,这里推荐大家阅读我的《 Keil系列教程 》。 1.打开工程管理,进入“Folders/Extensions”选项栏 可以通过菜单:Project -> Manage -> Project items进入。 也可以通过工具栏工程管理快捷图标: 2.修改(新增)编译器 3.工程选择编译器 新增编译器之后,就可以在工程配置中选择新增的编译器了: 此时就可以和往常一样正常使用了。 strongerHuang 4 额外说明 1.编译器注册 不管是Keil MDK,还是ARM编译器都是收费的工具,就会牵涉到注册的问题,如果按照上面的步骤直接使用ARM新增的编译,可能会出现如下错误: 意思就是没有进行注册,此时就需要花钱购买正版了。。。 不想花钱购买正版的同学自己想办法了,下面方法不要说是我给大家的哈: 正确注册之后,就会没问题: 2.工程选项配置的变化 如果我们选择了不同的编译器,可能你Keil MDK的工程选项就会发生变化: 当然,这个变化与你MDK版本还是有一定关系(不同版本可能不同)。 最后,大家想要阅读更多关于 Keil MDK的内容,后台回复”Keil” 即可查看。 ———— END ———— 推荐阅读: C语言实现面向对象的原理 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

摩登3登录_分享一个通过STM32的bin文件逆向分析代码的工具

转载:芯片之家 来源 | 阿莫论坛,作者 | ilovepp 首先你要有一个bin文件(bin文件的获取方法不在此展开介绍,今后有机会可以专门开一个贴聊一聊)。本次实验用到的bin文件 stm32_xwrtos.bin.zip (6 KB) 是用ST官方CMSIS和外设库编译的跑在stm32f103c8t6上的bin文件,比较具有代表性。 烧录文件下载 工具准备: 安装开源跨平台逆向神器r2: r2可运行在Windows、Linux、Mac等所有主流操作系统上(r2有多牛逼不在此展开介绍,今后有机会可以专门开一个贴聊一聊)。 实验步骤: 1)输入r2 -a arm -b 16 -m 0x08000000 stm32_xwrtos.bin 进入r2的控制台后,输入e asm.cpu=cortex。这一步是告诉r2以0x08000000为基址加载stm32_xwrtos.bin文件,并设置指令集为cortex系列的thumb。 2)输入aaaa,运行自动化分析。 3)输入pxw 268 @0x08000000 以小端四字节形式打印从0x08000000开始的268(268对应中断向量表大小)个字节,同时打开ST官方的启动文件startup_stm32f10x_md.s并找到.isr_vector段进行对照。 4)接着上一步,输入fs notes,创建并切换到一个名为notes(可以是任意其他名字)的符号记事本;按照f flag=address的形式,对照.isr_vector段,向符号记事本中录入感兴趣的符号地址对应关系。注意如果address是函数地址则需要减1(因为thumb指令的要求,具体原因此处不展开);最后输入af @flag形式的命令强制进行函数分析。 5) 输入pdf @Reset_Handler,对Reset_Handler函数进行反汇编 输入 VV 切换到流程图视图 通过阅读汇编代码,可以得到以下信息: 1.  data段的地址区间为0x20000000-0x20000028 bss段的地址区间为0x20000028-0x2000043C 2.  Reset_Handler用0x08002cbc开始的0x28字节初始化data段,用0填充bss段 3. 调用fcn.08001cc8函数 4. 调用fcn.08001734函数 6)输入pdf @fcn.08001cc8进行反汇编 发现对外设寄存器地址0x40021000的引用 通过查询数据手册,可判断fcn.08001cc8函数应为系统初始化化函数SystemInit(),它初始化了时钟。 7)根据经验判断fcn.0800173函数即为main函数 本篇主要作用是带大家熟悉和习惯r2的基本使用,以及对逆向有个感性认识。后面有机会再给大家讲述在逆向的基础上介绍stm32上的栈溢出漏洞的挖掘与利用。 再次感谢ilovepp兄分享的精彩文章! ———— END ———— 推荐阅读: C语言实现面向对象的原理 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

摩登3测速登陆_刨根问底,Kafka消息中间件到底会不会丢消息

大型互联网公司一般都会要求消息传递最大限度的不丢失,比如用户服务给代金券服务发送一个消息,如果消息丢失会造成用户未收到应得的代金券,最终用户会投诉。 为避免上面类似情况的发生,除了做好补偿措施,更应该在系设计的时候充分考虑各种异常,设计一个稳定、高可用的消息系统。 认识Kafka 看一下维基百科的定义 Kafka是分布式发布-订阅消息系统。它最初由LinkedIn公司开发,之后成为Apache项目的一部分。 Kafka是一个分布式的,可划分的,冗余备份的持久性的日志服务。它主要用于处理活跃的流式数据。 kafka架构 Kafka的整体架构非常简单,是显式分布式架构,主要由producer、broker(kafka)和consumer组成。 Kafka架构(精简版) Producer(生产者)可以将数据发布到所选择的topic(主题)中。生产者负责将记录分配到topic的哪一个 partition(分区)中。可以使用循环的方式来简单地实现负载均衡,也可以根据某些语义分区函数(如记录中的key)来完成。 Consumer(消费者)使用一个consumer group(消费组)名称来进行标识,发布到topic中的每条记录被分配给订阅消费组中的一个消费者实例。消费者实例可以分布在多个进程中或者多个机器上。 Kafka到底会不会丢失消息? 在讨论kafka是否丢消息前先来了解一下什么是消息传递语义。 消息传递语义 message delivery semantic 也就是消息传递语义,简单说就是消息传递过程中消息传递的保证性。主要分为三种: at most once:最多一次。消息可能丢失也可能被处理,但最多只会被处理一次。 at least once:至少一次。消息不会丢失,但可能被处理多次。可能重复,不会丢失。 exactly once:精确传递一次。消息被处理且只会被处理一次。不丢失不重复就一次。 理想情况下肯定是希望系统的消息传递是严格exactly once,也就是保证不丢失、只会被处理一次,但是很难做到。 回到主角Kafka,Kafka有三次消息传递的过程: 生产者发消息给Kafka Broker。 Kafka Broker 消息同步和持久化 Kafka Broker 将消息传递给消费者。 在这三步中每一步都有可能会丢失消息,下面详细分析为什么会丢消息,如何最大限度避免丢失消息。 生产者丢失消息 先介绍一下生产者发送消息的一般流程(部分流程与具体配置项强相关,这里先忽略): 生产者是与leader直接交互,所以先从集群获取topic对应分区的leader元数据; 获取到leader分区元数据后直接将消息发给过去; Kafka Broker对应的leader分区收到消息后写入文件持久化; Follower拉取Leader消息与Leader的数据保持一致; Follower消息拉取完毕需要给Leader回复ACK确认消息; Kafka Leader和Follower分区同步完,Leader分区会给生产者回复ACK确认消息。 生产者发送数据流程 生产者采用push模式将数据发布到broker,每条消息追加到分区中,顺序写入磁盘。消息写入Leader后,Follower是主动与Leader进行同步。 Kafka消息发送有两种方式:同步(sync)和异步(async),默认是同步方式,可通过producer.type属性进行配置。 Kafka通过配置request.required.acks属性来确认消息的生产: 0表示不进行消息接收是否成功的确认;不能保证消息是否发送成功,生成环境基本不会用。 1表示当Leader接收成功时确认;只要Leader存活就可以保证不丢失,保证了吞吐量。 -1或者all表示Leader和Follower都接收成功时确认;可以最大限度保证消息不丢失,但是吞吐量低。 kafka producer 的参数acks 的默认值为1,所以默认的producer级别是at least once,并不能exactly once。 敲黑板了,这里可能会丢消息的! 如果acks配置为0,发生网络抖动消息丢了,生产者不校验ACK自然就不知道丢了。 如果acks配置为1保证leader不丢,但是如果leader挂了,恰好选了一个没有ACK的follower,那也丢了。 all:保证leader和follower不丢,但是如果网络拥塞,没有收到ACK,会有重复发的问题。 Kafka Broker丢失消息 Kafka Broker 接收到数据后会将数据进行持久化存储,你以为是下面这样的: 消息持久化,无cache 没想到是这样的: 消息持久化,有cache 操作系统本身有一层缓存,叫做 Page Cache,当往磁盘文件写入的时候,系统会先将数据流写入缓存中,至于什么时候将缓存的数据写入文件中是由操作系统自行决定。 Kafka提供了一个参数 producer.type 来控制是不是主动flush,如果Kafka写入到mmap之后就立即 flush 然后再返回 Producer 叫同步 (sync);写入mmap之后立即返回 Producer 不调用 flush 叫异步 (async)。 敲黑板了,这里可能会丢消息的! Kafka通过多分区多副本机制中已经能最大限度保证数据不会丢失,如果数据已经写入系统 cache 中但是还没来得及刷入磁盘,此时突然机器宕机或者掉电那就丢了,当然这种情况很极端。 消费者丢失消息 消费者通过pull模式主动的去 kafka 集群拉取消息,与producer相同的是,消费者在拉取消息的时候也是找leader分区去拉取。 多个消费者可以组成一个消费者组(consumer group),每个消费者组都有一个组id。同一个消费组者的消费者可以消费同一topic下不同分区的数据,但是不会出现多个消费者消费同一分区的数据。 消费者群组消费消息 消费者消费的进度通过offset保存在kafka集群的__consumer_offsets这个topic中。 消费消息的时候主要分为两个阶段: 1、标识消息已被消费,commit offset坐标; 2、处理消息。 场景一:先commit再处理消息。如果在处理消息的时候异常了,但是offset 已经提交了,这条消息对于该消费者来说就是丢失了,再也不会消费到了。 场景二:先处理消息再commit。如果在commit之前发生异常,下次还会消费到该消息,重复消费的问题可以通过业务保证消息幂等性来解决。 总结 那么问题来了,kafka到底会不会丢消息?答案是:会! Kafka可能会在三个阶段丢失消息: (1)生产者发送数据; (2)Kafka Broker 存储数据; (3)消费者消费数据; 在生产环境中严格做到exactly…

摩登3登录_破4!《我想进大厂》之Java基础夺命连环16问

说说进程和线程的区别? 进程是程序的一次执行,是系统进行资源分配和调度的独立单位,他的作用是是程序能够并发执行提高资源利用率和吞吐率。 由于进程是资源分配和调度的基本单位,因为进程的创建、销毁、切换产生大量的时间和空间的开销,进程的数量不能太多,而线程是比进程更小的能独立运行的基本单位,他是进程的一个实体,可以减少程序并发执行时的时间和空间开销,使得操作系统具有更好的并发性。 线程基本不拥有系统资源,只有一些运行时必不可少的资源,比如程序计数器、寄存器和栈,进程则占有堆、栈。 知道synchronized原理吗? synchronized是java提供的原子性内置锁,这种内置的并且使用者看不到的锁也被称为监视器锁,使用synchronized之后,会在编译之后在同步的代码块前后加上monitorenter和monitorexit字节码指令,他依赖操作系统底层互斥锁实现。他的作用主要就是实现原子性操作和解决共享变量的内存可见性问题。 执行monitorenter指令时会尝试获取对象锁,如果对象没有被锁定或者已经获得了锁,锁的计数器+1。此时其他竞争锁的线程则会进入等待队列中。 执行monitorexit指令时则会把计数器-1,当计数器值为0时,则锁释放,处于等待队列中的线程再继续竞争锁。 synchronized是排它锁,当一个线程获得锁之后,其他线程必须等待该线程释放锁后才能获得锁,而且由于Java中的线程和操作系统原生线程是一一对应的,线程被阻塞或者唤醒时时会从用户态切换到内核态,这种转换非常消耗性能。 从内存语义来说,加锁的过程会清除工作内存中的共享变量,再从主内存读取,而释放锁的过程则是将工作内存中的共享变量写回主内存。 实际上大部分时候我认为说到monitorenter就行了,但是为了更清楚的描述,还是再具体一点。 如果再深入到源码来说,synchronized实际上有两个队列waitSet和entryList。 当多个线程进入同步代码块时,首先进入entryList 有一个线程获取到monitor锁后,就赋值给当前线程,并且计数器+1 如果线程调用wait方法,将释放锁,当前线程置为null,计数器-1,同时进入waitSet等待被唤醒,调用notify或者notifyAll之后又会进入entryList竞争锁 如果线程执行完毕,同样释放锁,计数器-1,当前线程置为null 那锁的优化机制了解吗? 从JDK1.6版本之后,synchronized本身也在不断优化锁的机制,有些情况下他并不会是一个很重量级的锁了。优化机制包括自适应锁、自旋锁、锁消除、锁粗化、轻量级锁和偏向锁。 锁的状态从低到高依次为无锁->偏向锁->轻量级锁->重量级锁,升级的过程就是从低到高,降级在一定条件也是有可能发生的。 自旋锁:由于大部分时候,锁被占用的时间很短,共享变量的锁定时间也很短,所有没有必要挂起线程,用户态和内核态的来回上下文切换严重影响性能。自旋的概念就是让线程执行一个忙循环,可以理解为就是啥也不干,防止从用户态转入内核态,自旋锁可以通过设置-XX:+UseSpining来开启,自旋的默认次数是10次,可以使用-XX:PreBlockSpin设置。 自适应锁:自适应锁就是自适应的自旋锁,自旋的时间不是固定时间,而是由前一次在同一个锁上的自旋时间和锁的持有者状态来决定。 锁消除:锁消除指的是JVM检测到一些同步的代码块,完全不存在数据竞争的场景,也就是不需要加锁,就会进行锁消除。 锁粗化:锁粗化指的是有很多操作都是对同一个对象进行加锁,就会把锁的同步范围扩展到整个操作序列之外。 偏向锁:当线程访问同步块获取锁时,会在对象头和栈帧中的锁记录里存储偏向锁的线程ID,之后这个线程再次进入同步块时都不需要CAS来加锁和解锁了,偏向锁会永远偏向第一个获得锁的线程,如果后续没有其他线程获得过这个锁,持有锁的线程就永远不需要进行同步,反之,当有其他线程竞争偏向锁时,持有偏向锁的线程就会释放偏向锁。可以用过设置-XX:+UseBiasedLocking开启偏向锁。 轻量级锁:JVM的对象的对象头中包含有一些锁的标志位,代码进入同步块的时候,JVM将会使用CAS方式来尝试获取锁,如果更新成功则会把对象头中的状态位标记为轻量级锁,如果更新失败,当前线程就尝试自旋来获得锁。 整个锁升级的过程非常复杂,我尽力去除一些无用的环节,简单来描述整个升级的机制。 简单点说,偏向锁就是通过对象头的偏向线程ID来对比,甚至都不需要CAS了,而轻量级锁主要就是通过CAS修改对象头锁记录和自旋来实现,重量级锁则是除了拥有锁的线程其他全部阻塞。 那对象头具体都包含哪些内容? 在我们常用的Hotspot虚拟机中,对象在内存中布局实际包含3个部分: 对象头 实例数据 对齐填充 而对象头包含两部分内容,Mark Word中的内容会随着锁标志位而发生变化,所以只说存储结构就好了。 对象自身运行时所需的数据,也被称为Mark Word,也就是用于轻量级锁和偏向锁的关键点。具体的内容包含对象的hashcode、分代年龄、轻量级锁指针、重量级锁指针、GC标记、偏向锁线程ID、偏向锁时间戳。 存储类型指针,也就是指向类的元数据的指针,通过这个指针才能确定对象是属于哪个类的实例。 如果是数组的话,则还包含了数组的长度 对于加锁,那再说下ReentrantLock原理?他和synchronized有什么区别? 相比于synchronized,ReentrantLock需要显式的获取锁和释放锁,相对现在基本都是用JDK7和JDK8的版本,ReentrantLock的效率和synchronized区别基本可以持平了。他们的主要区别有以下几点: 等待可中断,当持有锁的线程长时间不释放锁的时候,等待中的线程可以选择放弃等待,转而处理其他的任务。 公平锁:synchronized和ReentrantLock默认都是非公平锁,但是ReentrantLock可以通过构造函数传参改变。只不过使用公平锁的话会导致性能急剧下降。 绑定多个条件:ReentrantLock可以同时绑定多个Condition条件对象。 ReentrantLock基于AQS(AbstractQueuedSynchronizer 抽象队列同步器)实现。别说了,我知道问题了,AQS原理我来讲。 AQS内部维护一个state状态位,尝试加锁的时候通过CAS(CompareAndSwap)修改值,如果成功设置为1,并且把当前线程ID赋值,则代表加锁成功,一旦获取到锁,其他的线程将会被阻塞进入阻塞队列自旋,获得锁的线程释放锁的时候将会唤醒阻塞队列中的线程,释放锁的时候则会把state重新置为0,同时当前线程ID置为空。 CAS的原理呢? CAS叫做CompareAndSwap,比较并交换,主要是通过处理器的指令来保证操作的原子性,它包含三个操作数: 变量内存地址,V表示 旧的预期值,A表示 准备设置的新值,B表示 当执行CAS指令时,只有当V等于A时,才会用B去更新V的值,否则就不会执行更新操作。 那么CAS有什么缺点吗? CAS的缺点主要有3点: ABA问题:ABA的问题指的是在CAS更新的过程中,当读取到的值是A,然后准备赋值的时候仍然是A,但是实际上有可能A的值被改成了B,然后又被改回了A,这个CAS更新的漏洞就叫做ABA。只是ABA的问题大部分场景下都不影响并发的最终效果。 Java中有AtomicStampedReference来解决这个问题,他加入了预期标志和更新后标志两个字段,更新时不光检查值,还要检查当前的标志是否等于预期标志,全部相等的话才会更新。 循环时间长开销大:自旋CAS的方式如果长时间不成功,会给CPU带来很大的开销。 只能保证一个共享变量的原子操作:只对一个共享变量操作可以保证原子性,但是多个则不行,多个可以通过AtomicReference来处理或者使用锁synchronized实现。 好,说说HashMap原理吧? HashMap主要由数组和链表组成,他不是线程安全的。核心的点就是put插入数据的过程,get查询数据以及扩容的方式。JDK1.7和1.8的主要区别在于头插和尾插方式的修改,头插容易导致HashMap链表死循环,并且1.8之后加入红黑树对性能有提升。 put插入数据流程 往map插入元素的时候首先通过对key hash然后与数组长度-1进行与运算((n-1)&hash),都是2的次幂所以等同于取模,但是位运算的效率更高。找到数组中的位置之后,如果数组中没有元素直接存入,反之则判断key是否相同,key相同就覆盖,否则就会插入到链表的尾部,如果链表的长度超过8,则会转换成红黑树,最后判断数组长度是否超过默认的长度*负载因子也就是12,超过则进行扩容。 get查询数据 查询数据相对来说就比较简单了,首先计算出hash值,然后去数组查询,是红黑树就去红黑树查,链表就遍历链表查询就可以了。 resize扩容过程 扩容的过程就是对key重新计算hash,然后把数据拷贝到新的数组。 那多线程环境怎么使用Map呢?ConcurrentHashmap了解过吗? 多线程环境可以使用Collections.synchronizedMap同步加锁的方式,还可以使用HashTable,但是同步的方式显然性能不达标,而ConurrentHashMap更适合高并发场景使用。 ConcurrentHashmap在JDK1.7和1.8的版本改动比较大,1.7使用Segment+HashEntry分段锁的方式实现,1.8则抛弃了Segment,改为使用CAS+synchronized+Node实现,同样也加入了红黑树,避免链表过长导致性能的问题。 1.7分段锁 从结构上说,1.7版本的ConcurrentHashMap采用分段锁机制,里面包含一个Segment数组,Segment继承与ReentrantLock,Segment则包含HashEntry的数组,HashEntry本身就是一个链表的结构,具有保存key、value的能力能指向下一个节点的指针。 实际上就是相当于每个Segment都是一个HashMap,默认的Segment长度是16,也就是支持16个线程的并发写,Segment之间相互不会受到影响。 put流程 其实发现整个流程和HashMap非常类似,只不过是先定位到具体的Segment,然后通过ReentrantLock去操作而已,后面的流程我就简化了,因为和HashMap基本上是一样的。 计算hash,定位到segment,segment如果是空就先初始化 使用ReentrantLock加锁,如果获取锁失败则尝试自旋,自旋超过次数就阻塞获取,保证一定获取锁成功 遍历HashEntry,就是和HashMap一样,数组中key和hash一样就直接替换,不存在就再插入链表,链表同样 get流程 get也很简单,key通过hash定位到segment,再遍历链表定位到具体的元素上,需要注意的是value是volatile的,所以get是不需要加锁的。 1.8CAS+synchronized 1.8抛弃分段锁,转为用CAS+synchronized来实现,同样HashEntry改为Node,也加入了红黑树的实现。主要还是看put的流程。 put流程 首先计算hash,遍历node数组,如果node是空的话,就通过CAS+自旋的方式初始化 如果当前数组位置是空则直接通过CAS自旋写入数据 如果hash==MOVED,说明需要扩容,执行扩容 如果都不满足,就使用synchronized写入数据,写入数据同样判断链表、红黑树,链表写入和HashMap的方式一样,key hash一样就覆盖,反之就尾插法,链表长度超过8就转换成红黑树 get查询 get很简单,通过key计算hash,如果key hash相同就返回,如果是红黑树按照红黑树获取,都不是就遍历链表获取。 volatile原理知道吗? 相比synchronized的加锁方式来解决共享变量的内存可见性问题,volatile就是更轻量的选择,他没有上下文切换的额外开销成本。使用volatile声明的变量,可以确保值被更新的时候对其他线程立刻可见。volatile使用内存屏障来保证不会发生指令重排,解决了内存可见性的问题。 我们知道,线程都是从主内存中读取共享变量到工作内存来操作,完成之后再把结果写会主内存,但是这样就会带来可见性问题。举个例子,假设现在我们是两级缓存的双核CPU架构,包含L1、L2两级缓存。 线程A首先获取变量X的值,由于最初两级缓存都是空,所以直接从主内存中读取X,假设X初始值为0,线程A读取之后把X值都修改为1,同时写回主内存。这时候缓存和主内存的情况如下图。 线程B也同样读取变量X的值,由于L2缓存已经有缓存X=1,所以直接从L2缓存读取,之后线程B把X修改为2,同时写回L2和主内存。这时候的X值入下图所示。 那么线程A如果再想获取变量X的值,因为L1缓存已经有x=1了,所以这时候变量内存不可见问题就产生了,B修改为2的值对A来说没有感知。 image-20201111171451466 那么,如果X变量用volatile修饰的话,当线程A再次读取变量X的话,CPU就会根据缓存一致性协议强制线程A重新从主内存加载最新的值到自己的工作内存,而不是直接用缓存中的值。 再来说内存屏障的问题,volatile修饰之后会加入不同的内存屏障来保证可见性的问题能正确执行。这里写的屏障基于书中提供的内容,但是实际上由于CPU架构不同,重排序的策略不同,提供的内存屏障也不一样,比如x86平台上,只有StoreLoad一种内存屏障。 StoreStore屏障,保证上面的普通写不和volatile写发生重排序 StoreLoad屏障,保证volatile写与后面可能的volatile读写不发生重排序 LoadLoad屏障,禁止volatile读与后面的普通读重排序 LoadStore屏障,禁止volatile读和后面的普通写重排序 那么说说你对JMM内存模型的理解?为什么需要JMM? 本身随着CPU和内存的发展速度差异的问题,导致CPU的速度远快于内存,所以现在的CPU加入了高速缓存,高速缓存一般可以分为L1、L2、L3三级缓存。基于上面的例子我们知道了这导致了缓存一致性的问题,所以加入了缓存一致性协议,同时导致了内存可见性的问题,而编译器和CPU的重排序导致了原子性和有序性的问题,JMM内存模型正是对多线程操作下的一系列规范约束,因为不可能让陈雇员的代码去兼容所有的CPU,通过JMM我们才屏蔽了不同硬件和操作系统内存的访问差异,这样保证了Java程序在不同的平台下达到一致的内存访问效果,同时也是保证在高效并发的时候程序能够正确执行。 原子性:Java内存模型通过read、load、assign、use、store、write来保证原子性操作,此外还有lock和unlock,直接对应着synchronized关键字的monitorenter和monitorexit字节码指令。 可见性:可见性的问题在上面的回答已经说过,Java保证可见性可以认为通过volatile、synchronized、final来实现。 有序性:由于处理器和编译器的重排序导致的有序性问题,Java通过volatile、synchronized来保证。 虽然指令重排提高了并发的性能,但是Java虚拟机会对指令重排做出一些规则限制,并不能让所有的指令都随意的改变执行位置,主要有以下几点: 单线程每个操作,happen-before于该线程中任意后续操作 volatile写happen-before与后续对这个变量的读 synchronized解锁happen-before后续对这个锁的加锁 final变量的写happen-before于final域对象的读,happen-before后续对final变量的读 传递性规则,A先于B,B先于C,那么A一定先于C发生 说了半天,到底工作内存和主内存是什么? 主内存可以认为就是物理内存,Java内存模型中实际就是虚拟机内存的一部分。而工作内存就是CPU缓存,他有可能是寄存器也有可能是L1\L2\L3缓存,都是有可能的。 说说ThreadLocal原理? ThreadLocal可以理解为线程本地变量,他会在每个线程都创建一个副本,那么在线程之间访问内部副本变量就行了,做到了线程之间互相隔离,相比于synchronized的做法是用空间来换时间。 ThreadLocal有一个静态内部类ThreadLocalMap,ThreadLocalMap又包含了一个Entry数组,Entry本身是一个弱引用,他的key是指向ThreadLocal的弱引用,Entry具备了保存key…

摩登3平台首页_实战篇:一个核心系统3万多行代码的重构之旅

经典著作《重构》这本书中有这么一段话: 一开始,我所做的重构都停留在细枝末节上。随着代码趋向简洁,我发现自己可以看到一些设计层面的东西了,这些是我以前理解不到的,如果没有重构,我达不到这种高度。 重构,着实是一件让程序员兴奋的事情。 今年年初,我们团队完成了一个复杂项目的重构工作,它属于广告系统最核心的引擎部分,大概有 300 多个文件,3 万多行代码。 从技术方案设计到最终全量上线仅仅花了 1 个月左右的时间,而且没有产生事故。 这应该是我 8 年程序生涯中,经历过的最大型的同时最成功的一次重构项目:速度足够快、计划比较周全、质量过关。 01 先聊聊这个系统的历史包袱 我们的广告引擎在这次重构前大概经历了1年半时间的迭代,初期针对的是搜索场景,业务单一,流程清晰。 2019年开始,公司的广告业务开始快速扩张,收入几乎是指数级的增长。在这个过程中,我们的广告引擎面临了两个挑战: 1、业务场景开始变得复杂,除了搜索广告,还需要支持信息流推荐以及相似推荐场景。 2、广告流量开始快速增加,除了满足功能性需求,还需要兼顾好性能。 经过梳理,整个引擎有大部分逻辑是可以公用的,因此我们定义了一个主体框架,同时将可扩展部分进行了抽象。这样,各个场景能够根据自身业务的特殊性实现某些公共接口即可。另外,从性能角度考虑,我们牺牲了一些代码可读性,把某些逻辑并行化了。 随着业务的发展,搜索场景开始进入快速迭代期,新增策略越来越多,我们的主体框架也是在这个时候逐渐变得不灵活。 如果动主体框架,搜索以外的场景都需要跟着重构。 在业务的快速发展期,工期根本不允许,因此我们只能在现有框架上进行补丁式的开发。 这样,带来了两个很明显的问题: 1、为了兼容搜索的特殊逻辑,我们需要在其他场景中增加各种 if 判断来绕过这些逻辑。 2、广告策略越来越多,累计了几十个,当框架失去清晰的结构后,有些策略的实现开始变得定制化,缺少层次化的划分和可插拔式的抽象设计。 在这样的背景下,随着改动的积累,代码开始偏离了设计的初衷,技术债务越来越重。但是,我们又始终找不到合适的时机进行重构。 转机出现在 2019 年年底,由于广告业务的特殊性,流量开始自然走低,另外产品运营团队将重心放在了第 2 年的工作规划上,因此给了我们非常好的窗口期开始此次重构。 我们将工期定成了 1 个月,最终仅比预期晚上线了一天,虽然出现了两个线上问题,但是在灰度期都及时发现和修复了,并没有造成线上事故。 总体来说,这是一次难度颇大并且比较成功的重构项目,下面详细说一下我从这个项目中吸取到的宝贵经验。 02 重构前,我们做了哪些准备工作? 这次重构的代码量很大,3 万多行,而且是广告系统最核心的引擎部分。启动前,我们能预期到下面这些困难: 1、业务侧的阻力:广告是极其以业务为导向的,本次重构虽然能带来长期研发效率的提升,但是没法直接提升业务收益,而且开发周期不会太短,如何才能得到业务同学的支持? 2、技术侧的顾虑 :重构一旦引起线上事故,公司是有处罚制度的,如何让大家轻装上阵?同时,重构过程中如果还有非常重的业务迭代穿插,交付时间没人敢保证,质量也很难得到控制。 针对这两方的顾虑,我认为下面这几项工作起到了很关键的作用。 ▍让所有人看到痛点 前面提到:随着业务迭代,我们广告引擎的主体框架已经变得模糊不清,另外几十个广告策略散落在不同的业务场景中,配置凌乱。 针对这两个痛点,我们提前1个月启动了现有业务的梳理,走读旧代码、同时翻阅以前的需求文档,最终我们将不同场景的核心流程以及广告策略归类成了一张清晰的表格。 正是这一张表格,让技术和产品第一次很清晰地看到了我们引擎部分的全貌,体会到了业务的复杂度以及当前技术上的瓶颈。 ▍明确重构的目标和价值 让所有人感受到痛点后,我们规划了本次重构的两个核心目标: 1、主体框架的重构:将主流程模块化,重新定义上下层协议,确保接口清晰;各层级内部也需要做好抽象,具备良好的扩展性。 2、策略灵活可配置:将广告策略按照业务意图进行归类抽象,策略的执行条件动态可配置,同时策略可任意插拔。 此外,我们将这两个核心目标完成后可带来的预期收益进行了细化: 1、技术收益:代码结构更清晰,更容易理解和维护;可扩展性增强,引擎的开发效率将进一步提升。 2、业务收益:策略能做到更细粒度的配置和扩展,对业务支持更友好;研发提效后能进一步加快业务的迭代速度。 将重构的价值同步给大家后,进一步提升了所有人的兴奋度,让大家有了更强的动力参与进来。 ▍整体节奏的把控 整体节奏的把控也是非常重要的一环,能让所有人对这件事情有一个时间上的预期。 首先,我们将工期定成了 1 个月,一方面考虑了业务侧可以接受的最大周期,技术上也希望速战速决;另一方面,春节即将来临,我们必须赶在公司封网前上线,同时预留出1-2周的 buffer 以防意外情况发生。 此外,我们和业务侧达成了一致:重构期间,引擎部分的非紧急需求一律不接,这样可最大限度地减少并行开发和代码冲突,让团队精力更集中。 03 执行过程中有哪些可分享的经验? 这次重构能够实施得如此顺利,有 4 点我认为很有价值的经验跟大家分享下。 这一点得益于日常的要求,针对开发周期超过3天的项目我们都会进行技术方案设计,本次重构当然也不例外。 框架部分的整体架构、模块之间的协议设计、以及策略的可扩展性设计是本次技术方案的重点,团队前后讨论了不下3次。 在大方案定稿后,团队进一步对数据库、接口字段、缓存结构、日志埋点等公共部分进行了细化,因为涉及到多人协作开发,团队约定以文档作为沟通界面,文档始终保持和代码同步。 在这样的高要求下,团队产出了 5000 多字的技术方案文档,合计 36 页,这些为整体质量的保障打下了很好的基础。 2. 预重构出框架性代码 这一个 PR 非常关键,是我们从技术方案落地到代码最重要的一步。我们把重构后的包结构、模块划分、各层之间的API定义、不同广告策略的抽象进行了梳理,先忽略实现的细节。 这样主体代码基本成型,能很清楚地描绘出我们理想中的框架。然后,我们组织了多次集中代码审查,最终形成了统一意见。 这一步能很好地避免过早陷入实现细节,导致主体框架关注不够、代码不稳固,后期再返工反而会拖累效率。 3. 频繁沟通和成对代码 Review 机制 进入到细节实现阶段后,很重要的一点是:对现有逻辑的理解。引擎代码经过一年半的迭代,历史上被很多人开发过,但是本次只有 3 个同学参与重构。 整个过程中,我们遇到任何代码逻辑不明确的地方,都是反复沟通和求证,不主观猜想,这一份谨慎其实很关键。 另外在代码审查上,我们按模块分配了对这块业务比较熟悉的同学来负责,成对搭配,机制灵活。 4. 有效的测试方案 重构未动,测试先行。这个原则是《重构》一书中重点强调的,也是我们本次技术方案讨论的重点,我这里单独拎出来详细展开下。 首先,我们前期便约定好:不动任何老代码,完全建新的 package 进行重构。这样方便比对重构前后的结果,同时进行线上灰度实验。 测试方案上,以下 4 点值得借鉴: 1、端到端测试:本次重构不涉及功能性的调整,因此外层API的行为是不会有任何变化的,这样端到端的测试方法最为有效,这个是研发和QA测试最主要的手段。 2、冒烟测试:QA同学提供冒烟 Case,由研发同学进行冒烟,研发提测前必须保证所有冒烟 Case 执行通过。这一点在大部分互联网公司都不常见,但是对于大型项目绝对有效。 3、沙箱环境双流程验证:前面提到我们重构前后的代码都保留了,因此可以通过脚本抓取线上环境的入参作为case,然后用自动化的方式对 API 的返回字段进行逐一比对。 4、线上环境灰度实验:灰度对于重构非常重要,我们利用已有的ABTest平台,逐步放开灰度流量,从5%、到10%、到30%、最后到100%,制定了很谨慎的放量节奏,然后通过日志以及业务指标监控进行验证。 写在最后 回顾整个重构的过程,总结成下面 7 个关键点: 1、把握好重构时机 2、前期梳理很重要,先找到痛点 3、明确出目标和价值,让大家兴奋起来…

摩登3测速登录地址_白噪声和Chirp信号导航效果对比

在  十五届全国大学生智能汽车竞赛[1] 中安排后一个 声音信标导航[2] 竞赛组别。参赛同学反馈,声音信标发送的 Chirp信号[3] 在远处听起来会令人产生一种恐慌紧张的感觉,极大扰民。 如果将信标的声音改成同样宽带音频的白噪声信号,在听觉上与Chirp信号有很大区别,但在导航的效果上与Chirp信号相同。 下面是对比两种信号在声音导航效果的异同。 01 测试方式 通过 调频接收模块[4] 接收信标发送的调频信号,与麦克接收到的信号通过相关运算,获得声音时间延迟。将两个麦克的延迟时间和调频接收模块获得的信号强度显示在LCD上。 ▲ 车模上显示测试结果的液晶 1.显示屏显示说明: 第一行显示的是麦克风1到信标的距离,单位是3.14厘米,显示的6也就是距离: 第二行显示的是麦克风2到信标的距离,单位是3.14厘米,显示的11也就是距离: 第三行显示的FM的RSSI(信号强度)。 2.播放Chirp信号时的视频(附件1): 采用原始的chirp信号进行声音导航,从视频中每移动车模一段距离观察一次距离数据是否稳定,从视频中看出大概离信标3米多的时候代表距离的数据开始不稳定了。 ▲ 信标播放chirp信号时接收端的信号 黄色波形是麦克风接收到的波形 蓝色是FM收到的波形 3.播放随机白噪声时的视频(附件2): 采用matlab生成8192个随机白噪声数据,然后用单片机将随机数据按照40K的频率进行播放。8192个数据播放的时间为204.8毫秒。刚好与之前的chirp周期是一致的。 由于单片机的flash较小所以没有办法播放太长的序列。同样,视频中也采用每移动车模一段距离观察一次距离数据是否稳定,从视频中同样可以看出大概离信标3米多的时候代表距离的数据也开始不稳定了。 ▲ 信标播放白噪声信号时接收端的信号 黄色波形是麦克风接收到的波形 蓝色是FM收到的波形并对波形做了一个1MHz 的低通滤波,便于观看波形。 测试的时候车模端的程序是没有改变的, 也就是说测距算法不变的情况下,仅仅只改变信标的声源通过视频我们可以看到两者的效果是比较接近的。 02 结果分析 从测试结果来看,播放白噪声的确也可以实现测距,当然也可以实现声音定位,但从实际测试中也有以下疑问: 1.问题1: 从上面的白噪声波形看,如果由人直接判断接收到的波形,很难去分析当前接收到的波形质量是否可靠,因为信号源就是随机的白噪声,没有规律可循,从分析和写程序来说可能没有chirp信号那种波形能给人带来那么直观的感受。 ▲ 播放Chirp信号进行测量声音延迟 2.问题2: 相对于chirp信号来说,随机白噪声听起来会更容易让人产生一种心烦的情绪,对做这个组别的同学来说也挺头大的,但相对于之前的chirp信号来说,穿透力确实要弱一些,对于其他人的影响确实要小不少,但也能传比较远。 ▲ 播放噪声信号进行测量声音延迟 注:感谢逐飞科技范兵提供的测试报告参考资料 [1] 十五届全国大学生智能汽车竞赛: https://zhuoqing.blog.csdn.net/article/details/106581683 声音信标导航: https://zhuoqing.blog.csdn.net/article/details/104231420 作为工科学生的大学本科阶段,毕业设计(有的学校称之为综合论文训练)是最后一个重要的培养环节。在本阶段同学们在导师的指导下,完成综合专业知识的研究训练,是通向未来成长之路的重要环节。 对于电子信息类专业的同学,现在可以选择的各类可编程器件平台非常丰富。为此,热心电子教育的“电子森林”苏公雨老师准备在年底前制作各类开发板供即将毕业设计的同学们免费选用。下面的链接是对同学们需要哪些可编程器件开发平台做的调研。看看其中是否有你们打算使用的开发平台呢? https://appu1kfqmyl7963.h5.xiaoeknow.com/evaluation_wechat/customize_form/submit_info/form_CRGstmtktk2Hp0?entry=2&entry_type=2001 注:按动识别二维码,或者点按“阅读原文”,自动跳转到开发平台调查页面。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

摩登3测速代理_把握新时代,英飞凌以eSIM全力驱动物联网创新

出品 21ic中国电子网 蔡璐 网站:21ic.com 众所周知,SIM卡是移动通信中不可或缺的组成部分。但是,随着物联网时代的到来,智能终端的设备类型和应用场景越来越多,这种传统的插拔式SIM卡已经无法满足物联网各种极端条件下的通信要求了。在此背景下,eSIM技术应运而生,许多厂商都推出了全新一代的eSIM解决方案。 那么,eSIM具有哪些优势?这一技术又是如何推动物联网发展的?作为厂商,应该如何提升物联网应用能力?带着这些问题,21ic中国电子网记者采访了英飞凌科技数字安全解决方案事业部大中华区物联网安全产品线区域市场经理刘彤女士。 拥抱eSIM技术,引领新时代发展 或许大众消费者对eSIM还不太了解,下面先给大家科普一下什么是eSIM? eSIM是Embedded-SIM的缩写,即嵌入式SIM卡。简单来说,就是将传统的SIM卡直接嵌入到设备芯片上,而不是作为独立的可移除零部件加入设备中,用户无需插入物理SIM卡。这一做法在硬件上减少了卡槽空间,让产品的抗震性、耐高温和可靠性更强,因此eSIM卡具备低成本、更小体积、高稳定性、一号多终端、使用便捷与安全等诸多优势。 据刘彤介绍,作为全球最大的eSIM供应商,英飞凌在过去的几年里,针对物联网碎片化、场景化、定制化等特点,推出了全面而完整的解决方案组合,能够满足不同应用的需求,这里最值得一提的就是OPTIGA™ Connect eSIM解决方案。 该解决方案是一款面向移动消费终端的5G eSIM一站式解决方案,基于英飞凌成熟的SLC37安全芯片,支持主要移动运营商的配置文件,并且具备远程SIM卡配置功能,可提供最大至1.2 MB的可用内存,供用户存储运营商的配置文件、数据及其它应用。 图:英飞凌科技数字安全解决方案事业部 大中华区物联网安全产品线区域市场经理刘彤 在谈及产品亮点时,刘彤骄傲地说:“我们的OPTIGA™ Connect eSIM解决方案具有两大核心优势,首先在符合GSMA最新规范的同时,还支持最新的5G规范的产品,并且通过了业界最高标准CC EAL4+的安全认证。当然,封装也是非常小的,这是基本性的优势。”据悉,该解决方案超小型封装尺寸仅为2.9 mm x 2.5 mm x 0.4 mm,有助于实现崭新的设计,特别适用于印刷电路板空间非常敏感的终端。 “其次就是易用性,可以提供端到端硬件和软件统包的一站式解决方案,并且在IOT的解决方案预置初始码号。这是我们充分跟客户沟通,理解了市场上用户的需求和痛点,帮助客户切实解决的问题,这也是我们最大的优势。”刘彤谈道。 符合GSMA安全需求并通过CC EAL4+的测试,意味着这一解决方案配备了坚固耐用且值得信赖的安全保障,因而能够确保敏感数据和密钥的安全,防止欺诈;同时,全面支持从3G到5G的所有GSMA标准,可以安全地面向所签约的运营商网络进行登网鉴权。英飞凌通过将硬件和软件整合到一个易于集成的系统中,帮助制造商高效地开发产品设计,降低eSIM集成工作量,加快将产品推向市场。 市场增速明显,物联网将成主战场 如果说SIM是移动互联时代的物种,那么eSIM就是专门为万物互联时代量身打造的嵌入式集成芯片。在刘彤看来,物联网无疑是eSIM的重要应用市场之一。 对此,刘彤给出了进一步解释:“从物联网领域来看,这一应用可以细分成三大类,首先是车联网,比如车规级eSIM用于前装T-box,为车主提供方便的导航、紧急呼叫、OTA下载等功能。随着5G的发展,V2X车路协同等应用对于智能化、网联化提出了更高的要求。打个比方,将来的汽车可以想象成是四个轮子的智能手机。” “其次是工业物联网,比如能源管理、水电气三表、资产追踪等引用。第三是消费级物联网,比如可穿戴市场,今年疫情以来,大众对于健康越来越重视,很多智能手表都推出了健康监控功能(所谓手腕上的医生),还有运动手表的销量也越来越多,很多人跑步健身的时候都会带着有通话功能的运动手表,而不是手机,另外儿童手表也是重要的应用。” 随着物联网的风口越来越近,未来百亿级别物联设备的安全连接都要以eSIM作为基础。根据英国市场研究机构ABI Research此前发布的数据显示,2011-2014年,eSIM出货量分别达到460万、700万、1000万和1700万,呈现加速增长态势;与此同时,eSIM随着M2M设备的配售率也在逐年上升,2011-2014年分别为13%、17%、22%和28%。预计2020年,eSIM智能手机出货量将突破2.25亿大关。不难看出,eSIM技术在智能终端设备上的应用日渐增多。 另外,ABI Research还预测,到2024年的时候,内置eSIM的消费电子设备将达到6.44亿,其中智能手机约5亿;而在M2M、物联网领域,eSIM设备也有望达到2.32亿(年复合增长率18%),其中超过1亿来自汽车。可以预见,随着5G的到来,物联网行业面临的功耗高、带宽速度慢,以及成本高等阻碍也将得到有效解决。未来,eSIM卡将随着5G和物联网行业迎来爆发期。 深化战略合作,实现共赢新局面 鉴于eSIM巨大的市场潜力,英飞凌在推广eSIM类产品的大规模落地方面采取了一系列措施。 在采访中,刘彤表示:“我们看好eSIM在全球发展前景,同时中国在5G和物联网领域都处于世界领先地位,我们更加看好eSIM在中国的市场前景。目前,英飞凌的eSIM解决方案在中国已有落地,在主流车厂里面,大部分高端汽车前装T-BOX都会用到我们的车规级eSIM芯片,还有一些IoT设备、智能可穿戴设备,以及智能手机产品等也有采用英飞凌的eSIM芯片。” 此外,为了支持全球网络连接,英飞凌也一直在与全球大大小小的运营商进行深入合作。“比如,我们和印度运营商——塔塔通信进行合作。塔塔通信是全球领先的电信解决方案供应商,它也是非常看好eSIM市场,近年来不仅投资建立了MOVE™ eSIM Hub平台,还投入了大量资源和人力到这个领域。”刘彤介绍说。 据悉,英飞凌的解决方案预置了塔塔通信的MOVE™ eSIM辅助程序,可以支持制造商在全球范围内无缝、安全、可靠地采集、传输和管理物联网数据。目前,英飞凌OPTIGA™ Connect eSIM解决方案已在全球200多个国家和地区实现了预集成式网络覆盖。 此外,由于塔塔通信与全球640多个移动网络运营商(MNO)建立了合作关系,因此制造商无需与不同国家的移动网络运营商签订数以百计的本地连接协议,而是可以直接通过与塔塔通信的单一的全球连接协议实现设备联网。这极大简化了产品管理降低成本,并大幅加快了将产品推向市场的步伐。 总之,随着物联网设备的逐步兴起,越来越多的设备制造商、运营商、物联网企业和科技企业正在加入到eSIM生态中来。尤其是在5G时代,无论消费级应用,还是物联网应用,都迎来了全方位的爆发,因此eSIM更是大有用武之地。作为全球领先的半导体公司,英飞凌将以eSIM全力驱动物联网创新。  近期热度新闻 【1】剧透!围绕生态和体验,华为HMS亮出多款“杀手锏” 【2】周立功的公司也要上市了?拟募资约8.9亿 【3】AMD王炸!NVIDIA就这么被碾压了? 【1】初学不识“电容”意,看完这篇,电容高手非你莫属! 【2】必看!100个示波器基础知识问答 【3】 【1】终于整理齐了,电子工程师“设计锦囊”,你值得拥有! 【2】半导体行业的人都在关注这几个公众号 【3】 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

摩登3注册网址_两个线程,两个互斥锁,怎么形成一个死循环?

粉丝的提问,必须安排。 两个线程,两个互斥锁如何形成死锁? 程序流程图如下: 程序流程图 如上图所示: t0时刻,主线程创建子线程,并初始化互斥锁mutex1、mutex2; t1时刻,主线程申请到了mutex1、子线程申请到了mutex2; t2时刻,主线程和子线程都sleep 1秒钟,防止优先获得时间片的线程直接申请到了另外1个互斥锁,导致程序直接退出; t3时刻,主线程和子线程都想获得对方手里的互斥锁,但是对方都来不及释放自己手里的锁; t4时刻,主线程和子线双双进入休眠。 【注意】为了保证主线程和子线程都能够分别获得锁mutex1、mutex2,各自获得锁后一定要先sleep 1秒钟,否则创建完子线程后,主线程还有一定的时间片,主线程会申请到锁mutex2,无法形成死锁。 死锁 源码如下 #include  #include  #include  #include      unsigned int value1, value2, count;pthread_mutex_t  mutex1,mutex2;void *function(void *arg);void  *function(void *arg){ pthread_mutex_lock(&mutex2); printf("new thread get mutex2\n");  sleep(1); pthread_mutex_lock(&mutex1);  printf("new thread get mutex1\n");    pthread_mutex_unlock(&mutex1); printf("new thread release mutex1\n"); pthread_mutex_unlock(&mutex2);  printf("new thread release mutex2\n");    return  NULL; }  int main(int argc,  char *argv[]){ pthread_t  a_thread;          if (pthread_mutex_init(&mutex1, NULL) < 0) {  perror("fail to mutex_init");  exit(-1); }  if (pthread_mutex_init(&mutex2, NULL) < 0) {  perror("fail to mutex_init");  exit(-1); }               if (pthread_create(&a_thread, NULL, function, NULL) < 0) {     perror("fail to pthread_create");       exit(-1); }    while ( 1 )    {        pthread_mutex_lock(&mutex1);  printf("main thread get mutex1\n");  sleep(1);        pthread_mutex_lock(&mutex2);    printf("main thread get mutex2\n");        pthread_mutex_unlock(&mutex2);  printf("main thread release mutex2\n");        pthread_mutex_unlock(&mutex1);  printf("main thread release mutex1\n");    }    return 0; }              编译运行 从执行结果可以判断,主线程和子线程分别获得了互斥锁mutex1、mutex2,sleep 1秒后,他们都想再分别申请mutex2、mutex1,而双方都不想释放自己手中的锁,锁已形成了死锁,程序就一直处于休眠状态。 查看下该进程的线程 查看进程ID,为4204查看该进程创建的线程id:4204、4205。 -THE END- 推荐阅读 【1】 SPI转can芯片CSM300详解、Linux驱动移植调试笔记 【2】 到底什么是Cortex、ARMv8、arm架构、ARM指令集、soc?一文帮你梳理基础概念【科普】 必读 【3】Linux面试题100道,看看会多少?必读 【4】Modbus协议概念最详细介绍 必读 【5】I2C基础知识入门  必读 本公众号全部原创干货已整理成一个目录,点击 想加入请加一口君微信 一口君有问必答 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

摩登3登录_让传感器数据更直观之LCD曲线显示

前阵子公司有一个基于毒品检测的项目需要做一个曲线显示的功能,由于这块是我的技能短板,因为我之前搞软件的应用,逻辑,框架,架构设计这块比较多,而我师弟在底层方面非常精通,所以把这一块核心的功能交给了我师弟,让他帮忙来实现基本的库,然后我基于他的库完成产品所需要的功能。 又恰好在项目之前,RT-Thread发起了一个基于RT-Thread Nano的Mini示波器DIY的活动,作为RT-Thread社区工作小组一员的我,有幸能看到这个项目从头到尾的制作过程,也从中学习了LCD曲线数据处理和显示的一些思想。 活动链接如下: 【DIY活动】一起来做一个基于RT-Thread Nano的Mini示波器吧! 完成曲线显示大致需要以下三个步骤: 1、数据采集 2、数据处理 3、数据显示 废话不多说,咱们先看下显示效果: 严格意义上来说,小熊派这种SPI屏其实不太适合用来刷曲线,首先分辨率太低了,还有就是SPI的速率也不高,如果曲线显示条件苛刻一点,很容易导致LCD显示闪屏现象,体验感非常不好,但是针对传感器数据显示我们还是有能力实现的。 于是,我们需要对屏驱动做一些最基本的优化: 1、优化LCD驱动 1、提升刷屏速度 由于要刷曲线,所以只能想办法尽量提升屏的刷新速度,于是在LCD手册里有这么一个寄存器,可以提升屏的刷新速度: 在LCD驱动初始化代码里,这个寄存器默认配置的是60Hz,也就是0x0F这个值 /* Frame Rate Control in Normal Mode */LCD_Write_Cmd(0xC6);// LCD_Write_Data(0x0F); //60HZLCD_Write_Data(0x01);  //111Hz 提升屏的刷新速度 本来设置为0x00为119Hz,但是设置完LCD就黑屏了,改为0x01就不会,目前没找到具体原因,可能这是屏固件的BUG,暂时将就着用吧;或者有朋友知道的,感谢在留言区分享给我。 2、改用寄存器发送 /** * @brief LCD底层SPI发送数据函数 * * @param   data 数据的起始地址 * @param   size 发送数据大小 * * @return  void */static void LCD_SPI_Send(uint8_t *data, uint16_t size){    for(int i = 0 ; i < size ; i++)    {        *((uint8_t*)&hspi2.Instance->DR) = data[i];        while(__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE) != 1) {}    }} HAL库的HAL_SPI_Transmit函数发送会慢一些,改用寄存器发送会更快。 2、曲线显示逻辑 要在LCD上显示曲线,大家可能就会有这样的疑问: 我的数据可能上千,几万这样,如何转换成对应屏分辨率的显示?到底从哪里开始显示?怎么显示? 有一种比较好的思路,就是定义一个固定长度的数组,每次往数组尾部不断的更新数据,然后该数据会不断的往前推,这其实就是我们说的fifo(环形缓冲)队列,然后定义一个新的备份缓冲区,在这个备份缓冲区里找到数据的最大值以及最小值,求出针对LCD分辨率的缩放系数,根据计算结果将备份缓冲区用于LCD显示,这就是根据实际情况进行的缩放,也叫做局部缩放。以下是这个例程的曲线数据结构: #define DATA_SIZE   240/*曲线显示区域,即相对于LCD的宽度,X轴*/#define PLOT_DISPLAY_AREA_X  51/*曲线显示区域,即相对于LCD的高度,Y轴*/#define PLOT_DISPLAY_AREA_Y  210#define LCD_X 240#define LCD_Y 240/*曲线处理*/typedef struct{  /*实时曲线数据*/    uint16_t rel_data_data[DATA_SIZE];  /*旧的曲线数据*/    uint16_t old_plot_data[DATA_SIZE];  /*新的曲线数据*/    uint16_t new_plot_data[DATA_SIZE];} plot_data_handler ;extern plot_data_handler plot_handler ; 由于要做到一次性更新避免闪屏,这里定义了三个缓冲区,rel_data_data用于更新实时数据,old_plot_data为旧的已经处理的显示数据,new_plot_data为刚刚处理的显示数据,相当于双缓冲效果。 3、曲线显示实现 3.1 数据采样部分 由于刚开始显示,曲线的数据缓存是空的,所以我们要做一下初始化,保证曲线能够直接显示出来: smoke_value = mq2_sensor_interface.get_smoke_value(&mq2_sensor_interface);for(int i = 0 ; i < DATA_SIZE ; i++)   plot_handler.rel_data_data[i] = smoke_value ;memcpy(plot_handler.new_plot_data, plot_handler.rel_data_data, sizeof(plot_handler.new_plot_data));memcpy(plot_handler.old_plot_data, plot_handler.new_plot_data, sizeof(plot_handler.new_plot_data)); 接下来就是显示逻辑上提到的,我们需要有一个环形缓冲,不断的追加数据: smoke_value = mq2_sensor_interface.get_smoke_value(&mq2_sensor_interface) ;/*更新数据到队列*/for(i = 0 ; i <= DATA_SIZE - 2 ; i++)   plot_handler.rel_data_data[i] = plot_handler.rel_data_data[i + 1];plot_handler.rel_data_data[DATA_SIZE - 1] = smoke_value ; 这样我们就完成了最基本的数据采样部分。 3.2 数据处理部分 数据处理我定义了以下函数来实现: void LCD_Plot_Remap(uint16_t *cur_data, uint16_t *backup_data, uint16_t cur_data_size) cur_data表示当前的实时数据包 backup_data表示备份数据包 cur_data_size表示数据包的长度 实时数据包就是没有经过处理的数据包,备份数据包就是经过处理的数据包。 在这个函数中主要完成了找实时数据包的最大、最小值、计算缩放系数: 最大值查找: value = 0 ;for(i = 0; i < cur_data_size; i++)  if(cur_data[i] > value)    value = cur_data[i];max = value ; 最小值查找: value = cur_data[0];for(i = 0; i < cur_data_size; i++) if(cur_data[i] < value)   value = cur_data[i];min = value ; 缩放系数的计算: max_min_diff = (float)(max - min);scale = (float)(max_min_diff / height); 将处理的结果拷贝到备份数据包中。 完整实现如下: /*cur_data:当前要显示的曲线数据包cur_data_size:当前要显示的曲线数据包的大小*/void LCD_Plot_Remap(uint16_t *cur_data, uint16_t *backup_data, uint16_t cur_data_size){  uint32_t i = 0 ;  float temp = 0;  /*数据包最大值*/    uint16_t max = 0;  /*数据包最小值*/    uint16_t min = 0;  float scale = 0.0;  uint16_t value = 0;  float max_min_diff = 0.0;  /*曲线显示的高度*/  float height = PLOT_DISPLAY_AREA_Y;  char display_rel_buf[20] = {0};    char display_max_buf[20] = {0};  char display_min_buf[20] = {0};  char display_sub_buf[20] = {0};  /*显示X坐标轴*/  for(uint8_t i = PLOT_DISPLAY_AREA_X-1 ; i < 240 ; i++)        LCD_Draw_ColorPoint(i, 239, RED);  /*显示Y坐标轴*/    for(uint8_t i = LCD_Y-PLOT_DISPLAY_AREA_Y ; i < 240 ; i++)        LCD_Draw_ColorPoint(PLOT_DISPLAY_AREA_X-1, i, RED);  value = 0 ;  for(i = 0; i < cur_data_size; i++)        if(cur_data[i] > value)            value = cur_data[i];  max = value ;  value = cur_data[0];  for(i = 0; i < cur_data_size; i++)        if(cur_data[i] < value)            value = cur_data[i];  min = value ;    sprintf(display_rel_buf,"%04d",cur_data[DATA_SIZE-1]);  sprintf(display_max_buf,"%04d",max);  sprintf(display_min_buf,"%04d",min);  sprintf(display_sub_buf,"%04d",max-min);    LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+10,LCD_X,16,16,"rel:");  LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+20+10,LCD_X, 16, 16, display_rel_buf);    LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+50+10,LCD_X,16,16,"max:");  LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+70+10,LCD_X, 16, 16, display_max_buf);    LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+100+10,LCD_X,16,16,"min:");  LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+120+10,LCD_X, 16, 16, display_min_buf);    LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+150+10,LCD_X,16,16,"sub:");  LCD_ShowString(5,LCD_Y-PLOT_DISPLAY_AREA_Y+170+10,LCD_X, 16, 16, display_sub_buf);      if(min > max)    return ;    max_min_diff = (float)(max - min);    scale = (float)(max_min_diff / height);    if(cur_data_size < DATA_SIZE)    return;    for(i = 0; i < DATA_SIZE; i ++)    {        temp = cur_data[i] - min;        backup_data[i] =  DATA_SIZE - (uint16_t)(temp / scale) - 1;    }} 3.3 数据显示部分 这部分应该是最激动人心的,但是它的实现却是最简单的,就是将数据处理部分得到的备份数据包里的每一个数据依次用线段连接起来即可,为了让驱动更快一些,以下的处理采用寄存器发送: /*显示曲线*/void LCD_Plot_Display(uint16_t *pData, uint32_t size, uint16_t color){    uint32_t i, j;    uint8_t color_L = (uint8_t) color;    uint8_t color_H = (uint8_t) (color >> 8);    if(size < DATA_SIZE) return ;    for (i = PLOT_DISPLAY_AREA_X; i < DATA_SIZE - 1; i++)    {        if (pData[i + 1] >= pData[i])        {            LCD_Address_Set(i, pData[i], i, pData[i + 1]);            LCD_DC(1);            for (j = pData[i]; j <= pData[i + 1]; j++)            {                *((uint8_t*) &hspi2.Instance->DR) = color_H;                while (__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE) != 1);                *((uint8_t*) &hspi2.Instance->DR) = color_L;                while (__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE) != 1);            }        }        else        {            LCD_Address_Set(i, pData[i + 1], i, pData[i]);            LCD_DC(1);            for (j = pData[i + 1]; j <= pData[i]; j++)            {                *((uint8_t*) &hspi2.Instance->DR) = color_H;                while (__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE) != 1);                *((uint8_t*) &hspi2.Instance->DR) = color_L;                while (__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE) != 1);            }        }    }} 4、传感器数据实时曲线显示 实现逻辑如下: while (1){  smoke_value = mq2_sensor_interface.get_smoke_value(&mq2_sensor_interface) ;  /*更新数据到队列*/  for(i = 0 ; i <= DATA_SIZE - 2 ; i++)    plot_handler.rel_data_data[i] = plot_handler.rel_data_data[i + 1];  plot_handler.rel_data_data[DATA_SIZE - 1] = smoke_value ;  /*先将背景刷黑*/  LCD_Plot_Display(plot_handler.old_plot_data, DATA_SIZE, BLACK);  /*传感器数据处理*/  LCD_Plot_Remap(plot_handler.rel_data_data,plot_handler.new_plot_data, DATA_SIZE);  /*传感器数据曲线显示*/  LCD_Plot_Display(plot_handler.new_plot_data, DATA_SIZE, GREEN);  /*将处理完成的备份数据区的数据拷贝到旧的备份数据区*/  memcpy(plot_handler.old_plot_data, plot_handler.new_plot_data, sizeof(plot_handler.new_plot_data));  HAL_Delay(10);} 本节代码已同步到码云的代码仓库中,获取方法如下: 1、新建一个文件夹 项目开源仓库: https://gitee.com/morixinguan/bear-pi 这里我给大家申请到了福利,本公众号读者购买小熊派开发板可享受9折优惠,有需要购买小熊派以及腾讯物联网开发板的朋友,淘宝搜索即可,跟客服说你是公众号: 的粉丝,立享9折优惠! 自己动手撸个简单的LCD驱动框架吧! 嵌入式软件解决ADC电量显示问题经验分享 有关版本等信息的重要性(以STM32产品开发为例) TencentOS tiny危险气体探测仪产品级开发重磅高质量更新 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!