嵌入式Linux应用开发-驱动大全-第一章同步与互斥①
嵌入式Linux应用开发-驱动大全-第一章同步与互斥①
- 第一章 同步与互斥①
- 1.1 内联汇编
- 1.1.1 C语言实现加法
- 1.1.2 使用汇编函数实现加法
- 1.1.3 内联汇编语法
- 1.1.4 编写内联汇编实现加法
- 1.1.5 earlyclobber的例子
 
- 1.2 同步与互斥的失败例子
- 1.2.1 失败例子1
- 1.2.2 失败例子2
- 1.2.3 失败例子3
 
 
第一章 同步与互斥①

1.1 内联汇编
要深入理解 Linux内核中的同步与互斥的实现,需要先了解一下内联汇编:在 C函数中使用汇编代码。
 现代编译器已经足够优秀,大部分的 C代码转成汇编码后,效率都很高。但是有些特殊的算法需要我们手工优化,这时就需要手写汇编代码;或是有时需要调用特殊的汇编指令(比如使用 ldrex/strex实现互斥访问),这都涉及内联汇编。
 实际上你完全可以不使用内联汇编,单独写一个遵守 ATPCS规则的汇编函数,让 C函数去调用它。但是在 C函数中写汇编代码,可以不用另外新建一个汇编文件,比较方便。
 内联汇编的完整语法比较复杂,可以参考这 3篇文章:
 ① GNU C扩展汇编 https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
 ② ARM GCC 内嵌(inline)汇编手册 http://blog.chinaunix.net/uid-20543672-id-3194385.html
 ③ C内联汇编 https://akaedu.github.io/book/ch19s05.html
 这 3章文章写得细致而深入,也有些难以理解。你跟着我们的视频或文档,就可以掌握到足够的知识。 下面举 3个例子说明汇编函数、用 C函数中使用内联汇编的方法。
