一、 防止函数printk降低linux性能:
利用C语言中的编译指令(#if、#else、#endif等)。
现在修改printk_demo驱动代码,通过编译指令定义了一个pr_debug宏,并通过修改编译指令的条件值来控制是否调用printk函数。如下:
# if 1//此处为1,使用printk函数,为0,忽略printk函数
#define pr_debug(x,…) do { } while(0)
#endif
除此之外,我们还需要了解两个知识点:1.可变参数的宏:可变参数的宏与固定参数的宏之间的区别:可变参数的宏需要通过_VA_ARGS_宏【不支持可变参数个数为0的情况】获取可变参数的宏的可变参数。定义可变参数宏与定义可变参数函数的方法相同,都使用3个点(…)来表示可变参数,可变参数必须是宏和函数最后的参数。
二、 通过虚拟文件系统(/proc)进行数据交互:
必要性:在linux文件系统中,/proc经常被用来作为内核空间进行数据交互的工具。/proc是虚拟文件系统,也就是说,/proc并不是真正的文件系统,而是内存映射。所有读写/proc的操作军事对内存读写的操作。所以读写/proc文件系统的速度远比读写/dev要快。因此,/proc文件系统也可作为linux驱动与用户空间程序交互的工具。
很多信息就是通过/proc文件系统由内核空间的程序向外届提供的。例如:当前系统内存资源就是通过/proc/meminfo文件读取的,读者可以使用如下命令:查看/proc/meminfo文件的内容:Cat /proc/meminfo.我们可以通过执行free命令看看显示的信息是否和meminfo文件中的部分内容相匹配。
在linux驱动程序中可以使用内核函数在/proc目录中创建和删除虚拟文件,也可以建立和删除虚拟目录。
- 1. create_proc_entry 创建proc 文件
struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,struct proc_dir_entry *parent);
name: 要创建的文件名称;
mode: 该文件的保护掩码;
parent: 确定文件所在目录,如果置NULL ,则位置为/proc 下。
- 2. proc_mkdir 创建目录
/* 该函数在父目录parent 下创建一个目录name* */
struct proc_dir_entry * proc_mkdir (const char *name,struct proc_dir_entry *parent);
@name : 要创建的目录名
@parent : 这个目录的父目录
3.remove_proc_entry 删除文件或目录
/* 这个函数从proc 文件系统中删除一个文件或目录。
* 注意:1 .是通过参数name ,而不是通过创建时返回的指针来删除的。
* 2 .该函数不会递归删除目录下的文件。
* 3 .data 变量保存了分配的内存,要先释放对应内存,再删除该文件。
* */
void remove_proc_entry (const char *name,struct proc_dir_entry *parent);
@name : 要删除的文件或目录名
@parent : 所在的父目录
4.create_proc_read_entry 创建只读proc 文件
struct proc_dir_entry * create_proc_read_entry (const char
*name,mode_t mode,struct proc_dir_entry *parent,read_proc_t*
read_proc,void *data);
@name : 要创建的文件名
@mode : 要创建的文件的属性 默认0755
@parent : 这个文件的父目录
@read_proc : 当用户读这个文件时,内核调用的函数
@data : 传给read_proc 的参数
注意:删除虚拟文件目录之前,要先删除目录中的虚拟文件。
执行build.sh脚本文件,会将proc_demo驱动安装在Ubuntu Linux、开发板或Android虚拟机上。然后执行下面的命令查看/proc/proc_demo目录中的内容。
Ls –al /proc/proc_demo/bin2dec
Cat /proc/proc_demo/bin2dec
Cat /proc_demo/readonly
三、 调试工具:
- 用gdb调试用户空间程序:
Gdb可以跟踪调试用户空间的程序。
对于一个用于测试的可执行程序(gdb_debug.c),我们可以直接运行build.sh脚本文件,但注意加上命令参数-g,完整的编译命令如下:
#gcc –static –g –o gdb_debug /root/drivers/debug/gdb_debug.c
现在使用下面的命令来调试:
Gdb gdb_debug
Gdb包含的命令:
1> quit:用于退出gdb调试界面
2> list:用于列出程序中的代码。有三种调用格式:list:显示上一次调用list命令输出的最后一行后面的10行。 list-: 显示上一次调用list命令输出的第一行前面的10行,第一次调用list命令什么都不会显示。list n:显示第n行附近的10行,一般会显示第n行前面5 行和后面4行,加上第n行,正好是10行。
3> Break n:将指定行设置为断点,n表示行号
4> Clear n:清除指定行的断点。
5> Tbreak n:将指定行设置为断点,断点只能使用一次,使用完成后自动清零。
6> Cont/continue:跳过当前断点继续执行。[cont:跳过当前断点继续执行;cont n:跳过n次断点继续前行]
7> Next:继续执行下面的语句,但跳过这程序。等价于step over。[同上有两种格式next 及next n]
8> Nexti:单步执行语句,和next的区别:它会跟踪到子程序的内部,但不打印出子程序内部的语句。
9> Print var_name:查看变量值.
- 用gdbserver远程调试用户空间程序:
1.与GDB的区别:gdb用于PC上进行测试,而gdbserver测试运行于开发板、手机、Android模拟器上。
2.在开发板上使用gdbserver打开测试程序,然后通过串口、有线、无线网络可以在PC上进行测试。
3.第一步:进入Android模拟器的终端,然后进入data/local目录,并执行如下命令:gdbserver :4321 ./gdb_debug启动gdbserver监听程序。其中4321表示使用本机的4321端口号进行监听。
第二步:开启另一个Linux终端,将外部访问模拟器的4321端口的数据包转发到Android模拟器内部的4321端口:adb –s emulator-5544 forword tcp:4321[有多个Android设备时要加-s命令行参数指定具体的Android设备]。
第二个方法[映射端口]:1.进入telent:telent localhost 5554 2.映射端口:redir add tcp:4321:4321
第三步:进入gdb控制台:arm-none-linux-guneabi-gdb gdb_debug
第四步:连接Android模拟器:(gdb) target remote localhost:4321
最后:输入gdb命令进行调试。
通过IP方式连接开发板上的gdbserver:1.gdbserver localhost:4321 ./gdb_debug 2.在linux终端的gdb控制台链接开发板的gdbsrever:(gdb) target remote 192.168.17.103 ./gdb_debug.
通过串口方式:对应的应该是:1.gdbserver /dev/s3c2410_serial0 ./gdb_debug 2.(gdb) target remote /dev/ttyUSB0
- 用kgdb远程调试内核程序:
Kgdb除了提供类似printk函数的日志输出功能,还允许开发人员直接在PC上通过GDB链接目标设备。
Kgdb包含两个部分:kgdb内核和一套链接接口【目前支持串口tty设备链接和以太网连接】。其中串口连接需要通过内核参数kgdboc指定要链接的串口tty设备:以太网连接通过内核参数kgdboc指定IP和端口号。
要想用kgdb调试内核,首先需要配置linux内核。使用make menuconfig命令进入Linux内核的配置菜单。【Kernel hacking--àKGDB:kernel debugger】
配置内核参数时,这些参数通知Linux内核要如何进行调试。假设要通过USB转COM口数据线进行调试,需要将kgdboc参数值折为ttyUSB0,传输效率为115200,一般会指定kdbwait。这些参数需要在S3C开发板过程中按回车进入Uboot模式,然后使用setenv命令设置Linux内核的启动参数,然后使用saveenv和rest命令保存和重新启动Linux内核。
在设置完成后,主机就可以使用gdb命令像调试普通嵌入式应用程序一样调试Linux内核,执行的命令如下:
#gdb ./vmlinux
完成后,使用如下的命令设置传输速率和连接也要调试的Linux内核。
(gdb) set remoteband 115200
(gdb) target remote /dev/ttyUSB0
最后使用各种gdb命令进行Linux内核调试。