我偶尔会用到的调试方法 | Linux 内核

文章转自我朋友的公众号,以下为内容正文

====

大家好,我是你们的工具人老吴。

今天,和大家分享一下几个 Linux 内核的调试小技巧。

当你遇到一个 bug,你调试了 1 年半载都解决不了,这其实一件好事。

因为它会时刻提醒你平时写代码时要谨慎、要多看书、多去认识一些更资深的人,别问我为什么会有这样的感受,因为是亲身经历~

掌握一个调试工具是需要学习成本的,这里只是列举我自己会用到的工具,如果有某个你觉得特别牛逼的工具而我没提到的话,请原谅我。

好,下面开始正文。

最重要的是:思路

调试 bug 时不要急着做实验,先梳理一下思路。

一般可以总结成如下步骤:

1、理解问题;

2、重现问题;

3、定位问题,找到相关的代码;

4、尝试修复问题;

5、如果失败,回到第 1 步;

bug 一般分为这几类:

1、Crash,最常遇到的,可能是因为我是做设备驱动开发的缘故;

2、Lockup,比较少,这类问题预防比事后调试更重要;

3、Logic/implementation error,这个也比较容易遇到,一般是运行不报错,但是运行的结果不符合预期;

4、Resource leak,偶尔会遇到;

5、Performance,偶尔会遇到,对于做驱动开发的话,一般是先考虑功能,当性能达不到要求时,再考虑优化性能。

调试工具的类别:

1、很多人不知道,调试最重要的工具是:我们的大脑。换句话说,也就是我们对内核个子系统、驱动开发的理解;

2、Logs and dump analysis。内核很贴心,许多异常发生时都会有一堆的 Kernel Panic 的信息,经常能让我们直接定位到引起异常的代码;

3、Tracing/profiling。这类工具一般能让我们理解程序的运行流程,不仅适合用来调试问题,也适合用来学习和理解内核的各种功能实现。

4、Interactive debugging。主要就是 gdb,我个人用得很少。

5、Debugging frameworks。许多的调试工具经过不断地发展和完善后,就慢慢地形成了一整套的调试框架,例如 Ftrace、SystemTap。


下面是几个我常用的调试技巧 / 工具。

最常用的方法:打印

点击查看大图

关于打印的工具,主要是这 3 种:

1、printk()

最原始的打印 api,可以用但是主流观点已经不推荐使用了。

与之相关的是启动参数 loglevel,它决定了可以被打印出来的信息的最低优先级。


2、pr_*()

推荐用 pr_*() 来代替 printk(),这是一个函数族:

pr_emerg(), pr_alert(), pr_crit(), pr_err(), pr_warning(), pr_notice(), pr_info(), pr_cont(), pr_debug()

例如:

pr_info("Booting CPU %d\n", cpu);

内核会打印:

[ 202.350064] Booting CPU 1


3、dev_*()

同样是一个函数族:

dev_emerg(), dev_alert(), dev_crit(), dev_err(), dev_warn(), dev_notice(), dev_info(), dev_dbg()

它们的最大特点是需要传入一个 struct device 的参数,并且会打印出这个 device 的名字,一边是在驱动相关的代码里使用。

例如:

dev_info(&pdev->dev, "in probe\n");

内核会打印:

[ 25.878382] serial 48024000.serial: in probe


关于 pr_debug() and dev_dbg()

要使用这两个 api,需要在对应的代码里 #deinfe DEBUG。

当内核使能了 CONFIG_DYNAMIC_DEBUG,我们就可以通过 /sys/kernel/debug/dynamic_debug/control 动态地是否要打印 log,以及打印哪些 log。

使用方法,大致如下:

