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",找到位于fs\proc\proc_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: 等于,表示拥有者(usr)可读,其他任何人不能进行任何操作,如下图所示:

该参数和chmod命令参数一样,除了S_IRUSR还有很多参数,比如:
S_IRWXU: 等于, 表示拥有者(usr)可读(r)可写(w)可执行(x)
S_IRWXG: 等于, 表示拥有者和组用户 (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(, NULL, ))
return -EAGAIN;
return do_syslog(, buf, count); //开始读数据,buf:用户层地址,count:要读的数据长度
}
5.proc_kmsg_operations-> kmsg_read()->do_syslog(9, NULL, 0)的内容如下所示:

其中log_start和log_end就是环形缓冲区的两个标志, log_start也可以称为读标志位, log_end也可以称为写标志位,当写标志和读标志一致时,则表示没有读的数据了。
6.proc_kmsg_operations-> kmsg_read()->do_syslog(2, buf, count)的内容如下所示:
case : /* Read from log */
error = -EINVAL;
if (!buf || len < ) //判断用户层是否为空,以及读数据长度
goto out;
error = ;
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 = ;
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=; //记录读的位置
int W=; //记录写的位置
上面的代码,如下图1所示:

R:从数组[R]开始读数据
W:从数组[W]开始写数据
所以,当R==W时,则表示没有数据可读,通过这个逻辑便能写出读数据了
7.2当我们需要读数据时:
int read_buff(char *p) //p:指向要读出的地址
{
if(R==W)
return ; //读失败
*p=my_buff[R];
R=(R+)%; //R++
return ; //读成功
}
我们以W=3,R=0,为例,调用3次read_buff()函数,如下图所示:

读数据完成,剩下就是写数据了,很显然每写一个数据,W则++
7.3所以写数据函数为:
void write_buff(char c) //c:等于要写入的内容
{
my_buff [W]=c;
W=(W+)%; //W++
if(W==R)
R=(R+)%; //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=; //记录读的位置
unsigned long W=; //记录写的位置 int read_buff(char *p) //p:指向要读出的地址
{
if(R==W)
return ; //读失败
*p=my_buff[R];
R=(R+)%my_buff_len; //R++
return ; //读成功
} void write_buff(char c) //c:等于要写入的内容
{
my_buff [W]=c;
W=(W+)%my_buff_len; //W++
if(W==R)
R=(R+)%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=;i<len;i++)
{
write_buff(temporary_buff[i]);
}
return len;
} static int mykmsg_open(struct inode *inode, struct file *file)
{
return ;
} static int mykmsg_read(struct file *file, char __user *buf,size_t count, loff_t *ppos)
{
int error = ,i=;
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 ;
}
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=; //记录读的位置
unsigned long R_current=; //记录cat期间 读的位置
unsigned long W=; //记录写的位置 int read_buff(char *p) //p:指向要读出的地址
{
if(R_current==W)
return ; //读失败
*p=my_buff[R_current];
R_current=(R_current+)%my_buff_len; //R_current++
return ; //读成功
} void write_buff(char c) //c:等于要写入的内容
{
my_buff [W]=c;
W=(W+)%my_buff_len; //W++
if(W==R)
R=(R+)%my_buff_len; //R++
if(W==R_current)
R=(R+)%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=;i<len;i++)
{
write_buff(temporary_buff[i]);
}
return len;
} static int mykmsg_open(struct inode *inode, struct file *file)
{
R_current=R;
return ;
} static int mykmsg_read(struct file *file, char __user *buf,size_t count, loff_t *ppos)
{
int error = ,i=;
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 ;
}
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.测试运行

