驱动篇:设备驱动的调试(一)(摘录)

GNU 工具链有力地支撑了 Linux 系统的发展, 由于它可被看作许多嵌入式处理器的一个交叉编译器,所以在嵌入式软件 开发中相 当流行,其支持包括 ARM、StrongARM、XScale、PowerPC 、MIPS、68K/ColdFire、Intel x86/IA-32、Intel i960、Hitachi SH 在内的多种体系结构。 GNU 工具链中大多数有用的工具主要集中于以下几个源代码包中。

l GCC 包,主要包括 gcc(C 编译器)、g++(C++编译器)、cpp (C 预处理器)。
l Binutils (binary utilities) 包,主要包括 as(汇编程序)、ld(连接器)、objcopy(目标文件翻译器,用于从连接器输出中创建一个 ROM 映像)、 objdump (目标文件阅读器,用于反汇编目标文件)。
l glibc/uclibc/newlib,提供系统调用和基本函数的 C 库,比如 open()、malloc()、printf()等。
l Make,主要包括 make 工具。
l Debugger,主要包括 gdb(源代码调试器) 。

上述源代码包都可以从 gnu FTP 站点上直接下载,如为了建立 ARM Linux 的 GNU工具链,我们需下载 newlib、binutils、gcc 和 gdb 代码包。

GDB调试器用法
GDB是GNU开源组织发布的一个强大的UNIX 下的程序调试工具,GDB主要可帮助工程师完成下面4 个方面的功能。
l 启动程序,可以按照工程师自定义的要求运行程序。
l 让被调试的程序在工程师指定的断点处停住,断点可以是条件表达式。
l 当程序被停住时,可以检查此时程序中所发生的事,并追踪上文。
l 动态地改变程序的执行环境。

使用命令“gcc –g gdb_example.c –o gdb_example”编译上述程序,得到包含调试信息的二进制文件example,执行“gdb gdb_example”命令进入调试状态

调试嵌入式 Linux 内核的方法如下
l 目标机“插桩”,如打上 KGDB 补丁,这样主机上的 GDB 可与目标机的 KGDB通过串口或网口通信。
l 使用仿真器,仿真器可直接连接目标机的 JTAG/BDM,这样主机的 GDB 就可以通过与仿真器的通信来控制目标机。
l 在目标板上通过 printk()、oops、strace 等软件方法进行“观察”调试,这些方法不具备查看和修改数据结构、断点、单步等功能。

在 Linux 中,内核打印语句 printk()会将内核信息输出到内核信息缓冲区中。内核信息缓冲区是一个环形缓冲区(ring buffer),因此,如果塞入的消息过多,就会将之前的消息冲刷掉。Linux 的 klogd 进程(一个系统守护进程,它截获并且记录下 Linux 内核日志信息)会通过/proc/kmsg 文件读取缓冲区, 一旦读取完成,内核信息便从缓冲区中被删除。 之后,klogd 守护进程会将读取的内核信息派发给 syslogd 守护进程(syslogd 记录下系统里所有提供日志记录的程序给出的日志和信息内容,每一个被记录的消息至少包含时间戳和主机名),syslogd 这个守护进程会根据/etc/syslog.conf 将不同的服务产生的日志记录到不同的文件中。例如在/etc/syslog.conf 中增加“kern.* /tmp/kernel_debug.txt”一行,则内核信息也会被放置到/tmp/kernel_debug.txt 文件中。执行“insmod hello.ko”,我们看到/tmp/kernel_debug.txt 文件中多出如下一行:

Jul 6 00:40:51 localhost kernel: Hello World enter

用户也可以直接使用“cat /proc/kmsg” 命令来显示内核信息, 但是, 由于/proc/kmsg是一个“永无休止的文件”,因此,“cat /proc/kmsg”的进程只能通过“Ctrl+C”或 kill终止。另外,使用 dmesg 命令也可以直接读取 ring buffer 中的信息。
printk()定义了 8 个消息级别,分为级别 0~7,越低级别(数值越大)的消息越不重要,第 0 级是紧急事件级,第 7 级是调试级
在这里插入图片描述在 设备驱动 中 , 我们 经 常 需 要 输出 调试 或 系 统 信 息 , 尽 管 可 以 直接 采 用printk("<7>debug info …\n")方式的 printk()语句输出, 但是通常可以使用封装了 printk()的更高级的宏,如 pr_debug()、dev_debug()等。代码清单 22.4 所示 pr_debug()和 pr_info()的定义,代码清单 22.5 所示为 dev_dbg()、dev_err()、dev_info()等的定义,前一组的输出中不包含设备信息,后一组包含

替代 printk()的宏

 #ifdef DEBUG
 #define pr_debug(fmt,arg...) \
