使用 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
git clone https://github.com/torvalds/linux.git
cd linux

compile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 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)
bear -- make CC=clang LLVM=1 -j$(nproc)
make CC=clang binrpm-pkg LOCALVERSION=-debug -j$(nproc)
  • binrpm-pkg: Builds binary RPMs (kernel image, modules, headers). Use rpm-pkg for both binary and source RPMs.
  • LOCALVERSION=-debug: Appends a custom suffix (e.g., "-debug") to the kernel version for identification in GRUB.
1
2
3
dpkg -i kernel-*.deb
update-grub
reboot

在 Ubuntu 系统上,你可以通过 GRUB 引导菜单 在开机时手动选择要启动的内核(包括你刚编译安装的新内核,如带 -debug 后缀的那个)。这是最简单、最安全的临时/测试方式,不需要修改配置文件。

步骤:开机时选择内核

  1. 重启电脑
    执行 sudo reboot 或正常重启系统。

  2. 进入 GRUB 菜单

    • 在开机过程中(看到紫色/黑色屏幕或厂商 logo 后),立即按住 Shift 键(大多数 BIOS/UEFI 系统适用)。
    • 如果是纯 UEFI 系统且 Shift 不起作用,试试 按住 Esc 键快速连按 Shift
    • 目的是强制显示 GRUB 菜单(默认情况下,如果只有一个内核且超时为 0,它会直接启动而不显示菜单)。
  3. 在 GRUB 菜单中选择内核

    • 你会看到类似这样的主菜单(紫色背景):
      • Ubuntu
      • Advanced options for Ubuntu
      • (可能还有 Windows 或其他系统,如果是双系统)
    • 方向键 ↓ 选中 Advanced options for Ubuntu(Ubuntu 高级选项),按 Enter 进入子菜单。
  4. 在高级选项中挑选内核

    • 子菜单会列出所有已安装的内核版本,例如:
      • Ubuntu, with Linux 6.x.x-debug(你新编译的那个,通常带自定义后缀)
      • Ubuntu, with Linux 6.x.x-generic(旧的或默认的)
      • Ubuntu, with Linux 6.x.x-debug (recovery mode)
    • 方向键 选中你想要的内核(比如新编译的那个),然后按 Enter 启动。
  5. 启动成功后验证
    进入系统后,打开终端运行:

    1
    2
    3
    4
    5
    6
    7
       uname -r
    ```
    输出应该显示你选择的内核版本(例如 `6.x.x-debug`)。

    ### 额外小技巧
    - **临时只下一次启动某个内核**(不改默认):
    在进入系统后运行(替换成你想要的内核名称,从 `/boot/grub/grub.cfg` 中 grep 找):
    sudo grub-reboot "Advanced options for Ubuntu>Ubuntu, with Linux 6.x.x-debug" sudo reboot
    1
    2
    3
    4
      只影响下一次重启,下下次又恢复默认。

    - **如果 GRUB 菜单一直不显示**(超时为 0):
    编辑 `/etc/default/grub`:
    sudo nano /etc/default/grub
    1
    把 `GRUB_TIMEOUT=0` 改成 `GRUB_TIMEOUT=10`(单位:秒),保存后运行:  
    sudo update-grub
    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
      以后开机就会显示菜单 10 秒。

    - **想永久默认新内核**(不再手动选):
    参考之前回复:修改 `/etc/default/grub` 中的 `GRUB_DEFAULT=` 为新内核的完整菜单路径(或编号 0,如果它排第一),然后 `sudo update-grub`。

    这样操作后,你就可以自由测试不同内核了。如果新内核启动失败(比如黑屏、panic),重启再选回旧内核即可,非常安全。需要截图或具体报错也可以继续问!

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

    0. gcc 15 fix

    ```bash
    Option 1: Disable the tc Applet (Recommended if You Don't Need It)
    The tc command is for traffic control and may not be essential for your use case. Disabling it avoids the problem without patching code.

    Run make menuconfig (or make config for text-based) in your BusyBox source directory.
    Navigate to Networking Utilities > Disable tc.
    Save and exit.
    Re-run make to build BusyBox.

    Solution: Patch the Detection Script
    Edit scripts/kconfig/lxdialog/check-lxdialog.sh to update the test program for compatibility with modern GCC.

    Open the file:
    nano scripts/kconfig/lxdialog/check-lxdialog.sh (or use your preferred editor).
    Locate the section around lines 49-55 (may vary slightly) that echoes the test code:textecho "#include CURSES_LOC
    main() {}
    EOF
    Change it to:textecho "#include CURSES_LOC
    int main(void) { return 0; }
    EOF
    This adds an explicit int return type and a return statement to satisfy stricter standards.
    Using void for parameters avoids potential implicit declaration issues.

    Save and exit.
    Optionally, clean up:
    make clean
    Retry:
    make menuconfig

    This should now detect ncurses correctly and launch the menuconfig interface.

  6. 下载并编译 BusyBox

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
       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)
    # 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
    make -j$(nproc)
    make install

这里回到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/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米。其历史文化悠久,是杭州重要的生态绿肺和休闲胜地,以桃文化闻名,春季桃花盛开时吸引众多游客。周边有千桃园、皋亭山景区等景点,融合自然风光与人文遗迹(如南宋班师亭、抗元英雄文天祥纪念地)。交通便利,适合徒步、骑行等户外活动。

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

这个电路主要功能是三个LED灯轮流闪烁,通过RC充放电,以及三极管的导通截止来控制。

例8

一闪一闪亮晶晶。 实现电路

西溪元宵灯会

西溪元宵灯会,是杭州西溪湿地一年一度的传统盛会。每逢元宵佳节,西溪湿地便化身为灯光的海洋,各式各样的花灯点缀其间,既有传统的龙灯、鱼灯,也有现代创意灯组,流光溢彩,美不胜收。游客可以漫步在灯影交织的小径上,感受浓厚的节日氛围,体验传统文化的魅力。灯会期间,还有猜灯谜、赏民俗表演等活动,热闹非凡。2025年元宵节,不妨去西溪灯会,感受一场视觉与文化的盛宴!

元宵快乐

元宵节,给上2025的祝福

2025年元宵节,愿你月圆人圆事事圆,花好灯好心情好!愿新的一年,幸福如汤圆般甜甜蜜蜜,事业如花灯般红红火火!

Good Luck

这个电路主要功能是将麦克风输入的声音信号进行两级放大,并在信号强度达到一定程度时点亮LED指示灯。

例7

随便吼两嗓。 实现电路

长白山

长白山位于中国吉林省东南部,是中朝界山,主峰白头山海拔2749米。长白山以其丰富的自然资源和独特的火山地貌闻名,拥有天池、瀑布、温泉等景观。它是松花江、图们江和鸭绿江的发源地,也是东北虎、东北豹等珍稀动物的栖息地。长白山被列为世界自然遗产,是重要的生态旅游和科研基地。

新春快乐

春节长白山旅游,做一首诗

《春节游长白山》 雪覆长白岭,冰封天池边。 松涛迎远客,温泉暖寒天。 爆竹辞旧岁,灯笼映新年。 登高望故土,心随云海翩。

小雪人

2025年祝福

2025年,愿您如长白山的松柏,坚韧不拔;如天池的湖水,清澈宁静。愿您的事业如瀑布般奔腾不息,生活如温泉般温暖舒适。新年快乐,万事如意!

和糖糖一起
0%