Linux设备驱动--异步通知
作者:互联网
注:本文是《Linux设备驱动开发详解:基于最新的Linux 4.0内核 by 宋宝华 》一书学习的笔记,大部分内容为书籍中的内容。
书籍可直接在微信读书中查看:Linux设备驱动开发详解:基于最新的Linux4.0内核-宋宝华-微信读书 (qq.com)
Linux设备驱动中的异步通知和异步I/O
在设备驱动中使用异步通知可以使得在对设备访问时,由驱动主动通知应用程序进行访问。
使用非阻塞I/O的应用程序不需要轮训设备是否可以访问,而阻塞访问可以被类似“中断”的异步通知所取代。
除了异步通知以外,应用还可以在发起I/O请求后,立即返回。之后,再查询I/O完成情况,或者I/O完成后被调回。这个过程叫作异步I/O。
1.1 异步通知简介
异步通知:一旦设备就绪,则主动通知应用程序,应用程序不需要查询设备状态,较为准确的称呼为“信号驱动的异步I/O”。
信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达。
阻塞I/O意味着一直等待设备可访问后再访问。非阻塞I/O使用poll()意味着查询设备是否可访问。
下图呈现了阻塞I/O,结合轮询的非阻塞I/O,以及基于SIGIO的异步通知在时间先后顺序上的不同。
1.2 Linux异步通知编程
1.2.1 Linux信号
Linux中异步通知使用信号来实现,Linux中可用的信号及其定义如下:
除了SIGSTOP和SIGKILL两个信号外,进程能够忽略或捕获其他的全部信号。一个信号被捕获的意思是当一个信号到达时有相应的代码处理它。如果一个信号没有被这个进程所捕获,内核将采用默认行为处理。
1.2.2 信号的接收
在用户程序中,捕获信号可以使用signal()函数来设置对应信号的处理函数,函数原型为:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数signum:指定信号的值
参数handler:指定针对信号值的处理函数,若为SIG_IGN,表示忽略该信号;若为SIG_DFL,表示采用系统默认方式处理信号;若为用户自定义的函数,则信号捕获后,该函数被执行。
返回值:如果调用成功,返回最后一次为信号signum绑定的处理函数的handler值,失败则返回SIG_ERR。
在进程执行时,按下Ctrl+C将向其发出SIGINT信号,正在运行kill的进程将向其发出SIGTERM信号,捕获这两个信号并输出信号值的代码如下:
void sigterm_handler(int signo)
{
printf("Have caught sig N.0.%d\n", signo);
exit(0);
}
int main(void)
{
signal(SIGINT, sigterm_handler);
signal(SIGTERM, sigterm_handler);
while(1);
return 0;
}
编译、测试:
$ ./a.out
^C^CHave caught sig N.0.2
sigaction()函数:可用于改变进程接收到特定信号后的行为,函数原型为:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参数signum:信号的值,可以为SIGKILL以及SIGSTOP外的任何一个特定有效的信号。
参数act:指向结构体sigaction的一个实例的指针,在结构体sigaction的实例中指定了对特定信号的处理函数,若为空,则进程会以缺省方式对信号处理。
参数oldact:指向的对象用来保存原来对相应信号的处理函数,可指定为NULL。
如果把第二、第三个参数设置为NULL,该函数可用于检查信号的有效性。
返回值:成功返回0,失败返回-1。
1.2.3 异步通知的应用程序实例
通过signal(SIGIO, input_handler)对标准输入文件描述符STDIN_FIFLNO启动信号机制。用户输入后,应用程序将接收到SIGIO信号,其处理函数input_handler()将被调用。
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#define MAX_LEN 100
void input_handler(int num)
{
char data[MAX_LEN];
int len;
/* 读取并输出STDIN_FIFLNO */
len = read(STDIN_FILENO, &data, MAX_LEN);
data[len] = 0;
printf("input data:%s\n", data);
}
int main()
{
int oflags;
/* 启动信号驱动机制 */
signal(SIGIO, input_handler); //SIGIO信号安装input_handler()作为处理函数
fcntl(STDIN_FILENO, F_SETOWN, getpid()); //设置本进程为STDIN_FIFENO文件的拥有者
oflags = fcntl(STDIN_FILENO, F_GETFL);
fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC); //设置FASYNC
/* 最后进入一个死循环,保持进程不终止 */
while(1);
}
编译、测试:
$ gcc sigio_handler.c
$ ./a.out
i am chinese.
input data:i am chinese.
i love linux driver
input data:i love linux driver
可以看出,用户输入一串字符之后,标准输入设备释放SIGIO信号,这个信号“中断”驱使应用程序中的input_handler()得以执行,并在用户输入显示出来。
因此,在用户空间中处理一个设备释放的信号,必须完成以下三项内容:
1)通过F_SETOWN I/O控制命令设置设备文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进程接收到。
2)通过F_SETFL I/O控制命令设备设备文件以支持FASYNC,即异步通知模式。
3)通过signal()函数连接信号和信号处理函数。
1.2.4 设备驱动中异步通知
在设备驱动和应用程序的交互中,信号的源头在设备驱动端,捕获在应用程序端。因此,信号的释放应该在设备驱动中进行。
设备支持异步通知机制,驱动程序需要支持以下三项工作:
1)支持F_SETOWN命令,能在这个控制命令中处理file->f_owner为对应进程的ID,这个已经由内核完成,设备驱动无须处理。
2)支持F_SETFL命令,每当FASYNC标志改变时,驱动程序中的fasync()函数得到执行,设备驱动中应该实现fasync()函数。
3)在设备资源可获得时,调用kill_fasync()函数激发对应的信号。
驱动中的三项工作和应用程序中的三项工作是一一对应的,设备驱动异步通知处理和用户空间交互过程如下:
设备驱动中异步编程的函数涉及一个数据结构fasync_struct和两个函数fasync_helper()和kill_fasync():
(1)处理FASYNC标志变更的函数
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp);
(2)释放信号函数
void kill_fasync(struct fasync_struct **fp, int sig, int band);
在设备资源可以获得时,应该调用kill_fasync()释放SIGIO信号。在可读时,第三个参数band设置为POLL_IN;在可写时,第三个参数band设置为POLL_OUT。
fasync_struct数据结构体指针一般放在设备结构体中,支持异步通知的设备结构体模板如下:
struct xxx_dev {
struct cdev cdev;
... ....;
struct fasync_struct *async_queue; /* 异步结构体指针 */
... ...;
};
设备驱动的faync()函数:
int (*fasync) (int, struct file *, int);
支持异步通知的设备驱动程序fasync()函数的模板:
static int xxx_fasync(int fd, struct file *filp, int mode)
{
struct xxx_dev *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}
支持异步通知的设备驱动信号释放模板:
在资源可获得时,调用kill_fasync()函数释放SIGIO信号。
static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
struct xxx_dev *dev = filp->private_data;
... ...;
/* 产生异步读信号 */
if (dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
}
最后在文件关闭时,在设备的release()函数中,调用设备驱动的fasync()函数将文件从异步通知的列表中删除。支持异步通知的设备驱动release()函数模板如下:
static int xxx_release(struct inode *inode, struct file *filp)
{
/* 将文件从异步通知的列表中删除 */
xxx_fasync(-1, filp, 0);
... ...;
return 0;
}
1.3 支持异步通知的globalfifo驱动
1.3.1 设备驱动globalfifo的改写
首先,需要将异步数据结构体指针添加到globalfifo_dev设备结构体中:
/* 设备结构体 */
struct globalfifo_dev {
struct cdev cdev;
unsigned int current_len; /* 当前FIFO中有效数据的长度 */
unsigned char mem[GLOBALFIFO_SIZE];
struct mutex mutex;
wait_queue_head_t r_wait;
wait_queue_head_t w_wait;
struct fasync_struct *async_queue; /* 异步结构体指针 */
};
然后,编写异步通知的globalfifo设备驱动的fasync()函数:
static int globalfifo_fasync(int fd, struct file *filp, int mode)
{
struct globalfifo_dev *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}
然后,改写globalfifo设备驱动的写函数,在globalfifo设备被正确写入之后,可以进行读取,需要支持释放SIGIO信号,通过给应用程序捕获。
/**
* 写设备
* @param[in] filp:文件结构体指针
* @param[in] buf: 用户空间内存地址,不能在内核中直接读写
* @param[in] size: 写入的字节数
* @param[in/out] ppos: 写的位置相当于文件头的偏移
* @return 若成功返回实际写的字节数,若出错返回错误码
*/
static ssize_t globalfifo_write(struct file *filp,
const char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
struct globalfifo_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait, current);
mutex_lock(&dev->mutex);
add_wait_queue(&dev->w_wait, &wait);
while (dev->current_len == GLOBALFIFO_SIZE) {
if (filp->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&dev->mutex);
schedule();
if (signal_pending(current)) {
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}
if (count > GLOBALFIFO_SIZE - dev->current_len)
count = GLOBALFIFO_SIZE - dev->current_len;
/* 用户空间缓存区到内核空间缓存区的复制 */
if (copy_from_user(dev->mem + dev->current_len, buf, count)) {
ret = -EFAULT;
goto out;
} else {
dev->current_len += count;
printk(KERN_INFO "written %lu bytes(s) from %u\n", count, dev->current_len);
wake_up_interruptible(&dev->r_wait);
if (dev->async_queue) {
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
printk(KERN_INFO "%s kill SIGIO\n", __func__);
}
ret = count;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->w_wait, &wait);
set_current_state(TASK_RUNNING);
return ret;
}
globalfifo设备驱动的release()函数需要调用globalfifo_fasync()函数将文件从异步通知列表中删除:
static int globalfifo_release(struct inode *inode, struct file *filp)
{
/* 将文件从异步通知的列表中删除 */
globalfifo_fasync(-1, filp, 0);
return 0;
}
完整的设备驱动代码:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
/* 直接使用立即数当作命令不合理,暂定 */
#define MEM_CLEAR 0x1
#define GLOBALFIFO_MAJOR 230
#define GLOBALFIFO_SIZE 0x1000
static int globalfifo_major = GLOBALFIFO_MAJOR;
module_param(globalfifo_major, int, S_IRUGO);
/* 设备结构体 */
struct globalfifo_dev {
struct cdev cdev;
unsigned int current_len; /* 当前FIFO中有效数据的长度 */
unsigned char mem[GLOBALFIFO_SIZE];
struct mutex mutex;
wait_queue_head_t r_wait;
wait_queue_head_t w_wait;
struct fasync_struct *async_queue; /* 异步结构体指针 */
};
struct globalfifo_dev *globalfifo_devp;
static int globalfifo_fasync(int fd, struct file *filp, int mode)
{
struct globalfifo_dev *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}
static int globalfifo_open(struct inode *inode, struct file *filp)
{
/* 使用文件的私有数据作为获取globalfifo_dev的实例指针 */
filp->private_data = globalfifo_devp;
return 0;
}
static int globalfifo_release(struct inode *inode, struct file *filp)
{
/* 将文件从异步通知的列表中删除 */
globalfifo_fasync(-1, filp, 0);
return 0;
}
/**
* 设备ioctl函数
* @param[in] filp:文件结构体指针
* @param[in] cmd: 命令,当前仅支持MEM_CLEAR
* @param[in] arg: 命令参数
* @return 若成功返回0,若出错返回错误码
*/
static long globalfifo_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct globalfifo_dev *dev = filp->private_data;
switch (cmd) {
case MEM_CLEAR:
mutex_lock(&dev->mutex);
dev->current_len = 0;
memset(dev->mem, 0, GLOBALFIFO_SIZE);
mutex_unlock(&dev->mutex);
printk(KERN_INFO "globalfifo is set to zero\n");
break;
default:
return -EINVAL;
}
return 0;
}
/**
* 查询对一个或多个文件描述符的读或写是否会阻塞
* @param[in] filp:文件结构体指针
* @param[in] wait: 轮询表指针
* @return 返回位掩码指示是否非阻塞的读或写是可能的
*/
static unsigned int globalfifo_poll(struct file *filp,
struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct globalfifo_dev *dev = filp->private_data;
mutex_lock(&dev->mutex);
/* 调用select而阻塞的进程可以被r_wait和w_wait唤醒 */
poll_wait(filp, &dev->r_wait, wait);
poll_wait(filp, &dev->w_wait, wait);
if (dev->current_len != 0) {
/* 设备可以无阻塞的读,正常数据可用来读 */
mask |= POLLIN | POLLRDNORM;
}
if (dev->current_len != GLOBALFIFO_SIZE) {
/* 设备可以无阻塞的写 */
mask |= POLLOUT | POLLWRNORM;
}
mutex_unlock(&dev->mutex);
return mask;
}
/**
* 读设备
* @param[in] filp:文件结构体指针
* @param[out] buf: 用户空间内存地址,不能在内核中直接读写
* @param[in] size: 读取的字节数
* @param[in/out] ppos: 读的位置相当于文件头的偏移
* @return 若成功返回实际读的字节数,若出错返回错误码
*/
static ssize_t globalfifo_read(struct file *filp,
char __user *buf, size_t size, loff_t *ppos)
{
int ret = 0;
unsigned long count = size;
struct globalfifo_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait, current);
mutex_lock(&dev->mutex);
add_wait_queue(&dev->r_wait, &wait);
while (dev->current_len == 0) {
if (filp->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&dev->mutex);
schedule();
if (signal_pending(current)) {
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}
if (count > dev->current_len)
count = dev->current_len;
/* 内核空间到用户空间缓存区的复制 */
if (copy_to_user(buf, dev->mem, count)) {
ret = -EFAULT;
goto out;
} else {
memcpy(dev->mem, dev->mem + count, dev->current_len - count);
dev->current_len -= count;
printk(KERN_INFO "read %lu bytes(s) from %u\n", count, dev->current_len);
wake_up_interruptible(&dev->w_wait);
ret = count;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->r_wait, &wait);
set_current_state(TASK_RUNNING);
return ret;
}
/**
* 写设备
* @param[in] filp:文件结构体指针
* @param[in] buf: 用户空间内存地址,不能在内核中直接读写
* @param[in] size: 写入的字节数
* @param[in/out] ppos: 写的位置相当于文件头的偏移
* @return 若成功返回实际写的字节数,若出错返回错误码
*/
static ssize_t globalfifo_write(struct file *filp,
const char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
struct globalfifo_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait, current);
mutex_lock(&dev->mutex);
add_wait_queue(&dev->w_wait, &wait);
while (dev->current_len == GLOBALFIFO_SIZE) {
if (filp->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&dev->mutex);
schedule();
if (signal_pending(current)) {
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}
if (count > GLOBALFIFO_SIZE - dev->current_len)
count = GLOBALFIFO_SIZE - dev->current_len;
/* 用户空间缓存区到内核空间缓存区的复制 */
if (copy_from_user(dev->mem + dev->current_len, buf, count)) {
ret = -EFAULT;
goto out;
} else {
dev->current_len += count;
printk(KERN_INFO "written %lu bytes(s) from %u\n", count, dev->current_len);
wake_up_interruptible(&dev->r_wait);
if (dev->async_queue) {
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
printk(KERN_INFO "%s kill SIGIO\n", __func__);
}
ret = count;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->w_wait, &wait);
set_current_state(TASK_RUNNING);
return ret;
}
/**
* 文件偏移设置
* @param[in] filp:文件结构体指针
* @param[in] offset: 偏移值大小
* @param[in] orig: 起始偏移位置
* @return 若成功返回文件当前位置,若出错返回错误码
*/
static loff_t globalfifo_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig) {
case 0: /* 从文件头位置设置偏移 */
if (offset < 0) {
ret = -EINVAL;
break;
}
if ((unsigned int)offset > GLOBALFIFO_SIZE) {
ret = -EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1: /* 从当前位置设置偏移 */
if ((filp->f_pos + offset) > GLOBALFIFO_SIZE) {
ret = -EINVAL;
break;
}
if ((filp->f_pos + offset) < 0) {
ret = -EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = -EINVAL;
break;;
}
return ret;
}
static const struct file_operations globalfifo_fops = {
.owner = THIS_MODULE,
.llseek = globalfifo_llseek,
.read = globalfifo_read,
.write = globalfifo_write,
.unlocked_ioctl = globalfifo_ioctl,
.open = globalfifo_open,
.release = globalfifo_release,
.poll = globalfifo_poll,
.fasync = globalfifo_fasync,
};
static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
{
int err, devno = MKDEV(globalfifo_major, index);
/* 初始化cdev */
cdev_init(&dev->cdev, &globalfifo_fops);
dev->cdev.owner = THIS_MODULE;
/* 注册设备 */
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding globalfifo%d", err, index);
}
/* 驱动模块加载函数 */
static int __init globalfifo_init(void)
{
int ret;
dev_t devno = MKDEV(globalfifo_major, 0);
/* 获取设备号 */
if (globalfifo_major)
ret = register_chrdev_region(devno, 1, "globalfifo");
else {
ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");
globalfifo_major = MAJOR(devno);
}
if (ret < 0)
return ret;
/* 申请内存 */
globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
if (!globalfifo_devp) {
ret = -ENOMEM;
goto fail_malloc;
}
globalfifo_setup_cdev(globalfifo_devp, 0);
mutex_init(&globalfifo_devp->mutex);
init_waitqueue_head(&globalfifo_devp->r_wait);
init_waitqueue_head(&globalfifo_devp->w_wait);
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return ret;
}
module_init(globalfifo_init);
/* 驱动模块卸载函数 */
static void __exit globalfifo_exit(void)
{
cdev_del(&globalfifo_devp->cdev);
kfree(globalfifo_devp);
/* 释放设备号 */
unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);
}
module_exit(globalfifo_exit);
MODULE_AUTHOR("MrLayfolk");
MODULE_LICENSE("GPL v2");
1.3.2 用户空间globalfifo驱动验证
用户空间应用程序实现在接收到globalfifo发出的信号后输出信号值。
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#define MAX_LEN 100
static void signalio_handler(int signum)
{
printf("receive a signal, signal number:%d\n", signum);
}
int main()
{
int fd, oflags;
/* 以非阻塞方式打开设备文件 */
fd = open("/dev/globalfifo", O_RDONLY | S_IRUSR | S_IWUSR);
if (fd != -1) {
/* 启动信号驱动机制 */
signal(SIGIO, signalio_handler); //SIGIO信号安装input_handler()作为处理函数
fcntl(fd, F_SETOWN, getpid()); //设置本进程为STDIN_FIFENO文件的拥有者
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC); //设置FASYNC
while (1) {
sleep(100);
}
} else {
printf("device open failure\n");
}
return 0;
}
1.3.3 编译测试
(1)编译设备驱动ko,并插入ko
$ make
$ insmod globalfifo.ko
(2)创建设备节点
$ mknod /dev/globalfifo c 230 0
(3)编译用户程序,并且运行
$ gcc globalfifo_app.c
$ ./a.out
(4)向设备驱动写入数据,signalio_handler()会被调用
$ echo "hello" > /dev/globalfifo
$ echo "hello" > /dev/globalfifo
$ echo "hello" > /dev/globalfifo
$ ./a.out
receive a signal, signal number:29
receive a signal, signal number:29
receive a signal, signal number:29
标签:异步,filp,struct,--,globalfifo,dev,int,Linux,wait 来源: https://www.cnblogs.com/mrlayfolk/p/15858989.html