首先我们需要弄清楚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
这正是我们打印的信息,成功了!

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

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

    上一节的程序很振奋人心,我们自己实现了一个myprintk打印函数.但是这个函数存在一个致命的缺陷,那就是只能使用一次cat /proc/mymsg命令来读取mylog_buf的值.这是因为读到最后会 ...

  2. 驱动程序调试方法之printk——printk的原理与直接使用

    1.基本原理 (1)在UBOOT里设置console=ttySAC0或者console=tty1 这里是设置控制终端,tySAC0 表示串口, tty1 表示lcd(2)内核用printk打印 内核就 ...

  3. linux设备驱动学习笔记--内核调试方法之printk

    1,printk类似于用户态的printf函数,但是比printf函数多了一个日志级别,内核中最常见的日志输出都是通过调用printk来实现的,其打印级别有8种可能的记录字串, 在头文件 <Li ...

  4. ARM驱动调试方法、思路总结、笔记

    驱动程序的调试一. 打印: prink, 自制proc文件UBOOT传入console=ttySAC0 console=tty11. 内核处理UBOOT传入的参数console_setup add_p ...

  5. 如何用Bat批处理自制自解压文件

    转载▼http://blog.sina.com.cn/s/blog_48462a890102e0nu.html     1.在桌面上新建一个文本文档,如:“新建 文本文档.txt”,方法是:在桌面的空 ...

  6. linux设备驱动程序第四部分:从如何定位oops对代码的调试方法,驱动线

    在一个我们谈到了如何编写一个简单的字符设备驱动程序,我们不是神,编写肯定会失败的代码,在这个过程中,我们需要继续写代码调试.在普通c应用.我们经常使用printf输出信息.或者使用gdb要调试程序,然 ...

  7. ubuntu/linux mint 创建proc文件的三种方法(四)

    在做内核驱动开发的时候,能够使用/proc下的文件,获取对应的信息,以便调试. 大多数/proc下的文件是仅仅读的,但为了演示样例的完整性,都提供了写方法. 方法一:使用create_proc_ent ...

  8. ubuntu/linux mint 创建proc文件的三种方法(两)

    在这样做的内核驱动程序的开发时间.可以使用/proc下档.获取相应的信息.对于调试. 大多数/proc下的文件是仅仅读的.但为了演示样例的完整性.都提供了写方法. 方法一:使用create_proc_ ...

  9. linux下core文件调试方法

    http://www.cnblogs.com/li-hao/archive/2011/09/25/2190278.html 在程序不寻常退出时,内核会在当前工作目录下生成一个core文件(是一个内存映 ...

随机推荐

  1. 编程算法 - 水洼的数量 代码(C)

    水洼的数量 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 有一个大小为N*M的园子, 雨后起了积水. 八连通的积水被觉得是连接在一起的. 请求 ...

  2. The program yum-complete-transaction is found in the yum-utils package

    用yum安装的时候出现 The program yum-complete-transaction is found in the yum-utils package. 错误提示的解决方法:# 安装 y ...

  3. Exploring Micro-frameworks: Spring Boot--转载

    原文地址:http://www.infoq.com/articles/microframeworks1-spring-boot Spring Boot is a brand new framework ...

  4. 使用Python开发轻量级的Web框架以及基于WSGI的服务器来实现一个网站页面

    说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家! 目录 一丶项目说明 二丶数据准备 三丶使用网络TCP开发一个基于WSGI协议的Web服务器 四丶使用python3开发一个轻量级的 ...

  5. 【Codeforces Round #455 (Div. 2) A】Generate Login

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 枚举两个串的前缀长度就好. 组出来. 排序. 取字典序最小的那个. [代码] #include <bits/stdc++.h& ...

  6. 发送 email 过程

    发送 email 过程 SMTP基本命令集: 命令 描述 ---------- HELO 向服务器标识用户身份发送者能欺骗,说谎,但一般情况下服务器都能检测到. MAIL 初始化邮件传输 mail f ...

  7. C Tricks(十八)—— 整数绝对值的实现

    为正还是为负:(对 int 类型而言,第一位为符号位,其余为数值,则右移 31 位,再与 1 求与) 如果为正 ⇒ 返回原值 如果为负 ⇒ 对其二进制形式各位取反 + 1 int abs(int x) ...

  8. uiautomator_python使用汇总

    1.判断按钮状态 if d(resourceId="id",enabled=False): #判断当前按钮是否为未激活状态,为True则为激活状态2.获取toast提示语 d.to ...

  9. CC2530定时器使用

     定时器学习   文件夹 说明 依据数据手冊可知CC2530总共同拥有4个定时器,可是定时器2被系统占用,可用的仅仅有三个,分别为定时器1/3/4 Timer在协议栈的代码位置为hal_timer ...

  10. JS实现按下按键触发点击事件

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...