驱动程序调试方法之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);

EXPORT_SYMBOL(myprintk);
MODULE_LICENSE("GPL");

/*因为 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>

extern 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");

上两个C文件要放到同一个目录下,Makefile如下:
obj-m := kproctest3.o kproctest.o
KERNELDIR := /lib/modules/2.6.23-gentoo-r9/build/
PWD :=$(shell pwd)

modules:
	$(MAKE)  -C  $(KERNELDIR)  M=$(PWD)  modules

.PHONY :clean
clean:
	rm -rf *.o *ko

先加载proc.ko
在加载test.ko
cat /proc/mymsg,打印如下信息:
first_drv_init
这正是我们打印的信息,成功了!

http://liu1227787871.blog.163.com/blog/static/20536319720126954110820/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值