35.Linux-分析并制作环形缓冲区的更多相关文章
- linux device driver —— 环形缓冲区的实现
还是没有接触到怎么控制硬件,但是在书里看到了一个挺巧妙的环形缓冲区实现. 此环形缓冲区实际为一个大小为bufsize的一维数组,有一个rp的读指针,一个wp的写指针. 在数据满时写进程会等待读进程读取 ...
- linux网络编程--Circular Buffer(Ring Buffer) 环形缓冲区的设计与实现【转】
转自:https://blog.csdn.net/yusiguyuan/article/details/18368095 1. 应用场景 网络编程中有这样一种场景:需要应用程序代码一边从TCP/IP协 ...
- 环形缓冲区-模仿linux kfifo【转】
转自:https://blog.csdn.net/vertor11/article/details/53741681 struct kfifo{ uint8_t *buffer; uint32_t i ...
- linux下C语言实现多线程通信—环形缓冲区,可用于生产者(producer)/消费者(consumer)【转】
转自:http://blog.chinaunix.net/uid-28458801-id-4262445.html 操作系统:ubuntu10.04 前言: 在嵌入式开发中,只要是带操作系统的 ...
- Linux 最小系统制作
Linux 最小系统制作 一.制作工具Busybox 在制作文件系统的时候,我们需要使用“Busybox 工具”,即为附件压缩包“busybox-1.21.1.tar.bz2”.“BusyBox 工具 ...
- input子系统事件处理层(evdev)的环形缓冲区【转】
在事件处理层(evdev.c)中结构体evdev_client定义了一个环形缓冲区(circular buffer),其原理是用数组的方式实现了一个先进先出的循环队列(circular queue), ...
- STM32进阶之串口环形缓冲区实现(转载)
转载自微信公众号“玩转单片机”,感谢原作者“杰杰”. 队列的概念 在此之前,我们来回顾一下队列的基本概念:队列 (Queue):是一种先进先出(First In First Out ,简称 FIFO) ...
- 编译linux kernel及制作initrd ( by quqi99 )
编译linux kernel及制作initrd ( by quqi99 ) 作者:张华 发表于:2013-01-27 ( http://blog.csdn.net/quqi99 ) 运行一个l ...
- linux分析利刃之sar命令详解
一.sar的概述 在我使用的众多linux分析工具中,sar是一个非常全面的一个分析工具,可以比较瑞士军刀,对文件的读写,系统调用的使用情况,磁盘IO,CPU相关使用情况,内存使用情况,进程活动等都可 ...
随机推荐
- Java并发编程之显式锁机制
我们之前介绍过synchronized关键字实现程序的原子性操作,它的内部也是一种加锁和解锁机制,是一种声明式的编程方式,我们只需要对方法或者代码块进行声明,Java内部帮我们在调用方法之前和结束时加 ...
- 在Owin Self-Hosing下实现每个请求中共享上下文(数据)
问题 这几天在做公司的外部WebApi网关,由于使用了OAuth2.0,所以不得不使用Owin来部署网关. 而涉及到请求上下文的问题,为了使业务层能获取到请求头的信息,又不与网关耦合,决定把请求信息写 ...
- LeetCode 18. 4Sum (四数之和)
Given an array S of n integers, are there elements a, b, c, and d in S such that a + b + c + d = tar ...
- ABAP开发实用快捷键
在程序中注释代码往往受输入法影响,看了别人的一篇博客,结合自己的测试发现用如下方法可以直接注释源代码不受输入法影响 添加注释:ctrl + space + < 去掉注释:ctrl + space ...
- rewrite写法
RewriteRule ^/android-special-(\d+).html$ /special/index.php?c=index&a=specialDetail&speid=$ ...
- Catch him
Problem Description 在美式足球中,四分卫负责指挥整只球队的进攻战术和跑位,以及给接球员传球的任务.四分卫是一只球队进攻组最重要的球员,而且一般身体都相对比较弱小,所以通常球队会安排 ...
- javascript中new操作符
当代码var p= new Person("tom")执行时,其实内部做了如下几件事情: 1.创建一个空白对象(new Object()). 2.拷贝Person.prototyp ...
- js 添加事件 attachEvent 和 addEventListener 的区别
1.addEventListener 适用w3c标准方法addEventListener绑定事件,如下,事件的执行顺序和绑定顺序一致,执行顺序为method1->method2->meth ...
- 图片格式 WebP APNG
WebP 是一种支持有损压缩和无损压缩的图片文件格式,派生自图像编码格式 VP8.根据 Google 的测试,无损压缩后的 WebP 比 PNG 文件少了 45% 的文件大小,即使这些 PNG 文件 ...
- luogu P1563 玩具谜题
https://www.luogu.org/problemnew/show/1563 题目: 小南有一套可爱的玩具小人, 它们各有不同的职业. 有一天, 这些玩具小人把小南的眼镜藏了起来. 小南发现玩 ...