驱动程序调试方法之printk——自制proc文件(一)

首先我们需要弄清楚proc机制,来看看fs/proc/proc_misc.c这个文件,从入口函数开始看:

proc_misc_init(void)

        #ifdef CONFIG_PRINTK

{

struct proc_dir_entry *entry;

entry = create_proc_entry("kmsg", S_IRUSR, &proc_root);//这里创建了一个proc入口kmsg

if (entry)

entry->proc_fops = &proc_kmsg_operations;//设置操作函数,见注释1

}

 

注释1:

const struct file_operations proc_kmsg_operations = {

.read = kmsg_read,

.poll = kmsg_poll,

.open = kmsg_open,

.release = kmsg_release,

};

这个操作函数就用于多kmsg来进行操作

 

于是我们可以仿照来设计一下!

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/init.h>

#include <linux/delay.h>

#include <asm/uaccess.h>

#include <asm/irq.h>

#include <asm/io.h>

#include <asm/arch/regs-gpio.h>

#include <asm/hardware.h>

#include <linux/proc_fs.h>

 

struct proc_dir_entry *myentry;

 

const struct file_operations proc_mymsg_operations = {

};

 

static int mymsg_init(void)

{

myentry = create_proc_entry("mymsg", S_IRUSR, &proc_root);

if (myentry)

    myentry->proc_fops = &proc_mymsg_operations ;

 

    return 0;

}

 

void  mymsg_eixt(void)

