## 4.4. 使用观察来调试
有时小问题可以通过观察用户空间的应用程序的行为来追踪. 监视程序也有助于建立对驱动正确工作的信心. 例如, 我们能够对 scull 感到有信心, 在看了它的读实现如何响应不同数量数据的读请求之后.
有几个方法来监视用户空间程序运行. 你可以运行一个调试器来单步过它的函数, 增加打印语句, 或者在 strace 下运行程序. 这里, 我们将讨论最后一个技术, 当真正目的是检查内核代码时它是最有趣的.
strace 命令时一个有力工具, 显示所有的用户空间程序发出的系统调用. 它不仅显示调用, 还以符号形式显示调用的参数和返回值. 当一个系统调用失败, 错误的符号值(例如, ENOMEM)和对应的字串(Out of memory) 都显示. strace 有很多命令行选项; 其中最有用的是 -t 来显示每个调用执行的时间, -T 来显示调用中花费的时间, -e 来限制被跟踪调用的类型, 以及-o 来重定向输出到一个文件. 缺省地, strace 打印调用信息到 stderr.
strace 从内核自身获取信息. 这意味着可以跟踪一个程序, 不管它是否带有调试支持编译(对 gcc 是 -g 选项)以及不管它是否 strip 过. 你也可以连接追踪到一个运行中的进程, 类似于一个调试器的方式连接到一个运行中的进程并控制它.
跟踪信息常用来支持发给应用程序开发者的故障报告, 但是对内核程序员也是很有价值的. 我们已经看到驱动代码运行如何响应系统调用; strace 允许我们检查每个调用的输入和输出数据的一致性.
例如, 下面的屏幕输出显示(大部分)运行命令 strace ls /dev > /dev/scull0 的最后的行:
~~~
open("/dev", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY) = 3
fstat64(3, {st_mode=S_IFDIR|0755, st_size=24576, ...}) = 0
fcntl64(3, F_SETFD, FD_CLOEXEC) = 0
getdents64(3, /* 141 entries */, 4096) = 4088
[...]
getdents64(3, /* 0 entries */, 4096) = 0
close(3) = 0
[...]
fstat64(1, {st_mode=S_IFCHR|0664, st_rdev=makedev(254, 0), ...}) = 0
write(1, "MAKEDEV\nadmmidi0\nadmmidi1\nadmmid"..., 4096) = 4000
write(1, "b\nptywc\nptywd\nptywe\nptywf\nptyx0\n"..., 96) = 96
write(1, "b\nptyxc\nptyxd\nptyxe\nptyxf\nptyy0\n"..., 4096) = 3904
write(1, "s17\nvcs18\nvcs19\nvcs2\nvcs20\nvcs21"..., 192) = 192
write(1, "\nvcs47\nvcs48\nvcs49\nvcs5\nvcs50\nvc"..., 673) = 673
close(1) = 0
exit_group(0) = ?
~~~
从第一个 write 调用看, 明显地, 在 ls 结束查看目标目录后, 它试图写 4KB. 奇怪地(对ls), 只有 4000 字节写入, 并且操作被重复. 但是, 我们知道 scull 中的写实现一次写一个单个量子, 因此我们本来就期望部分写. 几步之后, 所有东西清空, 程序成功退出.
作为另一个例子, 让我们读取 scull 设备(使用 wc 命令):
~~~
[...]
open("/dev/scull0", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFCHR|0664, st_rdev=makedev(254, 0), ...}) = 0
read(3, "MAKEDEV\nadmmidi0\nadmmidi1\nadmmid"..., 16384) = 4000
read(3, "b\nptywc\nptywd\nptywe\nptywf\nptyx0\n"..., 16384) = 4000
read(3, "s17\nvcs18\nvcs19\nvcs2\nvcs20\nvcs21"..., 16384) = 865
read(3, "", 16384) = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
write(1, "8865 /dev/scull0\n", 17) = 17
close(3) = 0
exit_group(0) = ?
~~~
如同期望的, read 一次只能获取 4000 字节, 但是数据总量等同于前个例子写入的. 注意在这个例子里读取是如何组织的, 同前面跟踪的相反. wc 为快速读被优化过, 因此绕过了标准库, 试图一个系统调用读取更多数据. 你可从跟踪的读的行里看到 wc 是如何试图一次读取 16 KB.
Linux 专家能够从 strace 的输出中发现更多有用信息. 如果你不想看到所有的符号, 你可使用 efile 标志来限制你自己仅查看文件方法是如何工作的.
就个人而言, 我们发现 strace 对于查明系统调用的运行时错误是非常有用. 常常是应用程序或演示程序中的 perror 调用不足够详细, 并且能够确切说出哪个系统调用的哪个参数触发了错误是非常有帮助的.
- Linux设备驱动第三版
- 第 1 章 设备驱动简介
- 1.1. 驱动程序的角色
- 1.2. 划分内核
- 1.3. 设备和模块的分类
- 1.4. 安全问题
- 1.5. 版本编号
- 1.6. 版权条款
- 1.7. 加入内核开发社团
- 1.8. 本书的内容
- 第 2 章 建立和运行模块
- 2.1. 设置你的测试系统
- 2.2. Hello World 模块
- 2.3. 内核模块相比于应用程序
- 2.4. 编译和加载
- 2.5. 内核符号表
- 2.6. 预备知识
- 2.7. 初始化和关停
- 2.8. 模块参数
- 2.9. 在用户空间做
- 2.10. 快速参考
- 第 3 章 字符驱动
- 3.1. scull 的设计
- 3.2. 主次编号
- 3.3. 一些重要数据结构
- 3.4. 字符设备注册
- 3.5. open 和 release
- 3.6. scull 的内存使用
- 3.7. 读和写
- 3.8. 使用新设备
- 3.9. 快速参考
- 第 4 章 调试技术
- 4.1. 内核中的调试支持
- 4.2. 用打印调试
- 4.3. 用查询来调试
- 4.4. 使用观察来调试
- 4.5. 调试系统故障
- 4.6. 调试器和相关工具
- 第 5 章 并发和竞争情况
- 5.1. scull 中的缺陷
- 5.2. 并发和它的管理
- 5.3. 旗标和互斥体
- 5.4. Completions 机制
- 5.5. 自旋锁
- 5.6. 锁陷阱
- 5.7. 加锁的各种选择
- 5.8. 快速参考
- 第 6 章 高级字符驱动操作
- 6.1. ioctl 接口
- 6.2. 阻塞 I/O
- 6.3. poll 和 select
- 6.4. 异步通知
- 6.5. 移位一个设备
- 6.6. 在一个设备文件上的存取控制
- 6.7. 快速参考
- 第 7 章 时间, 延时, 和延后工作
- 7.1. 测量时间流失
- 7.2. 获知当前时间
- 7.3. 延后执行
- 7.4. 内核定时器
- 7.5. Tasklets 机制
- 7.6. 工作队列
- 7.7. 快速参考
- 第 8 章 分配内存
- 8.1. kmalloc 的真实故事
- 8.2. 后备缓存
- 8.3. get_free_page 和其友
- 8.4. 每-CPU 的变量
- 8.5. 获得大量缓冲
- 8.6. 快速参考
- 第 9 章 与硬件通讯
- 9.1. I/O 端口和 I/O 内存
- 9.2. 使用 I/O 端口
- 9.3. 一个 I/O 端口例子
- 9.4. 使用 I/O 内存
- 9.5. 快速参考
- 第 10 章 中断处理
- 10.1. 准备并口
- 10.2. 安装一个中断处理
- 10.3. 前和后半部
- 10.4. 中断共享
- 10.5. 中断驱动 I/O
- 10.6. 快速参考
- 第 11 章 内核中的数据类型
- 11.1. 标准 C 类型的使用
- 11.2. 安排一个明确大小给数据项
- 11.3. 接口特定的类型
- 11.4. 其他移植性问题
- 11.5. 链表
- 11.6. 快速参考
- 第 12 章 PCI 驱动
- 12.1. PCI 接口
- 12.2. 回顾: ISA
- 12.3. PC/104 和 PC/104+
- 12.4. 其他的 PC 总线
- 12.5. SBus
- 12.6. NuBus 总线
- 12.7. 外部总线
- 12.8. 快速参考
- 第 13 章 USB 驱动
- 13.1. USB 设备基础知识
- 13.2. USB 和 sysfs
- 13.3. USB 的 Urbs
- 13.4. 编写一个 USB 驱动
- 13.5. 无 urb 的 USB 传送
- 13.6. 快速参考
- 第 14 章 Linux 设备模型
- 14.1. Kobjects, Ksets 和 Subsystems
- 14.2. 低级 sysfs 操作
- 14.3. 热插拔事件产生
- 14.4. 总线, 设备, 和驱动
- 14.5. 类
- 14.6. 集成起来
- 14.7. 热插拔
- 14.8. 处理固件
- 14.9. 快速参考
- 第 15 章 内存映射和 DMA
- 15.1. Linux 中的内存管理
- 15.2. mmap 设备操作
- 15.3. 进行直接 I/O
- 15.4. 直接内存存取
- 15.5. 快速参考
- 第 16 章 块驱动
- 16.1. 注册
- 16.2. 块设备操作
- 16.3. 请求处理
- 16.4. 一些其他的细节
- 16.5. 快速参考
- 第 17 章 网络驱动
- 17.1. snull 是如何设计的
- 17.2. 连接到内核
- 17.3. net_device 结构的详情
- 17.4. 打开与关闭
- 17.5. 报文传送
- 17.6. 报文接收
- 17.7. 中断处理
- 17.8. 接收中断缓解
- 17.9. 连接状态的改变
- 17.10. Socket 缓存
- 17.11. MAC 地址解析
- 17.12. 定制 ioctl 命令
- 17.13. 统计信息
- 17.14. 多播
- 17.15. 几个其他细节
- 17.16. 快速参考
- 第 18 章 TTY 驱动
- 18.1. 一个小 TTY 驱动
- 18.2. tty_driver 函数指针
- 18.3. TTY 线路设置
- 18.4. ioctls 函数
- 18.5. TTY 设备的 proc 和 sysfs 处理
- 18.6. tty_driver 结构的细节
- 18.7. tty_operaions 结构的细节
- 18.8. tty_struct 结构的细节
- 18.9. 快速参考