查看rpi日志级别

1
2
cat /proc/sys/kernel/printk
3 4 1 3

printk的四个参数详解

1
2
cat /proc/sys/kernel/printk
3 4 1 3

四个数字分别表示: 1. 控制台日志级别(3) - 只有级别≤3的消息会输出到控制台(串口/显示器) 2. 默认消息级别(4) - 没有指定级别的printk()使用这个级别 3. 最低允许级别(1) - 允许设置的最低级别 4. 启动时默认级别(3) - 内核启动早期的控制台级别

消息级别对应关系

1
2
3
4
5
6
7
8
9
级别    宏定义             说明
0 KERN_EMERG 紧急(系统可能崩溃)
1 KERN_ALERT 警报(必须立即处理)
2 KERN_CRIT 严重(严重情况)
3 KERN_ERR 错误(错误情况) ← 这是你能在控制台看到的最低级别
4 KERN_WARNING 警告
5 KERN_NOTICE 通知(普通但重要)
6 KERN_INFO 信息(普通消息) ← dev_info() 用这个级别
7 KERN_DEBUG 调试信息

对串口输出的影响

情况1:控制台日志级别=3

1
2
3
4
5
6
7
8
9
10
11
// 这些会输出到串口 ✓
pr_emerg() // 0级 ✓
pr_alert() // 1级 ✓
pr_crit() // 2级 ✓
pr_err() // 3级 ✓

// 这些不会输出到串口 ✗
pr_warn() // 4级 ✗
pr_notice() // 5级 ✗
pr_info() // 6级 ✗ - 包括 dev_info()
pr_debug() // 7级 ✗

情况2:如果设置 loglevel=7

1
2
3
4
5
6
7
8
9
// 所有消息都会输出到串口 ✓
pr_emerg() ✓
pr_alert() ✓
pr_crit() ✓
pr_err() ✓
pr_warn() ✓
pr_notice() ✓ // linux_banner 用这个级别
pr_info() ✓ // dev_info() 现在能看到了
pr_debug() ✓

加打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
diff --git a/init/main.c b/init/main.c
index 821df1f05e9c..05be14b81a4c 100644
--- a/init/main.c
+++ b/init/main.c
@@ -916,6 +916,11 @@ void start_kernel(void)
{
char *command_line;
char *after_dashes;
+ printk(KERN_EMERG "\n\n");
+ printk(KERN_EMERG "################################\n");
+ printk(KERN_EMERG "# RPi CUSTOM KERNEL ACTIVE #\n");
+ printk(KERN_EMERG "################################\n");
+ printk(KERN_EMERG "\n\n");

set_task_stack_end_magic(&init_task);
smp_setup_processor_id();

根据前面【树莓派学习】003-cross-compile

1
2
3
bear -- make O=build -j10 Image.gz modules dtbs
make O=build modules_install INSTALL_MOD_PATH=.
./install-rpi.sh

观察串口打印

1
2
3
4
5
6
7
8
9
10
minicom -b 115200 -D /dev/cu.usbserial-0001
[ 0.000000] ################################

[ 0.000k00] # RPi CUSTOM KERNEL ACTIVE #

[ 0.000000] ################################

[ 0.000000]

[ 0.000000]

mac pro要交叉编译树莓派linux的源码, 最好还是用ubuntu镜像。

ubuntu24.04 docker中编译, 参考dev

1
docker pull ghcr.io/cybrid-systems/dev:ubuntu24.04

这里注意不要用mac mount的路径, 用docker中的文件系统路径。不然会有git工作区的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
warning: the following paths have collided (e.g. case-sensitive paths
on a case-insensitive filesystem) and only one from the same
colliding group is in the working tree:

'include/uapi/linux/netfilter/xt_CONNMARK.h'
'include/uapi/linux/netfilter/xt_connmark.h'
'include/uapi/linux/netfilter/xt_DSCP.h'
'include/uapi/linux/netfilter/xt_dscp.h'
'include/uapi/linux/netfilter/xt_MARK.h'
'include/uapi/linux/netfilter/xt_mark.h'
'include/uapi/linux/netfilter/xt_RATEEST.h'
'include/uapi/linux/netfilter/xt_rateest.h'
'include/uapi/linux/netfilter/xt_TCPMSS.h'
'include/uapi/linux/netfilter/xt_tcpmss.h'
'include/uapi/linux/netfilter_ipv4/ipt_ECN.h'
'include/uapi/linux/netfilter_ipv4/ipt_ecn.h'
'include/uapi/linux/netfilter_ipv4/ipt_TTL.h'
'include/uapi/linux/netfilter_ipv4/ipt_ttl.h'
'include/uapi/linux/netfilter_ipv6/ip6t_HL.h'
'include/uapi/linux/netfilter_ipv6/ip6t_hl.h'
'net/netfilter/xt_DSCP.c'
'net/netfilter/xt_dscp.c'
'net/netfilter/xt_HL.c'
'net/netfilter/xt_hl.c'
'net/netfilter/xt_RATEEST.c'
'net/netfilter/xt_rateest.c'
'net/netfilter/xt_TCPMSS.c'
'net/netfilter/xt_tcpmss.c'
'tools/memory-model/litmus-tests/Z6.0+pooncelock+poonceLock+pombonce.litmus'
'tools/memory-model/litmus-tests/Z6.0+pooncelock+pooncelock+pombonce.litmus'
This warning occurs on a case-insensitive filesystem (like macOS's APFS or Windows' NTFS) when a Git repository contains files whose names differ only in case. Git detects these as potential conflicts because the filesystem treats them as identical.

1
2
3
git clone --depth=1 https://github.com/raspberrypi/linux.git
cd linux
apt install bc
1
2
3
4
mkdir build
make O=build bcm2711_defconfig
bear -- make O=build -j10 Image.gz modules dtbs
make O=build modules_install INSTALL_MOD_PATH=.

debug config

1
2
3
4
5
6
7
8
9
10
11
12
cat >> build/.config << 'EOF'
# Early Debug Configuration
CONFIG_DEBUG_INFO=y
CONFIG_GDB_SCRIPTS=y
CONFIG_EARLY_PRINTK=y
CONFIG_DEBUG_LL=y
CONFIG_DEBUG_UART_PL011=y
CONFIG_DEBUG_UART_PHYS=0x3f215040
CONFIG_DEBUG_UART_VIRT=0xf215040
CONFIG_DEBUG_UNCOMPRESS=y
CONFIG_DEBUG_LL_INCLUDE="debug/8250.S"
EOF

~/.config/clangd/config.yaml

1
2
CompileFlags:
Remove: [-mabi=lp64]

install-rpi.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 🎯 完整传输(rsync + sudo,权限OK)

KVER=$(ls build/lib/modules/ | head -1)
echo "模块版本: $KVER"

# 1. Image.gz(压缩内核,必须)
rsync -avz build/arch/arm64/boot/Image.gz pi@192.168.1.95:/boot/firmware/ --rsync-path="sudo rsync"

# 2. DTB(设备树,bcm2711/rpi5)
rsync -avz build/arch/arm64/boot/dts/broadcom/*.dtb pi@192.168.1.95:/boot/firmware/ --rsync-path="sudo rsync"

# 3. Overlays(驱动叠加,必须完整目录)
rsync -avz --delete build/arch/arm64/boot/dts/overlays/ pi@192.168.1.95:/boot/firmware/overlays/ --rsync-path="sudo rsync"

# 4. Modules(驱动模块,关键!)
rsync -avz --delete build/lib/modules/$KVER/ pi@192.168.1.95:/lib/modules/$KVER/ --rsync-path="sudo rsync"
ssh pi@192.168.1.95 "sudo depmod -a $KVER && echo 'depmod完成'"

# 5. 更新 + 重启
ssh pi@192.168.1.95 'cd ~/code/kernel-update && ./update-kernel.sh && sudo reboot'

~/code/kernel-update/backup-kernel.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/bin/bash
# 保存到: ~/code/kernel-update/backup-kernel.sh

echo "=== 开始备份当前内核和配置 ==="

# 创建备份目录
sudo mkdir -p /boot/firmware/backup
BACKUP_DIR="/boot/firmware/backup/$(date +%Y%m%d_%H%M%S)"
sudo mkdir -p "$BACKUP_DIR"

# 备份内核文件
echo "备份内核文件..."
sudo cp /boot/firmware/kernel8.img "$BACKUP_DIR/"
sudo cp /boot/firmware/Image.gz "$BACKUP_DIR/" 2>/dev/null || true

# 备份配置文件
echo "备份配置文件..."
sudo cp /boot/firmware/config.txt "$BACKUP_DIR/"
sudo cp /boot/firmware/cmdline.txt "$BACKUP_DIR/"

# 备份设备树
echo "备份设备树..."
sudo cp /boot/firmware/*.dtb "$BACKUP_DIR/" 2>/dev/null || true
sudo cp -r /boot/firmware/overlays "$BACKUP_DIR/" 2>/dev/null || true

# 备份网络配置(重要!)
echo "备份网络配置..."
sudo cp -r /etc/netplan "$BACKUP_DIR/" 2>/dev/null || true
sudo cp -r /etc/NetworkManager "$BACKUP_DIR/" 2>/dev/null || true

echo "=== 备份完成 ==="
echo "备份位置: $BACKUP_DIR"
echo "当前备份列表:"
sudo ls -la "$BACKUP_DIR"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash
# 保存到: ~/code/kernel-update/update-kernel.sh

echo "=== 开始更新内核 ==="

# 检查新内核文件是否存在
if [ ! -f "/boot/firmware/Image.gz" ]; then
echo "错误: 未找到新内核文件 /boot/firmware/Image.gz"
echo "请先传输新内核文件"
exit 1
fi

# 备份当前配置
./backup-kernel.sh

# 配置使用新内核
echo "配置使用新内核..."
sudo cp /boot/firmware/config.txt /boot/firmware/config.txt.backup

# 在config.txt中启用新内核
if grep -q "kernel=Image.gz" /boot/firmware/config.txt; then
echo "新内核已配置"
else
# 注释原内核,启用新内核
sudo sed -i 's/^kernel=/#kernel=/' /boot/firmware/config.txt
echo "kernel=Image.gz" | sudo tee -a /boot/firmware/config.txt
fi

echo "=== 内核更新配置完成 ==="
echo "重启后生效: sudo reboot"
echo "验证命令: uname -r"

验证

1
2
3
4
5
6
7
8
9
10
11
12
% ssh pi@pi                                                                                             yuanqi@MacBook-Pro-2
Linux pi 6.12.57-v8+ #1 SMP PREEMPT Fri Nov 14 20:56:44 CST 2025 aarch64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Dec 7 14:02:19 2025 from 192.168.1.100
pi@pi:~ $ uname -r
6.12.57-v8+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/bin/bash
# 保存到: ~/code/kernel-update/restore-kernel.sh

echo "=== 内核恢复工具 ==="

# 查找最新的备份
LATEST_BACKUP=$(sudo ls -td /boot/firmware/backup/*/ | head -1)

