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 proc_dir_entry *entry;
        //这里创建了一个proc入口kmsg
        entry = create_proc_entry("kmsg", S_IRUSR, &proc_root);
        if (entry)
       /*构造一个
proc_fops结构*/
      entry
->proc_fops = &proc_kmsg_operations;
  .........................
}

在Kmsg.c (fs\proc) 文件中:

const struct file_operations proc_kmsg_operations = {
    .read        = kmsg_read,
    .poll        = kmsg_poll,
    .open        = kmsg_open,
    .release    = kmsg_release,
};

在用户空间中使用 cat /proc/kmsg的时候,会调用kmsg_open,在调用kmsg_read函数,读取log_buf中的数据,拷贝到用户空间显示。

3、在写之前,我们需要来学习一下循环队列

  信息来源:(http://blog.sina.com.cn/s/blog_8b200d440100xsug.html)

   环形队列是在实际编程极为有用的数据结构,它有如下特点。

      它是一个首尾相连的FIFO的数据结构,采用数组的线性空间,数据组织简单,能很快知道队列是否满为空。能以很快速度的来存取数据。
      因为有简单高效的原因,甚至在硬件都实现了环形队列。
 
     环形队列广泛用于网络数据收发,和不同程序间数据交换(比如内核与应用程序大量交换数据,从硬件接收大量数据)均使用了环形队列。

3.1.环形队列实现原理

    内存上没有环形的结构,因此环形队列实上是数组的线性空间来实现。那当数据到了尾部如何处理呢?它将转回到0位置来处理。这个的转回是通过取模操作来执行的。
       因此环列队列的是逻辑上将数组元素q[0]与q[MAXN-1]连接,形成一个存放队列的环形空间。
       为了方便读写,还要用数组下标来指明队列的读写位置。head/tail.其中head指向可以读的位置,tail指向可以写的位置。

 

 环形队列的关键是判断队列为空,还是为满。当tail追上head时,队列为满时,当head追上tail时,队列为空。但如何知道谁追上谁。还需要一些辅助的手段来判断.

    如何判断环形队列为空,为满有两种判断方法。
  一.是附加一个标志位tag
      当head赶上tail,队列空,则令tag=0,
      当tail赶上head,队列满,则令tag=1,
 
  二.限制tail赶上head,即队尾结点与队首结点之间至少留有一个元素的空间。
      队列空:   head==tail
      队列满:   (tail+1)% MAXN ==head

 

 4、程序编写

#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
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 int mylog_r_tmp = 0 ;

/* 休眠队列初始化 */
static DECLARE_WAIT_QUEUE_HEAD(mymsg_waitq);

/*
*判断环形队列是否为空
*返回0:表示不空  返回1:表示空
*/
static int is_mylog_empty( void )
{
   
return (mylog_r == mylog_w);
}

/*
*判断环形队列是否满
*返回0:表示不满  返回1:表示满
*/
static int is_mylog_full( void )
{
   
return((mylog_w + 1)% MYLOG_BUF_LEN == mylog_r);
}

/*
*在读取的时候,判断环形队列中数据是否为空
*返回0:表示不空  返回1:表示空
*/
static int is_mylog_empty_for_read( void )
{
   
return (mylog_r_tmp == mylog_w);
}

/*
*往循环队列中存字符
*输入:c字符 单位:1byte
*输出:无
*/
static void mylog_putc( char c)
{

   
if (is_mylog_full())
    {
       
/* 如果检测到队列已经满了,则丢弃该数据 */
        mylog_r
= (mylog_r + 1) % MYLOG_BUF_LEN;
       
       
/* mylog_r_tmp不能大于mylog_r */
        if((mylog_r_tmp + 1)% MYLOG_BUF_LEN == mylog_r)
            mylog_r_tmp
= mylog_r;
       
    }
    mylog_buf[mylog_w]
= c;
   
/* 当mylog_w=1023的时候 (mylog_w+1) % MYLOG_BUF_LEN =0,回到队列头,实现循环 */
    mylog_w
= (mylog_w + 1) % MYLOG_BUF_LEN;
   
/* 唤醒等待数据的进程 */    
    wake_up_interruptible(
& mymsg_waitq); 
}

/*
*从循环队列中读字符
*输入:*p 单位:1byte
*输出:1表示成功
*/
static int mylog_getc( char * p)
{
   
/* 判断数据是否为空 */
    if (is_mylog_empty_for_read())
    {
       
return 0 ;
    }
   
*p = mylog_buf[mylog_r_tmp ];
    mylog_r_tmp 
= (mylog_r_tmp  + 1) % MYLOG_BUF_LEN;
   
return 1 ;
}

/*
*调用myprintk,和printf用法相同
*/
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 ;
    size_t i
= 0 ;
   
char c;
   
/* 把mylog_buf的数据copy_to_user, return */

    /* 非阻塞 和 缓冲区为空的时候返回 */
    if ((file->f_flags & O_NONBLOCK) && is_mylog_empty())
       
return - EAGAIN;
   
   
/* 休眠队列wait_event_interruptible(xxx,0)-->休眠 */
    error
= wait_event_interruptible(mymsg_waitq, ! is_mylog_empty_for_read());
   
   
/* copy_to_user */
    while (!error && (mylog_getc(&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_tmp
= mylog_r;
   
return 0 ;
}


const struct file_operations proc_mymsg_operations = {
    .read
= mymsg_read,
    .open
= mymsg_open,
    };
static int mymsg_init( void )
{
   
struct proc_dir_entry * myentry; kmsg
    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 ");

 5、测试程序

 注意:在上面程序中 使用了 EXPORT_SYMBOL(myprintk);意思是把myprintk可以在整个内核空间使用。

使用方法:①extern int myprintk(const char *fmt, ...);声明

       ② myprintk("first_drv_open : %d\n", ++cnt);使用

#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>

static struct class * firstdrv_class;
static struct class_device    * firstdrv_class_dev;

volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;

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

static int first_drv_open( struct inode *inode, struct file * file)
{
   
static int cnt = 0 ;
    myprintk(
" first_drv_open : %d\n ", ++ cnt);
   
/* 配置GPF4,5,6为输出 */
    *gpfcon &= ~(( 0x3<<( 4* 2)) | ( 0x3<<( 5* 2)) | ( 0x3<<( 6* 2 )));
   
*gpfcon |= (( 0x1<<( 4* 2)) | ( 0x1<<( 5* 2)) | ( 0x1<<( 6* 2 )));
   
return 0 ;
}

static ssize_t first_drv_write( struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
   
int val;
   
static int cnt = 0 ;

    myprintk(
" first_drv_write : %d\n ", ++ cnt);

    copy_from_user(
&val, buf, count); //     copy_to_user();

    if (val == 1 )
    {
       
// 点灯
        *gpfdat &= ~(( 1<< 4) | ( 1<< 5) | ( 1<< 6 ));
    }
   
else
    {
       
// 灭灯
        *gpfdat |= ( 1<< 4) | ( 1<< 5) | ( 1<< 6 );
    }
   
   
return 0 ;
}

static struct file_operations first_drv_fops = {
    .owner 
=  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open 
=   first_drv_open,   
    .write   
=     first_drv_write,     
};


int major;
static int first_drv_init( void )
{
    myprintk(
" first_drv_init\n " );
    major
= register_chrdev( 0, " first_drv ", &first_drv_fops); // 注册, 告诉内核

    firstdrv_class
= class_create(THIS_MODULE, " firstdrv " );

    firstdrv_class_dev
= class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, " xyz "); /* /dev/xyz */

    gpfcon
= ( volatile unsigned long *)ioremap( 0x56000050, 16 );
    gpfdat
= gpfcon + 1 ;

   
return 0 ;
}

static void first_drv_exit( void )
{
    unregister_chrdev(major,
" first_drv "); // 卸载

    class_device_unregister(firstdrv_class_dev);
    class_destroy(firstdrv_class);
    iounmap(gpfcon);
}

module_init(first_drv_init);
module_exit(first_drv_exit);


MODULE_LICENSE(
" GPL ");

6、在tty中测试效果

# insmod my_msg.ko
# insmod first_drv.ko
# cat /proc/mymsg
mymsg_open mylog_r_tmp=0
first_drv_init

成功!!!

感谢韦东山老师!!!!

猜你喜欢

转载自www.linuxidc.com/Linux/2017-01/139031.htm