驱动篇:设备驱动的调试(一)(摘录)
发布日期:2021-06-29 11:35:00 浏览次数:2 分类:技术文章

本文共 6318 字,大约阅读时间需要 21 分钟。

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) #elsestatic inline int _ _attribute_ _ ((format (printf, 1, 2))) pr_debug(constchar * 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_tmode,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, intcount,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); // 创建/procif (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");

转载地址:https://blog.csdn.net/zytgg123456/article/details/110204628 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:驱动篇:设备驱动的调试(二)(摘录)
下一篇:驱动篇:音频设备驱动(二)(摘录)

发表评论

最新留言

关注你微信了!
[***.104.42.241]2024年05月01日 00时47分04秒