1. 基本概念
异步通知的意思是: 一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号驱动的异步I/O”。信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
阻塞I/O意味着一直等待设备可访问后再访问,非阻塞I/O中使用poll()意味着查询设备是否可访问,而异步通知则意味着设备通知用户自身可访问,之后用户再进行I/O处理。由此可见,这几种I/O方式可以相互补充。
2. 使用异步通知
使用信号进行进程间通信(IPC)是UNIX中的一种传统机制,Linux也支持这种机制。在Linux中,异步通知使用信号来实现, Linux中可用的信号及其定义如下:
除了SIGSTOP和SIGKILL两个信号外,进程能够忽略或捕获其他的全部信号。一个信号被捕获的意思是当一个信号到达时有相应的代码处理它,如果一个信号没有被这个进程所捕获,内核将采用默认行为处理。
2.1 信号的接收
在用户程序中, 为了捕获信号, 可以使用signal() 函数来设置对应信号的处理函数:
void (*signal(int signum, void (*handler))(int)))(int);
第一个参数指定信号的值,第二个参数指定针对前面信号值的处理函数,若为SIG_IGN,表示忽略该信号;若为SIG_DFL,表示采用系统默认方式处理信号;若为用户自定义的函数,则信号被捕获到后,该函数将被执行。如果signal() 调用成功,它返回最后一次为信号signum绑定的处理函数的handler值,失败则返回SIG_ERR。
信号接收部分完全在应用程序中,完整应用程序fasync_text.c代码如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
/* fasync_text
*/
void my_signal_fun(int signum)
{
printf("received signal SIGIO!\n");
}
int main(int argc, char **argv)
{
int Oflags;
int fd;
signal(SIGIO, my_signal_fun);
fd = open("/dev/test", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/test !\n");
}
fcntl(fd, F_SETOWN, getpid()); //定义该驱动要发送信号给哪个进程(PID)
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC);
while (1)
{
sleep(1000);
}
return 0;
}
fcntl(fd, F_SETOWN, getpid());
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC);
这3行代码的作用是为了能在用户空间中处理一个设备释放的信号, 分别的作用是:
(1) 通过F_SETOWN IO控制命令设置设备文件的拥有者为本进程, 这样从设备驱动发出的信号才能被本进程接收到。
(2) 通过F_SETFL IO控制命令设置设备文件以支持FASYNC, 即异步通知模式。
(3) 通过signal( ) 函数连接信号和信号处理函数。
这样,当应用程序接收到SIGIO这个信号时,就会调用my_signal_fun()函数了,就可以实现设备驱动通知应用程序去执行某些工作了。
2.2 信号的释放
应用程序端负责捕获信号,则设备驱动负责释放信号,需要在设备驱动程序中增加信号释放的相关代码。
为了使设备支持异步通知机制, 驱动程序中涉及3项工作:
(1) 支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID。不过此项工作已由内核完成,设备驱动无须处理。
(2) 支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。因此,驱动中应该实现fasync() 函数。
(3) 在设备资源可获得时,调用kill_fasync()函数激发相应的信号。
上述的3项工作和应用程序中的3项是对应的,他们之间的关系,如下图:
完整驱动程序fasync_drv.c代码如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/cdev.h>
int major;
static struct cdev test_cdev;
static struct class *test_class;
struct timer_list timer; //定义一个定时器
static struct fasync_struct *test_async;
/*每隔5秒发送一次SIGIO信号*/
void test_function(unsigned long arg)
{
mod_timer(&timer, jiffies + (5*HZ)); //重新设置定时器,每隔5秒执行一次
printk("send signal SIGIO to user!\n");
kill_fasync(&test_async, SIGIO, POLL_IN);
}
static int test_open(struct inode *inode, struct file *file)
{
init_timer(&timer); //初始化定时器
timer.expires = jiffies+(5*HZ); //设定超时时间,5秒
timer.data = 5; //传递给定时器超时函数的值
timer.function = test_function; //设置定时器超时函数
add_timer(&timer); //添加定时器,定时器开始生效
return 0;
}
static int test_fasync(int fd, struct file * file, int on)
{
printk("driver: test_fasync\n");
return fasync_helper(fd, file, on, &test_async);
}
int test_close(struct inode *inode, struct file *file)
{
del_timer(&timer); //删除定时器
test_fasync(-1, file, 0); //将文件从异步通知列表中删除
return 0;
}
static struct file_operations test_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = test_open,
.fasync = test_fasync,
.release = test_close,
};
static int test_init(void)
{
int result;
dev_t devid = MKDEV(major, 0); //从主设备号major,次设备号0得到dev_t类型
if (major)
{
result=register_chrdev_region(devid, 1, "test"); //注册字符设备
}
else
{
result=alloc_chrdev_region(&devid, 0, 1, "test"); //注册字符设备
major = MAJOR(devid); //从dev_t类型得到主设备
}
if(result<0)
return result;
cdev_init(&test_cdev, &test_fops);
cdev_add(&test_cdev, devid, 1);
test_class = class_create(THIS_MODULE, "test_drv");
class_device_create(test_class, NULL, MKDEV(major, 0), NULL, "test"); /* /dev/buttons */
return 0;
}
static void test_exit(void)
{
class_device_destroy(test_class, MKDEV(major, 0));
class_destroy(test_class);
cdev_del(&test_cdev);
unregister_chrdev_region(MKDEV(major, 0), 1);
}
module_init(test_init);
module_exit(test_exit);
MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");
Makefile如下:
KERN_DIR = /work/system/linux-2.6.22.6 //内核目录
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += fasync_drv.o
3. 测试
内核:linux-2.6.22.6
编译器:arm-linux-gcc-3.4.5
环境:ubuntu9.10
将fasync_drv.c、fasync_test.c、Makefile三个文件放入网络文件系统内,在ubuntu该目录下执行:
make
arm-linux-gcc -o fasync_test fasync_test.c
在挂载了网络文件系统的开发板上进入相同目录,执行“ls”查看:
装载驱动:
insmod fasync_drv.ko
运行测试程序:
./fasync_test
结果如下:
每隔5秒都会打印:
send signal SIGIO to user!
received signal SIGIO!
4. 程序说明
应用程序open打开驱动,驱动里就会创建一个5秒循环启动的定时器,然后应用程序设置好异步通知模式后,直接进入while死循环里。驱动程序每5秒启动定时器函数发送SIGIO信号,打印send signal SIGIO to user!,应用程序就会接受到信号并打印received signal SIGIO!。