在前面的I/O模型中,用户态程序都是主动获取设备的资源信息,而异步通知是当设备资源可用时,有驱动主动通知用户态程序,在由用户态程序发起读写操作。这种机制和中断非常相似,可以借用中断的思路来理解这一过程。
用户态程序
如上图,用户态程序的一般步骤是:
(1)注册信号处理函数
(2)设置设备文件的属主。为啥要设置属主呢?因为我们知道linux系统中,进程有单独的用户态空间,但所有的进程共享内核态空间,若不设置属主,驱动就不知道向哪个进程发送信号,所以这一步的目的是是使驱动根据文件的file结构,找到对应的进程,从而向该进程发送信号。
(3)设置当设备资源可用时设备驱动向进程发出的信号,这一步不是必须的,但如果要使用sigaction的高级特性,这步就需要。
(4)设置信号驱动标记,即一步通过fcntl的F_SETFL完成,设置FASYNC标记,这才真正启用异步通知机制,相当于中断使能。
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#define FILE_NAME "/dev/mydev"
#define MAX_LEN 64
int fd;
void myhandler(int arg)
{
int rlen;
char rbuf[MAX_LEN] = {0};
rlen = read(fd, rbuf, 10);
if (rlen < 0)
{
perror("Read error");
return;
}
printf("Read buf is %s\n", rbuf);
}
int main(void)
{
int ret,wlen,rlen,flag;
char wbuf[MAX_LEN] = "abcdefg";
char rbuf[MAX_LEN] = {0};
int i = 0;
//注意以非阻塞方式打开文件
fd = open(FILE_NAME, O_RDWR);
if (0 > fd)
{
printf("Open failed.\n");
return -1;
}
signal(SIGIO, myhandler);
ret = fcntl(fd, F_SETOWN, getpid());
if (0 > ret)
{
perror("Set owner");
return ret;
}
flag = fcntl(fd, F_GETFL);
if (0 > flag)
{
perror("Get flag");
return ret;
}
ret = fcntl(fd, F_SETFL, flag | FASYNC);
if (0 > ret)
{
perror("Set flag");
return ret;
}
wlen = write(fd, wbuf, strlen(wbuf));
if (wlen < 0)
{
perror("Write error");
return wlen;
}
printf("Write buf is %s\n", wbuf);
while (1)
{
}
close(fd);
return 0;
}
驱动程序
进程设置异步通知标记时,要调用驱动的fasync方法,以便把这个变化通知驱动程序,使其能正确响应。文件被打开时,FASYNC标记默认是被清除的,我们要手动设置。
struct file_operations {
int (*fasync) (int, struct file *, int);
}
第一个参数是文件描述符。
第二个参数是文件指针。
第三个参数是添加或者删除标记。
内核帮我们封装了一个函数fasync_helper函数,这个函数会构造fasync_struct节点,加入并假如到它的头中,函数原型如下:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
fasync的3个参数透传给fasync_helper函数,fasync_helper最后一个参数fasync_struct的二级指针,这里只需要传入fasync_struct的指针就行。
当驱动准备好资源时,就执行kill_fasync函数通知用户态程序异步读取数据。
void kill_fasync(struct fasync_struct **fp, int sig, int band)
第一个参数是和传入fasync_helper的同一个参数。
第二个参数是信号ID,比如SIGIO。
第三个参数是读出或者写入的标记,如POLL_IN、POLL_OUT。
最后,当文件关闭时,需要显示调用驱动实现的fasync接口函数,将节点从链表中移除,这样进程就不会再收到信号。
关键程序如下:
//异步结构定义
struct fasync_struct *my_fapp;
static int my_fasync(int fd, struct file *flip, int mode);
static int my_close(struct inode *pnode, struct file *pfile)
{
printk("Close cdev.\n");
my_fasync(-1, pfile, 0);
return 0;
}
//异步初始化
static int my_fasync(int fd, struct file *flip, int mode)
{
return fasync_helper(fd, flip, mode, &my_fapp);
}
ssize_t my_read(struct file *pf, char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
if (MAX_LEN < len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
//判断设备缓存是否为空
if (0 == kbuf_len)
{
//如果是非阻塞操作,返回-EAGAIN,用户态轮询
if (pf->f_flags& O_NONBLOCK)
return -EAGAIN;
else
{
//如果是阻塞操作,等待
if (wait_event_interruptible(my_read_queue, 0!= kbuf_len))
return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
}
}
ret = copy_to_user(ubuf, kbuf, len);
if (0 != ret)
{
printk("Copy to user failed.\n");
return -1;
}
//读出后,把对应的设备数据清空,保留未读出数据
memcpy(kbuf, kbuf + len, MAX_LEN - len);
memset(kbuf + MAX_LEN - len, 0, len);
//计算设备缓存长度
if (kbuf_len > len)
kbuf_len -= len;
else
kbuf_len = 0;
//当缓存长度不满时,唤醒写队列
if (MAX_LEN != kbuf_len)
{
wake_up_interruptible(&my_write_queue);
//发送可写通知
kill_fasync(&my_fapp, SIGIO, POLL_OUT);
}
return len;
}
ssize_t my_write(struct file *pf, const char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
//判断是否会写超过
if (MAX_LEN < len + kbuf_len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
if (MAX_LEN == kbuf_len)
{
//如果是非阻塞操作,返回-EAGAIN,用户态轮询
if (pf->f_flags& O_NONBLOCK)
return -EAGAIN;
else
{
//如果是阻塞操作,等待
if (wait_event_interruptible(my_write_queue, MAX_LEN != kbuf_len))
return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
}
}
ret = copy_from_user(kbuf + kbuf_len, ubuf, len);
if (0 != ret)
{
printk("Copy from user failed.\n");
return -1;
}
kbuf_len += len;
//当缓存长度不为空时,唤醒读队列
if (0 != kbuf_len)
{
wake_up_interruptible(&my_read_queue);
//发送可读通知
kill_fasync(&my_fapp, SIGIO, POLL_IN);
}
return len;
}
struct file_operations cdev_ops = {
.open = my_open,
.release = my_close,
.read = my_read,
.write = my_write,
.poll = my_poll,
.fasync = my_fasync,
};
完整驱动程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/wait.h>
#include <linux/poll.h>
#define MAJOR_CHAR 100
#define MINOR_CHAR 0
#define MAX_LEN 64
//异步结构定义
struct fasync_struct *my_fapp;
static int my_fasync(int fd, struct file *flip, int mode);
static int my_open(struct inode *pnode, struct file *pfile)
{
printk("Open cdev.\n");
return 0;
}
static int my_close(struct inode *pnode, struct file *pfile)
{
printk("Close cdev.\n");
my_fasync(-1, pfile, 0);
return 0;
}
static char kbuf[MAX_LEN] = {0};
static int kbuf_len = 0;
DECLARE_WAIT_QUEUE_HEAD(my_read_queue);
DECLARE_WAIT_QUEUE_HEAD(my_write_queue);
ssize_t my_read(struct file *pf, char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
if (MAX_LEN < len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
//判断设备缓存是否为空
if (0 == kbuf_len)
{
//如果是非阻塞操作,返回-EAGAIN,用户态轮询
if (pf->f_flags& O_NONBLOCK)
return -EAGAIN;
else
{
//如果是阻塞操作,等待
if (wait_event_interruptible(my_read_queue, 0!= kbuf_len))
return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
}
}
ret = copy_to_user(ubuf, kbuf, len);
if (0 != ret)
{
printk("Copy to user failed.\n");
return -1;
}
//读出后,把对应的设备数据清空,保留未读出数据
memcpy(kbuf, kbuf + len, MAX_LEN - len);
memset(kbuf + MAX_LEN - len, 0, len);
//计算设备缓存长度
if (kbuf_len > len)
kbuf_len -= len;
else
kbuf_len = 0;
//当缓存长度不满时,唤醒写队列
if (MAX_LEN != kbuf_len)
{
wake_up_interruptible(&my_write_queue);
//发送可写通知
kill_fasync(&my_fapp, SIGIO, POLL_OUT);
}
return len;
}
ssize_t my_write(struct file *pf, const char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
//判断是否会写超过
if (MAX_LEN < len + kbuf_len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
if (MAX_LEN == kbuf_len)
{
//如果是非阻塞操作,返回-EAGAIN,用户态轮询
if (pf->f_flags& O_NONBLOCK)
return -EAGAIN;
else
{
//如果是阻塞操作,等待
if (wait_event_interruptible(my_write_queue, MAX_LEN != kbuf_len))
return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
}
}
ret = copy_from_user(kbuf + kbuf_len, ubuf, len);
if (0 != ret)
{
printk("Copy from user failed.\n");
return -1;
}
kbuf_len += len;
//当缓存长度不为空时,唤醒读队列
if (0 != kbuf_len)
{
wake_up_interruptible(&my_read_queue);
//发送可读通知
kill_fasync(&my_fapp, SIGIO, POLL_IN);
}
return len;
}
static unsigned int my_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(filp, &my_write_queue, wait);//加写等待队列头
poll_wait(filp, &my_read_queue, wait);//加读等待队列头
if (0 != kbuf_len)//可读
{
mask |= POLLIN | POLLRDNORM; /*标示数据可获得*/
}
if (MAX_LEN != kbuf_len)//可写
{
mask |= POLLOUT | POLLWRNORM; /*标示数据可写入*/
}
return mask;
}
//异步初始化
static int my_fasync(int fd, struct file *flip, int mode)
{
return fasync_helper(fd, flip, mode, &my_fapp);
}
struct cdev cdevice;
struct file_operations cdev_ops = {
.open = my_open,
.release = my_close,
.read = my_read,
.write = my_write,
.poll = my_poll,
.fasync = my_fasync,
};
//加载
static int hello_init(void)
{
dev_t devno = MKDEV(MAJOR_CHAR,MINOR_CHAR);
int ret = -1;
printk(KERN_ALERT "Hello World.\n");
//up kernel
//1、注册设备号
ret = register_chrdev_region(devno, 1, "hello");
if (0 != ret)
{
printk("Register char device failed.\n");
return ret;
}
//2、初始化字符设备结构体
cdev_init(&cdevice, &cdev_ops);
cdevice.owner = THIS_MODULE;
//3、添加字符设备结构体给内核
ret = cdev_add(&cdevice,devno , 1);
if (0 != ret)
{
//注意释放设备号
unregister_chrdev_region(devno,1);
printk("Unregister char device.\n");
return ret;
}
printk("Register char device success.\n");
//down hardware
return 0;
}
//卸载函数(必须)
static void hello_exit(void)//返回值是void类型,函数名自定义,参数是void
{
dev_t devno = MKDEV(MAJOR_CHAR, MINOR_CHAR);
printk(KERN_ALERT "Goodbye World.\n");
// down hardware
// up kernel
//1、从内核中删除字符设备结构体
cdev_del(&cdevice);
//2、注销设备号
unregister_chrdev_region(devno, 1);
}
//注册(必须)
module_init(hello_init);
module_exit(hello_exit);
//license(必须)
MODULE_LICENSE("GPL");
//作者与描述(可选)
MODULE_AUTHOR("Ono Zhang");
MODULE_DESCRIPTION("A simple Hello World Module");
运行结果
$ sudo mknod /dev/mydev c 100 0
$ sudo insmod cdev.ko
$ sudo ./a.out
Write buf is abcdefg
Read buf is abcdefg
tobecontinue
每周三、周六更新