if [ -z "$LATEST_BACKUP" ]; then
echo "错误: 未找到备份文件"
exit 1
fi

echo "找到备份: $LATEST_BACKUP"
echo "恢复内容:"
sudo ls -la "$LATEST_BACKUP"

read -p "确认恢复?(y/n): " confirm
if [ "$confirm" != "y" ]; then
echo "取消恢复"
exit 0
fi

# 恢复内核文件
echo "恢复内核文件..."
sudo cp "$LATEST_BACKUP/kernel8.img" /boot/firmware/ 2>/dev/null || true
sudo cp "$LATEST_BACKUP/Image.gz" /boot/firmware/ 2>/dev/null || true

# 恢复配置文件
echo "恢复配置文件..."
sudo cp "$LATEST_BACKUP/config.txt" /boot/firmware/
sudo cp "$LATEST_BACKUP/cmdline.txt" /boot/firmware/

# 恢复设备树
echo "恢复设备树..."
sudo cp "$LATEST_BACKUP"/*.dtb /boot/firmware/ 2>/dev/null || true
sudo cp -r "$LATEST_BACKUP/overlays" /boot/firmware/ 2>/dev/null || true

# 恢复网络配置(如果需要)
if [ -d "$LATEST_BACKUP/netplan" ]; then
echo "恢复网络配置..."
sudo cp -r "$LATEST_BACKUP/netplan" /etc/
sudo netplan apply
fi

echo "=== 恢复完成 ==="
echo "请重启: sudo reboot"

使用 ftrace 观测 GPIO 从用户调用到驱动的完整轨迹需要跟踪系统调用、VFS 层、设备驱动等多个层次。以下是详细的观测方案:

1. 准备环境和测试程序

1.1 简单的 GPIO 测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// trace_gpio_test.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdint.h>

int main() {
int fd;
void *gpio_map;

printf("GPIO mmap 测试开始...\n");

// 打开 gpiomem 设备
fd = open("/dev/gpiomem", O_RDWR | O_SYNC);
if (fd < 0) {
perror("open /dev/gpiomem failed");
return -1;
}
printf("GPIO设备打开成功: fd=%d\n", fd);

// 执行 mmap
gpio_map = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (gpio_map == MAP_FAILED) {
perror("mmap failed");
close(fd);
return -1;
}
printf("GPIO映射成功: %p\n", gpio_map);

// 简单的 GPIO 访问测试
volatile uint32_t *gpio = (volatile uint32_t*)gpio_map;
uint32_t value = gpio[0]; // 读取第一个寄存器
printf("GPIO寄存器值: 0x%08x\n", value);

// 保持一段时间以便观察
printf("按回车键继续...\n");
getchar();

// 清理
munmap(gpio_map, 4096);
close(fd);
printf("测试完成\n");

return 0;
}

编译运行:

1
2
3
cd ~/code
gcc trace_gpio_test.c -o trace_gpio
./trace_gpio

2. 配置 ftrace 跟踪点

2.1 实际可用的跟踪脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#!/bin/bash
# trace_gpio_mmap.sh

echo "=== GPIO mmap 完整轨迹跟踪 ==="

# 挂载 debugfs
mount -t debugfs nodev /sys/kernel/debug/tracing 2>/dev/null
cd /sys/kernel/debug/tracing

# 1. 重置跟踪器
echo 0 | sudo tee tracing_on > /dev/null
echo | sudo tee trace > /dev/null
echo | sudo tee set_ftrace_filter > /dev/null
echo | sudo tee set_graph_function > /dev/null

# 2. 设置 function_graph 跟踪器
echo function_graph | sudo tee current_tracer > /dev/null

# 3. 设置已验证的函数名(根据 available_filter_functions)
echo "__arm64_sys_mmap" | sudo tee -a set_graph_function > /dev/null
echo "ksys_mmap_pgoff" | sudo tee -a set_graph_function > /dev/null
echo "vm_mmap_pgoff" | sudo tee -a set_graph_function > /dev/null
echo "do_mmap" | sudo tee -a set_graph_function > /dev/null
echo "mmap_region" | sudo tee -a set_graph_function > /dev/null
echo "__mmap_region" | sudo tee -a set_graph_function > /dev/null
echo "rpi_gpiomem_mmap" | sudo tee -a set_graph_function > /dev/null
echo "rpi_gpiomem_open" | sudo tee -a set_graph_function > /dev/null

# 4. 添加 VMA 相关函数
echo "vm_area_alloc" | sudo tee -a set_graph_function > /dev/null
echo "vma_merge_new_range" | sudo tee -a set_graph_function > /dev/null
echo "vma_link_file" | sudo tee -a set_graph_function > /dev/null
echo "vma_set_page_prot" | sudo tee -a set_graph_function > /dev/null

# 5. 添加内存映射核心函数
echo "remap_pfn_range" | sudo tee -a set_graph_function > /dev/null
echo "remap_pfn_range_internal" | sudo tee -a set_graph_function > /dev/null
echo "__pte_offset_map_lock" | sudo tee -a set_graph_function > /dev/null

# 6. 配置跟踪选项
echo 1 | sudo tee options/func_stack_trace > /dev/null
echo 1 | sudo tee options/sleep-time > /dev/null
echo 1 | sudo tee options/graph-time > /dev/null

# 7. 开始跟踪
echo 1 | sudo tee tracing_on > /dev/null

# 8. 运行测试程序
echo "运行测试程序..."
/home/pi/code/trace_gpio &

# 9. 等待并停止跟踪
sleep 3
echo 0 | sudo tee tracing_on > /dev/null

# 10. 保存跟踪结果
sudo cat trace > /tmp/gpio_mmap_complete_trace.log
echo "跟踪结果保存到: /tmp/gpio_mmap_complete_trace.log"

# 11. 显示关键部分
echo "=== GPIO 驱动调用路径 ==="
sudo cat trace | grep -A30 -B5 "rpi_gpiomem" | head -80

3. 详细的函数跟踪配置

3.1 完整的已验证函数列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#!/bin/bash
# trace_gpio_detailed.sh

cd /sys/kernel/debug/tracing

# 清除之前的设置
echo 0 | sudo tee tracing_on > /dev/null
echo nop | sudo tee current_tracer > /dev/null
echo | sudo tee set_ftrace_filter > /dev/null
echo | sudo tee set_graph_function > /dev/null

# 设置 function_graph
echo function_graph | sudo tee current_tracer > /dev/null

# 系统调用入口
echo "__arm64_sys_mmap" | sudo tee -a set_graph_function > /dev/null
echo "ksys_mmap_pgoff" | sudo tee -a set_graph_function > /dev/null

# 内存管理核心函数
echo "vm_mmap_pgoff" | sudo tee -a set_graph_function > /dev/null
echo "do_mmap" | sudo tee -a set_graph_function > /dev/null
echo "mmap_region" | sudo tee -a set_graph_function > /dev/null
echo "__mmap_region" | sudo tee -a set_graph_function > /dev/null

# VMA 操作函数
echo "vm_area_alloc" | sudo tee -a set_graph_function > /dev/null
echo "vma_merge_new_range" | sudo tee -a set_graph_function > /dev/null
echo "vma_link_file" | sudo tee -a set_graph_function > /dev/null
echo "vma_set_page_prot" | sudo tee -a set_graph_function > /dev/null

# 文件操作相关
echo "fget" | sudo tee -a set_graph_function > /dev/null
echo "fput" | sudo tee -a set_graph_function > /dev/null

# 驱动特定函数
echo "rpi_gpiomem_mmap" | sudo tee -a set_graph_function > /dev/null
echo "rpi_gpiomem_open" | sudo tee -a set_graph_function > /dev/null

# 页表操作函数
echo "remap_pfn_range" | sudo tee -a set_graph_function > /dev/null
echo "remap_pfn_range_internal" | sudo tee -a set_graph_function > /dev/null
echo "__pte_offset_map_lock" | sudo tee -a set_graph_function > /dev/null

# 安全相关函数
echo "security_mmap_file" | sudo tee -a set_graph_function > /dev/null
echo "security_mmap_addr" | sudo tee -a set_graph_function > /dev/null
echo "cap_mmap_file" | sudo tee -a set_graph_function > /dev/null
echo "cap_mmap_addr" | sudo tee -a set_graph_function > /dev/null

# 设置跟踪选项
echo 1 | sudo tee options/func_stack_trace > /dev/null
echo 1 | sudo tee options/graph-time > /dev/null

# 开始跟踪
echo 1 | sudo tee tracing_on > /dev/null
echo "跟踪开始,运行测试程序..."
/home/pi/code/trace_gpio &
sleep 3
echo 0 | sudo tee tracing_on > /dev/null

# 显示结果
sudo cat trace | head -100

4. 使用 trace-cmd 进行更精细控制

4.1 trace-cmd 配置(针对 GPIO)

1
2
3
4
5
6
# 安装 trace-cmd
sudo apt update
sudo apt install trace-cmd

# 验证安装
trace-cmd --version
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
# trace_gpio_tracecmd.sh

echo "使用 trace-cmd 跟踪 GPIO mmap..."

# 记录跟踪数据(使用 sudo)
timeout 10 sudo trace-cmd record \
-e syscalls:sys_enter_mmap \
-e syscalls:sys_exit_mmap \
-p function_graph \
-g __arm64_sys_mmap \
-g rpi_gpiomem_mmap \
-g remap_pfn_range \
-F /home/pi/code/trace_gpio

# 生成报告(旧版本命令)
echo "=== GPIO 相关事件 ==="
sudo trace-cmd report | grep -i gpio

echo -e "\n=== mmap 系统调用 ==="
sudo trace-cmd report | grep -i mmap

echo -e "\n=== 函数图分析 ==="
sudo trace-cmd report | grep -A10 -B5 "rpi_gpiomem_mmap\|__arm64_sys_mmap"

或者使用更简单的方法:

1
2
sudo trace-cmd record -e 'syscalls:*mmap*' -p function -F /home/pi/code/trace_gpio
sudo trace-cmd report

5. 分析 ftrace 输出日志

5.1 关键路径识别方法

根据 /tmp/gpio_mmap_complete_trace.log 分析 GPIO mmap 完整轨迹:

1. 识别系统调用入口

1
2
# 查找 mmap 系统调用开始
grep -n "__arm64_sys_mmap\|ksys_mmap" /tmp/gpio_mmap_complete_trace.log

2. 跟踪内存管理调用链

1
2
# 查看内存管理核心函数
grep -A10 -B5 "do_mmap\|mmap_region\|__mmap_region" /tmp/gpio_mmap_complete_trace.log

3. 定位驱动特定函数

1
2
# 查找 GPIO 驱动函数
grep -A20 -B10 "rpi_gpiomem_mmap\|rpi_gpiomem_open" /tmp/gpio_mmap_complete_trace.log

5.2 性能热点分析

分析页表操作频率:

1
2
3
# 统计 __pte_offset_map_lock 调用次数和总耗时
grep "__pte_offset_map_lock" /tmp/gpio_mmap_complete_trace.log | wc -l
grep "__pte_offset_map_lock" /tmp/gpio_mmap_complete_trace.log | awk '{sum += $3} END {print "总耗时:", sum, "us"}'

识别内存分配瓶颈:

1
2
# 查看 VMA 分配耗时
grep -A5 "vm_area_alloc\|kmem_cache_alloc" /tmp/gpio_mmap_complete_trace.log

5.3 调用深度分析

重建完整调用路径:

1
2
# 提取关键函数的时间线
awk '/__arm64_sys_mmap/,/rpi_gpiomem_mmap/' /tmp/gpio_mmap_complete_trace.log | head -50

验证驱动映射成功:

1
2
# 检查 remap_pfn_range 是否执行
grep -A10 "remap_pfn_range" /tmp/gpio_mmap_complete_trace.log
## mmap分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
tail -100 /tmp/gpio_mmap_complete_trace.log
3) 0.593 us | _raw_spin_lock();
3) 3.037 us | }
3) | __pte_offset_map_lock() {
3) 0.685 us | __rcu_read_lock();
3) 0.685 us | _raw_spin_lock();
3) 3.019 us | }
3) | __arm64_sys_mmap() {
3) | ksys_mmap_pgoff() {
3) | fget() {
3) 0.574 us | __rcu_read_lock();
3) 0.667 us | __rcu_read_unlock();
3) 3.056 us | }
3) | vm_mmap_pgoff() {
3) | security_mmap_file() {
3) 0.556 us | cap_mmap_file();
3) 1.870 us | }
3) 0.722 us | down_write_killable();
3) | do_mmap() {
3) | __get_unmapped_area() {
3) | mm_get_unmapped_area_vmflags() {
3) | generic_get_unmapped_area_topdown() {
3) 1.352 us | vm_unmapped_area();
3) 2.574 us | }
3) 3.889 us | }
3) | security_mmap_addr() {
3) 0.593 us | cap_mmap_addr();
3) 1.852 us | }
3) 7.463 us | }
3) | memfd_file_seals_ptr() {
3) 0.556 us | shmem_mapping();
3) 1.834 us | }
3) 0.685 us | path_noexec();
3) | mmap_region() {
3) | __mmap_region() {
3) 0.666 us | may_expand_vm();
3) 0.648 us | vms_clean_up_area();
3) | vma_merge_new_range() {
3) 0.592 us | can_vma_merge_right();
3) 1.870 us | }
3) | vm_area_alloc() {
3) 0.667 us | kmem_cache_alloc_noprof();
3) 0.685 us | kmem_cache_alloc_noprof();
3) 0.574 us | __init_rwsem();
3) 4.463 us | }
3) 0.574 us | vm_get_page_prot();
3) 0.629 us | kmem_cache_alloc_noprof();
3) | ext4_file_mmap() {
3) | touch_atime() {
3) | atime_needs_update() {
3) 0.574 us | make_vfsuid();
3) 0.555 us | make_vfsgid();
3) 3.000 us | }
3) 4.297 us | }
3) 5.408 us | }
3) 0.611 us | down_write();
3) 0.703 us | up_write();
3) | call_rcu() {
3) | __call_rcu_common.constprop.0() {
3) 0.574 us | rcu_segcblist_enqueue();
3) 1.870 us | }
3) 3.186 us | }
3) | vma_link_file() {
3) 0.611 us | down_write();
3) 0.796 us | vma_interval_tree_insert();
3) 0.593 us | up_write();
3) 4.481 us | }
3) 0.574 us | perf_event_mmap();
3) 0.574 us | vms_complete_munmap_vmas();
3) | vma_set_page_prot() {
3) 0.667 us | vm_get_page_prot();
3) 0.648 us | vma_wants_writenotify();
3) 2.907 us | }
3) + 37.925 us | }
3) + 39.185 us | }
3) + 52.093 us | }
3) 0.574 us | up_write();
3) + 58.315 us | }
3) 0.574 us | fput();
3) + 65.111 us | }
3) + 66.555 us | }
3) | __pte_offset_map_lock() {
3) 0.667 us | __rcu_read_lock();
3) 0.686 us | _raw_spin_lock();
3) 2.982 us | }
3) | __pte_offset_map_lock() {
3) 0.685 us | __rcu_read_lock();
3) 0.666 us | _raw_spin_lock();
3) 2.963 us | }
3) | __pte_offset_map_lock() {
3) 0.574 us | __rcu_read_lock();
3) 0.593 us | _raw_spin_lock();
3) 3.074 us | }
3) | __pte_offset_map_lock() {
3) 0.575 us | __rcu_read_lock();
3) 0.592 us | _raw_spin_lock();
3) 3.093 us | }
3) | __pte_offset_map_lock() {
3) 0.575 us | __rcu_read_lock();
3) 0.574 us | _raw_spin_lock();
3) 2.963 us | }

当用户调用mmap()的时候,内核会进行如下处理 1. 在进程的虚拟空间查找一块VMA 2. 将这块VMA进行映射 3. 如果设备驱动程序或者文件系统的file_operations定义了mmap()操作,则调用它。 4. 将这个VMA插入进程的VMA链表中

从 ftrace 输出可以清晰地验证 mmap 处理的四个步骤:

1. 查找 VMA (虚拟内存区域)

1
2
3
4
5
6
7
8
9
# 在进程虚拟空间查找空闲区域
__get_unmapped_area() {
mm_get_unmapped_area_vmflags() {
generic_get_unmapped_area_topdown() {
vm_unmapped_area(); # 查找合适的虚拟地址范围
}
}
}
security_mmap_addr(); # 安全检查

2. 分配和设置 VMA

1
2
3
4
5
6
7
8
vm_area_alloc() {  # 分配 VMA 结构体
kmem_cache_alloc_noprof(); # 从 slab 分配器分配内存
}
vm_get_page_prot(); # 设置页面保护权限
vma_set_page_prot() {
vm_get_page_prot(); # 获取页面保护标志
vma_wants_writenotify(); # 设置写通知
}

3. 调用驱动/文件系统的 mmap 操作

1
2
3
4
5
6
7
8
9
ext4_file_mmap() {  # 文件系统的 mmap 操作
touch_atime() { # 更新访问时间
atime_needs_update() {
make_vfsuid(); # 权限检查
make_vfsgid();
}
}
}
# 注意:这里调用的是 ext4 的 mmap,如果是 GPIO 驱动应该显示 rpi_gpiomem_mmap

4. 将 VMA 插入进程链表

1
2
3
4
5
vma_link_file() {  # 将 VMA 链接到进程的 VMA 链表
down_write(); # 获取写锁
vma_interval_tree_insert(); # 插入到区间树中
up_write(); # 释放锁
}

关键证据总结:

  1. VMA 查找__get_unmapped_area()vm_unmapped_area() 负责在进程地址空间找到合适区域
  2. VMA 分配vm_area_alloc() 明确分配了 VMA 结构体
  3. 驱动 mmap 调用ext4_file_mmap() 显示了文件操作的具体 mmap 实现(在你的 GPIO 案例中应该是 rpi_gpiomem_mmap
  4. VMA 链表插入vma_link_file()vma_interval_tree_insert() 完成了 VMA 到进程数据结构的链接

这个 trace 完美印证了 mmap 的四个处理步骤。

1. 在进程虚拟空间查找 VMA

源码路径: mm/mmap.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* mm/mmap.c */
unsigned long __get_unmapped_area(struct file *file, unsigned long addr,
unsigned long len, unsigned long pgoff, unsigned long flags)
{
if (flags & MAP_FIXED)
return addr;

// 根据布局策略查找空闲区域
if (addr)
return mm_get_unmapped_area_vmflags(file, addr, len, pgoff, flags);
else
return mm_get_unmapped_area(file, addr, len, pgoff, flags);
}

