Linux驱动异步通知实现
作者:互联网
前言
前几篇讲解了Linux驱动的阻塞与非阻塞访问机制,本篇讲解另一种Linux驱动的访问机制——异步通知机制。
一、异步通知的概念与作用
以下借用《Linux设备驱动开发详解》中的描述来向大家介绍异步通知:
异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号驱动的异步I/O”。信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,实际上,进程也不知道信号到底什么时候到达。
阻塞I/O意味着一直等待设备可访问后再访问,非阻塞I/O中使用poll()意味着要查询设备是否可访问,而异步通知则意味着设备通过自身可访问,实现了异步I/O。由此可见,这几种方式I/O可以互为补充。
二、Linux信号
1.Linux信号简介
由于异步通知机制依赖于信号实现,所以有必要对Linux的信号做一个简单介绍。
使用信号进行进程间通信(IPC)是Linux进程间通信的一种方式,Linux中部分可使用的信号及其定义方式如下表:
从上表可知,除了SIGSTOP和SIGKILL这两个信号外,进程能够忽略或捕获其他的全部信号。一个信号被捕获的意思是当一个信号到达一个进程时有相应的代码处理它。如果一个信号没有被这个进程所捕获,内核将采用默认的操作进行处理。
2.信号接收
在应用程序中,为了捕获信号,可以使用signal()函数来设置对应信号的处理函数,
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数含义如下:
第一个参数signum:指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号。
第二个参数handler:描述了与信号关联的动作,它可以取以下三种值:
SIG_IGN:表示忽略该信号
SIG_DEF:表示采用系统默认方式处理信号
若为用户自定义函数,则信号被捕获到后,该函数将被执行
如果signal()调用成功,它返回最后一次为信号signum绑定的处理函数handler值,失败则返回SIG_ERR.
3、信号释放
既然有信号的接收,那么也必定存在着信号的释放。在Linux设备驱动中,信号接收一般在应用层执行,信号释放一般在驱动处执行。信号释放的接口如下:
释放信号用的函数
void kill_fasync(struct fasync_struct **fa, int sig, int head);
处理FASYNC标志变更接口(放到设备驱动的fasync()函数中执行):
int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);
其中struct fasync_struct **fa仍会放在设备结构体中(struct xxx_dev).
4、关于信号接收和释放模板
对于信号接收和释放有一套模板,这个模板等到下面代码样例中再讲解。
三、代码样例
应用层代码
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<string.h>
#include <signal.h>
#define GLOBALFIFO "/dev/globalfifo"
#define RD_SIZE 64
//接收到异步读信号的操作
void input_handler(int signum)
{
printf("Receive a signum is %d, we can read FIFO!\n", signum);
#if 1
int fd_r;
char readbuf[RD_SIZE];
fd_r = open(GLOBALFIFO, O_RDONLY);
if(fd_r < 0) {
//printf("fail to open fd_r\n");
perror("fail to open");
return ;
}
read(fd_r, readbuf, sizeof(readbuf));
close(fd_r);
printf("Read msg is %s\n", readbuf);
#endif
}
int main()
{
int fd, oflags;
fd = open(GLOBALFIFO, O_RDWR, S_IRUSR | S_IWUSR);
if(-1 != fd){
//启动信号接收机制
//让input_handler()处理SIGIO信号
signal(SIGIO, input_handler);
/*
设置本进程为文件描述符fd的拥有者
没有这一步内核不知道应将信号发给
哪个进程
*/
fcntl(fd, F_SETOWN, getpid());
/*
取得当前文件描述符的所有标志位
*/
oflags = fcntl(fd, F_GETFL);
/*
为了启动异步机制,对文件描述符
添加FASYNC标志
*/
fcntl(fd, F_SETFL, oflags | FASYNC);
while(1){
sleep(100);
}
}else{
printf("Device open failure\n");
}
}
从上面的代码处可以看到
(1)fcntl(fd, F_SETOWN, getpid());,这一步是把/dev/globalfifo文件的文件拥有者设置为上面代码编译出的进程,只有这样内核才会直到要把信号发给这个进程;
(2)oflags = fcntl(fd, F_GETFL);这一步主要是为了读出本文件描述符当前所有的操作,为第(3)步做铺垫。
(3)fcntl(fd, F_SETFL, oflags | FASYNC);;这一步主要是为了设置设备文件支持FASYNC,注意FASYNC标志和以前存在的标志只能是“|”的关系,不能把oflags冲掉。
这三步进行完后,signal(SIGIO, input_handler);才能接收到驱动发来的信号。
驱动代码
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#define MEM_SIZE 0x1000 //全局内存最大4KB
#define MEM_CLEAR 0x1 //清零全局内存
#define GLOBALMEM_MAJOR 250 //预设的globalmem的主设备号
static int globalmem_major = GLOBALMEM_MAJOR;
/*
整体思想:申请一块全局结构体变量,把它当作
一个FIFO,只有当FIFO中有数据的时候,读进程才能
把数据读出,而且读取后的数据会从FIFO的全局内存
中被拿掉;只有当FIFO非满时,写进程才能往这个
FIFO中写入数据。
*/
typedef struct gm_cdev{
struct cdev cdev; //字符设备结构体
unsigned char mem[MEM_SIZE]; //全局内存
struct semaphore sem; //并发控制用的信号量
unsigned int current_len; //fifo有效数据长度
wait_queue_head_t r_wait; //阻塞读用的等待队列头
wait_queue_head_t w_wait; //阻塞写用的等待队列头
struct fasync_struct *async_queue; //异步结构体指针,用于读
}GLOBALFILO;
GLOBALFILO *globalmem_dev; //设备结构体指针
static int globalfifo_fasync(int fd, struct file *filp, int mode);
//文件打开参数
int globalmem_open(struct inode *inode, struct file *file)
{
//将设备结构体指针赋值给文件私有数据指针
file->private_data = globalmem_dev;
return 0;
}
//文件释放函数
int globalmem_release(struct inode *inode, struct file *file)
{
//将文件从异步通知列表中删除
globalfifo_fasync(-1, file, 0);
return 0;
}
//ioctl设备控制函数
long globalmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
GLOBALFILO *v_dev = file->private_data; //从文件结构体中取出设备结构体
switch(cmd){
case MEM_CLEAR:
/*
用信号量方式实现并发控制,不能用自旋锁,
因为copy_to_user会引起进程调度,可能导致内核
崩溃
*/
//获得信号量
if(down_interruptible(&globalmem_dev->sem)){
return -ERESTARTSYS;
}
memset(v_dev->mem, 0, MEM_SIZE);
//释放信号量
up(&globalmem_dev->sem);
printk(KERN_INFO "globalmem is set to zero\n");
break;
default:
return -EINVAL;
}
return 0;
}
//读函数
static ssize_t globalmem_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
int ret = 0;
GLOBALFILO *v_dev = file->private_data; //从文件结构体中取出设备结构体
DECLARE_WAITQUEUE(wait, current); //定义等待队列
/*
用信号量方式实现并发控制,不能用自旋锁,
因为copy_to_user会引起进程调度,可能导致内核
崩溃
*/
//获得信号量
if(down_interruptible(&v_dev->sem)){
return -ERESTARTSYS;
}
//把wait等待队列挂到等待队列头上
add_wait_queue(&v_dev->r_wait, &wait);
//等待FIFO非空
while(0 == v_dev->current_len){
if(file->f_flags & O_NONBLOCK){
//如果文件是非阻塞,则直接退出
ret = -EAGAIN;
goto out;
}
//改变进程状态为睡眠
__set_current_state(TASK_INTERRUPTIBLE);
//释放信号量
up(&v_dev->sem);
printk(KERN_INFO "%d, In globalmem_read, Begin schedule!\n", __LINE__);
//调用其他进程执行
schedule();
printk(KERN_INFO "%d, In globalmem_read, End schedule!\n", __LINE__);
//如果信号唤醒此进程
if(signal_pending(current)){
ret = -ERESTARTSYS;
goto out2;
}
//获得信号量
if(down_interruptible(&v_dev->sem)){
return -ERESTARTSYS;
}
}
//把数据从内核空间拷贝到用户空间
if(size > v_dev->current_len){
size = v_dev->current_len;
}
if(copy_to_user(buf, (void *)v_dev->mem, size)){
ret = -EFAULT;
goto out;
}else{
//fifo有效数据前移
memcpy(v_dev->mem, v_dev->mem+size, v_dev->current_len-size);
//有效数据长度减少
v_dev->current_len -= size;
printk(KERN_INFO "read %u bytes, current_len is %u\n", size, v_dev->current_len);
//唤醒写等待队列
wake_up_interruptible(&v_dev->w_wait);
ret = size;
}
out:
//释放信号量
up(&v_dev->sem);
out2:
//移除等待队列
remove_wait_queue(&v_dev->r_wait, &wait);
set_current_state(TASK_RUNNING);
printk(KERN_INFO "%d, It is end!\n", __LINE__);
return ret;
}
//写函数
static ssize_t globalmem_write(struct file *file, const char __user *buf,
size_t size, loff_t *ppos)
{
GLOBALFILO *v_dev = file->private_data; //从文件结构体中取出设备结构体
int ret = 0;
DECLARE_WAITQUEUE(wait, current); //定义等待队列
/*
用信号量方式实现并发控制,不能用自旋锁,
因为copy_to_user会引起进程调度,可能导致内核
崩溃
*/
//获得信号量
if(down_interruptible(&v_dev->sem)){
return -ERESTARTSYS;
}
//把wait等待队列挂到等待队列头上
add_wait_queue(&v_dev->w_wait, &wait);
//等待fifo非满
while(MEM_SIZE == v_dev->current_len){
if(file->f_flags & O_NONBLOCK){
//如果文件是非阻塞,则直接退出
ret = -EAGAIN;
goto out;
}
//改变进程状态为睡眠
__set_current_state(TASK_INTERRUPTIBLE);
//释放信号量
up(&v_dev->sem);
printk(KERN_INFO "%d, In globalmem_write, Begin schedule!\n", __LINE__);
//调用其他进程执行
schedule();
printk(KERN_INFO "%d, globalmem_write, End schedule!\n", __LINE__);
//如果信号唤醒此进程
if(signal_pending(current)){
ret = -ERESTARTSYS;
goto out2;
}
//获得信号量
if(down_interruptible(&v_dev->sem)){
return -ERESTARTSYS;
}
}
//把数据从用户空间拷贝到内核空间
if(size > MEM_SIZE - v_dev->current_len){
size = MEM_SIZE - v_dev->current_len;
}
if(copy_from_user( (void *)v_dev->mem+v_dev->current_len, buf, size)){
ret = -EFAULT;
goto out;
}else{
v_dev->current_len += size;
printk(KERN_INFO "write %u bytes, current len is %u\n", size, v_dev->current_len);
//唤醒读等待队列
wake_up_interruptible(&v_dev->r_wait);
//产生异步读信号
if(v_dev->async_queue){
printk(KERN_INFO "async_queue is not null!\n");
kill_fasync(&v_dev->async_queue, SIGIO, POLL_IN);
}
ret = size;
}
out:
//释放信号量
up(&v_dev->sem);
out2:
remove_wait_queue(&v_dev->w_wait, &wait);
set_current_state(TASK_RUNNING);
printk(KERN_INFO "%d, It is end!\n", __LINE__);
return ret;
}
//异步通知函数,为了向v_dev->async_queue中写入值
static int globalfifo_fasync(int fd, struct file *filp, int mode)
{
printk("-------------globalfifo_fasync-------------\n");
GLOBALFILO *v_dev = filp->private_data; //从文件结构体中取出设备结构体
return fasync_helper(fd, filp, mode, &v_dev->async_queue);
}
//文件操作结构体
static const struct file_operations global_fops = {
.owner = THIS_MODULE, //固定格式
.llseek = NULL,
.read = globalmem_read,
.write = globalmem_write,
.unlocked_ioctl = globalmem_ioctl,
.fasync = globalfifo_fasync,
.compat_ioctl = NULL,
.open = globalmem_open,
.release = globalmem_release,
};
//初始化并注册cdev设备
static void globalmem_setup_cdev(GLOBALFILO *cdev, int index)
{
int errno;
int devno = MKDEV(globalmem_major, index); //将主设备号和次设备号换成dev_t类型
cdev_init(&cdev->cdev, &global_fops); //静态内存定义初始化
cdev->cdev.owner = THIS_MODULE;
//把cdev设备加入到内核中去,devno为设备号,1为设备数量
errno = cdev_add(&cdev->cdev, devno, 1);
if(errno)
printk(KERN_NOTICE "Add %d dev is error %d", index, errno);
}
//设备驱动加载
int globalmem_init(void)
{
int result;
dev_t devno = MKDEV(globalmem_major, 0);
//申请设备号
if(globalmem_major){
//静态申请设备号
result = register_chrdev_region(devno, 1, "globalmem");
}else{
//动态申请设备号
result = alloc_chrdev_region(&devno, 0 , 1, "globalmem");
globalmem_major = MAJOR(devno);
}
if(result < 0){
return result;
}
//申请设备结构体内存
globalmem_dev = kmalloc(sizeof(GLOBALFILO), GFP_KERNEL);
if(!globalmem_dev){
//申请内存失败
result = -ENOMEM;
goto err;
}
memset(globalmem_dev, 0, sizeof(GLOBALFILO));
globalmem_setup_cdev(globalmem_dev, 0);
//初始化信号量
//init_MUTEX(&globalmem_dev->sem); //已被废除
sema_init(&globalmem_dev->sem, 1);
//初始化读等待队列头
init_waitqueue_head(&globalmem_dev->r_wait);
//初始化写等待队列头
init_waitqueue_head(&globalmem_dev->w_wait);
return 0;
err:
unregister_chrdev_region(devno, 1);
return result;
}
//设备驱动卸载
void globalmem_exit(void)
{
cdev_del(&globalmem_dev->cdev); //内核注销设备
kfree(globalmem_dev); //释放设备结构体内存
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); //释放设备号
}
MODULE_LICENSE("Dual BSD/GPL");
module_param(globalmem_major, int, S_IRUGO);
module_init(globalmem_init);
module_exit(globalmem_exit);
驱动代码中主要进行的是信号的释放操作,它与应用层代码的信号接收操作一一对应,步骤如下:
(1)支持应用层的F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应应用层进程的ID。此项工作已经由内核完成,设备驱动不必关心。
(2)支持应用层F_SETFL命令处理,每当FASYNC标志改变时,驱动程序中static int globalfifo_fasync(int fd, struct file *filp, int mode)函数将会执行,从而调用fasync_helper()函数。
(3)在设备资源可获得时,调用kill_fasync()函数激发相应的信号。
四、实际测试
代码编写完成后,分别进行编译,其中应用层代码编译得到的可执行文件在后台执行
驱动代码编译成ko文件后加载进系统,然后向/dev/globalfifo中写入数据,现象如下:
从上图可知,异步通知功能已经实现完毕。
标签:异步,struct,int,dev,globalmem,信号,Linux,驱动,wait 来源: https://blog.csdn.net/qq_16177869/article/details/111461968