printk(KERN_DEBUG fmt,##arg)
 #else
static inline int _ _attribute_ _ ((format (printf, 1, 2))) pr_debug(const
char * fmt, ...)
 {
return 0;
 }
 #endif
 #define pr_info(fmt,arg...) \
printk(KERN_INFO fmt,##arg)

包含设备信息的替代 printk()的宏

#define dev_printk(level, dev, format, arg...) \
printk(level "%s %s: " format , dev_driver_string(dev) ,
(dev)->bus_id , ## arg)
 #ifdef DEBUG
 #define dev_dbg(dev, format, arg...) \
dev_printk(KERN_DEBUG , dev , format , ## arg)
 #else
 #define dev_dbg(dev, format, arg...) do { (void)(dev); } while (0)
 #endif

#define dev_err(dev, format, arg...) \
dev_printk(KERN_ERR , dev , format , ## arg)
 #define dev_info(dev, format, arg...) \
dev_printk(KERN_WARNING , dev , format , ## arg)
 #define dev_notice(dev, format, arg...) \
dev_printk(KERN_INFO , dev , format , ## arg)
 #define dev_warn(dev, format, arg...)  \
dev_printk(KERN_NOTICE , dev , format , ## arg)

在 Linux 系统中,可用如下函数创建/proc 节点:

struct proc_dir_entry *create_proc_entry(const char *name, 
mode_t mode,struct proc_dir_entry *parent);
struct proc_dir_entry *create_proc_read_entry(const char *name, mode_t
mode,struct proc_dir_entry *base, read_proc_t *read_proc,void * data);

create_proc_entry() 函 数 用 于 创 建 /proc 节 点 ,create_proc_read_entry() 调 用
create_proc_entry()创建只读的/proc 节点。参数 name 为/proc 节点的名称,parent/base
为父目录的节点,如果为 NULL,则指/proc 目录,read_proc 是/proc 节点的读函数指
针。当 read()系统调用在/proc 文件系统中执行时,它映像到一个数据产生函数,而不是一个数据获取函数。

下列函数用于创建/proc 目录:

struct proc_dir_entry *proc_mkdir(const char *name, struct 
proc_dir_entry *parent);


结合 create_proc_entry()proc_mkdir(),代码清单 22.6 中的程序可用于先在
/proc下创建一个目录,而后在该目录下创建一个文件。

proc_mkdir()和 create_proc_entry()函数使用范例

/* 创建/proc 下的目录 */
 example_dir = proc_mkdir("procfs_example", NULL);
 if (example_dir == NULL)
 //创建失败
 {
 rv = - ENOMEM;
 goto out;
 }
 example_dir->owner = THIS_MODULE;
 /* 创建一个例子/proc 文件 */
example_file=create_proc_entry("example_file",0666,example_dir);
 if (example_file == NULL)
 //创建失败
 {
 rv = - ENOMEM;
 goto out;
 }
example_file->owner = THIS_MODULE;
example_file->read_proc = example_file_read;
example_file->write_proc = example_file_write;
out: ...

/proc 节点的读写函数的类型分别为:

typedef int (read_proc_t)(char *page, char **start, off_t off,
int count, int *eof, void *data);
typedef int (write_proc_t)(struct file *file, const char __user *buffer,
unsigned long count, void *data);

读函数中 page 指针指向用于写入数据的缓冲区, start 用于返回实际的数据写到内
存页的位置,eof 是用于返回读结束标志,offset 是读的偏移,count 是要读的数据长
度。
start 参数比较复杂,对于/proc 只包含简单数据的情况,通常不需要在读函数中设
置*start,意味着内核将认为数据保存在内存页偏移 0 的地方。如果将*start 设置为非
0,意味着内核将认为*start 指向的数据是 offset 偏移处的数据。
写函数与 file_operations 中的 write()成员类似,需要一次从用户缓冲区到内存
空间的复制过程

Linux 系统中可用如下函数删除/proc 节点:

void remove_proc_entry(const char *name,struct proc_dir_entry *parent);

一个简单的/proc 使用范例,这段代码在模块加载函数中创建/proc 文件节点,在模块卸载函数中撤销/proc 节点,而文件中只保存了一个 32 位的无符号整形值

#include ...

 static struct proc_dir_entry *proc_entry;
 static unsigned long val = 0x12345678;

 /* 读/proc 文件接口 */
 ssize_t simple_proc_read(char *page, char **start, off_t off, int
count,int*eof, void *data)
 {
 int len;
 if (off > 0) //不能偏移访问
 {
*eof = 1;
return 0;
 }
 len = sprintf(page, "%08x\n", val);
 return len;
 }
 /* 写/proc 文件接口 */
 ssize_t simple_proc_write(struct file *filp, const char _ _user *buff,
unsigned long len, void *data)
 {
 #define MAX_UL_LEN 8
 char k_buf[MAX_UL_LEN];
 char *endp;
 unsigned long new;
 int count = min(MAX_UL_LEN, len);
 int ret;
 if (copy_from_user(k_buf, buff, count))
 //用户空间->内核空间
 {
     ret = - EFAULT;
 goto err;
 }
 else
 {
 new = simple_strtoul(k_buf, &endp, 16); //字符串转化为整数
if (endp == k_buf)
//无效的输入参数
 {
 ret = - EINVAL;
 goto err;
 }
 val = new;
return count;

}
err:
return ret;
}
int _ _init simple_proc_init(void)
{
proc_entry = create_proc_entry("sim_proc", 0666, NULL); // 创建/proc
if (proc_entry == NULL)
{
printk(KERN_INFO "Couldn't create proc entry\n");
goto err;
}
else
{
proc_entry->read_proc = simple_proc_read;
proc_entry->write_proc = simple_proc_write;
proc_entry->owner = THIS_MODULE;
}
return 0;
err:
return - ENOMEM;
}
void _ _exit simple_proc_exit(void)
{
remove_proc_entry("sim_proc", &proc_root); //撤销/proc
}
module_init(simple_proc_init);
 module_exit(simple_proc_exit);
 MODULE_AUTHOR("xxx");
 MODULE_DESCRIPTION("A simple Module for showing proc");
 MODULE_VERSION("V1.0");
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值