zoukankan      html  css  js  c++  java
  • linux内核调试技术之自构proc

    1、简介

      在上一篇中,在内核中使用printk可以讲调试信息保存在log_buf缓冲区中,可以使用命令 #cat /proc/kmsg  将缓冲区的数区的数数据打印出来,今天我们就来研究一下,自己写kmsg这个文件,我们取名叫做 mymsg。

    2、查看内核中 /proc/kmsg怎么写的!

    在Proc_misc.c (fsproc) 文件中:

    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 (fsproc) 文件中:

    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");
    View Code

     5、测试程序

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

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

           ② myprintk("first_drv_open : %d ", ++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
    ", ++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
    ", ++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
    ");
        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");
    View Code

     6、在tty中测试效果

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

    成功!!!

    感谢韦东山老师!!!!

  • 相关阅读:
    98. 验证二叉搜索树
    236. 二叉树的最近公共祖先
    leetcode 字符串转换整数 (atoi)
    LeetCode 寻找两个正序数组的中位数 (找第k个数的变种)
    Leetcode 面试题 16.18. 模式匹配(逻辑题)(转)
    深入学习Redis(4):哨兵(转)
    【BAT面试题系列】面试官:你了解乐观锁和悲观锁吗?(转)
    CentOS 7.5 使用 yum 方式安装 MySQL 5.7
    CentOS7 安装 PHP7 完全详细教程
    ubunutu 18.04 编译php7.4.1
  • 原文地址:https://www.cnblogs.com/veryStrong/p/6221977.html
Copyright © 2011-2022 走看看