unsigned long mm_get_unmapped_area(struct file *file, unsigned long addr,
unsigned long len, unsigned long pgoff, unsigned long flags)
{
// 调用架构特定的查找函数
if (current->mm->get_unmapped_area)
return current->mm->get_unmapped_area(file, addr, len, pgoff, flags);

// 默认实现:从高地址向下查找
return generic_get_unmapped_area_topdown(file, addr, len, pgoff, flags);
}

关键函数调用链:

1
2
3
4
5
__arm64_sys_mmap
→ ksys_mmap_pgoff
→ vm_mmap_pgoff
→ do_mmap
→ __get_unmapped_area // 步骤1:查找空闲VMA区域

2. 分配和设置 VMA

源码路径: mm/mmap.c - mmap_region() 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* mm/mmap.c */
unsigned long mmap_region(struct file *file, unsigned long addr,
unsigned long len, vm_flags_t vm_flags, unsigned long pgoff,
struct list_head *uf)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;

// 步骤2.1:分配 VMA 结构体
vma = vm_area_alloc(mm);
if (!vma)
return -ENOMEM;

vma->vm_start = addr;
vma->vm_end = addr + len;
vma->vm_flags = vm_flags;
vma->vm_page_prot = vm_get_page_prot(vm_flags);
vma->vm_pgoff = pgoff;

