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
");
View Code
6、在tty中测试效果
# insmod my_msg.ko
# insmod first_drv.ko
# cat /proc/mymsg
mymsg_open mylog_r_tmp=0
first_drv_init
|
成功!!!
感谢韦东山老师!!!!