{

    remove_proc_entry("mymsg", &proc_root);

 

module_init(mymsg_init);

module_exit(mymsg_eixt);

 

这个函数只是在proc目录下面创建了一个入口而已!我们加载后:

ls /proc/mymsg -l

打印出如下信息:

-r--------    1 0        0               0 Feb  4 13:37 /proc/mymsg

 

如果我们想查看/proc/mymsg内容的话:

# cat /proc/mymsg

打印出来

cat: read error: Invalid argument

这也是理所当然的,因为我们根本没有读函数嘛!

 

所以我们接下来要做的就是来完成这个读函数,而这这个读函数里面我们要做的就是将mylog_buf中的中的数据拷贝到用户空间。有一个很关键的地方就是,我们的 mylog_buf 应该是一个环形队列,关于环形队列的概念我们先来说一下:

front指向队列首部,rear指向队列尾部,size表示队列长度。读只能从首部读,写的话只能从尾部写!写到尾部的话,就回去重头开始写!

当front==rear的时候,可以判断队列是空的。

当(rear+1)/size==front的时候,可以判断队列是满的。程序如下:

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/init.h>

#include <linux/delay.h>

#include <asm/uaccess.h>

#include <asm/irq.h>

#include <asm/io.h>

#include <asm/arch/regs-gpio.h>

#include <asm/hardware.h>

#include <linux/proc_fs.h>

 

#define MYLOG_BUF_LEN 1024

 

struct proc_dir_entry *myentry;

 

static char mylog_buf[MYLOG_BUF_LEN];

static char tmp_buf[MYLOG_BUF_LEN];

static int mylog_r = 0;    //用来标识读

static int mylog_w = 0;  //用来标识写

 

static DECLARE_WAIT_QUEUE_HEAD(mymsg_waitq);

 

//判断环形缓冲区是否为空

static int is_mylog_empty(void)

{

return (mylog_r == mylog_w);

}

//判断环形缓冲区是否已满

static int is_mylog_full(void)

{

return ((mylog_w + 1)% MYLOG_BUF_LEN == mylog_r);

}

 

/*写缓冲区:如果缓冲区已满的话,就让覆盖掉下一个要读的数据

*否则就直接写入

此外在写缓冲区函数里面还需要做的一件事情就是唤醒等待队列,

这是因为当缓冲区为空的时候,如果调用读函数的话,就会使进程

进入等待队列,理当在写入数据的时候唤醒进程

*/

static void mylog_putc(char c)

{

if (is_mylog_full())

{

/* 丢弃一个数据 */

mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN;

}

 

mylog_buf[mylog_w] = c;

mylog_w = (mylog_w + 1) % MYLOG_BUF_LEN;

 

/* 唤醒等待数据的进程 */

    wake_up_interruptible(&mymsg_waitq);   /* 唤醒休眠的进程 */

}

 

/*读缓冲区:如果缓冲区为空的话,就返回0

否则从首部读出一个数据,返回1

*/

static int mylog_getc(char *p)

{

if (is_mylog_empty())

{

return 0;

}

*p = mylog_buf[mylog_r];

mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN;

return 1;

}

 

/*打印函数:这个函数是参考sprintf函数得编写的

*它将传递进来的参数转换为固定的格式之后,放入到一个临时缓冲区里面

*然后将环形缓冲区的值写入到mylog_buf缓冲区里面,详见注释2

*/

int myprintk(const char *fmt, ...)

{

va_list args;

int i;

int j;

 

va_start(args, fmt);

i = vsnprintf(tmp_buf, INT_MAX, fmt, args);//将传进来的参数转换后放入tmp_buf

va_end(args);

 

for (j = 0; j < i; j++)

mylog_putc(tmp_buf[j]);//将tmp_buf里面的东东放入mylog_buf缓冲区里面

 

return i;

}

 

/*读函数:当在应用空间调用命令:cat /proc/mymsg的时候,会调用这个函数

*

*/

static ssize_t mymsg_read(struct file *file, char __user *buf,

 size_t count, loff_t *ppos)

{

int error = 0;

int i = 0;

char c;

 

/* 把mylog_buf的数据copy_to_user, return */

 

        //如果为非阻塞且mylog_buf为空,那么就出错返回

if ((file->f_flags & O_NONBLOCK) && is_mylog_empty())

return -EAGAIN;

 

        //如果mylog_buf为空的话进程进入等待队列,还记得我们在写缓冲区

       //函数里面会唤醒进程这件事情吧!

error = wait_event_interruptible(mymsg_waitq, !is_mylog_empty());

 

/* copy_to_user */

        //首先从缓冲区里面获得一个字符,然后拷贝到用户空间

        //如果缓冲区还有信息的话,就再次获得字符,拷贝到用户

       //空间,直到缓冲区为空

while (!error && (mylog_getc(&c)) && i < count) {

error = __put_user(c, buf);//将c的内容拷贝到用户空间

buf++;

i++;

}

 

if (!error)

error = i;

 

return error;

}

 

const struct file_operations proc_mymsg_operations = {

.read = mymsg_read,

};

 

static int mymsg_init(void)

{

myentry = create_proc_entry("mymsg", S_IRUSR, &proc_root);

if (myentry)

myentry->proc_fops = &proc_mymsg_operations;

return 0;

}

 

static void mymsg_exit(void)

{

remove_proc_entry("mymsg", &proc_root);

}

 

module_init(mymsg_init);

module_exit(mymsg_exit);

 

/*因为myprintk是我们自己写的打印语句

*所以需要导出才能被使用,使用的时候还需要声明一下:

extern int myprintk(const char *fmt,...);

*/

EXPORT_SYMBOL(myprintk);

 

MODULE_LICENSE("GPL");

 

我们在来总结一下:在本文件里面我们做了两件事情,一件事情是定义了一个写函数,当我们在用户空间使用命令:cat /proc/mymsg的时候,就会调用到这个读函数,这个读函数会将mylog_buf中的数据拷贝到用户空间,那么mylog_buf里面的数据哪里来的呢?这就是我们做的另外一件事情,我们定义了一个打印函数,这个打印函数会将要打印的数据写入一个临时缓冲区,然后又从临时缓冲区里面取出数据放入mylog_buf中。cat /proc/mymsg的候就会将mylog_buf中的数据拷贝到用户空间,就可以显示出来了!

 

注释2:

int sprintf(char * buf, const char *fmt, ...)

{

va_list args;

int i;

 

va_start(args, fmt);

i=vsnprintf(buf, INT_MAX, fmt, args);

va_end(args);

return i;

}

这个就是sprintf函数,它将传递进来的参数放入buf缓冲区,我们做的就是将这个缓冲区里面的值在放入mylog_buf缓冲区里面!相当的巧妙啊!

 

接下来我们不妨测试一下,测试函数如下:

 

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/init.h>

#include <linux/delay.h>

#include <asm/uaccess.h>

#include <asm/irq.h>

#include <asm/io.h>

#include <asm/arch/regs-gpio.h>

#include <asm/hardware.h>

 

xtern int myprintk(const char *fmt, ...);

 

static int first_drv_init(void)

{

myprintk("first_drv_init\n");

 

return 0;

}

 

static void first_drv_exit(void)

{

    myprintk("abcdefhg\n");

}

 

module_init(first_drv_init);

module_exit(first_drv_exit);

 

 

MODULE_LICENSE("GPL");

 

先加载proc.ko

在加载test.ko

cat /proc/mymsg,打印如下信息:

first_drv_init

这正是我们打印的信息,成功了!

时间: 2024-08-29 07:02:50

驱动程序调试方法之printk——自制proc文件(一)的相关文章

驱动程序调试方法之printk——自制proc文件(二)

上一节的程序很振奋人心,我们自己实现了一个myprintk打印函数.但是这个函数存在一个致命的缺陷,那就是只能使用一次cat /proc/mymsg命令来读取mylog_buf的值.这是因为读到最后会出现:mylog_r == mylog_w,表示缓冲区为空,下一次就不能在读到数据了.在本节里面我们就着手来解决这个问题,我们要实现的就是每次使用 cat /proc/mymsg 时,都会从头打印.那么我们就需要将入口做一个拷贝,一个保存起来,一个进行变换.这样的话,当下一次读的时候,我们可以将保存

Linux下core文件调试方法

在程序不寻常退出时,内核会在当前工作目录下生成一个core文件(是一个内存映像,同时加上调试信息).使用gdb来查看core文件,可以指示出导致程序出错的代码所在文件和行数. 1.core文件的生成开关和大小限制  1)使用ulimit -c命令可查看core文件的生成开关.若结果为0,则表示关闭了此功能,不会生成core文件.  2)使用ulimit -c filesize命令,可以限制core文件的大小(filesize的单位为kbyte).若ulimit -c unlimited,则表示c

