一、基本概念
1、步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是
“信号驱动的异步 I/O”。
2、信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过
任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
3、阻塞 I/O 意味着一直等待设备可访问后再访问, 非阻塞 I/O 中使用 poll()意味着查询设备是否可访问,而异步通知则意味着设备通知自身可访问,实现了异步 I/O。
由此可见,这几种方式 I/O 可以互为补充。
下面这张图对着几个概念描述的很清楚
二、重点代码分析
异步通知编程在底层需要实现一个相应的方法,如下:
static unsigned int mem_pool_fasync(int fd,struct file *filp, int mode)
{
/*获得设备结构体指针*/
struct mem_pool_dev *dev = filp->private_data;
printk("[1]dev->async_queue = %x\n",dev->async_queue);
return fasync_helper(fd,filp,mode,&dev->async_queue);
}
就像我们看到的那样,确实,看起来,这个方法里面好像什么都没有做,所有工作似乎都是由fasync_helper这个函数完成,但是,但是如果咩有驱动程序
中提供的这个方法,那么可想而知,fasync_helper函数也不可能完成这个功能的,因为辅助函数需要访问正确的dev->async_queue结构体,在我编程实验
的时候,就遇到了一个问题:
我们都知道,内核中结构体指针初始化都需要为其分配内存,在内核模块加载函数中,我们都能够找到为设备结构体分配内存的函数语句,但是要知道,该
dev->async_queue结构体在我们设备结构体中也是一个结构体指针,所以如果,没有其他的情况的话,我们也需要为其分配内存,但是一直让我好奇地是
为什么,我们并没有为其分配内存,但是系统依然不会挂掉,最开始的时候,我也是一直想不明白,甚至于,执着的硬性给他分配了内存,由于对指针处理
还不是很熟悉,中途调试,l虚拟机挂了几次,很明显是内核出现指针访问出错的问题。经过代码跟踪,终于发现,原来是在fasync_helper这个函数中会给
dev->async_queue结构体分配地址的,所以立马有种拨开云雾见青天的感觉。
三、示例代码分析
/*======================================================================
A mem_pool driver as an example of char device drivers
======================================================================*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include "mem_pool.h"
static mem_pool_major = mem_pool_MAJOR;
/*设备结构体指针*/
struct mem_pool_dev *mem_pool_devp;
/*文件打开函数*/
int mem_pool_open(struct inode *inode, struct file *filp)
{
/*将设备结构体指针赋值给文件私有数据指针*/
filp->private_data = mem_pool_devp;
return 0;
}
static unsigned int mem_pool_fasync(int fd,struct file *filp, int mode)
{
/*获得设备结构体指针*/
struct mem_pool_dev *dev = filp->private_data;
printk("[1]dev->async_queue = %x\n",dev->async_queue);
return fasync_helper(fd,filp,mode,&dev->async_queue);
}
/*读函数*/
static ssize_t mem_pool_read(struct file *filp, char __user *buf, size_t size,
loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
/*1. 获得设备结构体指针*/
struct mem_pool_dev *dev = filp->private_data;
/*2. 分析和获取有效的写长度*/
if (p >= mem_pool_SIZE)
return count ? - ENXIO: 0;
if (count > mem_pool_SIZE - p)
count = mem_pool_SIZE - p;
printk("dev->cur_size[%d] \n",dev->cur_size);
/*
if(!wait_event_interruptible(mem_pool_devp->test_queue, dev->cur_size)) //进入睡眠
{
printk("<1>""Sleeping ... ...");
}
*/
/*3. 内核空间->用户空间*/
if (copy_to_user(buf, (void*)(dev->mem + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk("<1>" "read %d bytes(s) from %d\n", count, p);
}
dev->cur_size -= ret;
return ret;
}
/*写函数*/
static ssize_t mem_pool_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
/*1. 获得设备结构体指针*/
struct mem_pool_dev *dev = filp->private_data;
/*2. 分析和获取有效的写长度*/
if (p >= mem_pool_SIZE)
return count ? - ENXIO: 0;
if (count > mem_pool_SIZE - p)
count = mem_pool_SIZE - p;
/*3. 用户空间->内核空间*/
if (copy_from_user(dev->mem + p, buf, count))
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk("<1>" "written %d bytes(s) from %d\n", count, p);
}
/* 当设备可读的时候,驱动发送信号给内核 */
if(dev->async_queue)
{
printk("<1>""writing ...\n");
printk("<1>""[2]dev->async_queue = %x \n",dev->async_queue);
kill_fasync(&dev->async_queue,SIGIO,POLLIN);
}
return ret;
}
/*文件释放函数*/
int mem_pool_release(struct inode *inode, struct file *filp)
{
mem_pool_fasync(-1,filp,0); //文件关闭的时候,将该结构体从队列中删除
return 0;
}
/*文件操作结构体*/
static const struct file_operations mem_pool_fops =
{
.owner = THIS_MODULE,
.read = mem_pool_read,
.write = mem_pool_write,
.open = mem_pool_open,
.release = mem_pool_release,
.fasync = mem_pool_fasync,
};
/*初始化并注册cdev*/
static void mem_pool_setup_cdev(struct mem_pool_dev *dev, dev_t devno)
{
int err;
/*1. 初始化cdev,绑定设备和文件操作函数*/
cdev_init(&dev->cdev, &mem_pool_fops);
dev->cdev.owner = THIS_MODULE;
/*2. 为文件操作提供具体实现方法*/
dev->cdev.ops = &mem_pool_fops;
/*3. 添加该cdev至内核*/
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk("<1>" "Error %d adding %d", err, devno);
}
/*设备驱动模块加载函数*/
int mem_pool_init(void)
{
int result;
dev_t devno = MKDEV(mem_pool_major, 0);
printk("<1>" "mem_pool_init !\n");
/*1. 申请设备号*/
if (mem_pool_major)
result = register_chrdev_region(devno, 1, "mem_pool");
else
{
/*2. 动态申请设备号 */
result = alloc_chrdev_region(&devno, 0, 1, "mem_pool");
mem_pool_major = MAJOR(devno);
}
if (result < 0)
return result;
/*3. 动态申请设备结构体的内存*/
mem_pool_devp = kmalloc(sizeof(struct mem_pool_dev), GFP_KERNEL);
/*4. 申请失败*/
if (!mem_pool_devp)
{
result = - ENOMEM;
goto fail_malloc;
}
/*5. 内存初始化*/
memset(mem_pool_devp, 0, sizeof(struct mem_pool_dev));
printk("<1>""mem_pool_devp = %x\n",mem_pool_devp);
printk("<1>""[0]mem_pool_devp->async_queue = %x\n",mem_pool_devp->async_queue);
/*6. 注册初始化设备*/
mem_pool_setup_cdev(mem_pool_devp, devno);
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return result;
}
/*模块卸载函数*/
void mem_pool_exit(void)
{
printk("<1>" "mem_pool_exit !\n");
/*1. 注销cdev*/
cdev_del(&mem_pool_devp->cdev);
/*2. 释放设备结构体内存*/
kfree(mem_pool_devp);
/*3. 释放设备号*/
unregister_chrdev_region(MKDEV(mem_pool_major, 0), 1);
}
MODULE_AUTHOR("shopping");
MODULE_LICENSE("Dual BSD/GPL");
module_param(mem_pool_major, int, S_IRUGO);
module_init(mem_pool_init);
module_exit(mem_pool_exit);
简单介绍在内核驱动中的相关流程
1)实现mem_pool_fasync方法,上述已经提到
2)当设备可读的时候,驱动发送信号给内核
if(dev->async_queue)
{
printk("<1>""writing ...\n");
printk("<1>""[2]dev->async_queue = %x \n",dev->async_queue);
kill_fasync(&dev->async_queue,SIGIO,POLLIN);
}
这里就实现了内核的通知机制
2、在应用程序中
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
int flag = 0;
void signal_handler(int m)
{
printf("hello,world ! \n");
flag = 1;
}
int main()
{
int fd = 0;
fd_set rds;
int fd_num = 0;
char buf[4096];
int f_flag;
/*1. 打开设备文件*/
fd = open("/dev/mem_pool",O_RDWR);
if (fd < 0)
{
printf("Open Dev mem_pool Error!\n");
return -1;
}
signal(SIGIO,signal_handler);
fcntl(fd,F_SETOWN,getpid());
f_flag = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,f_flag | FASYNC);
while(1)
{
printf("waiting to write ... \n");
sleep(3);
if(flag)
break;
}
/*2. 读设备文件 */
if(read(fd,buf,sizeof(buf)) < 0 )
{
printf("read error ... \n");
}
printf("read buf is %s \n",buf);
/*3. 关闭文件*/
close(fd);
return 0;
}
简单梳理下流程
1)打开设备文件,获取文件符
2)通过signal(SIGIO,signal_handler);实现SIGIO信号和signal_handler处理函数绑定
3)通过文件符控制函数fcntl(fd,F_SETOWN,getpid());实现将当前进程号设置为该文件符号的属主,意思是让内核知道将SIGIO发给当前进程,当前进程收到SIGIO后,
将会执行signal_handler函数,在(2)指出了。
4)
f_flag = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,f_flag | FASYNC);
这上面两句,将会启动异步通知机制,也就是说,标志位一旦被设定,那么就会执行底层的mem_fasync驱动函数。