结合scull驱动代码,来观察其实现使用。
1. 创建/proc文件调试
在/proc 下的每个文件都绑到一个内核函数上, 当文件被读的时候即时产生文件内容.
使用 /proc 的模块需要包含 <linux/proc_fs.h>
当一个进程读模块的 /proc 文件, 内核分配了一页内存(就是说, PAGE_SIZE 字节), 驱动可以写入数据来返回给用户空间. 那个缓存区传递给你的函数, 是一个file_operations的函数集方法:
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
struct proc_dir_entry *parent,
const struct file_operations *proc_fops,
void *data)
在驱动中通过如下调用:
proc_create_data("scullmem", 0 /* default mode */,
NULL /* parent dir */, &scullmem_proc_ops,
NULL /* client data */);
在/proc文件夹下创建scullmem文件,其对应的操作函数集合是
scullmem_proc_ops,如下:
static struct file_operations scullmem_proc_ops = {
.owner = THIS_MODULE,
.open = scullmem_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release
};
主要是scullmem_proc_open函数,就一条语句,调用scull_read_procmem.
static int scullmem_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, scull_read_procmem, NULL);
}
其中single_open在内核中定义如下:
int single_open(struct file *file, int (*show)(struct seq_file *, void *), void *data)
而scull_read_procmem函数实现真正的输出操作。
移除/proc下目录:
void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
/proc处理大文件时候容易出错。内核提供了seq_file接口。
2. seq_file
需要包含 <linux/seq_file.h> ,必须创建 4 个 iterator 方 法, 称为 start, next, stop, 和 show.
创建的proc下目录如下,并关联proc文件相关的操作:
proc_create("scullseq", 0, NULL, &scullseq_proc_ops);
其中scullseq_proc_ops如下:
static struct file_operations scullseq_proc_ops = {
.owner = THIS_MODULE,
.open = scullseq_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
在scullseq_proc_open中会调用seq_open函数,如下,并关联打开相关的函数集scull_seq_ops。
static int scullseq_proc_open(struct inode *inode, struct file *file)
{
return seq_open(file, &scull_seq_ops);
}
static struct seq_operations scull_seq_ops = {
.start = scull_seq_start,
.next = scull_seq_next,
.stop = scull_seq_stop,
.show = scull_seq_show
};
然后seq_open在内核中如下:
/**
* seq_open - initialize sequential file
* @file: file we initialize
* @op: method table describing the sequence
*
* seq_open() sets @file, associating it with a sequence described
* by @op. @op->start() sets the iterator up and returns the first
* element of sequence. @op->stop() shuts it down. @op->next()
* returns the next element of sequence. @op->show() prints element
* into the buffer. In case of error ->start() and ->next() return
* ERR_PTR(error). In the end of sequence they return %NULL. ->show()
* returns 0 in case of success and negative number in case of error.
* Returning SEQ_SKIP means "discard this element and move on".
* Note: seq_open() will allocate a struct seq_file and store its
* pointer in @file->private_data. This pointer should not be modified.
*/
int seq_open(struct file *file, const struct seq_operations *op)
3. ioctl
ioctl是一个系统调用, 作用于一个文件描述符;接收一个确定要进行的命令的数字和(可选地)另一个参数,常常是一个指针。可以作为使用 /proc 文件系统的替代,可以实现几个用来调试用的 ioctl 命令. 这些命令可以从 驱动拷贝相关的数据结构到用户空间, 这里你可以检查它们.
使用 ioctl 来获取信息比使用/proc 困难,运行比读取/proc快,不要求划分数据为小于一页的片段。
另外,ioctl 命令可能不记录文档并不为人知。如果驱动发生了怪异的事情, 仍将在那里.缺点是模块可能会稍微大些.
4. strace
strace其中最有用 的是 -t 来显示每个调用执行的时间, -T 来显示调用中花费的时间, -e 来限制被跟踪调 用的类型, 以及-o 来重定向输出到一个文件。
strace 从内核自身获取信息,所以不管它是否带有调试支持编译(对 gcc 是 -g 选项)以及不管它是否 strip 过,都可以跟踪。
5. gdb调试内核
使用gdb方式,gdb 不能修改内核数据,也不可能设置断点或观察点, 或单步内核函数。
#gdb /usr/src/linux/vmlinux /proc/kcore
(gdb)p jiffies
也可添加模块的符号:
(gdb)add-symbol-file scull.ko 0xffffffffc0184000 -s .bss 0xffffffffc0188d40 -s .data 0xffffffffc0188000
然后可以打印驱动中相关参数:
(gdb) p scull_devices[0]
$1 = {data = 0x0 <irq_stack_union>, quantum = 4000, qset = 1000, size = 0, access_key = 0, sem = {lock = {raw_lock = {val = {
counter = 0}}}, count = 1, wait_list = {next = 0xffff89b335861028, prev = 0xffff89b335861028}}, cdev = {kobj = {
name = 0x0 <irq_stack_union>, entry = {next = 0xffff89b335861040, prev = 0xffff89b335861040}, parent = 0x0 <irq_stack_union>,
kset = 0x0 <irq_stack_union>, ktype = 0xffffffffba661120, sd = 0x0 <irq_stack_union>, kref = {refcount = {refs = {counter = 1}}},
state_initialized = 1, state_in_sysfs = 0, state_add_uevent_sent = 0, state_remove_uevent_sent = 0, uevent_suppress = 0},
owner = 0xffffffffc0188a00, ops = 0xffffffffc0188000 <scull_fops>, list = {next = 0xffff89b335861088, prev = 0xffff89b335861088},
dev = 260046848, count = 1}}
其中地址符号来自/sys/module/scull/sections/.data
/sys/module/scull/sections/.text
/sys/module/scull/sections/.bss
要设置断点可以使用kdb,不过是汇编级别的。要代码级别的可以使用kgdb,只是需要两台机器通过串口来进行调试。