$ mount -t debugfs none /sys/kernel/debug/
$ cd /sys/kernel/debug/dynamic_debug/
$ echo “file xxx.c +p” > control
$ echo “file svcsock.c line 1603 +p” > control
$ echo “file drivers/usb/core/* +p” > control
$ echo “file xxx.c -p” > control

具体地,可以参考:

https://training.ti.com/sites/default/files/docs/Kernel-Debug-Series-Part4-dynamic-debug.pdf


分析 Kernel Panic 的 信息

举个例子,下面是一次 Kernel Panic:

$ cat /sys/class/gpio/gpio504/value
[23.688107] Unable to handle kernel NULL pointer dereference at virtual address 00000000
[23.696431] pgd = (ptrval)
[23.699167] [00000000] *pgd=28bd4831, *pte=00000000, *ppte=00000000
[23.705596] Internal error: Oops: 17 [#1] SMP ARM
[23.710316] Modules linked in:
[23.713394] CPU: 1 PID: 177 Comm: cat Not tainted 4.19.17 #8
[23.719060] Hardware name: Freescale i.MX6 Quad/DualLite (Device Tree)
[23.725606] PC is at mcp23sxx_spi_read+0x34/0x84
[23.730241] LR is at _regmap_raw_read+0xfc/0x384
[23.734866] pc : [<c0539c44>]
lr : [<c067d894>]
psr: 60040013
[23.741142] sp : d8c6da48 ip : 00000009 fp : d8c6da6c
[23.746375] r10: 00000040 r9 : d8a94000 r8 : d8c6db30
[23.751608] r7 : c12ed9d4 r6 : 00000001 r5 : c0539c10 r4 : c1208988
[23.758145] r3 : d8789f41 r2 : 2afb07c1 r1 : d8789f40 r0 : 00000000
[...] // 省略


关键信息:

  • PC is at mcp23sxx_spi_read+0x34

  • pc : [<c0539c44>]

PC 是当前执行的指令的地址。

接下来,我们可以借助 addr2line, 定位到具体是哪一行代码引起了panic:

$ arm-linux-addr2line -f -e vmlinux 0xc0539c44
mcp23sxx_spi_read
/home/sprado/elce/linux/drivers/pinctrl/pinctrl-mcp23s08.c:357

另外,还可以用 gdb 来定位代码:

$ arm-linux-gdb vmlinux
(gdb) list *(mcp23sxx_spi_read+0x34)
0xc0539c44 is in mcp23sxx_spi_read (drivers/pinctrl/pinctrl-mcp23s08.c:357)


earlyprintk

earlyprintk 一般用来处理一些发生在启动初期时的异常。

最常见的现象就是系统打印完 Starting Kernel... 后就 hang 住了。

用法:

1、配置内核:

CONFIG_EARLY_PRINTK
CONFIG_DEBUG_LL

2、设置启动参数,类似:

root=/dev/mmcblk0p2 rootwait rw earlyprintk console=ttyS0,115200


WARN_ON()

这个函数可以打印出当前的函数调用栈。

我一般会在高度可疑的地方使用它。

举个例子:

static int sun6i_spi_probe(struct platform_device *pdev)
{struct spi_master *master;struct sun6i_spi *sspi;[...]// 用于调试
WARN_ON(1);master = spi_alloc_master(&pdev->dev, sizeof(struct sun6i_spi));[...]


当运行到 WARN_ON(1) 时,内核会打印:

[    1.847018] WARNING: CPU: 1 PID: 1 at drivers/spi/spi-sun6i.c:549 sun6i_spi_probe+0x20/0x3ac
[    1.855454] Modules linked in:
[    1.858525] CPU: 1 PID: 1 Comm: swapper/0 Not tainted 4.14.111 #196
[    1.864781] Hardware name: sun8i
[    1.868032] [<c02287fc>] (unwind_backtrace) from [<c0225398>] (show_stack+0x10/0x14)
[    1.875776] [<c0225398>] (show_stack) from [<c0a1ba3c>] (dump_stack+0x94/0xa8)
[    1.882997] [<c0a1ba3c>] (dump_stack) from [<c0240c24>] (__warn+0xe8/0x100)
[    1.889953] [<c0240c24>] (__warn) from [<c0240cec>] (warn_slowpath_null+0x20/0x28)
[    1.897517] [<c0240cec>] (warn_slowpath_null) from [<c06a03c0>] (sun6i_spi_probe+0x20/0x3ac)
[    1.905953] [<c06a03c0>] (sun6i_spi_probe) from [<c0617980>] (platform_drv_probe+0x4c/0xb0)
[    1.914299] [<c0617980>] (platform_drv_probe) from [<c06160dc>] (driver_probe_device+0x234/0x2f0)
[    1.923162] [<c06160dc>] (driver_probe_device) from [<c0616244>] (__driver_attach+0xac/0xb0)
[    1.931592] [<c0616244>] (__driver_attach) from [<c06144ec>] (bus_for_each_dev+0x68/0x9c)
[    1.939762] [<c06144ec>] (bus_for_each_dev) from [<c0615654>] (bus_add_driver+0x198/0x210)
[    1.948020] [<c0615654>] (bus_add_driver) from [<c0616aec>] (driver_register+0x78/0xf8)
[    1.956017] [<c0616aec>] (driver_register) from [<c0201a70>] (do_one_initcall+0x40/0x16c)
[    1.964193] [<c0201a70>] (do_one_initcall) from [<c1000e6c>] (kernel_init_freeable+0x1c8/0x264)
[    1.972884] [<c1000e6c>] (kernel_init_freeable) from [<c0a2ef4c>] (kernel_init+0x8/0x114)
[    1.981054] [<c0a2ef4c>] (kernel_init) from [<c0222058>] (ret_from_fork+0x14/0x3c)
[    1.988686] ---[ end trace dc4e090f55ad2de8 ]---

我们可以很清晰地看到 sun6i_spi_probe() 被调用的流程。

这个方法跑起来很简单,但是每次使用都得编译和更新内核,非常不方便,只适合轻度使用。


Pstore

如果发生 Kernel panic 时,我们并没有连接串口终端,那么这一次的崩溃信息就丢失了。

Pstore (persistent storage) 就可以用来处理这种情况。

当发生 Kernel painic 时,Pstore 会自动保存 oops 和 panic 的 log,并且在软重启后仍可以查看 log 信息。

默认情况下,log 是存储在 RAM 的某个保留区域中,但也可以使用存储设备,例如闪存。


用法:

1、配置内核:

CONFIG_PSTORE
CONFIG_PSTORE_RAM

2、配置 dts,为 Pstore 预留一块内存,类似:

reserved-memory {#address-cells = <1>;#size-cells = <1>;ranges;ramoops: ramoops@0b000000 {compatible = "ramoops";reg = <0x20000000 0x200000>; /* 2MB */record-size = <0x4000>; /* 16kB */console-size = <0x4000>; /* 16kB */};
};

