字符设备驱动第八课----异步通知(信号驱动IO)
概述
类比运用程序中的kill-----signal,在运用程序中常常一个进程用kill(pid,sig)向另一
进程发信号,另一个进程用signal(sig,handler)绑定相应的处理函数,实现了异步通知。
今天要讲的就是:运用程序要读,但它并不知道啥时候有东西可读,用read()
阻塞去读显然效率不高,read()配合IO多路复用非阻塞一直在那里轮询的话效率也不好。
这里采用的办法是:驱动层有数据可读的时候kill一个SIGIO信号给运用层,
运用层收到SIGIO信号后调用预先绑定好的处理函数把数据读走。
若还迷糊概念,请看看这位大神的清晰讲解:
信号驱动IO与异步通知
<include/linux/fs.h>
struct file_operations {
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync); //用于异步通知
...
}
<include/linux/fs.h>
struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* singly linked list */
struct file *fa_file;
struct rcu_head fa_rcu;
};
/*
* 功能: 得到异步通知结构。根据mod,将异步通知结构体加入链表
* 运用程序端调用这些函数改变标志和owner时就调用了这个底层实现。
* fcntl(STDIN_FILENO,F_SETOWN,getpid());
* oflags = fcntl(STDIN_FILENO,F_GETFL);
* fctcl(STDIN_FILENO,F_SETFL,oflags | FASYNC);
* 输入参数: fd: 文件描述符
* filp: file结构体指针
* 输出参数:fapp:得到的异步通知结构体
*/
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
/*
* 功能:通过异步通知结构发信号,此信号发出时运用端就会收到SGIO信号,就会回调预先绑定的处理函数。
* 参数:struct fasync_struct **fp: 异步通知结构
* int signo: 信号(SIGIO)
* int events: 事件:POLLIN、POLLOUT
*/
void kill_fasync(struct fasync_struct **fp, int sig, int band)
范例:
1.驱动端:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <asm/current.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#include <asm/atomic.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/device.h>
static struct class *cls = NULL;
static int major = 0;
static int minor = 0;
const int count = 6;
#define DEVNAME "demo"
static struct cdev *demop = NULL;
static atomic_t tv;
static wait_queue_head_t wq;
static struct fasync_struct *fasync = NULL;//定义异步通知结构体
#define KMAX 1024
static char kbuf[KMAX];
static int counter = 0;
//打开设备
static int demo_open(struct inode *inode, struct file *filp)
{
//get major and minor from inode
printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
if(!atomic_dec_and_test(&tv)){
atomic_inc(&tv);
return -EBUSY;
}
memset(kbuf, 0, KMAX);
counter = 0;
return 0;
}
//关闭设备
static int demo_release(struct inode *inode, struct file *filp)
{
//get major and minor from inode
printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
atomic_inc(&tv);
return 0;
}
//读设备
//ssize_t read(int fd, void *buf, size_t count)
static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
int err = 0;
struct inode *inode = filp->f_path.dentry->d_inode; //获取文件的inod号
//get major and minor from inode
printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
if(!counter){
if(filp->f_flags & O_NONBLOCK){
return -EAGAIN;
}
err = wait_event_interruptible(wq, (0 != counter));//睡在条件上的等待队列
if(err){ //没东西可读,睡
return err;
}
}
if(counter < size){
size = counter;
}
if(copy_to_user(buf, kbuf, size)){
return -EAGAIN;
}
counter = 0;
return size;
}
//写设备
static ssize_t demo_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
struct inode *inode = filp->f_path.dentry->d_inode;
//get major and minor from inode
printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
if(size > KMAX){
return -ENOMEM;
}
if(copy_from_user(kbuf, buf, size)){
return -EAGAIN;
}
counter = size;
wake_up_interruptible(&wq);//广播唤醒等待队列
kill_fasync(&fasync, SIGIO, POLLIN);//向fasync结构体发信号,
//与fasync关联的进程(通过fcntl(...,pid)系列函数关联)就会收到SIGIO信号
return size;
}
/* IO多路复用支持*/
static unsigned int demo_poll(struct file *filp, struct poll_table_struct *pts)
{
unsigned int mask = 0;
struct inode *inode = filp->f_path.dentry->d_inode;
//get major and minor from inode
printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
poll_wait(filp, &wq, pts);//io多路复用支持,只有等待队列中有就绪的事件才会往下走,否则阻塞
if(counter){
mask = (POLLIN | POLLRDNORM);//返回,告诉运用层的poll函数:就绪事件是输入事件
}
return mask;
}
/*异步通知接口函数,应用层调fcntl()时调到此函数*/
static int demo_fasync(int fd, struct file *filp, int mode)
{
struct inode *inode = filp->f_path.dentry->d_inode;
//get major and minor from inode
printk(KERN_INFO "(major=%d, minor=%d), %s : %s : %d\n",
imajor(inode), iminor(inode), __FILE__, __func__, __LINE__);
return fasync_helper(fd, filp, mode, &fasync);//根据mod,将异步通知结构体加入链表
//或者从链表中移除。得到信息并填充到fasync结构体中
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = demo_open,
.release= demo_release,
.read = demo_read,
.write = demo_write,
.poll = demo_poll,
.fasync = demo_fasync,
};
static int __init demo_init(void)
{
dev_t devnum;
int ret, i;
struct device *devp = NULL;
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d\n",
current->comm, current->pid, __FILE__, __func__, __LINE__);
//1. alloc cdev obj
demop = cdev_alloc();
if(NULL == demop){
return -ENOMEM;
}
//2. init cdev obj
cdev_init(demop, &fops);
ret = alloc_chrdev_region(&devnum, minor, count, DEVNAME);
if(ret){
goto ERR_STEP;
}
major = MAJOR(devnum);
//3. register cdev obj
ret = cdev_add(demop, devnum, count);
if(ret){
goto ERR_STEP1;
}
cls = class_create(THIS_MODULE, DEVNAME);
if(IS_ERR(cls)){
ret = PTR_ERR(cls);
goto ERR_STEP1;
}
for(i = minor; i < (count+minor); i++){
devp = device_create(cls, NULL, MKDEV(major, i), NULL, "%s%d", DEVNAME, i);
if(IS_ERR(devp)){
ret = PTR_ERR(devp);
goto ERR_STEP2;
}
}
// init atomic_t
atomic_set(&tv, 1);
init_waitqueue_head(&wq);//初始化等待队列
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - ok.\n",
current->comm, current->pid, __FILE__, __func__, __LINE__);
return 0;
ERR_STEP2:
for(--i; i >= minor; i--){
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
ERR_STEP1:
unregister_chrdev_region(devnum, count);
ERR_STEP:
cdev_del(demop);
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - fail.\n",
current->comm, current->pid, __FILE__, __func__, __LINE__);
return ret;
}
static void __exit demo_exit(void)
{
int i;
//get command and pid
printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - leave.\n",
current->comm, current->pid, __FILE__, __func__, __LINE__);
for(i=minor; i < (count+minor); i++){
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
unregister_chrdev_region(MKDEV(major, minor), count);
cdev_del(demop);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Farsight");
MODULE_DESCRIPTION("Demo for kernel module");
2.运用程序端:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <errno.h>
int fd = -1;
void handler(int sig)//信号处理函数,与signal绑定的
{
struct pollfd pfd = {
.fd = fd,
.events = POLLIN,
};
int ret = poll(&pfd, 1, ~0);//监控pfd,最大文件描述符1,永不超时
if(0 >= ret){ //没一个就绪则阻塞,只要其中有任意一个就绪就往下走
perror("poll");
return;
}
#define MAX 1024
char buf[MAX];
memset(buf, 0, MAX);
if(0 > read(fd, buf, MAX)){
perror("read");
}else{
printf("RD: %s\n", buf);
}
}
int main(int num, char *argv[])
{
if(2 != num){
printf("Usage: %s /dev/devfile\n", argv[0]);
return -1;
}
fd = open(argv[1], O_RDWR|O_NONBLOCK);
if(0 > fd){
printf("pid = %d, %s\n", getpid(), (char *)strerror(errno));
return -1;
}
signal(SIGIO, handler);//绑定信号处理函数
fcntl(fd, F_SETOWN, getpid());//关联收发,设置对应文件的拥有者是本进程,这样接下来才能进行信号的收发
int flag = fcntl(fd, F_GETFL);//读取对应文件描述符上的flg信息
flag |= O_ASYNC;
fcntl(fd, F_SETFL, flag); //设置对应文件描述符上的flg信息,使其支持异步通知
//这个函数实质上最终调用的是操作方法集中的.fasync标准接口,对应到驱动层中的相应函数
while(1){
printf("---------w: 1----------\n");
#define MAX 1024
char buf[MAX];
fgets(buf, MAX, stdin);
write(fd, buf, strlen(buf));
}
close(fd);
return 0;
}