if (file) {
vma->vm_file = get_file(file);
// 步骤2.2:调用文件/驱动的 mmap 操作
error = call_mmap(file, vma);
if (error)
goto unmap_and_free_vma;
}

// 步骤2.3:设置页面保护
vma_set_page_prot(vma);
}

VMA 分配细节:

1
2
3
4
5
6
7
8
9
10
11
/* mm/vmalloc.c */
struct vm_area_struct *vm_area_alloc(struct mm_struct *mm)
{
// 从 slab 分配器分配 VMA 结构体
struct vm_area_struct *vma = kmem_cache_alloc(vm_area_cachep, GFP_KERNEL);
if (vma) {
vma_init(vma, mm); // 初始化 VMA
vma->vm_rb = RB_CLEAR_NODE;
}
return vma;
}

3. 调用驱动/文件系统的 mmap 操作

源码路径: include/linux/fs.h

1
2
3
4
5
6
/* include/linux/fs.h */
static inline int call_mmap(struct file *file, struct vm_area_struct *vma)
{
// 调用文件操作的 mmap 函数
return file->f_op->mmap(file, vma);
}

对于 GPIO 驱动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* drivers/char/broadcom/bcm2835-gpiomem.c */
static int bcm2835_gpiomem_mmap(struct file *file, struct vm_area_struct *vma)
{
struct bcm2835_gpiomem_dev *dev = file->private_data;

// 设置 VMA 标志
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

// 关键:建立物理地址到虚拟地址的映射
return remap_pfn_range(vma,
vma->vm_start,
dev->phys_addr >> PAGE_SHIFT, // 物理页帧号
vma->vm_end - vma->vm_start,
vma->vm_page_prot);
}

remap_pfn_range 内部实现:

1
2
3
4
5
6
7
/* mm/memory.c */
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
unsigned long pfn, unsigned long size, pgprot_t prot)
{
// 建立页表映射
return remap_pfn_range_internal(vma, addr, pfn, size, prot);
}

4. 将 VMA 插入进程 VMA 链表

源码路径: mm/mmap.c - __vma_link()__vma_link_file()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* mm/mmap.c */
static void __vma_link(struct mm_struct *mm, struct vm_area_struct *vma,
struct vm_area_struct *prev, struct rb_node **rb_link,
struct rb_node *rb_parent)
{
// 插入到红黑树中(用于快速查找)
__vma_link_rb(mm, vma, rb_link, rb_parent);

// 插入到双向链表中(用于顺序遍历)
__vma_link_list(mm, vma, prev);
}