3、假设刚发生了一次 Panic,并且已经软重启:

$ mount -t pstore pstore /sys/fs/pstore/$ ls /sys/fs/pstore/
dmesg-ramoops-0
dmesg-ramoops-1

通过上面这两个文件就可以看到内核的崩溃信息了。

内核文档:

Documentation/admin-guide/ramoops.rst


devmem2

这是一个命令行工具,它可以在用户空间去读写内存。

大多数情况,我是用它来读写寄存器,简单粗暴。

用法:

$ apt-get install devmem2

1、查看寄存器 TMR_IRQ_EN_REG:

$ devmem2 0x0x01C20C00
/dev/mem opened.
Memory mapped at address 0xb6f38000.
Value at address 0x0 (0xb6f38000): 0xEA000016

2、修改 TMR_IRQ_EN_REG:

# devmem2 0x0x01C20C00 w 0xEA000018
/dev/mem opened.
Memory mapped at address 0xb6fe8000.
Value at address 0x0 (0xb6fe8000): 0xEA000016
Written 0xEA000018; readback 0xEA000018


GDB

如果你想完全控制内核的运行,例如单步执行、查看变量等,可以用 GDB。

点击查看大图

这里采用的是 C/S 架构,在板子上运行 server (kgdb),在 PC 机上运行 client (gdb),通讯的方式可以是串口,或者网络,我一般是用串口。


如何配置:

1、配置内核:

CONFIG_KGDB
CONFIG_KGDB_SERIAL_CONSOLE
CONFIG_KGDB_KDB

2、设置启动参数:kgdoc

console=ttyS0,115200 kgdboc=ttyS0,115200 earlyprintk root=/dev/mmcblk0p2 oops=panic panic=0

ttyS0 是板子的调试串口。

要使用 kgdb,必须为其设置一个 I/O driver,我一般使用 kgdb over serial console (简称 kgdboc)

oops=panic panic=0 很重要。

另外,也通过在启动后设置 kgdboc:

echo ttyS0 > /sys/module/kgdboc/parameters/kgdboc

3、让内核进入 debug 模式:

$ echo g > /proc/sysrq-trigger
[ 1958.025927] sysrq: SysRq : DEBUG
[ 1958.029191] KGDB: Entering KGDB

4、让 PC 机连接板子

$ arm-linux-gdb vmlinux(gdb) set serial baud 115200
(gdb) target remote /dev/ttyUSB0
Remote debugging using /dev/ttyUSB0
0xc02c3540 in kgdb_breakpoint ()


举个例子:

配置好之后,通过 gdb 调试内核跟通过 gdb 调试应用的操作是一样的。

这里我举一个小例子。

首先,人为让内核 Crash:

$ echo WRITE_KERN > /sys/kernel/debug/provoke-crash/DIRECT
Entering kdb (current=0xffffffc0de55f040, pid 1470) on processor 4 Oops: (null)
due to oops @ 0xffffff80108bfa48
CPU: 4 PID: 1470 Comm: bash Not tainted 5.3.0-rc2+ #13
pc : __memcpy+0x48/0x180
lr : lkdtm_WRITE_KERN+0x4c/0x90
...

下面开始调试。

1、查看调用栈:

(gdb) bt
Call trace:
dump_backtrace+0x0/0x138
show_stack+0x20/0x2c
kdb_show_stack+0x60/0x84
...
do_mem_abort+0x4c/0xb4
el1_da+0x20/0x94
__memcpy+0x48/0x180
lkdtm_do_action+0x24/0x44
direct_entry+0x130/0x178

2、查看栈帧的内容:

(gdb) frame 1
#1 0xffffff801056584c in lkdtm_WRITE_KERN () at .../drivers/misc/lkdtm/perms.c:116
116
memcpy(ptr, (unsigned char *)do_nothing, size);

基本可以确定是使用 memcpy() 时导致 Crash。

3、查看相关代码:

(gdb) list
112  size = (unsigned long)do_overwritten - (unsigned long)do_nothing;
[...]
116  memcpy(ptr, (unsigned char *)do_nothing, size);

需要核查一下 ptr、do_nothing、size,这 3 个参数是否合法。

4、打印变量值:

(gdb) print size
$3 = 18446744073709551584(gdb) print do_overwritten - do_nothing
$4 = -32

最后发现 18446744073709551584 其实就是 (unsigned long) 的 -32。memcpy 的数据大小是 -32,导致了内核崩溃。


Ftrace

Ftrace 的作用是帮助开发人员了解 Linux 内核的运行时行为,以便进行故障调试或性能分析。

最早 Ftrace 是一个 function tracer,仅能够记录内核的函数调用流程。如今 ftrace 已经成为一个 framework,采用 plugin 的方式支持开发人员添加更多种类的 trace 功能。

用法:

$ mount -t tracefs none /sys/kernel/tracing
$ cd /sys/kernel/tracing/
$ cat available_tracers
hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop

跟踪器 tracer 表示的是要跟踪的目标。

假设我们抓一次 spi 传输的过程:

echo 0 > tracing_on
echo function_graph > current_tracer
echo *spi* > set_ftrace_filter
echo *dma* >> set_ftrace_filter
echo *spin* >> set_ftrace_notrace
echo 1 > tracing_on
./spidev_test
echo 0 > tracing_on
cat trace

得到的信息:

1) + 41.292 us   |  spidev_open();1)               |  spidev_ioctl() {1)               |    spi_setup() {1)   0.417 us    |      __spi_validate_bits_per_word.isra.0();1)               |      sunxi_spi_setup() {1)   0.834 us    |        sunxi_spi_check_cs();1)   0.875 us    |        spi_set_cs();1)   0.625 us    |        sunxi_spi_cs_control();1) + 17.125 us   |      }1)   0.833 us    |      spi_set_cs();1) + 30.458 us   |    }1) ! 699.875 us  |  }[...]

相关参考:

https://blog.csdn.net/Guet_Kite/article/details/101791125


Kdump

这个工具我没有用过,但是它似乎很强大,所以我觉得应该简单介绍一下。

kdump 是一种基于 kexec 系统调用 的内核崩溃转储机制。

当系统崩溃时,kdump 使用 kexec 启动进入到第二个内核 (dump-capture kernel),从而获得 coredump 信息。

用法:

1、设置启动参数:

crashkernel=64M

2、运行 kexec:

$ kexec --type zImage -p /boot/zImage \--initrd=<initrd-for-dump-capture-kernel> \--dtb=<dtb-for-dump-capture-kernel> \--command-line="XXX"

运行完 kexec 后,dump-capture kernel 就被加载进内存了。

以后如果发生了 kernel panic,dump-capture kernel 会被加载并运行。

我们可以在 dump-capture kernel 下,获得 coredump 文件:

$ cp /proc/vmcore <dump-file>

然后就可以在 PC 上使用 gdb/crash 来调试分析了:

