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 异步通知简介
异步通知:一旦设备就绪,则主动通知应用程序,应用程序不需要查询设备状态,较为准确的称呼为“信号驱动的异步I/O”。
信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达。
阻塞I/O意味着一直等待设备可访问后再访问。
非阻塞I/O使用poll()意味着一直轮询查看设备是否可访问。
下图呈现了阻塞I/O,结合轮询的非阻塞I/O,以及基于SIGIO的异步通知在时间先后顺序上的不同。
2 Linux异步通知编程
异步通知的核心就是信号,在arch\arm\include\uapi\asm\signal.h中定义了Linux使用的所有信号。
2.1 Linux信号
Linux中异步通知使用信号来实现,Linux中可用的信号及其定义如下:
信号 | 值 | 含义 |
---|---|---|
SIGHUP | 1 | 终端挂起或控制进程终止 |
SIGINT | 2 | 终端中断(Ctrl+C组合键) |
SIGQUIT | 3 | 终端退出(Ctrl+\组合键) |
SIGILL | 4 | 非法指令 |
SIGTRAP | 5 | debug使用,有断点指令产生 |
SIGABRT | 6 | 由abort(3)发出的退出指令 |
SIGIOT | 6 | IOT指令 |
SIGBUS | 7 | 总线错误 |
SIGFPE | 8 | 浮点运算错误 |
SIGKILL | 9 | 杀死、终止进程 |
SIGUSR1 | 10 | 用户自定义信号1 |
SIGSEGV | 11 | 段违例(无效的内存段) |
SIGUSR2 | 12 | 用户自定义信号2 |
SIGPIPE | 13 | 向非读管道写入数据 |
SIGALRM | 14 | 闹钟 |
SIGTERM | 15 | 软件终止 |
SIGSTKFLT | 16 | 栈异常 |
SIGCHLD | 17 | 子进程结束 |
SIGCONT | 18 | 进程继续 |
SIGSTOP | 19 | 停止进程的执行,只是暂停 |
SIGTSTP | 20 | 停止进程的运行(Ctrl+Z组合键) |
SIGTTIN | 21 | 后台进程需要从终端读取数据 |
SIGTTOU | 22 | 后台进程需要向终端写数据 |
SIGURG | 23 | 有“紧急”数据 |
SIGXCPU | 24 | 超过CPU资源限制,超额使用CPU时间 |
SIGXFSZ | 25 | 文件大小超额 |
SIGVTALRM | 26 | 虚拟时钟信号 |
SIGPROF | 27 | 时钟信号描述 |
SIGWINCH | 28 | 窗口大小改变 |
SIGIO | 29 | 可以进行输入/输出操作 |
SIGPOLL | SIGIO | |
SIGPWR | 30 | 断电重启 |
SIGSYS | 31 | 非法的系统调用 |
SIGUNUSED | 31 | 未使用信号 |
除了SIGSTOP(19)和SIGKILL(9)两个信号外,进程能够忽略或捕获其他的全部信号。一个信号被捕获的意思是当一个信号到达时有相应的代码处理它。如果一个信号没有被这个进程所捕获,内核将采用默认行为处理。
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(15)信号,捕获这两个信号并输出信号值的代码如下:
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。
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控制命令设置设备文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进程接收到。
fcntl(fd, F_SETOWN, getpid());
2)通过F_SETFL I/O控制命令设备设备文件以支持FASYNC,即异步通知模式。
flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */
3)通过signal()函数连接信号和信号处理函数。
void xxx_signal_handler(int num)
{
... ...
}
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标志变更的函数
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;
};
/* 参数fd、filp、on通过fasync函数的三个参数传入;
* 参数fapp:要初始化的fasync_struct结构体指针变量。
*/
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; /* 异步结构体指针 */
... ...;
};
(3)设备驱动file_operations操作集中的fasync()函数
int (*fasync) (int fd, struct file *filp, int mode);
支持异步通知的设备驱动程序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);
}
在release函数中释放fasync_struct,同样调用fasync_helper实现:
static int xxx_release(struct inode *inode, struct file *filp)
{
return xxx_fasync(-1, filp, 0);
}
支持异步通知的设备驱动信号释放模板:
在资源可获得时,调用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;
}
3 支持异步通知的globalfifo驱动
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");
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;
}
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
4 按键中断使用信号
4.1 驱动程序
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#define KEY_DEVICE_CNT 1 /* 设备个数 */
#define KEY_NAME "key_noblock" /*设备名字*/
/* 定义键值 */
#define KEY0_VALUE 0x01 /* KEY0按键值 */
#define INVALID_KEY 0xFF /* 无效键值 */
#define KEY_NUM 1 /* 按键数量 */
/* 中断IO描述结构体 */
struct irq_key_desc {
int gpio; /* GPIO */
int irq_num; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name[10]; /* 名字 */
irqreturn_t(*handler)(int, void *); /* 中断处理函数 */
};
/* KEY设备结构体 */
struct chrdev_key {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
atomic_t key_value; /* 有效的按键值 */
atomic_t release_key; /* 标记一次完成的按键是否完成 */
struct timer_list timer; /* 定时器 */
struct irq_key_desc irq_key[KEY_NUM]; /* 按键描述 */
unsigned char cur_key_num; /* 当前的按键号 */
wait_queue_head_t r_wait; /* 读等待队列头 */
struct fasync_struct *async_queue; /* 异步相关结构体 */
};
struct chrdev_key chrdev_key;
/* 中断服务函数:开启定时器,延时10ms,定时器用于消抖
* 参数irq:中断号
* 参数dev_id:设备结构
* 返回值:中断处理结果
*/
irqreturn_t key0_irq_handler(int irq, void *dev_id)
{
struct chrdev_key *dev = (struct chrdev_key *)dev_id;
dev->cur_key_num = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 定时器服务函数:消除按键抖动,定时器到了后再次读取按键值,如果按键处于按下则表示按键有效
* arg:设备结构体变量
*/
void timer_func(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_key_desc *key_desc = NULL;
struct chrdev_key *dev = (struct chrdev_key *)arg;
num = dev->cur_key_num;
key_desc = &dev->irq_key[num];
value = gpio_get_value(key_desc->gpio);
if (value == 0) { /* 按下按键 */
atomic_set(&dev->key_value, key_desc->value);
} else { /* 按键松开 */
atomic_set(&dev->key_value, 0x80 | key_desc->value);
atomic_set(&dev->release_key, 1); /* 标记松开按键 */
}
/* 唤醒进程 */
if (atomic_read(&dev->release_key)) { /* 完成一次按键过程 */
if (dev->async_queue) {
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
}
}
}
/* 按键IO初始化 */
static int key_gpio_init(void)
{
unsigned char i = 0;
int ret = 0;
chrdev_key.nd = of_find_node_by_path("/key");
if (chrdev_key.nd == NULL) {
printk("Cannot find device node!\r\n");
return -EINVAL;
}
/* 提取GPIO */
for (i = 0; i < KEY_NUM; i++) {
chrdev_key.irq_key[i].gpio = of_get_named_gpio(chrdev_key.nd, "key-gpio", i);
if (chrdev_key.irq_key[i].gpio < 0) {
printk("Cannot get key%d!\r\n", i);
}
}
/* 初始化key使用的IO,并且设置为中断模式 */
for (i = 0; i < KEY_NUM; i++) {
memset(chrdev_key.irq_key[i].name, 0x0, sizeof(chrdev_key.irq_key[i].name));
sprintf(chrdev_key.irq_key[i].name, "KEY%d", i);
gpio_request(chrdev_key.irq_key[i].gpio, chrdev_key.irq_key[i].name);
gpio_direction_input(chrdev_key.irq_key[i].gpio); /* 设置为输入 */
chrdev_key.irq_key[i].irq_num = irq_of_parse_and_map(chrdev_key.nd, i);
printk("key%d: gpio:%d, irq_num:%d\r\n", i, chrdev_key.irq_key[i].gpio, chrdev_key.irq_key[i].irq_num);
}
/* 申请中断 */
chrdev_key.irq_key[0].handler = key0_irq_handler;
chrdev_key.irq_key[0].value = KEY0_VALUE;
for (i = 0; i < KEY_NUM; i++) {
ret = request_irq(chrdev_key.irq_key[i].irq_num, chrdev_key.irq_key[i].handler,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_SHARED,
chrdev_key.irq_key[i].name, &chrdev_key);
if (ret < 0) {
printk("key%d irq %d request failed!\r\n", i, chrdev_key.irq_key[i].irq_num);
return -EFAULT;
}
printk("key%d irq %d request successfully!\r\n", i, chrdev_key.irq_key[i].irq_num);
}
/* 创建定时器 */
init_timer(&chrdev_key.timer);
chrdev_key.timer.function = timer_func;
/* 初始化等待队列头 */
init_waitqueue_head(&chrdev_key.r_wait);
return 0;
}
static int key_open(struct inode *inode, struct file *filp)
{
filp->private_data = &chrdev_key;
printk("Key open successful!\r\n");
return 0;
}
/* 从设备读取数据
* 参数filp:要打开的设备文件(文件描述符)
* 参数buf:要返回给用户空间的数据缓冲区
* 参数cnt:要读取的数据长度
* 参数offt:相对于文件首地址的偏移
* 返回值:读取的字节数,如果为负值,表示读取失败。
*/
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char key_value = 0;
unsigned char release_key = 0;
struct chrdev_key *dev = filp->private_data;
if (filp->f_flags & O_NONBLOCK) { /* 非阻塞访问 */
if (atomic_read(&dev->release_key) == 0) { /* 没有按键按下 */
return -EAGAIN;
}
}
/* 加入等待队列,等待被唤醒,也就是有按键按下 */
ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->release_key));
if (ret) {
goto wait_error;
}
key_value = atomic_read(&dev->key_value);
release_key = atomic_read(&dev->release_key);
if (release_key) { /* key按下 */
if (key_value & 0x80) {
key_value &= ~0x80;
ret = copy_to_user(buf, &key_value, sizeof(key_value));
} else {
key_value = INVALID_KEY;
ret = copy_to_user(buf, &key_value, sizeof(key_value));
goto data_error;
}
/* 按下标记清0 */
atomic_set(&dev->release_key, 0x0);
} else {
goto data_error;
}
return 0;
wait_error:
return ret;
data_error:
return -EINVAL;
}
static unsigned int key_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct chrdev_key *dev = filp->private_data;
poll_wait(filp, &dev->r_wait, wait);
if (atomic_read(&dev->release_key)) { /* 按键按下 */
mask = POLLIN | POLLRDNORM; /* 返回PLLIN */
}
return mask;
}
static int key_fasync(int fd, struct file *filp, int on)
{
struct chrdev_key *dev = filp->private_data;
return fasync_helper(fd, filp, on, &dev->async_queue);
}
static int key_release(struct inode *inode, struct file *filp)
{
return key_fasync(-1, filp, 0);
}
/* 设备操作函数 */
static struct file_operations key_fops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.poll = key_poll,
.fasync = key_fasync,
.release = key_release,
};
static int __init chrdev_key_init(void)
{
/* 注册字符设备驱动 */
/* 创建设备号 */
if (chrdev_key.major) { /* 定义了设备号 */
chrdev_key.devid = MKDEV(chrdev_key.major, 0);
register_chrdev_region(chrdev_key.devid, KEY_DEVICE_CNT, KEY_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&chrdev_key.devid, 0, KEY_DEVICE_CNT, KEY_NAME);
chrdev_key.major = MAJOR(chrdev_key.devid);
chrdev_key.minor = MINOR(chrdev_key.devid);
}
/* 初始化cdev */
chrdev_key.cdev.owner = THIS_MODULE;
cdev_init(&chrdev_key.cdev, &key_fops);
/* 添加一个cdev */
cdev_add(&chrdev_key.cdev, chrdev_key.devid, KEY_DEVICE_CNT);
/* 创建类 */
chrdev_key.class = class_create(THIS_MODULE, KEY_NAME);
if (IS_ERR(chrdev_key.class)) {
return PTR_ERR(chrdev_key.class);
}
/* 创建设备 */
chrdev_key.device = device_create(chrdev_key.class, NULL, chrdev_key.devid, NULL, KEY_NAME);
if (IS_ERR(chrdev_key.device)) {
return PTR_ERR(chrdev_key.device);
}
/* 初始化按键 */
atomic_set(&chrdev_key.key_value, INVALID_KEY);
atomic_set(&chrdev_key.release_key, 0x0);
if (key_gpio_init() != 0) {
goto ERROR;
}
printk("Key device driver register successful!\r\n");
return 0;
ERROR:
/* 注销字符设备驱动 */
cdev_del(&chrdev_key.cdev);
unregister_chrdev_region(chrdev_key.devid, KEY_DEVICE_CNT);
device_destroy(chrdev_key.class, chrdev_key.devid);
class_destroy(chrdev_key.class);
return -EINVAL;
}
static void __exit chrdev_key_exit(void)
{
unsigned char i = 0;
/* 删除定时器 */
del_timer_sync(&chrdev_key.timer);
/* 释放中断 */
for (i = 0; i < KEY_NUM; i++) {
free_irq(chrdev_key.irq_key[i].irq_num, &chrdev_key);
}
/* 注销字符设备驱动 */
cdev_del(&chrdev_key.cdev);
unregister_chrdev_region(chrdev_key.devid, KEY_DEVICE_CNT);
device_destroy(chrdev_key.class, chrdev_key.devid);
class_destroy(chrdev_key.class);
printk("Key device driver unregister successful!\r\n");
}
module_init(chrdev_key_init);
module_exit(chrdev_key_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("MrLayfolk");
4.2 应用程序
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <linux/ioctl.h>
#include <poll.h>
#include <signal.h>
static int fd = 0;
static void sigio_signal_handler(int signum)
{
int err = 0;
unsigned int key_value = 0;
err = read(fd, &key_value, sizeof(key_value));
if (err < 0) {
printf("read file error! error code = %d!\r\n", err);
} else {
printf("sigio signal caught, key value = %d.\r\n", key_value);
}
}
int main(int argc, char *argv[])
{
int flags = 0;
char *file_name= NULL;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
file_name = argv[1];
/* open file */
fd = open(file_name, O_RDWR);
if (fd < 0) {
printf("file %s open failded!\r\n", file_name);
return -1;
}
/* 设置SIGIO处理函数 */
signal(SIGIO, sigio_signal_handler);
fcntl(fd, F_SETOWN, getpid()); /* 将当前进程的进程号告诉内核 */
flags = fcntl(fd, F_GETFD); /* 获取当前进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 设置进程启动异步通知功能 */
while (1) {
sleep(2);
}
close(fd);
return 0;
}
Linux设备驱动--异步通知的更多相关文章
- linux设备驱动归纳总结(三):7.异步通知fasync【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-62725.html linux设备驱动归纳总结(三):7.异步通知fasync xxxxxxxxxxx ...
- Linux设备驱动中的异步通知与异步I/O
异步通知概念: 异步通知的意识是,一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上的“中断”概念,比较准确的称谓是“信号驱动的异步IO”,信号是在软件层次 ...
- 蜕变成蝶~Linux设备驱动之异步通知和异步I/O
在设备驱动中使用异步通知可以使得对设备的访问可进行时,由驱动主动通知应用程序进行访问.因此,使用无阻塞I/O的应用程序无需轮询设备是否可访问,而阻塞访问也可以被类似“中断”的异步通知所取代.异步通知类 ...
- 【Linux开发】linux设备驱动归纳总结(三):7.异步通知fasync
linux设备驱动归纳总结(三):7.异步通知fasync xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- Linux的fasync驱动异步通知详解【转】
本文转载自:http://blog.csdn.net/coding__madman/article/details/51851338 版权声明:本文为博主原创文章,未经博主允许不得转载. 工作项目用有 ...
- Linux 进程间通信 --- 信号通信 --- signal --- signal(SIGINT, my_func); --- 按键驱动异步通知(转)
信号 ( signal ) 机制是 UNIX 系统中最为古老的进程间通信机制,很多条件可以产生一个信号. 信号的产生: 1,当用户按下某些按键时,产生信号. 2,硬件异常产生信号:除数为 0 ,无效 ...
- linux设备驱动归纳总结(十二):简单的数码相框【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-116926.html linux设备驱动归纳总结(十二):简单的数码相框 xxxxxxxxxxxxxx ...
- Smart210学习记录-------Linux设备驱动结构
cdev结构体 1 struct cdev { 2 struct kobject kobj; /* 内嵌的 kobject 对象 */ 3 struct module *owner; /*所属模块*/ ...
- Linux 设备驱动 Edition 3
原文网址:http://oss.org.cn/kernel-book/ldd3/index.html Linux 设备驱动 Edition 3 By Jonathan Corbet, Alessand ...
- 《Linux设备驱动开发详解(第2版)》配套视频登录51cto教育频道
http://edu.51cto.com/course/course_id-379-page-1.html http://edu.51cto.com/course/course_id-379-page ...
随机推荐
- CDN应用进阶 | 正确使用CDN 让你更好规避安全风险
为了帮助用户更好地了解和使用CDN产品,CDN应用实践进阶系统课程开课了.12月17日,阿里云CDN产品专家彭飞在线分享了<正确使用CDN,让你更好规避安全风险>议题,内容主要包括以下几个 ...
- 国内唯一!阿里云容器服务进入 Forrester 领导者象限
简介:近日,国际权威咨询机构 Forrester 发布< The Forrester WaveTM: Public Cloud Container Platforms, Q1 2022 > ...
- [GPT] Linux 如何查看 crontab 的运行记录
要查看crontab的运行记录,可以使用以下命令: $ grep CRON /var/log/syslog 或者 $ tail /var/log/syslog 这将在 /var/log/syslo ...
- WPF 推荐一个剪贴板内容查看工具
本文来安利大家一个好用的 Windows 剪贴板的内容查看工具 这是在 GitHub 上完全免费开源的应用,由 walterlv 开发的应用,详细请看 https://github.com/walte ...
- SpringMVC拦截器配置后端登录校验
引 创建拦截器的方法有多种,可以继承HandlerInterceptorAdapter类,也可实现HandlerInterceptor接口.接口中有三个方法: preHandle:在业务处理器处理请求 ...
- Raft 共识算法1-Raft基础
Raft 共识算法1-Raft基础 Raft算法中译版地址:http://www.redisant.cn/etcd/contact 英原论文地址:https://raft.github.io/raft ...
- Multisim 教程
Multisim 教程 Multisim主要是用来做电路图绘制.仿真的程序.本教程介绍Multisim的功能和使用方法. Multisim 界面简介 Multisim是电路设计套件里完成电路图绘制和仿 ...
- S/4 HANA 中的 Email Template
电子邮件是非常常见的业务需求. SAP 了解这一点,并在 S/4 HANA(cloud和on premise)中引入了非常有趣的功能--Email Template.它将CDS视图和HTML模板结合了 ...
- 如何使用Splashtop在家里进行有效的新人入职培训
编辑搜图 请点击输入图片描述 今天的新闻有点不简单,不得了. 简而言之,利用Splashtop可以在家里进行有效的新人入职培训.最棒的地方就在于--两个用户可以远程访问同一台计算机,并且可以看到彼此的 ...
- C语言:窗口控制台颜色改变(不断换色)
了解了stdlib头文件中的system 函数之后突发奇想想要做一个蹦迪效果后台 代码如下: #include <stdio.h> #include <stdlib.h> #i ...