static void __vma_link_file(struct vm_area_struct *vma)
{
struct file *file = vma->vm_file;

if (file) {
struct address_space *mapping = file->f_mapping;

// 将 VMA 链接到文件的地址空间
vma_interval_tree_insert(vma, &mapping->i_mmap);

// 更新统计信息
flush_dcache_mmap_lock(mapping);
mapping->i_mmap_writable++;
flush_dcache_mmap_unlock(mapping);
}
}

完整的 mmap_region 流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* mm/mmap.c */
unsigned long mmap_region(struct file *file, unsigned long addr,
unsigned long len, vm_flags_t vm_flags, unsigned long pgoff,
struct list_head *uf)
{
// ... 前面的 VMA 分配和 mmap 调用

// 步骤4:将 VMA 插入进程数据结构
vma_link(mm, vma, prev, rb_link, rb_parent);

// 如果是文件映射,链接到文件的 VMA 树
if (file)
__vma_link_file(vma);

// 通知内存管理子系统
perf_event_mmap(vma);

return addr;
}

完整的调用链总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
用户空间: mmap() 系统调用

__arm64_sys_mmap (arch/arm64/kernel/sys.c)

ksys_mmap_pgoff (mm/mmap.c)

vm_mmap_pgoff (mm/mmap.c)

do_mmap (mm/mmap.c)

__get_unmapped_area() // 步骤1:查找空闲VMA区域

mmap_region (mm/mmap.c)

vm_area_alloc() // 步骤2:分配VMA结构体

call_mmap() → file->f_op->mmap() // 步骤3:调用驱动mmap操作

remap_pfn_range() // 建立页表映射

vma_link() // 步骤4:插入VMA链表

__vma_link_rb() // 插入红黑树

__vma_link_list() // 插入双向链表

这个流程清晰地展示了 Linux 内核如何处理 mmap 请求:从虚拟地址空间的查找,到 VMA 结构的分配和设置,再到驱动特定映射操作的执行,最后将 VMA 整合到进程的内存管理数据结构中。

实验名称:树莓派PWM信号生成与示波器分析

实验目标

  1. 通过Python编程,让树莓派生成一个可调占空比的PWM信号。
  2. 用示波器捕捉并测量该PWM信号的频率和占空比,验证软件控制与硬件输出的一致性。

所需设备

  1. 硬件
    • 树莓派 (任何型号,已安装系统)
    • MacBook
    • 数字示波器
    • 面包板、LED灯、220Ω电阻、杜邦线若干
    • USB转TTL串口模块 (可选,用于串口调试)
  2. 软件
    • MacBook上的终端 (用于SSH或串口连接)
    • 树莓派上的Python3及 RPi.GPIO

具体步骤

第一步:硬件连接

  1. 树莓派GPIO连接
    • 将LED的正极(长脚)通过一个220Ω的限流电阻,连接到树莓派的 GPIO 18 (物理引脚12)。这是因为GPIO 18支持硬件PWM。
    • 将LED的负极(短脚)连接到树莓派的 GND (物理引脚6)。
  2. 示波器连接
    • 将示波器探头的尖端钩在 GPIO 18 引脚上。
    • 将示波器探头的接地夹夹在树莓派的 GND 引脚上。
  3. MacBook连接树莓派
    • 方法A (推荐,使用SSH):确保MacBook和树莓派在同一个Wi-Fi下,在Mac终端输入:ssh pi@<你的树莓派IP地址>,然后输入密码。
    • 方法B (使用串口):通过USB转TTL模块连接树莓派的UART引脚,在Mac上用 screen 命令连接。

连接示意图:

1
2
3
树莓派 GPIO 18 ---[220Ω电阻]--- LED(+) --- LED(-) --- GND
|
+--- (连接至示波器探头)

1. 命令行工具:pinout

这是最直接、最推荐的方法。

在你的树莓派终端中,直接输入:

1
pinout
这个命令会返回一个清晰的ASCII艺术图,明确标出了: - 物理引脚编号 (1, 2, 3...) - BCM GPIO编号 (GPIO 2, GPIO 3...),这是 RPi.GPIO 库默认使用的编号。 - 电源引脚 (3.3V, 5V, GND) - 特殊功能引脚 (例如,我们实验要用到的GPIO 18)