$ arm-linux-gdb path/to/vmlinux -c path/to//vmcore
$ crash path/to/vmlinux path/to/vmcore

内核文档:

Documentation/kdump/kdump.txt


总结

预防为主,调试为辅。

软件开发没有银弹,同样的,bug 调试也没有银弹。但是多熟悉一些调试工具,是有好处的。

当然还有很多调试工具、技巧是我不知道了,欢迎大家分享给我。

Anyway, what we know is a drop, what we don't know is an ocean.

祝周末愉快。

—— The End ——

感谢完成阅读,我是喜欢打篮球的写代码的篮球球痴,这个是我的公众号,感谢你关注并支持。我从大学开始接触电子和嵌入式软件知识,至今,已经毕业工作了9年,我喜欢嵌入式,也愿意从事这个行业。不管是从技术还是职场经验,都积累了足够多的经验,目前在一个非常优秀的团队中做开发工作。

很高兴认识每一个对技术努力,对人用心的朋友。

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~

嵌入式Linux

微信扫描二维码,关注我的公众号

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/465707.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux应用编程之共享内存实例

1共享内存实例01主要内容 上一小节小哥跟大家介绍了一下共享内存的知识&#xff0c;今天主要是做一个实战的演示&#xff0c;从而更好的理解共享内存的原理和实际应用。02程序示例 1#include <stdlib.h>2#include <stdio.h>3#include <string.h>4#inclu…

洛谷P1279 字串距离 (动态规划)

题目描述 设有字符串X&#xff0c;我们称在X的头尾及中间插入任意多个空格后构成的新字符串为X的扩展串&#xff0c;如字符串X为”abcbcd”&#xff0c;则字符串“abcb□cd”&#xff0c;“□a□bcbcd□”和“abcb□cd□”都是X的扩展串&#xff0c;这里“□”代表空格字符。 如…

Python3——函数

Python3——函数 目录 Python3——函数 定义函数 实参和形参 返回值 将函数存储在模块中 定义函数 关键字def告知Python要定义一个函数。 最后一行是调用此函数&#xff0c;此函数不带参数和返回值。 实参和形参 函数参数可以有一个或者多个&#xff0c;可以是简单数据…

ASP.NET MVC 整合 Spring.net(1)- Controller进容器