1.1.1 C语言实现加法
使用 GIT下载后,源码在“07_驱动大全\source\01_inline_assembly\01_c_code\main.c”:
01 #include <stdio.h> 
02 #include <stdlib.h> 
03 
04 int add(int a, int b) 
05 { 
06      return a+b; 
07 } 
08 
09 int main(int argc, char **argv) 
10 { 
11      int a; 
12      int b; 
13 
14      if (argc != 3) 
15      { 
16              printf("Usage: %s <val1> <val2>\n", argv[0]); 
17              return -1; 
18      } 
19 
20      a = (int)strtol(argv[1], NULL, 0); 
21      b = (int)strtol(argv[2], NULL, 0); 
22 
23      printf("%d + %d = %d\n", a, b, add(a, b)); 
24      return 0; 
25 } 
上面的 add函数代码最简单,但是对应的汇编也挺复杂:需要入栈、出栈等操作,效率不算高。看看test.dis:
266 00010404 <add>: 
267    10404:   b480            push    {r7} 
268    10406:   b083            sub     sp, #12 
269    10408:   af00            add     r7, sp, #0 
270    1040a:   6078            str     r0, [r7, #4] 
271    1040c:   6039            str     r1, [r7, #0] 
272    1040e:   687a            ldr     r2, [r7, #4] 
273    10410:   683b            ldr     r3, [r7, #0] 
274    10412:   4413            add     r3, r2                // 真正实现加法的只有这条指令 275    10414:   4618            mov     r0, r3 
276    10416:   370c            adds    r7, #12 
277    10418:   46bd            mov     sp, r7 
278    1041a:   f85d 7b04       ldr.w   r7, [sp], #4 
279    1041e:   4770            bx      lr 
280 
1.1.2 使用汇编函数实现加法
使用 GIT下载后,源码在“07_驱动大全\source\01_inline_assembly\02_assembly\add.S”:
01 .text            // 放在代码段 
02 .global  add     // 实现全局函数 add 
03 .thumb           // 使用 thumb指令, main.c默认使用 thumb指令, 所以这里也使用 thumb指令 
04 
05 add: 
06      add r0, r0, r1 
07      bx lr 
08 
根据 ATPCS规则,main函数调用 add(a, b)时,会把第一个参数存入 r0寄存器,把第二个参数存入 r1寄存器。
 在上面第 06行里,把 r0、r1累加后,结果存入 r0:根据 ATPCS规则,r0用来保存返回值。
 可以看到,这个 add函数连栈都没有使用,非常高效。
 这只是一个很简单的例子,我们工作中并不使用汇编来进行“加法优化”,在计算量非常大的地方可以考虑单独编写汇编函数实现优化。
1.1.3 内联汇编语法
从上面例子可以看到,我们完全可以新建一个汇编文件,在 ATPCS规则之下编写代码,这样 C函数就可以直接调用汇编函数。
 但是,需要新建汇编文件,有点麻烦。
 使用内联汇编,可以在 C代码中内嵌汇编代码。
 先看看内联汇编的语法。
 
内联汇编语法:
 ① asm
 也可以写作“asm”,表示这是一段内联汇编。
 ② asm-qualifiers
 有 3个取值:volatile、inline、goto。
 volatile的意思是易变的、不稳定的,用来告诉编译器不要随便优化这段代码,否则可能出问题。比如汇编指令“mov r0, r0”,它把 r0的值复制到 r0,并没有实际做什么事情,你的本意可能是用这条指令来延时。编译器看到这指令后,可能就把它去掉了。加上 volatile的话,编译器就不会擅自优化。
 其他 2个取值我们不关心,也比较难以理解,不讲。
 ③ AssemblerTemplate
 汇编指令,用双引号包含起来,每条指令用“\n”分开,比如:
“mov  %0, %1\n” 
“add  %0, %1, %2\n” 
④ OutputOperands
 输出操作数,内联汇编执行时,输出的结果保存在哪里。
 格式如下,当有多个变量时,用逗号隔开:
 [ [asmSymbolicName] ] constraint (cvariablename)
 asmSymbolicName是符号名,随便取,也可以不写。 constraint表示约束,有如下常用取值:
 
constraint前还可以加上一些修饰字符,比如“=r”、“+r”、“=&r”,含义如下:
 
 variablename:C语言的变量名。
示例 1如下:
 [result] “=r” (sum)
 它的意思是汇编代码中会通过某个寄存器把结果写入 sum变量。在汇编代码中可以使用“%[result]”来引用它。
示例 2如下:
 “=r” (sum)
 在汇编代码中可以使用“%0”、“%1”等来引用它,这些数值怎么确定后面再说。
⑤ InputOperands
 输入操作数,内联汇编执行前,输入的数据保存在哪里。
 格式如下,当有多个变量时,用逗号隔开:
 [ [asmSymbolicName] ] constraint (cexpression)
asmSymbolicName是符号名,随便取,也可以不写。
 constraint表示约束,参考上一小节,跟 OutputOperands类似。
cexpression:C语言的表达式。
示例 1如下:
 [a_val]“r”(a), [b_val]“r”(b)
 它的意思变量 a、b的值会放入某些寄存器。在汇编代码中可以使用%[a_val]、%[b_val]使用它们。
示例 2如下:
 “r”(a), “r”(b) 它的意思变量 a、b的值会放入某些寄存器。在汇编代码中可以使用%0、%1等使用它们,这些数值后面再说。
⑥ Clobbers 在汇编代码中,对于“OutputOperands”所涉及的寄存器、内存,肯定是做了修改。但是汇编代码中,也许要修改的寄存器、内存会更多。比如在计算过程中可能要用到 r3保存临时结果,我们必须在“Clobbers”中声明 r3会被修改。 下面是一个例子: : “r0”, “r1”, “r2”, “r3”, “r4”, “r5”, “memory”
我们常用的是有“cc”、“memory”,意义如下:
 
1.1.4 编写内联汇编实现加法
使用 GIT下载后,源码在“07_驱动大全\source\01_inline_assembly\03_inline_assembly\main.c”:
04 int add(int a, int b) 
05 { 
06      int sum; 
07      __asm__ volatile ( 
08              "add %0, %1, %2" 
09              :"=r"(sum) 
10              :"r"(a), "r"(b) 
11              :"cc" 
12      ); 
13      return sum; 

所以第 08行代码就是:把第 1、2个操作数相加,存入第 0个操作数。也就是把 a、b相加,存入 sum。
 还可以使用另一种写法,在 Linux内核中这种用法比较少见。
 使用 GIT下载后,源码在“07_驱动大全\source\01_inline_assembly\03_inline_assembly\main2.c”:
 
1.1.5 earlyclobber的例子
OutputOperands的约束中经常可以看到“=&r”,其中的“&”表示 earlyclobber,它是最难理解的。有
 一些输出操作数在汇编代码中早早就被写入了新值 A,在这之后,汇编代码才去读取某个输入操作数,这个输出操作数就被称为 earlyclobber(早早就被改了)。
 这可能会有问题:假设早早写入的新值 A,写到了 r0寄存器;后面读输入操作数时得到数值 B,也可能写入 r0寄存器,这新值 A就被破坏了。
 核心原因就在于输出操作数、输入操作数都用了同一个 r0寄存器。为什么要用同一个?因为编译器不知道你是 earlyclobber的,它以为是先读入了所有输入操作数,都处理完了,才去写输出操作数的。按这流程,没人来覆盖新值 A。
 所以,如果汇编代码中某个输出操作数是 earlyclobber的,它的 constraint就要加上“&”,这就是告诉编译器:给我分配一个单独的寄存器,别为了省事跟输入操作数用同一个寄存器。
 使用 GIT下载后,源码在“07_驱动大全\source\01_inline_assembly\04_earlyclobber\main.c”:
 
上面的代码中,输出操作数%0对应的寄存器是 r3,输入操作数%1对应的寄存器也是 r3。
 第 8行更新了%0的值后,第 9行修改%1的值,由于%0、%1是同一个寄存器,所以%0的值也被修改了。 最终返回的累加值是错的,增加了 1,如下图所示:
 
怎么修改?在第 11行加“&”就可以了,这是告诉编译器,对于%0操作数它是 earlyclobber的,不能跟其他操作数共用寄存器,如下:
 
从右边的反汇编码可以知道,%0跟%1、%2使用不一样的寄存器,所以后面第 9、10行无法影响到%0的值。
 程序运行结果如下图所示:
 
1.2 同步与互斥的失败例子
注意:本节在 GIT上没有源码。
 一句话理解同步与互斥:我等你用完厕所,我再用厕所。
 什么叫同步?就是条件不允许,我要等等。
 什么是互斥?你我早起都要用厕所,谁先抢到谁先用,中途不被打扰。
 同步与互斥经常放在一起讲,是因为它们之的关系很大,“互斥”操作可以使用“同步”来实现。我“等”你用完厕所,我再用厕所。这不就是用“同步”来实现“互斥”吗?
 有时候看代码更容易理解,伪代码如下:
01 void  抢厕所(void) 
02 { 
03    if (有人在用) 我眯一会; 
04    用厕所; 
05    喂,醒醒,有人要用厕所吗; 
06 } 
假设有 A、B两人早起抢厕所,A先行一步占用了;B慢了一步,于是就眯一会;当 A用完后叫醒 B,B也就愉快地上厕所了。
 在这个过程中,A、B是互斥地访问“厕所”,“厕所”被称之为临界资源。我们使用了“休眠-唤醒”的同步机制实现了“临界资源”的“互斥访问”。
 上面是一个有“味道”的例子,回到程序员的世界,一个驱动程序同时只能有一个 APP使用,怎么实现?
1.2.1 失败例子1
01 static int valid = 1; 
02 
03 static ssize_t gpio_key_drv_open (struct inode *node, struct file *file) 04 { 
05      if (!valid) 
06      { 
07              return -EBUSY; 
08      } 
09      else 
10      { 
11              valid = 0; 
12      } 
13 
14      return 0; //成功 
15 } 
16 
17 static int gpio_key_drv_close (struct inode *node, struct file *file) 
18 { 
19      valid = 1; 
20      return 0; 21 } 
22 
看第 5行,我们使用一个全局变量 valid来实现互斥访问。这有问题吗?很大概率没问题,但是并非万无一失。
 注意:编写驱动程序时,要有系统的概念,程序 A调用驱动程序时,它可能被程序 B打断,程序 B也去调用这个驱动程序。
 下图是一个例子,程序 A在调用驱动程序的中途被程序 B抢占了 CPU资源:
 
程序 A执行到第 11行之前,被程序 B抢占了,这时 valid尚未被改成 0;
 程序 B调用 gpio_key_drv_open时,发现 valid等于 1,所以成功返回 0;
 当程序 A继续从第 11行执行时,它最终也成功返回 0;
 这样程序 A、B都成功打开了驱动程序。
 注意:在内核态,程序 A不是主动去休眠、主动放弃 CPU资源;而是被优先级更高的程序 B抢占了,这种行为被称为“preempt”(抢占)。
1.2.2 失败例子2
上面的例子是不是第 5行到第 11行的时间跨度大长了?再优化一下程序行不行?代码如下:
01 static int valid = 1; 
02 
03 static ssize_t gpio_key_drv_open (struct inode *node, struct file *file) 
04 { 
05      if (--valid) 
06      { 
07              valid++; 
08              return -EBUSY; 
09      } 
10      return 0; 
11 } 
12 
13 static int gpio_key_drv_close (struct inode *node, struct file *file) 
14 { 
15      valid = 1; 
16      return 0; 
17 } 
18 
第 5行先减 1再判断,这样可以更大概率地避免问题,但是还是不能确保万无一失。对数据的修改分为 3步:读出来、修改、写进去。请看下图:
 
进程 A在读出 valid时发现它是 1,减 1后为 0,这时 if不成立;但是修改后的值尚未写回内存; 假设这时被程序 B抢占,程序 B读出 valid仍为 1,减 1后为 0,这时 if不成立,最后成功返回; 轮到 A继续执行,它把 0值写到 valid变量,最后也成功返回。
 这样程序 A、B都成功打开了驱动程序。
1.2.3 失败例子3
前面 2个例子,都是在修改 valid的过程中被别的进程抢占了,那么在修改 valid的时候直接关中断不就可以了吗?
01 static int valid = 1; 
02 
03 static ssize_t gpio_key_drv_open (struct inode *node, struct file *file) 
04 { 
05       unsigned long flags; 
06       raw_local_irq_save(flags); // 关中断 
07      if (--valid) 
08      { 
09              valid++; 
10              raw_local_irq_restore(flags);  // 恢复之前的状态 
11              return -EBUSY; 
12      } 
13       raw_local_irq_restore(flags);          // 恢复之前的状态 
14      return 0; 
15 } 
16 
17 static int gpio_key_drv_close (struct inode *node, struct file *file) 
18 { 
19      valid = 1; 
20      return 0; 
21 } 
第 06行直接关中断,这样别的线程、中断都不能来打扰本线程了,在它读取、修改 valid变量的过程中无人打扰。
 没有问题了?
对于单 CPU核的系统上述代码是没问题的;但是对于 SMP系统,你只能关闭当前 CPU核的中断,别的CPU核还可以运行程序,它们也可以来执行这个函数,同样导致问题,如下图:
 
假设 CPU0上进程 A、CPU1上进程 B同时运行到上图中读出 valid的地方,它们同时发现 valid都是 1,减减后都等于 0,在第 07行判断条件都不成立,所以在第 14行都可以返回 0,都可以成功打开驱动。
相关文章:
 
嵌入式Linux应用开发-驱动大全-第一章同步与互斥①
嵌入式Linux应用开发-驱动大全-第一章同步与互斥① 第一章 同步与互斥①1.1 内联汇编1.1.1 C语言实现加法1.1.2 使用汇编函数实现加法1.1.3 内联汇编语法1.1.4 编写内联汇编实现加法1.1.5 earlyclobber的例子 1.2 同步与互斥的失败例子1.2.1 失败例子11.2.2 失败例子21.2.3 失败…...
 
【计算机网络】 基于UDP的简单通讯(客户端)
文章目录 客户端流程代码实现添加头文件以及库依赖加载库创建套接字发送接收数据关闭套接字、卸载库 测试 客户端 流程 客户端跟服务端差不多,也要先加载库,在加载库之后也要创建套接字,但是客户端一定是没有绑定ip地址的,之后是…...
 
【云备份项目】:环境搭建(g++、json库、bundle库、httplib库)
文章目录 1. g 升级到 7.3 版本2. 安装 jsoncpp 库3. 下载 bundle 数据压缩库4. 下载 httplib 库从 Win 传输文件到 Linux解压缩 1. g 升级到 7.3 版本 🔗链接跳转 2. 安装 jsoncpp 库 🔗链接跳转 3. 下载 bundle 数据压缩库 安装 git 工具 sudo yum…...
 
电脑右键新建记事本不见了--设置恢复篇(无需操作注册表)
电脑右键新建记事本不见了–设置恢复篇(无需修改注册表) 电脑不知怎么想右键新建记事本结果竟然不见了,搜寻网上的都是什么修改注册表,粘贴代码修复(感觉太复杂了),这里介绍通过设置内重新对记…...
JavaScript内置对象 - Array数组(四)- 序列生成器
序列生成器是生成一个指定起始值和结束值的序列,并且根据指定间隔长度,生成序列数组。 完成此功能需要使用到Array内置对象的from()对象,以及类数组相关知识,前面几篇有相关案例进行演示。 地址一:JavaScript内置对象…...
 
GD32F103x IIC通信
1. IIC通信 1.IIC的介绍 IIC总线有两条串行线,其一是时钟线SCK(同步),其二是数据线SDA。只有一条数据线属于半双工。应用中,单片机常常作为主机,外围器件可以挂载多个。(当然主机也可以有多个。…...
 
什么是FOSS
FOSS 是指 自由和开放源码软件(Free and Open Source Software)。这并不意味着软件是免费的。它意味着软件的源代码是开放的,任何人都可以自由使用、研究和修改代码。这个原则允许人们像一个社区一样为软件的开发和改进做出贡献。...
 
C++语言GDAL批量裁剪多波段栅格图像:基于像元个数裁剪
本文介绍基于C 语言的GDAL模块,按照给定的像元行数与列数,批量裁剪大量多波段栅格遥感影像文件,并将所得到的裁剪后新的多波段遥感影像文件保存在指定路径中的方法。 在之前的文章中,我们多次介绍了在不同平台,或基于不…...
 
简单丝的tab切换栏(html/CSS)
#html <!DOCTYPE html> <html lang"en" > <head><meta charset"UTF-8"><title>CSS实现左右滑动选项卡效果</title><link rel"stylesheet" href"https://cdnjs.cloudflare.com/ajax/libs/meyer-res…...
 
LabVIEW开发带式谱感测技术
LabVIEW开发带式谱感测技术 如今,通过无线网络传输的数据量正在迅速增加,并导致频谱稀缺。超过数十亿的无线设备将被连接起来,并需要互联网接入。因此,无线电频谱管理方案的效率不足以授予对所有设备的访问权限。在频谱分配中&am…...
 
认识柔性数组
在C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员 限制条件是: 结构体中最后一个成员未知大小的数组 1.柔性数组的形式 那么我们怎样写一个柔性数组呢 typedef struct st_type {int i;int a[0];//柔性数组成员 }ty…...
 
熔断、限流、降级 —— SpringCloud Alibaba Sentinel
Sentinel 简介 Sentinel 是阿里中间件团队开源的,面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性 Sentinel 提供了两个服务组件…...
python经典百题之反向输出数字
题目:输入一个整数,并将其反转后输出。 程序分析 我们需要对输入的整数进行反转,即将整数的数字反向排列。 方法1:使用字符串反转 解题思路 将整数转换为字符串,然后对字符串进行反转。 代码实现 def reverse_integer_usin…...
复习Day08:哈希表part01:242.有效的字母异位词、349. 两个数组的交集、1. 两数之和、160. 相交链表
之前的blog:https://blog.csdn.net/weixin_43303286/article/details/131765317 我用的方法是在leetcode再过一遍例题,明显会的就复制粘贴,之前没写出来就重写,然后从拓展题目中找题目来写。辅以Labuladong的文章看。然后刷题不用…...
 
用 Pytest+Allure 生成漂亮的 HTML 图形化测试报告
本篇文章将介绍如何使用开源的测试报告生成框架 Allure 生成规范、格式统一、美观的测试报告。 通过这篇文章的介绍,你将能够: 将 Allure 与 Pytest 测试框架相结合; 如何定制化测试报告内容 执行测试之后,生成 Allure 格式的测…...
 
Python字符串索引解码乱码谜题
输入数行“数字字母”字符组成的乱码字符串,根据谜题规则解码出乱码字符串中隐藏的单词信息。 (本笔记适合熟悉python字符串索引操作的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网:https://www.python.org/ Free:大咖免费“圣经”…...
 
协议栈——收发数据(拼接网络包,自动重发,滑动窗口机制)
目录 协议栈何时发送数据~ 数据长度 IP模块的分片功能 发送频率 网络包序号~利用syn拼接网络包ack确认网络包完整 确定偏移量 服务器ack确定收到数据总长度 序号作用 双端告知各自序号 协议栈自动重发机制 大致流程 ack等待时间如何调整 是…...
 
传输层协议——TCP、UDP
目录 1、UDP 协议(用户数据报协议) 协议特点 报文首部格式 2、TCP 协议(传输控制协议) 协议特点 报文首部格式 TCP连接建立时的三次握手 TCP拆除连接的四次挥手 TCP的流量控制 TCP的拥塞控制 3、传输层端口号 三类端口…...
优化您的Spring应用程序:缓存注解的精要指南
优化您的Spring应用程序:缓存注解的精要指南 前言详细说明1. Cacheable:2. CacheEvict:3. CachePut:4. Caching:5. CacheConfig: 项目中的实现前提使用 前言 当我们构建和运行Spring应用程序时,…...
Java之原子性问题的解决
2. 原子性 2.1 volatile-问题 代码分析 : package com.itheima.myvolatile;  public class Demo {public static void main(String[] args) {MyThread1 t1 new MyThread1();t1.setName("小路同学");t1.start(); MyThread2 t2 new MyThread2();t2.setName(&q…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
 
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
 
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
 
NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
 
HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
 
处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...
 
LabVIEW双光子成像系统技术
双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制,展现出显著的技术优势: 深层组织穿透能力:适用于活体组织深度成像 高分辨率观测性能:满足微观结构的精细研究需求 低光毒性特点:减少对样本的损伤…...
 
Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu  2、Hello World fn main() { println…...
