转自:http://liu1227787871.blog.163.com/blog/static/205363197201269111953844/
先检测一下框架:
再加载test.ko
cat /proc/mymsg,打印如下信息:
first_drv_init
这正是我们打印的信息,成功了!
这样我们自己实现了一个myprintk打印函数。但是这个函数存在一个致命的缺陷,那就是只能使用一次cat /proc/mymsg命令来读取mylog_buf的值。这是因为读到最后会出现:mylog_r == mylog_w,表示缓冲区为空,下一次就不能在读到数据了。这里我们就着手来解决这个问题,我们要实现的就是每次使用 cat /proc/mymsg 时,都会从头打印。那么我们就需要将入口做一个拷贝,一个保存起来,一个进行变换。这样的话,当下一次读的时候,我们可以将保存的入口重新做个拷贝,然后让拷贝进行变化。具体程序如下:
在printk打印的时候,会把要打印的信息会存放在log_buf中,通过文件/proc/kmsg可以来访问这个buf,然后将信息打印出来。那么我们调试驱动的时候还有很多printk打印的消息我们不需要去看,太杂了,我们是否能做到只打印我们的自己想要的调试信息到我们自己定义的kmsg里面呢?我们可以构造这样一个mylog_buf,里面存放我们所需要的打印信息,通过一个/proc/kmsg文件可以访问该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>
#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缓冲区里面
*/
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中的数据拷贝到用户空间,就可以显示出来了!
先检测一下框架:
#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");
先加载proc.ko
再加载test.ko
cat /proc/mymsg,打印如下信息:
first_drv_init
这正是我们打印的信息,成功了!
这样我们自己实现了一个myprintk打印函数。但是这个函数存在一个致命的缺陷,那就是只能使用一次cat /proc/mymsg命令来读取mylog_buf的值。这是因为读到最后会出现:mylog_r == mylog_w,表示缓冲区为空,下一次就不能在读到数据了。这里我们就着手来解决这个问题,我们要实现的就是每次使用 cat /proc/mymsg 时,都会从头打印。那么我们就需要将入口做一个拷贝,一个保存起来,一个进行变换。这样的话,当下一次读的时候,我们可以将保存的入口重新做个拷贝,然后让拷贝进行变化。具体程序如下:
#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_r_for_read = 0;//这个用来拷贝mylog_r ,它将改变,但是mylog_r 不变
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_empty_for_read(void)
{
return (mylog_r_for_read == mylog_w);
}
static int is_mylog_full(void)
{
return ((mylog_w + 1)% MYLOG_BUF_LEN == mylog_r);
}
//这个函数是被myprintk函数调用的
static void mylog_putc(char c)
{
if (is_mylog_full())
{
mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN;
/*加上下面三行的原因是:如果读的时候,也一直在调用printk写的话,
*当写的速度比较快的时候,可能会导致mylog_w超过mylog_r_for_read,
*这时就需要更新mylog_r_for_read,使mylog_r_for_read 指向新的入口
*当mylog_w超过入口mylog_r时,mylog_r会一直跟着更新的!
*/
if ((mylog_r_for_read + 1) % MYLOG_BUF_LEN == mylog_r)
{
mylog_r_for_read = mylog_r;
}
}
mylog_buf[mylog_w] = c;
mylog_w = (mylog_w + 1) % MYLOG_BUF_LEN;
wake_up_interruptible(&mymsg_waitq);
}
static int mylog_getc_for_read(char *p)
{
if (is_mylog_empty_for_read())
{
return 0;
}
*p = mylog_buf[mylog_r_for_read];
mylog_r_for_read = (mylog_r_for_read + 1) % MYLOG_BUF_LEN;
return 1;
}
int myprintk(const char *fmt, ...)
{
va_list args;
int i;
int j;
va_start(args, fmt);
i = vsnprintf(tmp_buf, INT_MAX, fmt, args);
va_end(args);
for (j = 0; j < i; j++)
mylog_putc(tmp_buf[j]);
return i;
}
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;
if ((file->f_flags & O_NONBLOCK) && is_mylog_empty_for_read())
return -EAGAIN;
error = wait_event_interruptible(mymsg_waitq, !is_mylog_empty_for_read());
/* copy_to_user */
while (!error && (mylog_getc_for_read(&c)) && i < count) {
error = __put_user(c, buf);
buf++;
i++;
}
if (!error)
error = i;
return error;
}
static int mymsg_open(struct inode *inode, struct file *file)
{
mylog_r_for_read = mylog_r;
return 0;
}
const struct file_operations proc_mymsg_operations = {
.open = mymsg_open,
.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");
关于这个函数,当我们在用户空间,使用命令:cat /proc/mymsg时,首先会调用open函数,在open函数里面会将入口做一个拷贝,然后拿出一份来作为变化量,另外一个作为入口不改变。这样,每次cat /proc/mymsg时,都会从入口处开始打印!