Linux内核调试技术之自构proc

1.简介 在内核中使用printk可以讲调试信息保存在log_buf缓冲区中,可以使用命令 #cat /proc/kmsg 将缓冲区的数区的数数据打印出来,今天我们就来研究一下,自己写kmsg这个文件,我们取名叫做 mymsg. 2.查看内核中 /proc/kmsg怎么写的! 在Proc_misc.c (fs\proc) 文件中: void __init proc_misc_init(void){      .........................          struct pr

Ubuntu中为Android系统上编写Linux内核驱动程序实现方法_Android

        在智能手机时代,每个品牌的手机都有自己的个性特点.正是依靠这种与众不同的个性来吸引用户,营造品牌凝聚力和用户忠城度,典型的代表非iphone莫属了.据统计,截止2011年5月,AppStore的应用软件数量达381062个,位居第一,而Android Market的应用软件数量达294738,紧随AppStore后面,并有望在8月份越过AppStore.随着Android系统逐步扩大市场占有率,终端设备的多样性亟需更多的移动开发人员的参与.据业内统计,Android研发人才缺口至

Ubuntu中为Android系统上编写Linux内核驱动程序实现方法

在智能手机时代,每个品牌的手机都有自己的个性特点.正是依靠这种与众不同的个性来吸引用户,营造品牌凝聚力和用户忠城度,典型的代表非iphone莫属了.据统计,截止2011年5月,AppStore的应用软件数量达381062个,位居第一,而Android Market的应用软件数量达294738,紧随AppStore后面,并有望在8月份越过AppStore.随着Android系统逐步扩大市场占有率,终端设备的多样性亟需更多的移动开发人员的参与.据业内统计,Android研发人才缺口至少30万.目前,

VxWorks操作系统shell命令与调试方法总结

VxWorks下的调试手段 主要介绍在Tornado集成开发环境下的调试方法和利用支撑定位问题的步骤.思路. 1         Tornado的调试工具 嵌入式实时操作系统VxWorks和集成开发环境Tornado的组成结构如下图1.分为主机和目标机系统. 图1 集成开发环境结构图 在Tornado下调试相关操作在Debug菜单下包括 图2 Debug菜单 简单解释各菜单项的功能 1.1        WindShell 1.1.1         简介 Vxworks的Shell分为两种ho

Linux下的段错误产生的原因及调试方法

Linux下的段错误产生的原因及调试方法     简而言之,产生段错误就是访问了错误的内存段,一般是你没有权限,或者根本就不存在对应的物理内存,尤其常见的是访问0地址. 一般来说,段错误就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由gdtr来保存的,他是一个48位的寄存器,其中的32位是保存由它指向的gdt表,后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别,指向的gdt是由以64位为一个单位的表,在这张表中就保存着程序运行的代码段以

php程序调试方法总结

  相信很多朋友们都有调试程序的经历,然而很多时候调试程序是痛苦而又漫长的过程;它不仅需要细心,更需要耐心,切忌心浮气躁.但是当找出问题并顺利解决它时,又会给人无比激动的喜悦.这里总结一下笔者在程序调试中的使用的原则,工具,以及方法.这里需要说明的是,某些原则性的东西,各种语言都是差不多的,而涉及到具体的工具和某些具体的调试方法,这里只涉及web开发方面的内容. 总体原则: 1.找出问题原因: 程序需要调试,是因为程序有问题.而调试的第一目标是找到原因.常见调试方法, 排除法: 当我们面对整个复

visual studio-关于Visual Studio2015调试无法查找或打开PDF文件的问题

问题描述 关于Visual Studio2015调试无法查找或打开PDF文件的问题 如图,已连接源服务器也没有用,求教如何调好 解决方案 不是pdf文件,是pdb文件.这个是调试符号.没有调试符号不影响你调试的. 解决方案二: Visual Studio ""无法查找或打开PDB文件""解决方法Visual Studio调试之符号文件Visual Studio调试之符号文件