目录
title: 驱动调试(二)-环形缓冲区到文件
date: 2019/1/10 22:57:04
toc: true
---
printk
是将信息先保存到log_buf
,然后通过打印级别来选择是否输出.log_buf
存储在/proc/kmsg
中,该文件是包含了
打印级别的cat
去获取这个文件是读后清的,使用dmsg
是允许反复读的参考上述的描述,尝试达成如下目标
my_log_bug[]
,存储到文件/proc/mymsg
read
的接口供cat
使用,使用环形缓冲区保存,提供读后清和读后不清的版本my_printk
输出到my_log_bug
写入我们的/proc
实际上是一个虚拟的文件系统,我们使用mount
或者cat /proc/mount
来查看挂接了哪些
# mount
rootfs on / type rootfs (rw)
/dev/root on / type yaffs (rw)
proc on /proc type proc (rw)
sysfs on /sys type sysfs (rw)
tmpfs on /dev type tmpfs (rw)
devpts on /dev/pts type devpts (rw)
# cat /proc/mounts
rootfs / rootfs rw 0 0
/dev/root / yaffs rw 0 0
proc /proc proc rw 0 0
sysfs /sys sysfs rw 0 0
tmpfs /dev tmpfs rw 0 0
devpts /dev/pts devpts rw 0 0
这个文件系统是我们在脚本文件中指挂载的,mount -a
表示挂载所有/etc/fstab
的文件系统
# cat /etc/init.d/rcS
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
# cat /etc/fstab
#device mount-ponit type options dump fsck
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
我们在printk
中可以指定级别来输出打印,可以使用dmesg
来查看所有的信息log_buf
,这个命令实际是去读取文件/proc/kmsg
,可以直接使用cat
来读取这个信息
注意 这个文件只能cat
一次,然后就清空了,使用dmesg
可以多次查看的,使用cat
命令是能够看到打印级别的
# cat /proc/kmsg
]=PATH=/sbin:/bin:/usr/sbin:/usr/bin
<4>envp[2]=ACTION=add
<4>envp[3]=DEVPATH=/class/tty/ttyw9
<4>envp[4]=SUBSYSTEM=tty
搜索kmsg
,找到文件fs\proc\proc_misc.c
,接下来开始分析了,我们从入口函数开始分析proc_misc_init
创建一个文件kmsg
,父目录是proc_root
,创建成功则同时提供相应的读写操作
#ifdef CONFIG_PRINTK
{
struct proc_dir_entry *entry;
// 创建一个文件 kmsg ,父目录是 proc_root
entry = create_proc_entry("kmsg", S_IRUSR, &proc_root);
//创建成功则同时提供相应的读写操作
if (entry)
entry->proc_fops = &proc_kmsg_operations;
}
#endif
const struct file_operations proc_kmsg_operatiOns= {
.read = kmsg_read,
.poll = kmsg_poll,
.open = kmsg_open,
.release = kmsg_release,
};
参见程序1,创建mymsg目录
static ssize_t kmsg_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
if ((file->f_flags & O_NONBLOCK) && !do_syslog(9, NULL, 0))
return -EAGAIN;
return do_syslog(2, buf, count);
}
// 非阻塞方式判断是否是空
do_syslog(9, NULL, 0))
case 9: /* Number of chars in the log buffer */
error = log_end - log_start;
break;
//阻塞方式,进入休眠唤醒了
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)) {
error = -EFAULT;
goto out;
}
//这里判断数据是否为空,wait_event_interruptible 中第二个参数为0是睡眠
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
仿照着写一个驱动,产生一个 my_msg 的文件
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct proc_dir_entry *my_entry;
const struct file_operations proc_mymsg_operations;
static int hello_init(void)
{
my_entry = create_proc_entry("mymsg", S_IRUSR, &proc_root);
if (my_entry)
my_entry->proc_fops = &proc_mymsg_operations;
return 0;
}
static void hello_exit(void)
{
remove_proc_entry("mymsg",&proc_root);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
测试下,确实生成了文件,无法cat是因为没有提供读写函数
# insmod mymsg.ko
# ls /proc/mymsg -l
-r-------- 1 0 0 0 Jan 5 04:38 /proc/mymsg
# cat /proc/mymsg
cat: read error: Invalid argument
我们提供下读函数,避免cat
报错
ssize_t *mymsg_read (struct file * myfile , char __user * myuser , size_t len , loff_t * myloff )
{
printk("print by mymsg\n");
return 0; //这里如果不return0 ,就一直打印了
}
const struct file_operations proc_mymsg_operatiOns=
{
.read=mymsg_read,
};
测试如下
# insmod mymsg.ko
# cat /proc/mymsg
print by mymsg
这里提供一个全局数组,复制到用户态
struct proc_dir_entry *my_entry;
static char mylog_buf[1024];
ssize_t *mymsg_read (struct file * myfile , char __user * myuser , size_t len , loff_t * myloff )
{
//printk("print by mymsg\n");
copy_to_user(myuser,mylog_buf,10);
return 10;
}
static int hello_init(void)
{
sprintf(mylog_buf,"this is a log buf\n");
...
}
测试后发现一直打印,这是引文read函数一直有返回,应该是cat后不断去read的原因
# cat /proc/mymsg
this is a this is a this is a this is a
this is a this is a this is a this is a
环形缓冲区就是有头尾指针的一个数组,这里有一个巧妙的判断是否为满的方法
写的位置+1====读的位置,则是满
具体的函数如下
static int is_mylog_empty(void)
{
return (mylog_r == mylog_w);
}
static int is_mylog_full(void)
{
return ((mylog_w + 1)% MYLOG_BUF_LEN == mylog_r);
}
static void mylog_putc(char c)
{
if (is_mylog_full())
{
/* 丢弃一个数据 */
mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN;
}
mylog_buf[mylog_w] = c;
mylog_w = (mylog_w + 1) % MYLOG_BUF_LEN;
}
static int mylog_getc(char *p)
{
if (is_mylog_empty())
{
return 0;
}
*p = mylog_buf[mylog_r];
mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN;
return 1;
}
接下来使用唤醒队列来处理,也就是当读取的时候如果没有数据,则睡眠,写数据的时候触发休眠的队列
static void mylog_putc(char c)
{
写操作
...
/* 唤醒等待数据的进程 */
wake_up_interruptible(&mymsg_waitq); /* 唤醒休眠的进程 */
}
接着根据原有的.read=kmsg_read
函数模仿写一个
static ssize_t mymsg_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
int error = 0;
int i = 0;
char c;
// 非阻塞方式读取,没有数据的时候直接返回
if ((file->f_flags & O_NONBLOCK) && is_mylog_empty())
return -EAGAIN;
//阻塞方式 如果为空则睡眠
error = wait_event_interruptible(mymsg_waitq, !is_mylog_empty());
// 唤醒后,也就是有数据,读取数据复制到用户态
while (!error && (mylog_getc(&c)) && i
创建一个printf函数,参考printk
中将缓存赋值中使用了
printed_len = vscnprintf(printk_buf, sizeof(printk_buf), fmt, args);
int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
{
int i;
i=vsnprintf(buf,size,fmt,args);
return (i >= size) ? (size - 1) : i;
}
或者看下
int sprintf(char * buf, const char *fmt, ...)
{
va_list args;
int i;
va_start(args, fmt);
i=vsprintf(buf,fmt,args);
va_end(args);
return i;
}
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
提供myprintk
供其他驱动程序调用写入缓冲
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
extern int myprintk(const char *fmt, ...);
EXPORT_SYMBOL(myprintk);
static DECLARE_WAIT_QUEUE_HEAD(mymsg_waitq);
struct proc_dir_entry *my_entry;
#define LEN_LOG 1024
static char mylog_buf[LEN_LOG];
static char tmp_buf[LEN_LOG];
static int pt_read=0,pt_write=0;
#define pt_add(pt) ((pt+1)%LEN_LOG)
// ret =1 means empty
int isEmpty(void)
{
return (pt_read == pt_write);
}
// ret =1 means full
int isFull(void)
{
return (pt_read == pt_add(pt_write));
}
//putchar
void myputc(char c)
{
if (isFull()) {
pt_read = pt_add(pt_read);
}
mylog_buf[pt_write]=c;
pt_write=pt_add(pt_write);
/* 唤醒等待数据的进程 */
wake_up_interruptible(&mymsg_waitq); /* 唤醒休眠的进程 */
}
//getchar
int mygetchar(char * p)
{
if (isEmpty()) {
return 0;
}
*p = mylog_buf[pt_read];
pt_read=pt_add(pt_read);
return 1;
}
//printf for user
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 f_flags & O_NONBLOCK) && isEmpty())
return -EAGAIN;
error = wait_event_interruptible(mymsg_waitq, !isEmpty());
/* copy_to_user */
while (!error && (mygetchar(&c)) && i proc_fops = &proc_mymsg_operations;
return 0;
}
static void hello_exit(void)
{
remove_proc_entry("mymsg",&proc_root);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
调用myprintk
在write
时写入缓冲
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static struct class *firstdrv_class;
static struct class_device *firstdrv_class_dev;
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);
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);
return 0;
}
static struct file_operations first_drv_fops = {
.owner = 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");
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv");
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");
调用open
打开测试驱动,使用write
以调用myprintk
写入缓冲
#include
#include
#include
#include
/* firstdrvtest on
* firstdrvtest off
*/
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/xyz", O_RDWR);
if (fd <0)
{
printf("can't open!\n");
}
if (argc != 2)
{
printf("Usage :\n");
printf("%s \n", argv[0]);
return 0;
}
if (strcmp(argv[1], "on") == 0)
{
val = 1;
}
else
{
val = 0;
}
write(fd, &val, 4);
return 0;
}
加载两个驱动
# insmod ../mymsg.ko
# insmod first_drv.ko
加载驱动程序
# ./test on
# ./test off
获取打印信息
# cat /proc/mymsg &
first_drv_init
first_drv_open : 1
first_drv_write : 1
first_drv_open : 2
first_drv_write : 2
在这里其实更应该理解成三个指针
头指针,指向数据有效区域头
尾指针,指向数据有效区的尾巴
读指针,当前读取的区域
修改的部分
判断空的函数,应该判断读指针是否到达尾指针
int isEmpty(void)
{
return (pt_now_read == pt_write);
}
读取函数,其中的读指针更改为这个新增的指针
//getchar
int mygetchar(char * p)
{
if (isEmpty()) {
return 0;
}
*p = mylog_buf[pt_now_read];
pt_now_read=pt_add(pt_now_read);
return 1;
}
写数据的时候,如果写入的数据一次性超过缓冲区的大小,比如 缓冲区比较小,一次写入大于缓冲
也就是比如当前是 start=3,end=2,now=2
,存入数据后依然是start=3,end=2,now=2
,这个时候需要手动调整now=start
//putchar
void myputc(char c)
{
if (isFull()) {
pt_read = pt_add(pt_read);
// 这里其实就是判断 当前读的指针在逻辑上必须大于有数据的 读的指针,也就是数据起始指针
if (pt_add(pt_now_read) == pt_read) {
#if(1)
pt_now_read=pt_read;
#endif
printk("<<<>>> \n");
}
}
mylog_buf[pt_write]=c;
pt_write=pt_add(pt_write);
printk("put in %d \n",pt_write);
/* 唤醒等待数据的进程 */
wake_up_interruptible(&mymsg_waitq); /* 唤醒休眠的进程 */
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
extern int myprintk(const char *fmt, ...);
EXPORT_SYMBOL(myprintk);
extern void get_pt(void);
EXPORT_SYMBOL(get_pt);
static DECLARE_WAIT_QUEUE_HEAD(mymsg_waitq);
struct proc_dir_entry *my_entry;
#define LEN_LOG 23
static char mylog_buf[LEN_LOG];
static char tmp_buf[LEN_LOG];
static int pt_read=0,pt_write=0;
static int pt_now_read=0;
//printf for user
void get_pt(void )
{
printk("<<>> \n", pt_read, pt_write, pt_now_read);
}
#define pt_add(pt) ((pt+1)%LEN_LOG)
// ret =1 means empty
int isEmpty(void)
{
return (pt_now_read == pt_write);
}
// ret =1 means full
int isFull(void)
{
return (pt_read == pt_add(pt_write));
}
//putchar
void myputc(char c)
{
if (isFull()) {
pt_read = pt_add(pt_read);
// 这里其实就是判断 当前读的指针在逻辑上必须大于有数据的 读的指针,也就是数据起始指针
if (pt_add(pt_now_read) == pt_read) {
#if(1)
pt_now_read=pt_read;
#endif
printk("<<<>>> \n");
}
}
mylog_buf[pt_write]=c;
pt_write=pt_add(pt_write);
printk("put in %d \n",pt_write);
/* 唤醒等待数据的进程 */
wake_up_interruptible(&mymsg_waitq); /* 唤醒休眠的进程 */
}
//getchar
int mygetchar(char * p)
{
if (isEmpty()) {
return 0;
}
*p = mylog_buf[pt_now_read];
pt_now_read=pt_add(pt_now_read);
return 1;
}
//printf for user
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 f_flags & O_NONBLOCK) && isEmpty())
return -EAGAIN;
error = wait_event_interruptible(mymsg_waitq, !isEmpty());
/* copy_to_user */
while (!error && (mygetchar(&c)) && i proc_fops = &proc_mymsg_operations;
return 0;
}
static void hello_exit(void)
{
remove_proc_entry("mymsg",&proc_root);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
更改下测试驱动,使得有方法显示当前的指针 调用get_pt
显示当前指针
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static struct class *firstdrv_class;
static struct class_device *firstdrv_class_dev;
extern int myprintk(const char *fmt, ...);
static int first_drv_open(struct inode *inode, struct file *file)
{
//static int cnt = 0;
//myprintk(">>Open>>%d\n", ++cnt);
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;
copy_from_user(&val,buf,count);
if (val==0) {
get_pt();
}
else
{
myprintk(">>1234567890123456Read>>%d\n", ++cnt);
}
return 0;
}
static struct file_operations first_drv_fops = {
.owner = 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");
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv");
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");
更改下应用程序使得有方法显示当前的指针./test show
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/xyz", O_RDWR);
if (fd <0)
{
printf("can't open!\n");
}
if (argc != 2)
{
printf("Usage :\n");
printf("%s \n", argv[0]);
return 0;
}
if (strcmp(argv[1], "on") == 0)
{
val = 1;
}
else
{
val = 0;
}
write(fd, &val, 4);
return 0;
}
shell mount -t nfs -o nolock,vers=2 192.168.95.222:/home/book/stu /mnt insmod ../mymsg.ko && insmod first_drv.ko && cat /proc/mymsg & rmmod first_drv && rmmod mymsg echo "7 1 4 7 "> /proc/sys/kernel/printk
运行测试程序
shell ./test on #写入缓冲区 ./test on1 # 显示当前的三个 头指针,尾指针,以及当前的读指针
测试错误的驱动,这里驱动(mymsg
)程序,我测试了两个版本,一个是写数据的时候不判断是否一次就写满缓冲,另外一个是判断写缓冲的,可以发现不判断写缓冲的,打印输出不对
# ./test show
<<>>
# ./test on
put in 1
put in 2
put in 3
put in 4
put in 5
put in 6
put in 7
put in 8
put in 9
put in 10
put in 11
put in 12
put in 13
put in 14
put in 15
put in 16
put in 17
put in 18
put in 19
put in 20
put in 21
put in 22
<<<>>>
put in 0
put in 1
put in 2
put in 3
>1 ########这里打印明显出错了,缓冲区已经改变了起始位置
# ./test show
<<>>
测试正确的驱动程序
# mount -t nfs -o nolock,vers=2 192.168.95.222:/home/book/stu /mnt
#
# cd /mnt/code/first_drv_myprintk/
# insmod ../mymsg.ko && insmod first_drv.ko && cat /proc/mymsg &
#
# echo "7 1 4 7 "> /proc/sys/kernel/printk
# ./test show
<<>>
# ./test on
put in 1
put in 2
put in 3
put in 4
put in 5
put in 6
put in 7
put in 8
put in 9
put in 10
put in 11
put in 12
put in 13
put in 14
put in 15
put in 16
put in 17
put in 18
put in 19
put in 20
put in 21
put in 22
<<<>>>
put in 0
<<<>>>
put in 1
<<<>>>
put in 2
<<<>>>
put in 3
# 34567890123456Read>>1 #############打印正确
# ./test show
<<>>