• 详解C/C 堆栈的工作机制

    关注、星标公众号,直达精彩内容 来源:轻松学编程 地址:https://segmentfault.com/a/1190000038292644 前言 我们经常会讨论这样的问题:什么时候数据存储在堆栈(Stack)中,什么时候数据存储在堆(Heap)中。我们知道,局部变量是存储在堆栈中的;debug时,查看堆栈可以知道函数的调用顺序;函数调用时传递参数,事实上是把参数压入堆栈,听起来,堆栈象一个大杂烩。那么,堆栈(Stack)到底是如何工作的呢?本文将详解C/C 堆栈的工作机制。阅读时请注意以下几点: 1)本文讨论的编译环境是 Visual C/C ,由于高级语言的堆栈工作机制大致相同,因此对其他编译环境或高级语言如C#也有意义。 2)本文讨论的堆栈,是指程序为每个线程分配的默认堆栈,用以支持程序的运行,而不是指程序员为了实现算法而自己定义的堆栈。 3)  本文讨论的平台为intel x86。 4)本文的主要部分将尽量避免涉及到汇编的知识,在本文最后可选章节,给出前面章节的反编译代码和注释。 5)结构化异常处理也是通过堆栈来实现的(当你使用try…catch语句时,使用的就是c 对windows结构化异常处理的扩展),但是关于结构化异常处理的主题太复杂了,本文将不会涉及到。 从一些基本的知识和概念开始 1) 程序的堆栈是由处理器直接支持的。在intel x86的系统中,堆栈在内存中是从高地址向低地址扩展(这和自定义的堆栈从低地址向高地址扩展不同),如下图所示: 因此,栈顶地址是不断减小的,越后入栈的数据,所处的地址也就越低。 2) 在32位系统中,堆栈每个数据单元的大小为4字节。小于等于4字节的数据,比如字节、字、双字和布尔型,在堆栈中都是占4个字节的;大于4字节的数据在堆栈中占4字节整数倍的空间。 3) 和堆栈的操作相关的两个寄存器是EBP寄存器和ESP寄存器的,本文中,你只需要把EBP和ESP理解成2个指针就可以了。ESP寄存器总是指向堆栈的栈顶,执行PUSH命令向堆栈压入数据时,ESP减4,然后把数据拷贝到ESP指向的地址;执行POP命令时,首先把ESP指向的数据拷贝到内存地址/寄存器中,然后ESP加4。EBP寄存器是用于访问堆栈中的数据的,它指向堆栈中间的某个位置(具体位置后文会具体讲解),函数的参数地址比EBP的值高,而函数的局部变量地址比EBP的值低,因此参数或局部变量总是通过EBP加减一定的偏移地址来访问的,比如,要访问函数的第一个参数为EBP 8。 4) 堆栈中到底存储了什么数据?包括了:函数的参数,函数的局部变量,寄存器的值(用以恢复寄存器),函数的返回地址以及用于结构化异常处理的数据(当函数中有try…catch语句时才有,本文不讨论)。这些数据是按照一定的顺序组织在一起的,我们称之为一个堆栈帧(Stack Frame)。一个堆栈帧对应一次函数的调用。在函数开始时,对应的堆栈帧已经完整地建立了(所有的局部变量在函数帧建立时就已经分配好空间了,而不是随着函数的执行而不断创建和销毁的);在函数退出时,整个函数帧将被销毁。 5) 在文中,我们把函数的调用者称为caller(调用者),被调用的函数称为callee(被调用者)。之所以引入这个概念,是因为一个函数帧的建立和清理,有些工作是由Caller完成的,有些则是由Callee完成的。 开始讨论堆栈是如何工作的 我们来讨论堆栈的工作机制。堆栈是用来支持函数的调用和执行的,因此,我们下面将通过一组函数调用的例子来讲解,看下面的代码: int foo1(int m, int n){ int p=m*n; return p;}int foo(int a, int b){ int c=a 1; int d=b 1; int e=foo1(c,d); return e;}int main(){ int result=foo(3,4); return 0;} 这段代码本身并没有实际的意义,我们只是用它来跟踪堆栈。下面的章节我们来跟踪堆栈的建立,堆栈的使用和堆栈的销毁。 堆栈的建立 我们从main函数执行的第一行代码,即int result=foo(3,4); 开始跟踪。这时main以及之前的函数对应的堆栈帧已经存在在堆栈中了,如下图所示: 图1 参数入栈 当foo函数被调用,首先,caller(此时caller为main函数)把foo函数的两个参数:a=3,b=4压入堆栈。参数入栈的顺序是由函数的调用约定(Calling Convention)决定的,我们将在后面一个专门的章节来讲解调用约定。一般来说,参数都是从右往左入栈的,因此,b=4先压入堆栈,a=3后压入,如图: 图2 返回地址入栈 我们知道,当函数结束时,代码要返回到上一层函数继续执行,那么,函数如何知道该返回到哪个函数的什么位置执行呢?函数被调用时,会自动把下一条指令的地址压入堆栈,函数结束时,从堆栈读取这个地址,就可以跳转到该指令执行了。如果当前"call foo"指令的地址是0x00171482,由于call指令占5个字节,那么下一个指令的地址为0x00171487,0x00171487将被压入堆栈: 图3 代码跳转到被调用函数执行 返回地址入栈后,代码跳转到被调用函数foo中执行。到目前为止,堆栈帧的前一部分,是由caller构建的;而在此之后,堆栈帧的其他部分是由callee来构建。 EBP指针入栈 在foo函数中,首先将EBP寄存器的值压入堆栈。因为此时EBP寄存器的值还是用于main函数的,用来访问main函数的参数和局部变量的,因此需要将它暂存在堆栈中,在foo函数退出时恢复。同时,给EBP赋于新值。 1)将EBP压入堆栈 2)把ESP的值赋给EBP 图4 这样一来,我们很容易发现当前EBP寄存器指向的堆栈地址就是EBP先前值的地址,你还会发现发现,EBP 4的地址就是函数返回值的地址,EBP 8就是函数的第一个参数的地址(第一个参数地址并不一定是EBP 8,后文中将讲到)。因此,通过EBP很容易查找函数是被谁调用的或者访问函数的参数(或局部变量)。 为局部变量分配地址 接着,foo函数将为局部变量分配地址。程序并不是将局部变量一个个压入堆栈的,而是将ESP减去某个值,直接为所有的局部变量分配空间,比如在foo函数中有ESP=ESP-0x00E4,(根据烛秋兄在其他编译环境上的测试,也可能使用push命令分配地址,本质上并没有差别,特此说明)如图所示: 图5 奇怪的是,在debug模式下,编译器为局部变量分配的空间远远大于实际所需,而且局部变量之间的地址不是连续的(据我观察,总是间隔8个字节)如下图所示: 图6 我还不知道编译器为什么这么设计,或许是为了在堆栈中插入调试数据,不过这无碍我们今天的讨论。 通用寄存器入栈 最后,将函数中使用到的通用寄存器入栈,暂存起来,以便函数结束时恢复。在foo函数中用到的通用寄存器是EBX,ESI,EDI,将它们压入堆栈,如图所示: 图7 至此,一个完整的堆栈帧建立起来了。 堆栈特性分析 上一节中,一个完整的堆栈帧已经建立起来,现在函数可以开始正式执行代码了。本节我们对堆栈的特性进行分析,有助于了解函数与堆栈帧的依赖关系。 1)一个完整的堆栈帧建立起来后,在函数执行的整个生命周期中,它的结构和大小都是保持不变的;不论函数在什么时候被谁调用,它对应的堆栈帧的结构也是一定的。 2)在A函数中调用B函数,对应的,是在A函数对应的堆栈帧“下方”建立B函数的堆栈帧。例如在foo函数中调用foo1函数,foo1函数的堆栈帧将在foo函数的堆栈帧下方建立。如下图所示: 图8 3)函数用EBP寄存器来访问参数和局部变量。我们知道,参数的地址总是比EBP的值高,而局部变量的地址总是比EBP的值低。而在特定的堆栈帧中,每个参数或局部变量相对于EBP的地址偏移总是固定的。因此函数对参数和局部变量的的访问是通过EBP加上某个偏移量来访问的。比如,在foo函数中,EBP 8为第一个参数的地址,EBP-8为第一个局部变量的地址。 4)如果仔细思考,我们很容易发现EBP寄存器还有一个非常重要的特性,请看下图中: 图9 我们发现,EBP寄存器总是指向先前的EBP,而先前的EBP又指向先前的先前的EBP,这样就在堆栈中形成了一个链表!这个特性有什么用呢,我们知道EBP 4地址存储了函数的返回地址,通过该地址我们可以知道当前函数的上一级函数(通过在符号文件中查找距该函数返回地址最近的函数地址,该函数即当前函数的上一级函数),以此类推,我们就可以知道当前线程整个的函数调用顺序。事实上,调试器正是这么做的,这也就是为什么调试时我们查看函数调用顺序时总是说“查看堆栈”了。 返回值是如何传递的 堆栈帧建立起后,函数的代码真正地开始执行,它会操作堆栈中的参数,操作堆栈中的局部变量,甚至在堆(Heap)上创建对象,balabala….,终于函数完成了它的工作,有些函数需要将结果返回给它的上一层函数,这是怎么做的呢? 首先,caller和callee在这个问题上要有一个“约定”,由于caller是不知道callee内部是如何执行的,因此caller需要从callee的函数声明就可以知道应该从什么地方取得返回值。同样的,callee不能随便把返回值放在某个寄存器或者内存中而指望Caller能够正确地获得的,它应该根据函数的声明,按照“约定”把返回值放在正确的”地方“。下面我们来讲解这个“约定”: 1)首先,如果返回值等于4字节,函数将把返回值赋予EAX寄存器,通过EAX寄存器返回。例如返回值是字节、字、双字、布尔型、指针等类型,都通过EAX寄存器返回。 2)如果返回值等于8字节,函数将把返回值赋予EAX和EDX寄存器,通过EAX和EDX寄存器返回,EDX存储高位4字节,EAX存储低位4字节。例如返回值类型为__int64或者8字节的结构体通过EAX和EDX返回。 3)  如果返回值为double或float型,函数将把返回值赋予浮点寄存器,通过浮点寄存器返回。 4)如果返回值是一个大于8字节的数据,将如何传递返回值呢?这是一个比较麻烦的问题,我们将详细讲解: 我们修改foo函数的定义如下并将它的代码做适当的修改: MyStruct foo(`int a, int b)`{ ...} MyStruct定义为: struct MyStruct{ int value1; __int64 value2; bool value3;}; 这时,在调用foo函数时参数的入栈过程会有所不同,如下图所示: 图10 caller会在压入最左边的参数后,再压入一个指针,我们姑且叫它ReturnValuePointer,ReturnValuePointer指向caller局部变量区的一块未命名的地址,这块地址将用来存储callee的返回值。函数返回时,callee把返回值拷贝到ReturnValuePointer指向的地址中,然后把ReturnValuePointer的地址赋予EAX寄存器。函数返回后,caller通过EAX寄存器找到ReturnValuePointer,然后通过ReturnValuePointer找到返回值,最后,caller把返回值拷贝到负责接收的局部变量上(如果接收返回值的话)。 你或许会有这样的疑问,函数返回后,对应的堆栈帧已经被销毁,而ReturnValuePointer是在该堆栈帧中,不也应该被销毁了吗?对的,堆栈帧是被销毁了,但是程序不会自动清理其中的值,因此ReturnValuePointer中的值还是有效的。 堆栈帧的销毁 当函数将返回值赋予某些寄存器或者拷贝到堆栈的某个地方后,函数开始清理堆栈帧,准备退出。堆栈帧的清理顺序和堆栈建立的顺序刚好相反:(堆栈帧的销毁过程就不一一画图说明了) 1)如果有对象存储在堆栈帧中,对象的析构函数会被函数调用。 2)从堆栈中弹出先前的通用寄存器的值,恢复通用寄存器。 3)ESP加上某个值,回收局部变量的地址空间(加上的值和堆栈帧建立时分配给局部变量的地址大小相同)。 4)从堆栈中弹出先前的EBP寄存器的值,恢复EBP寄存器。 5)从堆栈中弹出函数的返回地址,准备跳转到函数的返回地址处继续执行。 6)ESP加上某个值,回收所有的参数地址。 前面1-5条都是由callee完成的。而第6条,参数地址的回收,是由caller或者callee完成是由函数使用的调用约定(calling convention )来决定的。下面的小节我们就来讲解函数的调用约定。 函数的调用约定(calling convention) 函数的调用约定(calling convention)指的是进入函数时,函数的参数是以什么顺序压入堆栈的,函数退出时,又是由谁(Caller还是Callee)来清理堆栈中的参数。有2个办法可以指定函数使用的调用约定: 1)在函数定义时加上修饰符来指定,如 void __thiscall mymethod();{ ...} 2)在VS工程设置中为工程中定义的所有的函数指定默认的调用约定:在工程的主菜单打开Project|Project Property|Configuration Properties|C/C |Advanced|Calling Convention,选择调用约定(注意:这种做法对类成员函数无效)。 常用的调用约定有以下3种: 1)__cdecl。这是VC编译器默认的调用约定。其规则是:参数从右向左压入堆栈,函数退出时由caller清理堆栈中的参数。这种调用约定的特点是支持可变数量的参数,比如printf方法。由于callee不知道caller到底将多少参数压入堆栈,因此callee就没有办法自己清理堆栈,所以只有函数退出之后,由caller清理堆栈,因为caller总是知道自己传入了多少参数。 2)__stdcall。所有的Windows API都使用__stdcall。其规则是:参数从右向左压入堆栈,函数退出时由callee自己清理堆栈中的参数。由于参数是由callee自己清理的,所以__stdcall不支持可变数量的参数。 3) __thiscall。类成员函数默认使用的调用约定。其规则是:参数从右向左压入堆栈,x86构架下this指针通过ECX寄存器传递,函数退出时由callee清理堆栈中的参数,x86构架下this指针通过ECX寄存器传递。同样不支持可变数量的参数。如果显式地把类成员函数声明为使用__cdecl或者__stdcall,那么,将采用__cdecl或者__stdcall的规则来压栈和出栈,而this指针将作为函数的第一个参数最后压入堆栈,而不是使用ECX寄存器来传递了。 反编译代码的跟踪(不熟悉汇编可跳过) 以下代码为和foo函数对应的堆栈帧建立相关的代码的反编译代码,我将逐行给出注释,可对照前文中对堆栈的描述: main函数中 int result=foo(3,4); 的反汇编: 008A147E push 4 //b=4 压入堆栈008A1480 push 3 //a=3 压入堆栈,到达图2的状态008A1482 call foo (8A10F5h) //函数返回值入栈,转入foo中执行,到达图3的状态008A1487 add esp,8 //foo返回,由于采用__cdecl,由Caller清理参数008A148A mov dword ptr [result],eax //返回值保存在EAX中,把EAX赋予result变量 下面是foo函数代码正式执行前和执行后的反汇编代码 008A13F0 push ebp //把ebp压入堆栈008A13F1 mov ebp,esp //ebp指向先前的ebp,到达图4的状态008A13F3 sub esp,0E4h //为局部变量分配0E4字节的空间,到达图5的状态008A13F9 push ebx //压入EBX008A13FA push esi //压入ESI008A13FB push edi //压入EDI,到达图7的状态008A13FC lea edi,[ebp-0E4h] //以下4行把局部变量区初始化为每个字节都等于cch008A1402 mov ecx,39h008A1407 mov eax,0CCCCCCCCh008A140C rep stos dword ptr es:[edi]...... //省略代码执行N行......008A1436 pop edi //恢复EDI008A1437 pop esi //恢复ESI008A1438 pop ebx //恢复EBX008A1439 add esp,0E4h //回收局部变量地址空间008A143F cmp ebp,esp //以下3行为Runtime Checking,检查ESP和EBP是否一致008A1441 call @ILT 330(__RTC_CheckEsp) (8A114Fh)008A1446 mov esp,ebp008A1448 pop ebp //恢复EBP008A1449 ret //弹出函数返回地址,跳转到函数返回地址执行 //(__cdecl调用约定,Callee未清理参数) 参考 Debug Tutorial Part 2: The Stack Intel汇编语言程序设计(第四版) 第8章 来源整理于网络素材,版权归原作者所有,如有侵权,请联系删除,谢谢。 ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧关注我的微信公众号,回复“加群”按规则加入技术交流群。

    技术让梦想更伟大

  • 吸引住妹子的trace_event技术

    一天,有人报上了一个问题,发现一台服务器上空闲内存不足,slab占用了40多G,想知道什么原因,然后拉我进入在线会议远程看看。 我进入会议常规检测一番,于是想看看哪个slab占用内存比较多,直接上小脚本: while sleep 1; do cat /proc/slabinfo | awk '{name=$1; size=$2*$4/4096; \printf "%s %lu\n", name, size;}' | sort -n -r -k 2 | head -n 20; \ echo "--------------";done; 结果显示类似如下: TCPv6 9347580  (单位:4K, 大约36G) inode_cache 3519 ext3_inode_cache 3427 dentry 2285 kmem_cache 1389 sysfs_dir_cache 832 buffer_head 682 radix_tree_node 675 vm_area_struct 505 size-2048 500 task_struct 496 size-1024 464 ... 可以看到TCPv6占用了36G左右, 然后会议上有个负责业务应用的妹子问,能知道是哪个进程占用的吗? 我装着不忙地喝了一口百岁山,于是派上trace_event出场:(以下操作过程中全场安静,都盯着我的键盘输出) 首先通过/proc/slabinfo 查看到TCPv6 object size=1856,然后: cd /sys/kernel/debug/tracing/echo 'bytes_alloc==1856' >events/kmem/kmem_cache_alloc/filterecho 1 > ./options/stacktracecat ./trace 从./trace中打印出的堆栈信息和进程号,确认是他们的业务进程xxx正在干什么事(已排除内存泄漏) 这时候妹子抢占了会上所有人的讲话,笑着说:"能把history打印出来吗?",连续提醒了我三次,说想学习一下。<真是一个好学的童鞋 :-)> 这个时候本想顺道宣传一下我在阅码场发布的tracers视频课程,视频课程里面各个traces都有很详细的讲解和案例. 但是工作时间要体现一定的专业和严肃性,并没有宣传,如果她有机会能看到这篇公众号之后再去订阅会更好:-) 最后我又喝下一口百岁山, 敲下history | tail -20 之后独自退出了会议... ---end---

    Linux阅码场 ev ce

  • 干货分享:CAN总线详解 整车的控制只需要一条线

    CAN(“Controller Area Network”,控制器局域网) 作用:将整车中各种不同的控制器连接起来,实现信息的可靠共享,并减少整车线束数量。可以设想一种极端情况,如下图所示: 如:果整车上所有的用电设备都是一个独立的CAN总线节点,并且每一个节点都向外发送自己当前的状态,并接受来自外部的信息, 那么整车的控制只需要一条CAN总线控制线和电源线就可以了! CAN总线的基本工作原理 CAN总线的通信通过一种类似于“会议” 的机制实现的,只不过会议的过程并不是由一方(节点)主导,而 是,每一个会议参加人员都可以自由的提出会议议题(多主通信模式),二者对应关系如下: CAN总线工作流程 CAN总线的优势 数据传输速度高1Mbit/s,距离远 抗干扰能力强(差分数据线) 具有自我诊断能力(错误侦测) CAN总线网络结构 01 CAN总线网络节点结构 02 为何CAN收发器 照BOSCH CAN总线标准将0或1逻辑信号转换为标准中规定的电平,同时有反馈功能 CAN总线上的电平 CAN2.0A/B标准规定:总线空闲时,CAN_H和CAN_L上的电压为2.5V 在数据传输时,显性电平(逻辑 0):CAN_H 3.5V CAN_L 1.5V 隐性电平(逻辑 1):CAN_H 2.5V CAN_L 2.5V 03总线长度的思考 影响总线长度的主要因素: (1)CAN总线通信的应答机制,即成功接收到一帧报文的节点必须在 应答场的”应答间隙“期间发送一位“显性位”表示成功接收到一帧数据 如:通信速率为250Kbit/s,传送一个bit所需时间为:1/250×1000 = 4μ 那么,该信号在总线上的延时时间必须小于(2μ?)才能保证发送节点成功的在应答间隙期间接收到该“显性电平”。 任何一根导线都可以简化为左图所示的电路模型,可以看到,其中既有电感又有电容,因此,电流在其中传输并不是光速,而是需要一定的时间。 对于双绞线而言,信号在其中的传播延时时间约为,5ns/m(典型值)。当通信速率达到1Mbit/s时,40m的总线长度, 延时时间就达到200ns,而允许延时时间为600ns左右,还是不能不考虑的! 由上面的分析可知: 总线通信速率越高,通信距离越短,对物理传输线的要求就越高,在双绞线、屏蔽线还是其他的传输线选择上,通信速率是一个很关键的参数。 影响总线长度的其他因素: 信号在节点ECU内部的延时时间 振荡器的容差(各个节点ECU内部晶振频率的差别) 这些因素加起来就形成了CAN总线通信中总的信号延时。 CAN总线的硬件抗干扰 共模电感作用:共模电压有较大的感 抗,差模电压感抗为零,相当于电感滤波。对共模电流有较大的阻碍作用。 终 端 电阻 120 欧姆并非固定不变,这跟使用的导线有关! 总线长度的限制——位定时、同步 CAN总线控制器按照时间片的概念将每一个bit的时间划分成了n个时间片。这样做的目的就是为了实现CAN总线的同步、保证不同节点间时间的一致性。 如:晶振和CAN CLOCK,频率均为4MHz,那么每一个时间片最小时间就为0.25μs,通信波特率为250Kbit/s,那么每一个bit的时间就为4μs, 因此,每一个bit的总的时间片数目就为16。当然可以进一步提高晶振频率,使得每一个bit被划分的更加细致。 CAN2.0A/B将每一个bit的时间划分成了4段,同步段、传输段、相位段1和相位段2,每一段占用一定的时间片 Can总线报文帧结构 CAN总线共有四种报文: 1 数据帧 2 远程帧 3 错误帧 4 过载帧 数据帧定义 帧起始:1bit。从图中看出,在帧间隙后由逻辑1(至少两个bit)向逻辑 0 的跳变就被认为是帧起始,它的作用就是为了硬同步。 仲裁场:由29bit的ID标示符和IDE、SRR、RTR位构成。IDE位用于标示该帧是扩展帧(29bit ID)还是标准帧(11bit ID);SRR在扩展帧 中 为 一 隐 性 位 ;R T R 位 为 远 程 帧 标 志 位 。 由上图可以看出,11bit的基本ID首先被发送(ID28~ID18),然后在发送18bit的扩展ID(ID17~ID0) CAN总线的仲裁机制 要点 (1)首先发送ID的29位,优先级问题 (2)总线电平由谁决定 CAN总线总裁机制的实现也就实现了CAN总线的多主机模式,总线节点不存在谁主谁从的概念 注意:我们可以人为的给29位的ID赋予一定的意义从而区分不同的报文类型! 报文滤波 报文滤波可以通过软件编程的方式实现,也可以通过硬件(芯片内部的报文滤波寄存器)实现,但二者实现的原理是相同的,如下图所示: 数据帧中的其他场作用 控制场:包括两位保留位(必须为0),和数据长度位(DLC0~DLC3) 数据场:包括最多8个字节的数据 CRC场:是一种算法,对数据进行CRC校验,共15bit,其后跟了一位CRC界定符——为1(隐性电平) 应答场:为两个1(总线电平为低电平),其中一位为应答间隙,另一位为应答界定符。成功接收到数据的节点必须发送一位显性位(总线电平为高电平) 来应答该发送节点,必须注意:该显性位必须在应答间隙期间, 即1bit的时间内将总线电平拉高。帧结尾:7个连续的1组成(隐性电平) CAN总线的侦听机制—支持仲裁及错误检查 帧听就是发出去的数据再采样回来,比较采样回来的数据是否和发出的数据一致! CAN总线错误检测 CAN总线通过如下几个方面进行错误检测 当节点赢得总线发送权后,会对总线电平进行检测,当发送的电平和检测到的总线电平不一致时,认为错误; 出现6个连续相同的电平时,认为是填充错误; CRC错误,接收数据的节点按照与发送数据的节点相同的方法计算数据的CRC校验值,如果接收节点的计算结果与数据包中CRC场的数据不一致, 认为是CRC错误; 应答错误,在应答场如果没有监控到一个显性电平,那么就认定一个应答错误; 固定位错误,例如:CRC界定符等,其电平是固定的,当监控到该电平不相符时,认定一个错误; 另:总线同步机制也是CAN总线容错的一种方式; 注意:通过上面5种错误检测机制,发送节点和接收节点均可以检测到总线上的错误,并通过错误的累加来实现总线节点的关闭等操作 CAN总线负载率计算 计算例子: 假设CAN总线波特率为250Kbit/s,总线报文发送时间间隔为10ms, 报文为数据帧(8个字节数据),那么10ms内总线能够支持的最大报文数量为多少? 第一步:根据通信波特率计算10ms总共可以发送多少bit (250000/1000)*10 = 2500bit 第二步:计算最长的一帧报文有多少个bit 1sof 29id 1ide 1rtr 1srr 2r 4dlc 8*8data 16crc 2ack 7eof = 128bit 第三步:计算10ms内可以支持的报文数目 2500/128 ≈ 19 由上面的计算可知,当10ms间隔的报文数量超过19条时,就会出现丢帧,总线饱和。 计算报文数量也是设计CAN网络所要考虑的,可以查阅相关文献看负载率在多少时合适 来源:头条 亿佰特物联网实验室 —— The End ——

    小麦大叔 控制 CAN总线

  • 认识他们,我才知道什么是优秀!

    大家好,我是小麦,这次给大家推荐几个电子行业内的优质公众号,每一个号都有自己的特点,号主毕业于名校,资历丰富,专业技术很强。认识他们,我才知道什么是优秀!快去看看他们在公众号分享的内容吧,如果有你喜欢的公众号,不妨扫码关注一下。一口Linux公众号「一口Linux」号主彭老师,曾就职于中兴等全球知名企业,曾任华清远见教学总监。《从0学Linux驱动第一期》视频共32期已经更新完毕,并发布于B站,ARM系列入门视频正在更新中。彭老师精通Linux系统编程、计算机网络、ARM、Linux驱动、龙芯、物联网,已建立多个高质量粉丝群,耐心解答问题,在读者中有非常好的反响。如果你缺乏动力,或不知所措,或无法坚持,或迷茫懈怠,可以找彭老师聊一聊,一定会有新的收获。一口Linux / 原创文章汇总一口Linux / 从0学ARMLinux驱动关注,后台回复 【电子文档】,获取精选Linux入门视频和海量资料。▼点击下方即可关注公众号▼电力电子技术与新能源电力电子技术与新能源,在这里有电力电子、新能源干货、行业发展趋势分析、最新产品介绍、众多技术达人与您分享经验,欢迎关注微信公众号:电力电子技术与新能源(Micro_Grid),论坛:www.21micro-grid.com,建立的初衷就是为了技术交流,作为一个与产品打交道的技术人员,市场产品信息和行业技术动态也是必不可少的,希望大家不忘初心,怀有一颗敬畏之心,做出更好的产品!光伏逆变器,光储一体机,风电变流器, PCS,新能源汽车,充电桩,车载电源,双向DCDC,储能,直流微网、交流微网以及APF,SVG ,DVR, UPQC等谐波治理和无功补偿装置等。电力电子技术与新能源文章电力电子技术与新能源视频▼点击下方即可关注公众号▼嵌入式客栈公众号【嵌入式客栈】,号主逸珺,高级嵌入式软件工程师,从事嵌入式软硬件开发多年,主要分享Linux系统构建、Linux驱动开发、实战信号处理算法(数字滤波器、谱分析等)、单片机技术、AIOT学习笔记等相关技术内容。目前在一家外资芯片相关公司工作。【嵌入式客栈】精选文章汇总对嵌入式Linux, 数字信号处理 和单片机技术感兴趣的小伙伴欢迎关注公众号【嵌入式客栈】,查看更多精彩内容。▼点击下方即可关注公众号▼痞子衡嵌入式号主痞子衡,211本硕,CSDN博客专家,博客园前3000名榜主,坚持技术写作已超4年,目前就职于恩智浦(NXP)半导体(属于原飞思卡尔半导体派系),担任高级系统应用工程师。痞子衡会定期分享硬核嵌入式技术原创文章,行文主要特色是不空谈技术理论,而从一个个真实客户项目案例中出发,于实战中融入嵌入式开发技巧。痞子衡也正在推出《嵌入式半月刊》,计划办100期,收录嵌入式领域有用有趣的工具或项目以及热点新闻。【痞子衡嵌入式】精选文章汇总关注公众号【痞子衡嵌入式】,查看更多精彩内容。来吧,跟痞子衡一起玩转嵌入式。▼点击下方即可关注公众号▼

    小麦大叔

  • librtmp推流库在瑞芯微RV1109平台上的移植和应用(一)

    点击上方「嵌入式云IOT技术圈」,选择「置顶公众号」第一时间查看嵌入式笔记!Hello,大家好,我又回来了!好些天没时间写文章了,最近在研究生课程、案例分析、论文、小组团队会议、研讨会等上花了很多时间,导致近期睡眠时间严重不足:今天介绍在嵌入式Linux下跑RTMP推流用的librtmp库的两种移植方法。目前,在网上发现很多作者写的东西都是错的,基本上都是复制粘贴,完全没有经过验证就照搬过去,对于技术学习角度来说,这是不严谨的,所以我决定自己重新再梳理一遍。关于rtmp,目前我所了解的库有librtmp和srs-lib-rtmp,其中srs是比较容易的,它可以直接将H.264的码流推送到RTMP服务器,而librtmp则需要对H.264再做一次的封装,我们先来介绍librtmp在嵌入式平台上的移植,接下来我将用librtmp来实现MIPI摄像头的实时推流。5、测试参考:https://blog.csdn.net/fteworld/article/details/51171731?locationNum=7

    嵌入式云IOT技术圈 移植 tmp

  • 一个软硬件开源的低功耗LCD时钟

    作者 | strongerHuang 微信公众号 | 嵌入式专栏 这是一款基于 AVR128DA48 的超低功耗 LCD 时钟,能够使用 CR2032 纽扣电池或太阳能电池运行三年以上: 它使用 AVR128DA48 的片上温度传感器,用 ADC 读取其自身的电源电压。还有一个 I2C 接口,你可以连接一个外部传感器,如湿度传感器。 介绍 尽管LCD液晶显示是相对较旧的技术,但与新型显示器相比,它们仍具有多项优势,包括低功耗、低成本和可读性。 1.硬件电路LCD 时钟的电路: 基于 AVR128DA48单片机 LCD显示LCD显示器为四位七段静态LCD,40引脚,可显示温湿度,LCD 显示器安装在电路板的正面,元件在背面。 MCU处理器该处理器是采用 TQFP-48 封装的 AVR128DA48,但该 PCB 可与一系列其他 48 引脚处理器配合使用,比如选择内存容量更低、价格更低的一些MCU代替。 电池(电源) 这里采用CR2032 或类似电池为其供电,或者使用太阳能电池,附加超级电容来供电: I2C接口为方便扩展,这里添加了I2C接口,这里可以添加温湿度传感器,或者其他I2C从设备。 源码 这里先分享一些主要源码内容,最后提供源码链接。 1.IO配置 void PortSetup () { for (int p=0; p<4; p ) Digit[p]->DIR = 0xFF; // All pins outputs PORTE.DIR = PIN0_bm | PIN1_bm; // COMs outputs, PE0 and PE1 PORTF.DIR = PIN5_bm | PIN4_bm; // 1A, colon} 2.时钟这里节省成本,并非使用时钟芯片或模块,用单片机定时器计数实现时钟的功能。 利用定时器中断实现时钟计数、更新: ISR(RTC_PIT_vect) { static uint8_t cycles = 0; static unsigned long halfsecs; RTC.PITINTFLAGS = RTC_PI_bm; // Clear interrupt flag // Toggle segments for (int p=0; p<4; p ) Digit[p]->OUTTGL = 0xFF; // Toggle all PORTA,B,C,D pins PORTE.OUTTGL = PIN0_bm | PIN1_bm; // Toggle COMs, PE0 and PE1 PORTF.OUTTGL = PIN5_bm | PIN4_bm; // Toggle segment 1A, Colon cycles ; if (cycles < 32) return; cycles = 0; // Update time halfsecs = (halfsecs 1) % 172800; // 24 hours uint8_t ticks = halfsecs % 120; // Half-second ticks if (MinsButton()) halfsecs = ((halfsecs/7200)*60 (halfsecs/120 1)%60)*120; if (HoursButton()) halfsecs = halfsecs 7200; if (MinsButton() || HoursButton() || ticks < 108) DisplayTime(halfsecs); else if (ticks == 108) DisplayVoltage(); else if (ticks == 114) DisplayTemp();} 3.显示时间LCD显示部分就LCD有关: void DisplayTime (unsigned long halfsecs) { uint8_t minutes = (halfsecs / 120) % 60; #ifdef TWELVEHOUR uint8_t hours = (halfsecs / 7200) % 12 1; #else uint8_t hours = (halfsecs / 7200) % 24; #endif Digit[0]->OUT = Char[hours/10]; Digit[1]->OUT = Char[hours%10]; Digit[2]->OUT = Char[minutes/10]; uint8_t units = Char[minutes%10]; Digit[3]->OUT = units; uint8_t colon = (halfsecs

    嵌入式大杂烩 LCD 低功耗 时钟

  • 实用 | 一个简单易用的菜单框架

    来源 | 屋脊雀 菜单框架介绍 声明:本处所说的菜单是用在128*64这种小屏幕的菜单,例如下面这种,不是彩屏上的GUI。 作为一个底层驱动工程师,驱动写完了,是要写硬件测试程序的。这个测试程序,一般给测试部/硬件工程师用来测试硬件, 也会给工厂产线测试准成品。 开始的人偷懒,不想一秒就直接上,所有菜单都这样做,一层套一层: void test_main(void) { while(1) { get_key(

    嵌入式大杂烩

  • 不要再误解C volatile了

    作者:Liam Huang 最近在讨论多线程编程中的一个可能的 false sharing 问题时,有人提出加 volatile 可能可以解决问题。这种错误的认识荼毒多年,促使我写下这篇文章。 约定 Volatile 这个话题,涉及到计算机科学多个领域多个层次的诸多细节。仅靠一篇博客,很难穷尽这些细节。因此,若不对讨论范围做一些约定,很容易就有诸多漏洞。到时误人子弟,就不好了。以下是一些基本的约定: 1. 这篇博文讨论的 volatile 关键字,是 C 和 C 语言中的关键字。Java 等语言中,也有 volatile 关键字。但它们和 C/C 里的 volatile 不完全相同,不在这篇博文的讨论范围内。 2. 这篇博文讨论的 volatile 关键字,是限定在 C/C 标准之下的。这也就是说,我们讨论的内容应该是与平台无关的,同时也是与编译器扩展无关的。 3. 相应的,这篇文章讨论的「标准」指的是 C/C 的标准,而不是其他什么东西。 4. 我们希望编写的代码是 (1) 符合标准的,(2) 性能良好的,(3) 可移植的。这里 (1) 保证了代码执行结果的正确性,(2) 保证了高效性,(3) 体现了平台无关性(以及编译器扩展等的无关性)。 含义 单词 volatile 的含义 在谈及 C/C 中的 volatile 关键字时,总有人会拿 volatile 这个英文单词的中文解释说事。他们把 volatile 翻译作「易变的」。但事实上,对于翻译来说,很多时候目标语言很难找到一个词能够反映源语言中单词的全部含义和细节。此处「易变的」就无法做到这一点。 Volatile 的意思,若要详细理解,还是应该查阅权威的英英字典。在柯林斯高阶学习词典中,volatile 是这样解释的: A situation that is volatile is likely to change suddenly and unexpectedly. 这里对 volatile 的解释有三个精髓的形容词和副词,体现了 volatile 的含义。 1. likely:可能的。这意味着被 volatile 形容的对象「有可能也有可能不」发生改变,因此我们不能对这样的对象的状态做出任何假设。 2. suddenly:突然地。这意味着被 volatile 形容的对象可能发生瞬时改变。 3. unexpectedly:不可预期地。这与 likely 相互呼应,意味着被 volatile 形容的对象可能以各种不可预期的方式和时间发生更改。 因此,volatile 其实就是告诉我们,被它修饰的对象出现任何情况都不要奇怪,我们不能对它们做任何假设。 程序中 volatile 的含义 对于程序员来说,程序本身的任何行为都必须是可预期的。那么,在程序当中,什么才叫 volatile 呢?这个问题的答案也很简单:程序可能受到程序之外的因素影响。 考虑以下 C/C 代码。 volatile int *p = /* ... */;int a, b;a = *p;b = *p; 若忽略 volatile,那么 p 就只是一个「指向 int 类型的指针」。这样一来,a = *p; 和 b = *p; 两句,就只需要从内存中读取一次就够了。因为从内存中读取一次之后,CPU 的寄存器中就已经有了这个值;把这个值直接复用就可以了。这样一来,编译器就会做优化,把两次访存的操作优化成一次。这样做是基于一个假设:我们在代码里没有改变 p 指向内存地址的值,那么这个值就一定不会发生改变。 此处说的「读取内存」,包括了读取 CPU 缓存和读取计算机主存。 然而,由于 MMIP(Memory mapped I/O)的存在,这个假设不一定是真的。例如说,假设 p 指向的内存是一个硬件设备。这样一来,从 p 指向的内存读取数据可能伴随着可观测的副作用:硬件状态的修改。此时,代码的原意可能是将硬件设备返回的连续两个 int 分别保存在 a 和 b 当中。这种情况下,编译器的优化就会导致程序行为不符合预期了。 总结来说,被 volatile 修饰的变量,在对其进行读写操作时,会引发一些可观测的副作用。而这些可观测的副作用,是由程序之外的因素决定的。 关键字 volatile 的含义 CPP reference 网站是对 C 和 C 语言标准的整理。因此,绝大多数时候,我们可以通过这个网站对语言标准进行查询。关于 volatile 关键字,有 C 语言标准和 C 语言标准可查。这里摘录两份标准对 volatile 访问的描述。 C 语言:Every access (both read and write) made through an lvalue expression of volatile-qualified type is considered an observable side effect for the purpose of optimization and is evaluated strictly according to the rules of the abstract machine (that is, all writes are completed at some time before the next sequence point). This means that within a single thread of execution, a volatile access cannot be optimized out or reordered relative to another visible side effect that is separated by a sequence point from the volatile access. C 语言:Every access (read or write operation, member function call, etc.) made through a glvalue expression of volatile-qualified type is treated as a visible side-effect for the purposes of optimization (that is, within a single thread of execution, volatile accesses cannot be optimized out or reordered with another visible side effect that is sequenced-before or sequenced-after the volatile access. This makes volatile objects suitable for communication with a signal handler, but not with another thread of execution, see std::memory_order). Any attempt to refer to a volatile object through a non-volatile glvalue (e.g. through a reference or pointer to non-volatile type) results in undefined behavior. 这里首先解释两组概念:值类型和序列点(执行序列)。 值类型指的是左值(lvalue)右值(rvalue)这些概念。关于左值和右值,前作有过介绍。简单的理解,左值可以出现在赋值等号的左边,使用时取的是作为对象的身份;右值不可以出现在赋值等号的左边,使用时取的是对象的值。除了 lvalue 和 rvalue,C 还定义了其他的值类型。其中,xvalue 大体可以理解为返回右值引用的函数调用或表达式,而 glvalue 则是 lvalue 和 xvalue 之和。 序列点则是 C/C 中讨论执行顺序时会提到的概念。对于 C/C 的表达式来说,执行表达式有两种类型的动作:(1) 计算某个值、(2) 副作用(例如访问 volatile 对象,原子同步,修改文件等)。因此,如果在两个表达式 E1 和 E2 中间有一个序列点,或者在 C 中 E1 于序列中在 E2 之前,则 E1 的求值动作和副作用都会在 E2 的求值动作和副作用之前。关于序列点和序列顺序规则,可以参考:这里和这里。 因此我们讲,在 C/C 中,对 volatile 对象的访问,有编译器优化上的副作用: 1. 不允许被优化消失(optimized out); 2. 于序列上在另一个对 volatile 对象的访问之前。 这里提及的「不允许被优化」表示对 volatile 变量的访问,编译器不能做任何假设和推理,都必须按部就班地与「内存」进行交互。因此,上述例中「复用寄存器中的值」就是不允许的。 需要注意的是,无论是 C 还是 C 的标准,对于 volatile 访问的序列性,都有单线程执行的前提。其中 C 标准特别提及,这个顺序性在多线程环境里不一定成立。 volatile 与多线程 volatile 可以解决多线程中的某些问题,这一错误认识荼毒多年。例如,在知乎「volatile」话题下的介绍就是「多线程开发中保持可见性的关键字」。为了拨乱反正,这里先给出结论(注意这些结论都基于本文第一节提出的约定之上): 1. volatile 不能解决多线程中的问题。 2. 按照 Hans Boehm

    技术让梦想更伟大 volatile

  • UWB定位技术与其他定位技术比较

    来源:RFID世界网 目前,常见的定位技术主要有:蓝牙、RFID、WIFI、超宽带(UWB)、超声波等。下面就详细扒一扒几种常见的无线定位技术。 UWB技术 超宽带(UWB)无线定位技术由于功耗低、抗多径效果好、安全性高、系统复杂度低,尤其是能提供非常精确的定位精度等优点,而成为未来无线 定位技术的热点和首选。 UWB技术为一种发射功率较弱,传输速率惊人(上限达到1000Mbps以上),穿透能力相对优秀,空间容量充足,而且是根据极窄脉冲下的一种无线技术,且无载波。通过这些优势,在室内定位中发挥的淋漓尽致,起到了很好的效果。通常,UWB技术的内部定位采用TDOA测距位置确定算法,这是一种无线电通信系统,该系统生成,发送,接收并在信号到达时间的差处理所述极窄的脉冲信号。超宽带室内定位系统包括UWB接收器、UWB参考标签和主动UWB标签。在位置确定由UWB接收器接收标签发射的UWB信号,通过过滤电磁波传输过程中夹杂的各种噪声干扰,得到含有效信息的信号,再通过中央处理单元进行测距定位计算分析。 射频识别(RFID)技术 它是利用电磁感应原理,通过无线激发近距离无线标签,实现信息读取的技术。射频识别距离从几厘米到十几米。RFID 用于人员定位的典型应用来自人员考勤系统的拓展,相比UWB定位技术,RFID主要用于人员是否存在于某个区域的辨识,不能做到实时跟踪,并且定位应用还没有标准的网络体系。 因此,不适用于大型设备的巡检,人员安全的确认等用途。 WI-FI技术 Wi-Fi定位应用采用在区域内安置无线基站,根据待定位 Wi-Fi 设备的信号特征,结合无线基站的拓扑结构,综合确定待定位 Wi-Fi 设备的坐标。Wi-Fi 定位技术便于利用现有的无线设备实现定位功能。 但相比于UWB定位来说, Wi-Fi 的安全性较差,功耗较高,频谱资源已趋近饱和,因此,不利于终端设备的长期携带和大规模应用。 蓝牙技术 蓝牙则是通过测量信号强度来设置定位的 它的存在是一种能量消耗慢,应用与近距离环境下的的无线传输技术,在室内安置相应的蓝牙局域网接入点,通过模式的调节,将网络配置设定为多用户的连接模式,需要确定蓝牙局域网接入点始终是这个piconet的主设备,才能达到获取用户位置的效果。 蓝牙存在的问题是,蓝牙系统的稳定性跟不上,在复杂的环境下很容易被干扰,特别是声音、其他信号,还有蓝牙设备的价格一直是处于考虑的地方。 超声波定位技术 采用反射式测距法是超声波定位最常采用的方法。该系统由一个主测距器与多个个电子标签组成,主测距器一般布置于移动机器人本体上,各个电子标签则较固定一些,布置于室内空间的固定位置。定位过程如下:先由上位机发送同频率的信号给各个电子标签,电子标签接收到后又反射传输给主测距器,从而可以确定各个电子标签到主测距器之间的距离,并得到定位坐标。 相比之下,超声波在传输过程中衰减明显从而影响其定位有效范围。 下面,则是通过一个表格,直观的进行比较。 来源整理于网络素材,版权归原作者所有,如有侵权,请联系删除,谢谢。 ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧关注我的微信公众号,回复“加群”按规则加入技术交流群。 欢迎关注我的视频号: 点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。

    技术让梦想更伟大 定位技术 UWB

  • Facebook 经验:如何从工程的角度学Python?

    其实现在程序员学 Python 不是新鲜事,甚至不少人会把 Python 当作第一语言来学习。也难怪,Python 的优点太多了,它语言简洁、开发效率高、可移植性强,并且可以和其他编程语言(比如C )轻松无缝衔接。 而且,学好 Python,之后做Python程序员爬虫,往数据分析、数据挖掘、人工智能、深度学习等多个方向都可以顺利转型。 可谓条条大路通罗马。 不过尽管 Python 上手轻松,但精通却很难。看似语法记得滚瓜烂熟,但一进入实际项目,瞬间被打回了原型。比如这些问题,你能第一时间想到答案吗? Python 中的协程和线程有什么区别? 生成器如何进化成协程? 并发编程中的 future 和 asyncio 有什么关系? 如何写出线程安全的高性能代码呢? 大部分初学者可能会卡在这里,包括自称 Python “老鸟”的我,也有分不清“列表”“元组”“字典”“集合”用法的时候,也曾苦苦钻研面向对象的理念,却在被要求设计一个稍复杂点的系统时束手无策…… 说到底,还是方法不对。 想起之前我看到 Facebook 资深工程师景霄的文章:他说他们公司刚入职的工程师,100 个里至少有 95 个,以前都从未用过 Hack 或者 PHP(Facebook 的主流语言是 Hack、PHP )。但是,这些人上手都特别快,基本上一两周后,日常编程便毫无压力了。 这是怎么做到的呢?景霄说这些工程师遵循的唯一原则,就是“从工程的角度去学习语言”。就拿学习 Python 来说,想要做到精通,必须真正理解知识概念,适当从源码层面深化认知,然后熟悉实际的工程应用,独立完成项目开发。 先分享一个景霄总结的「Python知识框架图」,建议收藏 这个图谱出自于景霄的专栏《Python核心技术与实战》,结合了他多年的工作经历,从工程角度,更为实战地梳理了Python核心知识点,从基础数据结构到装饰器、迭代器,再到并发编程、垃圾回收机制等等。可以说,跟着这套学习框架学,你会事半功倍掌握Python。 这个专栏一共 47 讲,超 2.3W 人学习,评价都挺不错的,而且景霄还会直接带你手把手完成一个用Python搭建的交易系统,让你上手练习,即学即用,能让你迅速从初学者进阶为优秀的 Python 工程师。现在只需要 ¥89 ,推荐给大家。 输入口令「Happy2021」再减¥10 到手 ¥89,原价¥129 新人价到手 ¥59.9 内容上从实际出发,以工作中遇到的实例为主线,去讲解 Python 的核心技术和应用,还附了课程的练习代码,带你从基础语法起步,掌握语言的高级用法,再到项目中实战开发,让你把学到的知识融会贯通,形成自己的 Python 框架图。 值得一提的是每篇文章后,作者都会留下一个思考题,帮助大家更好吸收知识。举个例子,在「第4篇 | 字典、集合,你真的了解吗?」中,作者讲解了 Python 最常见的 2种数据结构,留下了这道思考题: 在留言区,你能看到各种各样的解题思路,有的你可能会意想不到,收获惊喜。每个人都针对课程的思考题或知识点,留言探讨,作者也能及时给予反馈和解答。像这样既有高手带路、还能有一群共同学习的人相伴,不可多得。 换句接地气的话说:“就是找到组织的感觉”。 最后,再给大家介绍下《Python核心技术与实战》都讲什么内容。具体分成以下四大块内容: 1、Python 基础入门 必学知识:Python 基础数据结构、Python 基础语法、文件操作、错误与异常处理、Python 面向对象、模块化 2、Python 进阶核心知识 必学知识:Python 协议、Python 高级语法、Python 正则表达式、Python 并发编程、垃圾回收机制、项目实战 3、编写高质量的 Python 程序 这部分着重于教你把程序写得更加规范、更加稳定,用具体的编程操作和技巧,教你提高代码质量。比如,如何合理地分解代码、运用 assert,如何写单元测试等等。 4、Python 实战,串联整个知识体系:带你搭建量化交易系统 必学知识点:RESTful、Socket、Pandas、Numpy、Kafka、RabbitMQ、MySQL、Django 真正要掌握一门编程语言,仅仅学会分散的知识点是不够的,必须要把知识点串联起来,通过项目实战才能有更深的领悟与提高。所以这部分,景霄用量化交易系统这个具体的实战案例,带你综合运用前面所学的Python知识。 Python 必然是未来很耀眼的编程语言,无论是数据分析、人工智能,还是深度学习,掌握Python就是给自己多一条职场选择。

    技术让梦想更伟大 Facebook

  • Linux系统噪音统计(osnoise tracer)

    在Linux系统中作为一个普通线程是非常苦逼的。不仅NMI 、硬中断、软中断可以打断它,甚至其它普通线程也可以来打断干扰到它的运行。 如果没有这些打断事件,一个普通线程执行while循环,可以high过天际。这些打断事件对一个普通线程来说,就相当于噪音一样的存在。 从Linux 5.14-rc1开始引入了一个新的tracer---(osnoise tracer)。就是从一个线程thread的角度把这些噪音全部详细统计出来。 上图中 在1秒内普通线程(pid=98) 受到的各个干扰事件的次数和cpu available百分比等都可以显示出来。 统计到这个程度,感觉还是不够详细。 可以打开osnoise对应的trace event. 上面的interference 5说明在一个采样周期内被打断了5次(包括4次中断和一次a.out线程事件产生的噪音),上面的每一次打断都有事件名称和对应的时间统计: 1232 1222 1192 1262 3994882=4000242-452 (~4000242) 统计时间约等于4000242ns 因为包含了检查代码的时间时间。 代码实现: 在以上每个打断事件处理函数中都插上trace event的钩子函数 来统计事件的执行时间,然后在每个cpu上运行一个内核线程进行周期性统计. 这个强大的osnoise tracer使用到的技术仅仅是用到了tracer event提供的基础设施。 我在阅码场发布过一个视频课程,对linux系统中各个tracer的使用和代码实现都有非常详细的讲解: ---end---

    Linux阅码场 os 噪音 ce

  • 元宇宙,互联网的下一个风口?

    最近周末看到好几篇元宇宙文章,突然感觉一些东西又火了起来,很多人可能会说:"这个不就是多年前的VR吗,当年是很火,但后面不是又熄火了吗 ", 我相信大家第一感觉都是这样,但这次它又升华了。 就让我们看一看这个元宇宙到底是什么? Metaverse——元宇宙,一个出自1992年科幻小说「雪崩」(Snow Crash)的概念。 描述了一个人们以虚拟形象在三维空间中与各种软件进行交互的世界 在2021年突然火爆了起来。各路资本也纷纷下场,似乎这个「元宇宙」马上就要实现了一样。首先,技术成熟度的拐点似乎已经到来。Metaverse所需要的VR/AR/MR、AI、NLP、计算机视觉渲染、云端虚拟化、脑机接口等多种技术,都已经发展到了一定阶段,这为Metaverse的落地奠定了基础,甚至有人称2021年是「元宇宙」元年。 通常认为元宇宙开始被人熟知是借助2018年上映的科幻电影「头号玩家」,其中的虚拟世界「绿洲」(Oasis)似乎是在告诉所有人,一个触手可及的元宇宙就在眼前。 在2018在「头号玩家」中,斯皮尔伯格呈现了一个看起来荒诞不经但又令人神往的世界。当你穿上一身装备,站在一个可以自由奔跑的平台上时,一个崭新的世界便呈现了出来。 这里不仅有繁华的都市,独立的经济系统,而且玩家的身份将不再受到现实的影响,即可以做普通人,也可以成为英雄 ,甚至也可以成为一个虚拟角色。不过大概需要购买版权? 最近上映的《 失控玩家 》,这部电影主要讲的是在一个电子游戏里,平凡的银行出纳员盖发现自己其实是开放世界电子游戏中的背景角色,于是决定成为英雄,并改写自己的故事。该片的上映也再次引发了人们对于“元宇宙”概念的关注。 堡垒之夜对于沉迷其中的玩家来说,除了占据了他们25%的闲暇时间以外,俨然已经成为了一个社交的平台。这也就不难理解为什么会有艺术家开演唱会,以及电影首映了。 「星球大战」最新电影片段的虚拟首映 2020年4月,美国歌手Travis Scott举办虚拟演唱会,观众达到1230万 这场演唱会格外得惊艳,除了陆地上的场景,还有深海和太空。精致的建模和酷炫的特效,让这个虚拟演唱会创造了游戏史上最高同时在线观看人数的记录。 为什么有人向往? 恩斯特·克莱恩的科幻小说《头号玩家》中,书中人物几乎每时每刻都沉浸在一个名为“绿洲”的虚拟世界中。这是个自闭症天才詹姆斯·哈利迪用电脑生成的虚拟现实世界,是他们逃避丑恶现实的唯一出路。 这些人物来自各种不同的背景,也从没谈过各自的背景。他们一起合作解决了问题,然后才在现实世界中去相互认识。最终,他们都发现了更好的自己。 我认为,“绿洲”这样的环境为人们提供了一种方式,可以进行尝试、整合,无惧失败,进行决策,可迅速聚集世界各地的、视角不同的一群人来共同解决问题。我自己并不想要这种方式,但这不重要,数字化新世代人想要这种方式。他们有上千个从未谋面的朋友。远程工作、虚拟合作对他们来说没什么好奇怪的,特别是考虑到这样创建出的工具也是那么真正实用。 比如最近爆火的游戏:人生重开模拟器 大家都想体验不一样的人生,而在元宇宙的非常逼真的沉浸式体验更能实现这个梦想!

    Linux阅码场 互联网

  • 精致全景图 | linux内核输出的日志去哪里了

    因为图片比较大,微信公众号上压缩的比较厉害,所以很多细节都看不清了,我单独传了一份到github上,想要原版图片的,可以点击下方的阅读原文,或者直接使用下面的链接,来访问github:https://github.com/wangyuntao/linux-kernel-illustrated另外,精致全景图系列文章,以及之后的linux内核分析文章,我都会整理到这个github仓库里,欢迎大家star收藏。熟悉linux内核,或者看过linux内核源码的同学就会知道,在内核中,有一个类似于c语言的输出函数,叫做printk,使用它,我们可以打印各种我们想要的信息,比如内核当前的运行状态,又或者是我们自己的调试日志等,非常方便。那当我们调用printk函数后,这些输出的信息到哪里去了呢?我们又如何在linux下的用户态,查看这些信息呢?为了解答这些疑问,我画了一张printk全景图,放在了文章开始的部分,这张图既包含了printk在内核态的实现,又包含了其输出的信息在用户态如何查看。我们可以根据这张图,来理解printk的整体架构。在内核编码时,如果想要输出一些信息,通常并不会直接使用printk,而是会使用其衍生函数,比如 pr_err / pr_info / pr_debug 等,这些衍生函数附带了日志级别、所属模块等其他信息,比较友好,但其最终还是调用了printk。printk函数会将每次输出的日志,放到内核为其专门分配的名为ring buffer的一个槽位里。ring buffer其实就是一个用数组实现的环形队列,不过既然是环形队列,就会有一个问题,即当ring buffer满了的时候,下一条新的日志,会覆盖最开始的旧的日志。ring buffer的大小,可以通过内核参数来修改。printk在将日志放到ring buffer后,会再调用系统console的相关方法,将还未输出到系统控制台的消息,继续输出到控制台,这个后面会详细说,这里就暂不赘述。以上就是printk在内核态的实现。在用户态,我们有几个方式,可以查看printk输出的内核日志,比如使用dmesg命令,cat /proc/kmsg文件,或者是使用klogctl函数等,这些方式分别对应于全景图中用户态的橙色、绿色、和蓝色的部分。dmesg命令,在默认情况下,是通过读取/dev/kmsg文件,来实现查看内核日志的。当该命令运行时,dmesg会先调用open函数,打开/dev/kmsg文件,该打开操作在内核中的逻辑,会为dmesg分配一个file实例,在这个file实例里,会有一个seq变量,该变量记录着下一条要读取的内核日志在ring buffer中的位置。刚打开/dev/kmsg文件时,这个seq指向的就是ring buffer中最开始的那条日志。之后,dmesg会以打开的/dev/kmsg文件为媒介,不断的调用read函数,从内核中读取日志消息,每读取出一条,seq的值都会加一,即指向下一条日志的位置,依次往复,直到所有的内核日志读取完毕,dmesg退出。以上就是dmesg的主体实现。第二种查看内核日志的方式,是通过 cat /proc/kmsg 命令。该命令和dmesg命令的实现机制基本类似,都是通过读文件,只不过cat读取的是/proc/kmsg文件,而dmesg读取的是/dev/kmsg文件。读取这两个文件最大的区别是,/dev/kmsg文件每次打开时,内核都会为其分配一个单独的seq变量,而/proc/kmsg文件每次打开时,用的都是同一个全局的静态seq变量,叫做syslog_seq。syslog_seq指向的也是下一条要读取的内核日志在ring buffer中的位置,但因为它是一个全局的静态变量,当有多个进程要读取/proc/kmsg文件时,就会有一个比较严重的问题,即内核日志会被这几个进程随机抢占读取,也就是说,每个进程读到的都是整个内核日志的一部分,是不完整的,这也是dmesg命令默认不使用/proc/kmsg文件的原因。第三种查看内核日志的方式,是通过klogctl函数。该函数是glibc对syslog系统调用的一个简单封装,其具体使用方式,可以参考全景图中用户态的蓝色部分。klogctl函数可以指定很多命令,在上图的示例中,我们使用的是SYSLOG_ACTION_READ命令,以此来模拟 cat /proc/kmsg 行为。其实在内核层面,cat /proc/kmsg命令,使用的就是klogctl对应的syslog系统调用的SYSLOG_ACTION_READ命令的处理逻辑,所以示例中的klogctl函数相关代码,和 cat /proc/kmsg 命令其实是等价的。也就是说,klogctl函数在内核里使用的也是syslog_seq变量,它也有和/proc/kmsg文件同样的问题。其实还有一种方式可以查看内核日志,就是通过系统控制台。但这种方式和前面讲的三种方式都不一样,它是完全被动的,是内核在调用printk函数,将日志信息放到ring buffer后,再去通知系统控制台,告知其可以输出这些日志。系统控制台也是通过一个console_seq变量,记录下一条要输出内核日志的所在位置。这里说的系统控制台,是指我们在开机的时候,黑色屏幕输出的那些内容,但当我们进入图形化界面后,我们就看不到系统控制台的输出了,除非我们再用 ctrl alt f1/f2/f3 等方式,切换成系统控制台。系统控制台输出的内容,是被日志级别过滤过的,内核默认的日志过滤级别是7,即debug级别以上的日志,比如info / err 等,这些都会输出,但debug级别不会输出。该日志过滤级别,可以通过很多方式改变,比如说,可以通过内核参数 loglevel,所以,如果发现系统控制台没有输出想要的日志信息,先看下其是否被过滤掉了。以上就是printk生态的完整实现。了解printk函数的实现,对于内核开发者或研究者来说,意义非常大,但对于普通的应用开发人员来说,又有什么帮助呢?其实,随着技术的深入,我们不应该再只关心应用层面的行为,而且还要关心系统层面的行为,这样我们才能更好的去定位问题,更好的去保证我们应用的健康运行。比如,当我们的应用需要内存时,会向操作系统申请,操作系统此时给我们的,其实是虚拟内存,只有当我们的进程真正的在使用这些内存时,比如读/写,操作系统才会为其分配物理内存。但假设此时物理内存没有了,那操作系统会怎么办?对于linux内核来说,它会选择一个使用内存最多的进程,然后将其kill掉,以此来释放内存,保证后续的内存分配操作能够成功,这个我在之前文章 为什么我的进程被kill掉了 有详细讲过。对于内核的这种行为,我们就应该多加关注,而关注的方式,就是查看内核日志。比如,linux内核在kill掉进程时,会用pr_err记录一行日志:如果我们发现一个进程跑着跑着就没有了,就可以通过dmesg命令,查看是否有这个日志,如果有,说明该进程因为系统内存不足,被操作系统kill掉了。类似的,内核里还有很多error级别,甚至更高级别的日志需要我们关注,通过这些日志,我们可以及时的发现系统的异常情况,必要时可以人工介入进行干预。总之,对系统了解的越深,内核日志对我们的帮助就越大。就这些,希望你喜欢。

    Linux阅码场 linux内核

  • 超详细

    ▼点击下方名片,关注公众号▼一、什么是USB Type-C?USB Type-C是一种相对较新的标准,旨在提供高达10Gb / s的高速数据传输以及高达100W的功率流。这些功能可以使USB Type-C成为现代设备的真正通用连接标准。二、USB Type-C功能USB Type-C接口有三个主要功能:1、它有一个可翻转的连接器。接口的设计使插头可以相对于插座翻转。2、它支持USB 2.0,USB 3.0和USB 3.1 Gen 2标准。此外,它还可以在称为备用模式的操作模式下支持第三方协议,如DisplayPort和HDMI。3、它允许设备协商并通过接口选择适当的功率流。三、USB Type-C针脚USB Type-C连接器有24个引脚。图1和图2分别显示了USB Type-C插座和插头的插针。图1. USB Type-C插座图2. USB Type-C插头USB 2.0差分对D 和D-引脚是用于USB 2.0连接的差分对。插座中有两个D 引脚和两个D-引脚。但是,引脚相互连接,实际上只有一个USB 2.0数据差分对可供使用。仅包括冗余以提供可翻转的连接器。电源和接地引脚VBUS和GND引脚是电源和信号的返回路径。默认的VBUS电压为5 V,但标准允许器件协商并选择VBUS电压而不是默认值。电源传输允许VBUS具有高达20 V的电压。最大电流也可以升高到5A。因此,USB Type-C可以提供100 W的最大功率。当为诸如笔记本电脑的大型设备充电时,高功率流可能是有用的。图3显示了RICHTEK的示例,其中降压 - 升压转换器用于生成笔记本电脑所请求的适当电压。图3. RICHTEK示例请注意,电源传输技术使USB Type-C比旧标准更通用,因为它使功率水平适应负载的需要。您可以使用同一根电缆为智能手机和笔记本电脑充电。RX和TX引脚有两组RX差分对和两组TX差分对。这两个RX对中的一个以及TX对可用于USB 3.0 / USB 3.1协议。由于连接器是可翻转的,因此需要多路复用器通过电缆正确地重新路由所采用的差分对上的数据。请注意,USB Type-C端口可以支持USB 3.0 / 3.1标准,但USB Type-C的最小功能集不包括USB 3.0 / 3.1。在这种情况下,USB 3.0 / 3.1连接不使用RX / TX对,并且可以被其他USB Type-C功能使用,例如备用模式和USB供电协议。这些功能甚至可以利用所有可用的RX / TX差分对。CC1和CC2针脚这些引脚是通道配置引脚。它们执行许多功能,例如电缆连接和移除检测,插座/插头方向检测和当前广告。这些引脚也可用于Power Delivery和Alternate Mode所需的通信。下面的图4显示了CC1和CC2引脚如何显示插座/插头方向。在此图中,DFP代表下游面向端口,该端口充当数据传输中的主机或电源。UFP表示上游面向端口,它是连接到主机或电力消费者的设备。图4.CC1和CC2引脚DFP通过Rp电阻上拉CC1和CC2引脚,但UFP通过Rd将它们拉低。如果没有连接电缆,则源在CC1和CC2引脚处看到逻辑高电平。连接USB Type-C电缆可创建从5V电源到地的电流路径。由于USB Type-C电缆内只有一根CC线,因此只形成一条电流路径。例如,在图4的上图中,DFP的CC1引脚连接到UFP的CC1引脚。因此,DFP CC1引脚的电压低于5 V,但DFP CC2引脚仍处于逻辑高电平。因此,监控DFP CC1和CC2引脚上的电压,我们可以确定电缆连接及其方向。除电缆方向外,Rp-Rd路径还用作传递源电流能力信息的方式。为此,功耗(UFP)监视CC线上的电压。当CC线上的电压具有其最低值(约0.41 V)时,源可以分别为USB 2.0和USB 3.0提供500 mA和900 mA的默认USB电源。当CC线电压约为0.92 V时,源可提供1.5 A的电流。最高CC线电压约为1.68 V,对应于3A的源电流能力。VCONN引脚如上所述,USB Type-C旨在提供超快的数据传输速度以及高水平的功率流。这些特征可能需要使用通过在内部使用芯片进行电子标记的特殊电缆。此外,一些有源电缆利用重新驱动芯片来加强信号并补偿电缆等引起的损耗。在这些情况下,我们可以通过施加5 V,1 W电源为电缆内部的电路供电提供给VCONN引脚。如图5所示。图5.VCONN引脚如您所见,有源线缆使用Ra电阻来下拉CC2引脚。Ra的值与Rd不同,因此DFP仍然可以通过检查DFP CC1和CC2引脚上的电压来确定电缆方向。确定电缆方向后,与“有源电缆IC”对应的通道配置引脚将连接到5 V,1 W电源,为电缆内部的电路供电。例如,在图5中,有效的Rp-Rd路径对应于CC1引脚。因此,CC2引脚连接到VCONN表示的电源。SBU1和SBU2针脚这两个引脚对应于仅在备用模式下使用的低速信号路径。四、USB供电和备用模式如上所述,使用USB Type-C标准的半导体设备可以通过接口协商并选择适当水平的功率流。这些功率协商是通过称为USB Power Delivery的协议实现的,该协议是上面讨论的CC线上的单线通信。下面的图6显示了一个示例USB供电,其中接收器向源发送请求并根据需要调整VBUS电压。首先,要求提供9 V总线。在源稳定总线电压为9 V后,它会向接收器发送“电源就绪”消息。然后,接收器请求一个5V总线,并且源提供它并再次发送“电源就绪”消息。图6.USB供电示例值得注意的是,“USB供电”不仅仅涉及与供电相关的谈判,其他谈判,例如与备用模式相关的协商,都是使用标准CC线上的供电协议完成的。替代操作模式允许我们使用USB Type-C标准实现第三方协议,如DisplayPort和HDMI。所有备用模式必须至少支持USB 2.0和USB供电连接。来自|电子开发圈end微信公众号后台回复关键字“加群”,添加小编微信,拉你入技术群。

    8号线攻城狮

  • 二极管反向恢复电流测试案例

    ▼点击下方名片,关注公众号▼最近在网上有看到测试二极管反向恢复时间的例子,大家可以看一下,加深印象。从中可以看到,反向电流是可以很大的,甚至是可以超过正向时的电流可以看到,举例的两个二极管可以看到明显的差异。来源|百度文库,详情可查看原文链接end微信公众号后台回复关键字“加群”,添加小编微信,拉你入技术群。

    8号线攻城狮 二极管

发布文章