• 35.Linux-分析并制作环形缓冲区


    在上章34.Linux-printk分析、使用printk调试驱动里讲述了:

         printk()会将打印信息存在内核的环形缓冲区log_buf[]里, 可以通过dmesg命令来查看log_buf[]

     


    1.环形缓冲区log_buf[]又是存在内核的哪个文件呢?

    位于/proc/kmsg里,所以除了dmesg命令查看,也可以使用cat /proc/kmsg来查看

     

    2.但是,dmesg命令和cat /proc/kmsg有所不同

    2.1 dmesg命令

    每次使用,都会打印出环形缓冲区的所有信息

    2.2 cat /proc/kmsg

    只会打印出每次新的环形缓冲区的信息

    比如,第一次使用cat /proc/kmsg,会打印出内核启动的所有信息

    第二次使用cat /proc/kmsg,就不会出现之前打印的信息,只打印继上次使用cat /proc/kmsg之后的新的信息,比如下图所示:

     

    3.接下来我们便进入内核,/proc/kmsg文件在哪生成的

    搜索"kmsg",找到位于fsprocproc_misc.c 文件的proc_misc_init()函数中,

    该函数主要用来生成登记的设备文件,具体代码如下所示:

    const struct file_operations proc_kmsg_operations = {
           .read              = kmsg_read,               //读函数
           .poll        = kmsg_poll,
           .open             = kmsg_open,
           .release   = kmsg_release,
    };
     
    
    void __init proc_misc_init(void)
    {
      ... ...
      struct proc_dir_entry *entry;                                   // 用来描述文件的结构体,
             entry = create_proc_entry("kmsg", S_IRUSR, &proc_root); //使用create_proc_entry()创建文件
    if (entry)
             entry->proc_fops = &proc_kmsg_operations;    //对创建的文件赋入file_ operations
        ... ...
    }

    从上面代码得出,/proc/kmsg文件,也是有file_operations结构体的,而cat命令就会一直读/proc/kmsg的file_operations->read(),实现读log_buf[]的数据

    且/proc/kmsg文件是通过create_proc_entry()创建出来的,参数如下所示:

    "kmsg":文件名

    &proc_root:父目录,表示存在/proc根目录下

    S_IRUSR: 等于400,表示拥有者(usr)可读,其他任何人不能进行任何操作,如下图所示:

     

    该参数和chmod命令参数一样,除了S_IRUSR还有很多参数,比如:

    S_IRWXU: 等于700, 表示拥有者(usr)可读(r)可写(w)可执行(x)

    S_IRWXG: 等于070, 表示拥有者和组用户 (group)可读(r)可写(w)可执行(x)

    4.为什么使用dmesg命令和cat /proc/kmsg会有这么大的区别?

    我们进入proc_kmsg_operations-> kmsg_read()看看,就知道了

    static ssize_t kmsg_read(struct file *file, char __user *buf,size_t count, loff_t *ppos)
    {
           /*若在非阻塞访问,且没有读的数据,则立刻return*/
           if ((file->f_flags & O_NONBLOCK) && !do_syslog(9, NULL, 0))
                  return -EAGAIN;
           return do_syslog(2, buf, count);          //开始读数据,buf:用户层地址,count:要读的数据长度
    }

    5.proc_kmsg_operations-> kmsg_read()->do_syslog(9, NULL, 0)的内容如下所示:

     

    其中log_startlog_end就是环形缓冲区的两个标志, log_start也可以称为读标志位, log_end也可以称为写标志位,当写标志和读标志一致时,则表示没有读的数据了。

    6.proc_kmsg_operations-> kmsg_read()->do_syslog(2, buf, count)的内容如下所示:

    case 2:           /* Read from log */
                  error = -EINVAL;
                  if (!buf || len < 0)           //判断用户层是否为空,以及读数据长度
                         goto out;
                  error = 0;
                  if (!len)
                         goto out;
                  if (!access_ok(VERIFY_WRITE, buf, len)) {      // access_ok:检查用户层地址是否访问OK
                         error = -EFAULT;
                         goto out;
                  }
    
                  /*若没有读的数据,则进入等待队列*/
                  error = wait_event_interruptible(log_wait, (log_start - log_end));
                  if (error)
                        goto out;
    
                  i = 0;
                  spin_lock_irq(&logbuf_lock);        
                  while (!error && (log_start != log_end) && i < len) {
                         c = LOG_BUF(log_start);         // LOG_BUF:取环形缓冲区log_buf[]里的某个位置的数据
                         log_start++;                        //读地址++
                         spin_unlock_irq(&logbuf_lock);
                         error = __put_user(c,buf);            //和 copy_to_user()函数一样,都是上传用户数据
                         buf++;                                       //用户地址++
                         i++;                                        //读数据长度++
                         cond_resched();
                         spin_lock_irq(&logbuf_lock);
                  }
                  spin_unlock_irq(&logbuf_lock);
                  if (!error)
                         error = i;
                  break;}
    out:
           return error;
    }

    显然就是对环形缓冲区的读操作,而环形缓冲区的原理又是什么?

    7.接下来便来分析环形缓冲区的原理

    和上面函数一样, 环形缓冲区需要一个全局数组,还需要两个标志:读标志R、写标志W

    我们以一个全局数组my_buff[7]为例,来分析:

    7.1环形缓冲区初始时:

    int R=0;             //记录读的位置
    int W=0;             //记录写的位置    

    上面的代码,如下图1所示:

     

     

    R:从数组[R]开始读数据

    W:从数组[W]开始写数据

    所以,当R==W时,则表示没有数据可读,通过这个逻辑便能写出读数据了

    7.2当我们需要读数据时:

    int read_buff(char  *p)              //p:指向要读出的地址
    {
    if(R==W)            
          return 0;       //读失败
    *p=my_buff[R];
    R=(R+1)%7;      //R++    
    return  1;         //读成功  
    }

    我们以W=3,R=0,为例,调用3次read_buff()函数,如下图所示:

     

     

    读数据完成,剩下就是写数据了,很显然每写一个数据,W则++

    7.3所以写数据函数为:

    void write_buff(char c)              //c:等于要写入的内容
    {
      my_buff [W]=c;         
      W=(W+1)%7;    //W++          
    if(W==R)
      R=(R+1)%7;      //R++
    }

    7.3.1 上面的代码,为什么要判断if((W==R)?

    比如,当我们写入一个8个数据,而my_buff[]只能存7个数据,必定会有W==R的时候,如果不加该判断,效果图如下所示:

     

     

    然后我们再多次调用read_buff(),就会发现只读的出第8个数据的值,而前面7个数据都会被遗弃掉

    7.3.2 而加入判断后,效果图如下所示:

     

     

    然后我们再多次调用read_buff(),就可以读出my_buff [2]~ my_buff [0]共6个数据出来

    总结:

    由于read_buff()后,R都会+1,所以每次 cat /proc/kmsg , 都会清空上次的打印信息。

    8.环形缓冲区分析完毕后,我们就可以直接来写一个驱动,模仿/proc/kmsg文件来看看

    流程如下:

    • 1)定义全局数组my_buff[1000]环形缓冲区,R标志,W标志,然后提供写函数,读函数
    • 2)自制一个myprintk(),通过传入的数据来放入到my_buff[]环形缓冲区中
    • (PS:需要通过EXPORT_SYMBOL(myprintk)声明该myprintk,否则不能被其它驱动程序调用 )
    • 3)写入口函数
    •    ->3.1) 通过create_proc_entry()创建/proc/mykmsg文件
    •    ->3.2 )并向mykmsg文件里添加file_operations结构体
    • 4)写出口函数
    •    ->4.1) 通过remove_proc_entry()卸载/proc/mykmsg文件
    • 5)file_operations->read()函数
    •    ->5.1) 仿照/proc/kmsg的read()函数,来读my_buff[]环形缓冲区的数据

    具体代码如下所示:

    #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 my_buff_len   1000          //环形缓冲区长度
    
    static struct proc_dir_entry *my_entry;
    
    
    /*    声明等待队列类型中断 mybuff_wait      */
    static DECLARE_WAIT_QUEUE_HEAD(mybuff_wait);
    
    
    static char my_buff[my_buff_len]; 
    unsigned long R=0;                      //记录读的位置
    unsigned long W=0;                    //记录写的位置
     
    int read_buff(char  *p)         //p:指向要读出的地址
    {
       if(R==W)          
                 return 0;               //读失败
            *p=my_buff[R]; 
             R=(R+1)%my_buff_len;       //R++
            return  1;                   //读成功   
    }
    
    void write_buff(char c)          //c:等于要写入的内容
    {    
            my_buff [W]=c;       
            W=(W+1)%my_buff_len;     //W++
            if(W==R)
                R=(R+1)%my_buff_len;     //R++
           wake_up_interruptible(&mybuff_wait);     //唤醒队列,因为R != W 
    }
    
    /*打印到my_buff[]环形缓冲区中*/
    int myprintk(const char *fmt, ...)
    {
           va_list args;
           int i,len;
           static char temporary_buff[my_buff_len];        //临时缓冲区
           va_start(args, fmt);
           len=vsnprintf(temporary_buff, INT_MAX, fmt, args);
           va_end(args);
    
            /*将临时缓冲区放入环形缓冲区中*/
           for(i=0;i<len;i++)       
           {
                write_buff(temporary_buff[i]);
           }
           return len;
    }
    
    static int mykmsg_open(struct inode *inode, struct file *file)
    {
            return 0;
    }  
    
    static int mykmsg_read(struct file *file, char __user *buf,size_t count, loff_t *ppos)
    {
          int error = 0,i=0;
          char c;
    
            if((file->f_flags&O_NONBLOCK)&&(R==W))      //非阻塞情况下,且没有数据可读
                return  -EAGAIN;
          
    error
    = -EINVAL;
    if (!buf || !count ) goto out;
    error
    = wait_event_interruptible(mybuff_wait,(W!=R)); if (error) goto out;
    while (!error && (read_buff(&c)) && i < count) { error = __put_user(c,buf); //上传用户数据 buf ++; i++; } if (!error) error = i; out: return error; } const struct file_operations mykmsg_ops = { .read = mykmsg_read, .open = mykmsg_open, }; static int mykmsg_init(void) { my_entry = create_proc_entry("mykmsg", S_IRUSR, &proc_root); if (my_entry) my_entry->proc_fops = &mykmsg_ops; return 0; } static void mykmsg_exit(void) { remove_proc_entry("mykmsg", &proc_root); } module_init(mykmsg_init); module_exit(mykmsg_exit); EXPORT_SYMBOL(myprintk); MODULE_LICENSE("GPL");

    PS:当其它驱动向使用myprintk()打印函数,还需要在文件中声明,才行:

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

    且还需要先装载mykmsg驱动,再来装载要使用myprintk()的驱动,否则无法找到myprintk()函数

    9.测试运行

    如下图所示,挂载了mykmsg驱动,可以看到生成了一个/proc/mykmsg文件

     

    挂载/proc/mykmsg期间,其它驱动使用myprintk()函数,就会将信息打印在/proc/mykmsg文件中,如下图所示:

      

    和cat /proc/kmsg一样,每次cat 都会清上一次的打印数据

    10.若我们不想每次清,和dmesg命令一样, 每次都能打印出环形缓冲区的所有信息,该如何改mykmsg驱动?

    上次我们分析过了,每次调用read_buff()后,R都会+1。

    要想不清空上次的信息打印,还需要定义一个R_ current标志来代替R标志,这样每次cat结束后,R的位置保持不变。

    每次cat时,系统除了进入file_operations-> read(),还会进入file_operations-> open(),所以在open()里,使R_ current=R,然后在修改部分代码即可,

    10.1我们还是以一个全局数组my_buff[7]为例, 如下图所示:

    10.2所以,修改的代码如下所示:

    #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 my_buff_len 1000 //环形缓冲区长度 static struct proc_dir_entry *my_entry; /* 声明等待队列类型中断 mybuff_wait */ static DECLARE_WAIT_QUEUE_HEAD(mybuff_wait); static char my_buff[my_buff_len]; unsigned long R=0; //记录读的位置 unsigned long R_current=0; //记录cat期间 读的位置 unsigned long W=0; //记录写的位置 int read_buff(char *p) //p:指向要读出的地址 { if(R_current==W) return 0; //读失败 *p=my_buff[R_current]; R_current=(R_current+1)%my_buff_len; //R_current++ return 1; //读成功 } void write_buff(char c) //c:等于要写入的内容 { my_buff [W]=c; W=(W+1)%my_buff_len; //W++ if(W==R) R=(R+1)%my_buff_len; //R++ if(W==R_current) R=(R+1)%my_buff_len; //R_current++ wake_up_interruptible(&mybuff_wait); //唤醒队列,因为R !=W } /*打印到my_buff[]环形缓冲区中*/ int myprintk(const char *fmt, ...) { va_list args; int i,len; static char temporary_buff[my_buff_len]; //临时缓冲区 va_start(args, fmt); len=vsnprintf(temporary_buff, INT_MAX, fmt, args); va_end(args); /*将临时缓冲区放入环形缓冲区中*/ for(i=0;i<len;i++) { write_buff(temporary_buff[i]); } return len; } static int mykmsg_open(struct inode *inode, struct file *file) { R_current=R; return 0; } static int mykmsg_read(struct file *file, char __user *buf,size_t count, loff_t *ppos) { int error = 0,i=0; char c; if((file->f_flags&O_NONBLOCK)&&(R_current==W)) //非阻塞情况下,且没有数据可读 return -EAGAIN; error = -EINVAL; if (!buf || !count ) goto out; error = wait_event_interruptible(mybuff_wait,(W!=R_current)); if (error) goto out; while (!error && (read_buff(&c)) && i < count) { error = __put_user(c,buf); //上传用户数据 buf ++; i++; } if (!error) error = i; out: return error; } const struct file_operations mykmsg_ops = { .read = mykmsg_read, .open = mykmsg_open, }; static int mykmsg_init(void) { my_entry = create_proc_entry("mykmsg", S_IRUSR, &proc_root); if (my_entry) my_entry->proc_fops = &mykmsg_ops; return 0; } static void mykmsg_exit(void) { remove_proc_entry("mykmsg", &proc_root); } module_init(mykmsg_init); module_exit(mykmsg_exit); EXPORT_SYMBOL(myprintk); MODULE_LICENSE("GPL");

    11.测试运行

     

     

     

     

  • 相关阅读:
    输入汉字转拼音
    DBGridEh(RestoreGridLayoutIni)用法
    当选中节点的同时选中父节点
    implsments
    HTML中的post和get
    SmartUpload中文乱码
    调查平台,考试系统类型的数据收集型项目
    final
    职业生涯中12个最致命的想法
    abstract
  • 原文地址:https://www.cnblogs.com/lifexy/p/8000515.html
Copyright © 2020-2023  润新知