我们都知道Asp.net MVC自有一套执行机制。通过分析MVC的MvcHandler关键代码ProcessRequest protectedinternalvirtualvoidProcessRequest(HttpContextBase httpContext) { AddVersionHeader(httpContext); //Get the controller typestringcontrollerNam…

再读王垠的《编程的智慧》,有怎样的感想?

王垠老师的《编程的智慧》这篇文章已经读了最起码5遍了&#xff0c;最近的项目做完一个阶段&#xff0c;到了把他做干净的时候&#xff0c;也就是优化代码&#xff0c;全面整理的阶段&#xff0c;这个时候我又想起了这篇编程的智慧&#xff0c;有一些启发与大家分享。王垠老师的…

国外流行的五款免费在线图片编辑器评测

也许当你在度假的时候&#xff0c;不喜欢携带着你的笔记本电脑&#xff0c;但你在度假的时候一定会拍照。现在&#xff0c;你可以打理这些照片&#xff0c;甚至还可以在“网络咖啡屋”中进行一些高级的图像编辑。一些基于网络的照片编辑程序在去年逐渐兴起&#xff0c;大多是基…

void 型指针的高阶用法,你掌握了吗?

[导读] 要比较灵活的使用C语言实现一些高层级的框架时&#xff0c;需要掌握一些进阶编程技巧&#xff0c;这篇来谈谈void指针的一些妙用。测试环境采用 IAR for ARM 8.40.1推荐一首中文歌曲<<后来>>&#xff0c;英文翻唱<<life>>来自瑞典歌手Sofia Kal…

电子美图更新36张!

电子美图更新36张&#xff0c;下面请欣赏&#xff01;如果喜欢&#xff0c;请帮忙点“赞”和"在看"哦&#xff01;推荐阅读&#xff1a;专辑|Linux文章汇总专辑|程序人生专辑|C语言我的知识小密圈关注公众号&#xff0c;后台回复「1024」获取学习资料网盘链接。欢迎点…

C#多线程JOIN方法初探

[说明&#xff1a;刚接触多线程时&#xff0c;弄不明白Join()的作用&#xff0c;查阅了三本书&#xff0c;都不明不白。后来经过自己的一番试验&#xff0c;终于弄清了Join()的本质。大家看看我这种写法是否易懂&#xff0c;是否真的写出了Join()的本质&#xff0c;多提宝贵意见…

STM32F0单片机快速入门八 聊聊 Coolie DMA

1.苦力 DMA世上本没有路&#xff0c;走的人多了&#xff0c;便成了路。世上本没有 DMA&#xff0c;需要搬运的数据多了&#xff0c;便有了 DMA。大多数同学应该没有在项目中用过这个东西&#xff0c;因为一般情况下也真不需要这个东西。在早期的单片机中也不存在DMA模块。再加上…

Python学习之==第三方模块的安装、模块导入

一、模块&包 1、模块 模块实质上就是一个Python文件&#xff0c;它是用来组织代码的。意思就是把Python代码写在里面&#xff0c;文件名就是模块的名称。例如&#xff1a;random.py&#xff0c;random就是模块的名称。 2、包 包又叫pageage&#xff0c;本质就是一个文件夹&…

操作系统中抢占式和非抢占式内核的区别

编排 | strongerHuang微信公众号 | 嵌入式专栏操作系统分为抢占式内核和非抢占式内核&#xff0c;通常RTOS都是抢占式内核。下面就来讲讲抢占式内核和非抢占式内核的内容。非抢占式内核非抢占式内核要求每个任务&#xff08;线程&#xff09;都做一些事情来明确放弃对 CPU 的控…

Python3——简单的TCP实例

Python3网络编程——简单的TCP实例 服务器&#xff1a;创建套接字——绑定服务器地址——监听连接——接受连接——数据接收/发送 客户端&#xff1a;创建套接字——连接服务器地址——数据接收/发送 """ server.py encode()/decode() """ fro…

UDP协议 sendto 和 recvfrom 浅析与示例

图片/在思考的樱木花道UDP&#xff08;user datagram protocol&#xff09;用户数据报协议&#xff0c;属于传输层。UDP是面向非连接的协议&#xff0c;它不与对方建立连接&#xff0c;而是直接把数据报发给对方。UDP无需建立类如三次握手的连接&#xff0c;使得通信效率很高。…

劝你要看一些有门槛的机会

最近发了很多招聘信息&#xff0c;招聘的岗位算不错的&#xff0c;但是投简历的人不多。我想起来刚开始工作那几年&#xff0c;工资虽然很低&#xff0c;但是也不怎么想鞠躬投简历&#xff0c;毕竟那个时候把面子这个事情看的比什么都重要。自己觉得自己有才&#xff0c;不过后…

Python3——简单的UDP实例

Python3——简单的UDP实例 服务器&#xff1a;创建套接字——绑定套接字——数据接收/发送 客户端&#xff1a;创建套接字——数据接收/发送 """ server.py encode()/decode() """ from socket import * from time import ctimeHOST PORT 11…

怎么得到自增列的下一个会插入的id

代码 1declareTable_namevarchar(60) 2setTable_namePay_inputpay; 3Selectso.name Table_name, --表名字4sc.name Iden_Column_name, --自增字段名字5ident_current(so.name) curr_value, --自增字段当前值6ident_incr(so.name) incr_value,…

ESP32,使用gitee搭建 ESP-IDF 开发框架

ESP32便宜&#xff0c;开发方便&#xff0c;非常适合初学者用来学习&#xff0c;之前我自己写的开发环境可能不再适合&#xff0c;推荐下面这篇文章。关于如何搭建ESP32的开发环境&#xff0c;乐鑫官方给出了很详细的教程和文档&#xff0c;基本上跟着官方教程来操作&#xff0…

jQuery的ajax技术

编辑本博客 ajax异步的JavaScript和html load() 从服务器加载数据&#xff0c;并把返回的数据放入备选元素中。这里加载回来的数据可以只有一个p标签&#xff0c;无需head元素等 $("selector").load(url,data,callback) url&#xff1a;必选&#xff0c;规定加载的ur…

Linux设备树的传递以及kernel中对设备树的解析

当U-Boot将设备树加载到内存指定位置后&#xff0c;ARM内核的SoC以通用寄存器r2来传递dtb在内存中的地址。kernel获取到该地址后对dtb文件做进一步的处理。#设备树的传递当使用bootm加载kernel镜像时&#xff08;bootz是对bootm的一种封装以及功能扩展&#xff0c;实质一样&…