示例输出(部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
   3V3  (1) ◉◉ (2)  5V
GPIO2 (3) ◉◉ (4) 5V
GPIO3 (5) ◉◉ (6) GND
GPIO4 (7) ◉◉ (8) GPIO14
GND (9) ◉◉ (10) GPIO15
GPIO17 (11) ◉◉ (12) GPIO18 <-- 我们要用的引脚!
GPIO27 (13) ◉◉ (14) GND
GPIO22 (15) ◉◉ (16) GPIO23
3V3 (17) ◉◉ (18) GPIO24
GPIO10 (19) ◉◉ (20) GND
GPIO9 (21) ◉◉ (22) GPIO25
GPIO11 (23) ◉◉ (24) GPIO8
GND (25) ◉◉ (26) GPIO7

从图中可以看到,物理引脚12 对应的就是 BCM GPIO 18

2. GPIO库的Python命令

如果你已经在Python环境中,可以这样验证:

  1. 在树莓派终端输入 python3 进入Python交互环境。
  2. 依次输入以下命令:
    1
    2
    3
    4
    5
    import RPi.GPIO as GPIO
    GPIO.setmode(GPIO.BCM) # 设置为BCM编号模式
    # 将GPIO 18设置为输出模式,这会初始化该引脚
    GPIO.setup(18, GPIO.OUT)
    print("GPIO 18 设置成功!")
    如果没有报错,说明GPIO 18是可用的。

第二步:创建并运行Python脚本

  1. 在树莓派上,创建一个Python文件,例如 pwm_test.py

    1
    vi pwm_test.py

  2. 将以下代码复制进去:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    #!/usr/bin/env python3
    import RPi.GPIO as GPIO
    import time
    import atexit

    PWM_FREQUENCY = 1000 # 频率
    PWM_DUTY_CYCLE = 50 # 占空比
    # 全局变量跟踪PWM状态
    pwm_active = False
    pwm_instance = None

    def cleanup_resources():
    """在程序退出时清理资源"""
    global pwm_active, pwm_instance
    if pwm_active and pwm_instance:
    try:
    pwm_instance.stop()
    pwm_active = False
    pwm_instance = None
    except:
    pass
    try:
    GPIO.cleanup()
    except:
    pass

    # 注册退出处理函数
    atexit.register(cleanup_resources)

    try:
    # 设置GPIO
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(18, GPIO.OUT)

    # 创建并启动PWM
    pwm_instance = GPIO.PWM(18, PWM_FREQUENCY)
    pwm_instance.start(PWM_DUTY_CYCLE)
    pwm_active = True

    print(f"PWM已启动,频率{PWM_FREQUENCY}Hz,占空比{PWM_DUTY_CYCLE}%。按 Ctrl+C 停止程序。")

    # 主循环
    while True:
    time.sleep(1)

    except KeyboardInterrupt:
    print("\n程序被用户中断")

    finally:
    cleanup_resources()
    print("资源清理完成")

  3. 保存并退出 (在nano中按 Ctrl+X, 然后按 Y, 最后按 Enter)。

  4. 运行脚本 (需要sudo权限来访问GPIO):

    1
    sudo python3 pwm_test.py
    ### 硬件PWM

方法1:从源码编译安装pigpio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装编译依赖
sudo apt update
sudo apt install python3-pigpio

# 下载并编译pigpio
cd ~
wget https://github.com/joan2937/pigpio/archive/refs/heads/master.zip
unzip master.zip
cd pigpio-master
make -j
sudo make install

# 启动守护进程
sudo pigpiod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/env python3
import pigpio
import time
import signal
import sys

# 硬件PWM引脚(仅GPIO12, GPIO13, GPIO18, GPIO19支持硬件PWM)
PWM_PIN = 18
FREQUENCY = 1000000 # 1MHz
DUTY_CYCLE = 500000 # 50% (范围: 0-1000000)

pi = pigpio.pi()

def cleanup(signum=None, frame=None):
"""清理资源"""
pi.set_mode(PWM_PIN, pigpio.INPUT)
pi.stop()
print("\n资源清理完成")
sys.exit(0)

# 注册信号处理
signal.signal(signal.SIGINT, cleanup)
signal.signal(signal.SIGTERM, cleanup)

try:
# 设置硬件PWM
pi.set_mode(PWM_PIN, pigpio.OUTPUT)
pi.hardware_PWM(PWM_PIN, FREQUENCY, DUTY_CYCLE)

print(f"硬件PWM已启动: GPIO{PWM_PIN}, {FREQUENCY}Hz, 50%占空比")
print("按 Ctrl+C 停止")

while True:
time.sleep(1)

except Exception as e:
print(f"错误: {e}")
cleanup()

**方法2:gpiozero

1
2
# 安装gpiozero(通常已预装)
sudo apt install python3-gpiozero
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/env python3
from gpiozero import PWMOutputDevice
import time
import signal
import sys

# 使用GPIO18(硬件PWM引脚)
pwm = PWMOutputDevice(18, frequency=1000)

def cleanup(signum, frame):
pwm.close()
print("\n资源清理完成")
sys.exit(0)

signal.signal(signal.SIGINT, cleanup)

try:
pwm.value = 0.5 # 50%占空比
print("硬件PWM已启动: 1kHz, 50%占空比")
print("按 Ctrl+C 停止")

while True:
time.sleep(1)

except Exception as e:
print(f"错误: {e}")
cleanup()
1
pwm_hw_test.py

第三步:使用示波器测量

  1. 打开示波器。
  2. 将探头连接到 物理引脚12 (GPIO 18)GND
  3. 在示波器上执行 自动设置 或手动调整:
    • 时基:调到 500μs/div 左右,以清晰看到1kHz的周期。
    • 电压刻度:调到 1V/div2V/div
    • 触发:设置为边沿触发,源选择你连接的通道,电平调到约1.6V (3.3V的一半)。
  4. 你应该能立即在屏幕上看到一个稳定的 方波
  5. 进行测量:
    • 频率:使用示波器的测量功能,选择“频率”,读数应接近 1.000 kHz
    • 占空比:选择“占空比”测量,读数应接近 50.0%
    • 电压:高电平应在 3.3V 左右,低电平在 0V

软件PWM

rpi-001-expriment

硬件PWM 10MHz

rpi-001-10M-expriment

树莓派PWM频率对比

软件PWM (RPi.GPIO)

  • 最高频率:约 1-10kHz (实际使用建议 ≤1kHz)
  • 限制因素:Python解释器性能、系统负载
  • 特点:所有GPIO都可用,但精度差、抖动明显

硬件PWM (pigpio/gpiozero)

  • 最高频率:理论上可达 125MHz (树莓派4)
  • 实际可用频率:通常 1kHz - 30MHz
  • 支持引脚:仅 GPIO12, GPIO13, GPIO18, GPIO19

具体频率对比表

方法 最高频率 推荐频率 精度 稳定性
RPi.GPIO软件PWM ~10kHz ≤1kHz
pigpio硬件PWM 125MHz 1kHz-30MHz 极高 优秀
gpiozero硬件PWM 125MHz 1kHz-30MHz 极高 优秀
Linux PWM子系统 125MHz 1kHz-30MHz 极高 优秀

第四步:实验扩展

修改代码中的 pwm.start(50),将 50 改为 10 (10%占空比) 或 90 (90%占空比),重新运行程序。再次用示波器测量,你会看到波形的“高电平”部分明显变短或变长,但频率保持不变。

恭喜你! 你已经成功地用软件生成一个精确的硬件信号,并用示波器完成了验证。这是嵌入式系统调试中最基础的技能之一。

compile the kernel

clone和compile在容器中操作

1
2
3
4
docker run --privileged -d -it --name angel -v `pwd`:/root/code -w /root/code ghcr.io/cybrid-systems/dev
docker exec -it --detach-keys="ctrl-z,z" angel /bin/zsh
git config --global user.name $your_name
git config --global user.email $your_email

clone code

1
2
3
git clone https://mirrors.tuna.tsinghua.edu.cn/git/linux-stable.git
cd linux-stable
gco v5.15.194

compile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 生成默认配置
make defconfig

# 2. 启用调试选项
./scripts/config --enable DEBUG_KERNEL
./scripts/config --enable DEBUG_INFO
./scripts/config --enable KGDB
./scripts/config --enable FTRACE
./scripts/config --set-val DEBUG_INFO_DWARF4 y

# 3. 自动处理依赖关系
make olddefconfig

# 4. 可选:检查配置
grep -E "DEBUG_KERNEL|DEBUG_INFO|KGDB|FTRACE" .config

# 5. 编译内核
make CC=clang -j$(nproc)

使用 BusyBox 创建最小 rootfs 的步骤如下:

  1. 下载并编译 BusyBox
    1
    2
    3
    4
    5
    6
    7
    wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
    tar xf busybox-1.36.1.tar.bz2
    cd busybox-1.36.1
    make defconfig
    make menuconfig # 可选:静态编译 Settings → Build static binary (no shared libs)
    make -j$(nproc)
    make install
1
2
3
4
5
6
7
8
9
# 1. 先拿一份默认配置
make defconfig

# 2. 用 sed 三行命令完成上述三项修改
sed -i 's/^# CONFIG_STATIC is not set/CONFIG_STATIC=y/' .config
sed -i 's|CONFIG_PREFIX=.*|CONFIG_PREFIX="./rootfs"|' .config

# 3. 让 Kbuild 自动补齐依赖(重要!)
make oldconfig >/dev/null </dev/null

这里回到mac宿主机。

  1. 创建必要的目录结构
1
2
cd rootfs
mkdir -p {etc,dev,proc,sys,tmp,var,usr/bin,usr/sbin,lib,root}
  1. 创建基础设备节点
1
2
3
4
5
6
7
8
cd rootfs/dev

# 创建设备文件
sudo mknod console c 5 1
sudo mknod null c 1 3
sudo mknod zero c 1 5
sudo mknod ttyAMA0 c 204 64
sudo chmod 666 console null zero ttyAMA0
  1. 创建初始化脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
cd rootfs
cat > init << 'EOF'
#!/bin/sh

echo "Starting system..."

# 挂载必要文件系统
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs tmpfs /tmp

# 设置终端
exec </dev/ttyAMA0 >/dev/ttyAMA0 2>&1

# 设置环境变量
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
export HOME=/root
export USER=root

# 显示系统信息
echo "=== BusyBox Linux ==="
echo "Kernel: $(uname -r)"
echo "System is ready!"

# 启动shell(修复job control)
exec setsid sh -c 'exec sh </dev/ttyAMA0 >/dev/ttyAMA0 2>&1'
EOF
chmod +x init
  1. 创建简单的fstab
1
2
3
4
5
cat > etc/fstab << 'EOF'
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
EOF
  1. 创建rootfs镜像
1
2
3
4
5
6
7
8
9
10
11
12
cd rootfs

# 创建CPIO格式的rootfs(用于initrd)
find . | cpio -H newc -o | gzip > ../rootfs.cpio.gz

# 或者创建ext4文件系统镜像
dd if=/dev/zero of=../rootfs.ext4 bs=1M count=64
mkfs.ext4 ../rootfs.ext4
mkdir -p /mnt/rootfs
sudo mount ../rootfs.ext4 /mnt/rootfs
sudo cp -ra . /mnt/rootfs/
sudo umount /mnt/rootfs
  1. 使用QEMU启动
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    qemu-system-aarch64 \
    -machine virt \
    -cpu cortex-a72 \
    -smp 4 \
    -m 2G \
    -kernel linux-stable/arch/arm64/boot/Image \
    -initrd busybox-1.36.1/rootfs.cpio.gz \
    -append "console=ttyAMA0 rdinit=/init" \
    -nographic \
    -no-reboot

✅ 今天学会:

认识时间变量 t

掌握位置函数的魔法公式

画出运动向量的轨迹 🎮 终极任务:设计你的精灵运动动画!

复习向量 - 从静态到动态

"上节课的'走路箭头'还记得吗?"

  • 第二课回顾:静态向量 {3,2} = 从原点走到(3,2)
  • 新发现:如果这个箭头会动呢?

课堂提问:箭头变长时,终点位置如何变化?(引出位置向量动态化)

认识新变量 - 时间魔法师

"小精灵的隐形遥控器 - 时间 t"

实验:学生用秒表计时,教师移动精灵玩偶

t(秒) 0 1 2
位置 起点 ? ?

t 是变量 - 像第一课的电阻/电压,可以自由改变!

"把时间翻译成位置的魔法"

教室活动:

学生A匀速向右走:x(t) = 2×t(步数)

学生B模拟下落:y(t) = 5 - 5×t²

板书公式:

1
2
3
位置函数: 
x(t) = 起点x + 速度x × t
y(t) = 起点y + 速度y × t - 5×t²

函数机器图解
graph LR
    t["输入 t"] --> 机器["魔法公式"] --> 输出["输出 (x(t),y(t))"]

联系第一课: 欧姆定律:输入V,R → 输出I 位置函数:输入t → 输出位置向量!

动手计算 - 我的精灵设计

"设计你的精灵飞行"

时间 t 计算 x(t)=1+2×t i计算 y(t)=3+4×t-5×t² 位置向量
0 1 3 (1, 3)
1 3 3+4-5=2 (3, 2)
2 5 3+8-20=-9 (5, -9)

任务:

  1. 选精灵(飞机/小鸟/超人)
  2. 自定起点和速度
  3. 完成表格计算

"把位置向量连起来!"

  • 学生活动:在坐标纸画出三个位置向量终点
  • 重大发现:三点连成抛物线!
1
ListPlot[{{1,3},{3,2},{5,-9}}, PlotRange->{{0,6},{-10,5}}]

教师强调: 每个位置向量是瞬间快照 连起来就是运动轨迹(函数的图像)

教学总结

通过时间变量、x(t)、y(t),两个位置函数组成向量。通过不同时间的t,表格绘制,感觉变化的向量。 要数形结合,结合前面变量、函数、向量的概念,让学生体会运动物体的轨迹,当时举的例子是抛一个🎾,水平的力让球有水平的动能,产生位移随时间变化,垂直方向是重力,高度随时间变化,产生轨迹。接下来学点概率呗。

四年级数学·暑期第二课

《小小向量大冒险——用 Mathematica 玩“位置魔法”》 (完整课件,1 课时 45 min)

————————— 【课程包】 PPT 10 页 + 学生讲义 1 张 + 教师 Mathematica 笔记本 1 份
(全部放在同名文件夹,打开即用)

————————— 课前 2 分钟 教师把教室地面贴成 6×6 方格,原点 (0,0) 贴星星贴纸,孩子踩格子热身。

————————— ## 课件流程

Slide 1 题目 & 目标
• 今天学会:用“小箭头”表示走路,用电脑把箭头画出来。
• 回家任务:画一张“我的上学路线图”。

Slide 2 故事
“小机器人 Turtle 收到两张藏宝图,先向右 3、向上 2,再向右 1、向上 3,它最后停在哪?”
(配图:两个箭头首尾相连)

Slide 3 动手踩格子
叫 2 位学生上台:
1 号同学从原点出发,按第一张图走;
2 号同学从 1 号终点出发,按第二张图走。
全班一起喊出最终格子坐标 (4,5)。
→ 把体验翻译成数学:
v = {3, 2},w = {1, 3},v + w = {4, 5}

Slide 4 Mathematica 第一次亮相
教师现场运行(大屏投影):

1
2
3
4
5
6
7
8
9
v = {3, 2}; w = {1, 3};
A = {0, 0};
Graphics[{
Red, Arrow[{A, A + v}],
Blue, Arrow[{A + v, A + v + w}],
Green, Arrow[{A, A + v + w}],
Black, PointSize[0.05], Point[A + v + w]
}, Axes -> True, GridLines -> Automatic,
PlotRange -> {{-1, 6}, {-1, 6}}]

孩子看到红箭头 + 蓝箭头 = 绿箭头,形状像滑梯。

Slide 5 向量加法公式(老师板书)
v = (x₁, y₁)
w = (x₂, y₂)
v + w = (x₁ + x₂, y₁ + y₂)
口诀:横加横,竖加竖。

Slide 6 “魔法缩放”滑条
教师拖动滑条 s,箭头忽大忽小:

1
2
3
4
5
6
Manipulate[
Graphics[{
Red, Arrow[{{0, 0}, s*{3, 2}}]
}, Axes -> True, PlotRange -> {{-5, 5}, {-5, 5}}],
{s, -2, 2, 0.1}
]

学生喊:
“s = 2 变两倍!”
“s = -1 反方向!”

Slide 7 长度(拓展给学有余力的孩子)
|v| = √(x² + y²) (老师口算 3²+2²=13,电脑 √13≈3.6)
告诉孩子:箭头越长,走路越远。

Slide 8 小挑战
教师发一张练习纸:
1. 画向量 u = {2,4}。
2. 计算并画出 2u。
3. 写出 2u 的长度。
(5 min 完成,同桌交换批改)

Slide 9 回家任务
• 量一量:从“家”到“学校”横走几格,竖走几格,写成向量 h。
• 拍照发群里,老师用 Mathematica 把全班的 h 画成一张“上学地图”。

Slide 10 预告下一课
“把 4 个箭头围成四边形——发现平行四边形面积的秘密!”

—————————
学生讲义(A5 双面)

正面:
【向量小卡片】
v = (→ , ↑)  w = (→ , ↑)  v + w = ( , )

【魔法缩放】
把 v 放大 2 倍:2v = ( , )

背面:
【回家任务单】
1. 我的上学向量 h = (___ , ___)。
2. 画在方格纸上,拍照。
3. 明天带来,拼成“全班上学地图”!

—————————
教师 Mathematica 笔记本(已写好,直接运行)
文件名:Lesson2_Vector.nb
包含:
• 踩格子演示 cell
• 加法动画 cell
• 缩放滑条 cell
• 自动收集学生照片并绘图的函数:

1
2
3
DrawClassMap[list_] := 
Graphics[{Arrow[{{0, 0}, #}] & /@ list}, Axes -> True,
PlotRange -> {{-10, 10}, {-10, 10}}]

—————————
一句话总结(给孩子)
“把两个走路指令加起来,就是一次走到底!”

课程记录

开场讲了下x,y坐标,然后家到学校的走法。接下来用讯飞本子上画了两个向量,以及他们相加,进入第一个mathematica的作图,结合代码进行讲解调试。

第二步讲了向量的加法,具体计算步骤,然后给出公式。

第三步讲了标量乘向量,用methematica的可操作图进行讲解分析。

进一步讲解了norm的计算,估计v{6, 4}的长度。介绍了底层的计算原理。

最后让果果画出u={3, 2}和2u。

x86的一堆软件重新装一下,主要是emacs重新编译native。

emacs 30.1重新编译

prepaer

1
2
3
4
5
brew install autoconf automake cmake libgccjit gnutls texinfo jansson tree-sitter gcc libgccgit
curl -O https://mirrors.ustc.edu.cn/gnu/emacs/emacs-30.1.tar.xz
tar -xvf emacs-30.1.tar.xz
cd emacs-30.1
./autogen.sh

. gcc_env.sh

1
2
3
export CC="/opt/homebrew/opt/gcc/bin/gcc-15"
export CXX="/opt/homebrew/opt/gcc/bin/g++-15"
export LIBRARY_PATH="/opt/homebrew/opt/gcc/lib/gcc/15:$LIBRARY_PATH"

configure and make

1
2
3
4
5
6
7
8
./configure \
--with-native-compilation \
--with-json \
--with-tree-sitter \
--with-gnutls \
--without-ns
make -j
make install

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ doom doctor
The doctor will see you now...

> Checking your Emacs version...
> Checking for Doom's prerequisites...
> Checking for Emacs config conflicts...
> Checking for missing Emacs features...
> Checking for private config conflicts...
> Checking for common environmental issues...
> Checking for stale elc files...
> Checking for problematic git global settings...
> Checking Doom Emacs...
✓ Initialized Doom Emacs 3.0.0-pre
✓ Detected 37 modules
✓ Detected 133 packages
> Checking Doom core for irregularities...
Found Symbols Nerd Font Mono
> Checking for stale elc files in your DOOMDIR...
> Checking your enabled modules...

Everything seems fine, happy Emacs'ing!

好嘞!整得挺明白啊老铁!Emacs 整得溜光水滑的,这回可劲儿造吧!代码敲得咔咔的,插件跑得嗖嗖的,那叫一个得劲儿!有啥不明白的随时吱声,咱这嘎达给你支棱起来!开心就完事儿了! 😄

第一课:变量与函数——从欧姆定律开始

(适合三年级以上,结合电路实验)

📌 课程目标

  1. 理解“变量”:在电路中,电压(V)、电流(I)、电阻(R)都是可以变化的量,它们就是变量
  2. 理解“函数”:欧姆定律(V = I × R)就是一个函数,它告诉我们变量之间的关系
  3. 动手实验:用真实的电路测量、观察变量如何变化,并用万用表记录数据,绘制函数曲线。

🔧 实验材料

  • 电路元件:电池(9V 或 2节AA)、电阻(1kΩ、10kΩ)、电位器(可调电阻)、LED、面包板(可选)
  • 测量工具:万用表(测电压、电流、电阻)
  • 其他:导线、纸笔(记录数据)、坐标纸(画函数图)

🚀 实验步骤

1. 认识变量:电压、电流、电阻

🔹 电压(V):电池的“推力”,单位是伏特(V)。
🔹 电流(I):电子的“流动”,单位是安培(A),实验中常用毫安(mA)。
🔹 电阻(R):阻碍电流的“关卡”,单位是欧姆(Ω)。

小实验:用万用表测量

  • 测电池电压(V):万用表拨到 DCV 20V,红表笔接电池+,黑表笔接电池-。
  • 测电阻(R):万用表拨到 Ω(欧姆档),直接测电阻的阻值。
  • 测电流(I):万用表拨到 DCA 20mA串联进电路(后面实验会做)。

2. 欧姆定律实验:V = I × R

电路搭建:

1
电池 (+) —— 电位器(可调 R) —— 固定电阻(保护 LED) —— LED —— 电池 (-)
实验步骤:
1. 调节电位器(改变 R),用万用表测量当前的电阻值(R)。
2. 串联万用表(测电流 I)
- 把万用表拨到 DCA 20mA断开 LED 和电阻的连接,把表笔串联进去(红表笔接电阻,黑表笔接 LED)。
3. 记录数据
- 调节电位器 5 次,每次记录 R(Ω)I(mA)

实验次数 电阻 R (Ω) 电流 I (mA) 计算 V = I × R
1 1000 9.0 9.0 × 1000 = 9V
2 2000 4.5 4.5 × 2000 = 9V
3 3000 3.0 3.0 × 3000 = 9V
4 4000 2.25 2.25 × 4000 = 9V
5 5000 1.8 1.8 × 5000 = 9V

观察规律:
电阻 R 变大,电流 I 变小(反比关系)。
V = I × R 始终≈电池电压(9V),验证欧姆定律!


3. 绘制函数曲线:I = V / R

在坐标纸上:
- X 轴:电阻 R(Ω)
- Y 轴:电流 I(mA)
- 画点:把实验数据标在图上,连成曲线。

结论:
- 这条曲线就是 函数 I = V / R 的图形!
- 电阻 R 是输入变量,电流 I 是输出变量,欧姆定律就是它们之间的关系!


4. 计算机思维连接:变量 & 函数

🔹 变量就像“盒子”
- 在计算机里,RIV 都是变量,可以存储不同的值。
- 比如:

1
2
3
4
R = 1000  # 电阻是 1000Ω
V = 9 # 电池是 9V
I = V / R # 计算电流 I
print(I) # 输出 0.009 A(即 9mA)
🔹 函数就像“魔法机器”
- 欧姆定律就是一个函数:
1
2
def calculate_current(V, R):
return V / R
- 输入 VR,输出 I


🎯 课后挑战

  1. 改变电池电压(V):用 2 节 AA 电池(3V)再做实验,看看 IR 的关系是否还是 I = V / R
  2. 用 Scratch 模拟:在 Scratch 里做一个“欧姆定律计算器”,输入 VR,自动计算 I

📝 总结

变量:电路中可以变化的量(V、I、R)。
函数:变量之间的关系(欧姆定律 V = I × R)。
实验:用真实电路测量数据,画出函数曲线!
计算机连接:变量存储数据,函数计算关系!

下一课:电容充放电——时间变量和指数函数! 🚀

课程回顾

mathematica代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
data = {{3.0, 3.0, 1}, {3.0, 1.5, 2}, {3.0, 1.3, 2.2}, {3.7, 1.7, 
2.2}, {3.0, 0.8, 3.3}, {3.8, 1.1, 3.3}, {4.5, 1.3, 3.3}, {3.5,
0.7, 4.7}, {4.8, 1, 4.7}, {4.8, 0.9, 5.1}, {4.3, 0.8, 5.1}};

(*提取电压=3.0V的数据并按电阻排序*)
constantVoltageData = Select[data, #[[1]] == 3.0 &];
sortedData = SortBy[constantVoltageData, #[[3]] &];

ListLinePlot[sortedData[[All, {3, 2}]], PlotMarkers -> Automatic,
Frame -> True, FrameLabel -> {"电阻 (k\[CapitalOmega])", "电流 (mA)"},
PlotLabel -> "电压恒定(3.0V): 电阻\[UpArrow], 电流\[DownArrow]",
FrameStyle -> Directive[Black, 12], GridLines -> Automatic,
ImageSize -> 400]
(*按电阻值分组*)groupedData = GatherBy[data, #[[3]] &];
(*只保留有多个数据点的组*)
multiPointGroups = Select[groupedData, Length[#] > 1 &];

ListLinePlot[#[[All, {1, 2}]] & /@ multiPointGroups,
PlotMarkers -> Automatic, Frame -> True,
FrameLabel -> {"电压 (V)", "电流 (mA)"},
PlotLabel -> "电阻恒定: 电压\[UpArrow], 电流\[UpArrow]",
FrameStyle -> Directive[Black, 12],
PlotLegends -> ("R = " <> ToString[#[[1, 3]]] <>
" k\[CapitalOmega]" & /@ multiPointGroups),
GridLines -> Automatic, ImageSize -> 400]
constantVoltageData = {{3.0, 3.0, 1}, {3.0, 1.5, 2}, {3.0, 1.3,
2.2}, {3.0, 0.8, 3.3}};
TableForm[constantVoltageData,
TableHeadings -> {None, {"电压(V)", "电流(mA)", "电阻(k\[CapitalOmega])"}}]
(*绘制实验数据点*)
dataPoints =
ListPlot[constantVoltageData[[All, {3, 2}]],
PlotStyle -> {Red, PointSize[0.02]}, Frame -> True,
FrameLabel -> {"电阻R (k\[CapitalOmega])", "电流I (mA)"},
GridLines -> Automatic, PlotRange -> All];

(*绘制理论函数 I=3.0/R*)
theoreticalPlot =
Plot[3.0/r, {r, 0.5, 5}, PlotStyle -> Blue,
PlotLegends -> {"I = 3.0/R"}];

(*合并图形*)
Show[theoreticalPlot, dataPoints,
PlotLabel -> "函数关系: 电流I = 电压V / 电阻R"]
V = 3.0;
predictedI = V/5 (*输出:0.6 mA*)
Manipulate[
Plot[V/r, {r, 1, 10}, Frame -> True,
FrameLabel -> {"R (k\[CapitalOmega])", "I (mA)"}], {V, 1, 10, 0.1},
Initialization :> (V = 3.0)]
TableForm[Table[{r, 3.0/r}, {r, 1, 5, 0.5}],
TableHeadings -> {None, {"R (k\[CapitalOmega])", "I (mA)"}}]
  1. 实验测试了多组数据记录
  2. 用mathematica进行可视化讲解
  3. 用计算器进行一些验证
  4. 讨论分析为什么理论和测量的偏差
  5. 最后给出V=3的I和R的函数,计算和实验

doom sync会遇到网络问题。解决方法如下:

1
2
git clone --depth 1 https://github.com/hlissner/doom-emacs ~/.config/emacs
sed -i '129s|:repo "https://git.savannah.gnu.org/git/emacs/nongnu.git"|:repo "https://github.com/emacsmirror/nongnu_elpa.git"|' ~/.config/emacs/lisp/lib/packages.el

然后doom install或者doom sync

提供一个开发环境镜像dev-machine

东明山

位置:浙江省杭州市余杭区,毗邻良渚文化遗址区,属天目山余脉。 特色: - 自然生态:森林覆盖率超95%,以竹林、古树为胜,空气清新,适合徒步、骑行。 - 人文古迹:内有东明寺(传建文帝隐居地)、明代古道等历史遗迹。 - 休闲体验:登山步道、露营基地,春季可赏樱,秋季观红叶。

东明山踏春

皋亭山

皋亭山位于中国浙江省杭州市东北部,属天目山余脉,海拔约361米。其历史文化悠久,是杭州重要的生态绿肺和休闲胜地,以桃文化闻名,春季桃花盛开时吸引众多游客。周边有千桃园、皋亭山景区等景点,融合自然风光与人文遗迹(如南宋班师亭、抗元英雄文天祥纪念地)。交通便利,适合徒步、骑行等户外活动。

一游皋亭山王子安 二游皋亭山王子安 皋亭山玩泥巴泥

0%