Linux设备驱动中的异步通知与异步I/O学习s

1、异步通知的概念和作用

异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上"中断"的概念 ,比较准确的称谓是"信号驱动的异步I/O"。信号是在软件层次上对中断机制的一种模拟,**在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。**信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。

2、Linux异步通知编程

Linux信号。使用信号在进程间通信(IPC)是UNIX系统中的一种传统机制,Linux系统也支持这种机制。在Linux系统中,异步通知使用信号来实现,Linux系统中可用的信号及其定义如下图所示。

除了SIGSTOP和SIGKILL两个信号外,进程能够忽略或捕获其他全部的信号。一个信号被捕获的意思是当一个信号到达时有相应的代码处理它。如果一个信号没有被这个进程捕获,内核将采取默认处理


信号的接收 。在用户程序 中为了捕获信号,可以使用signal()函数来设置对应信号的处理函数,如下所示,第一个参数指定信号的值,第二个参数指定针对前面信号值的处理函数,若为SIG_IGN,表示忽略该信号;若为SIG_DFL,表示采用系统默认方式处理信号;若位用户自定义的函数,则信号被捕获到后,该函数将被执行。

cpp 复制代码
sighandler_t signal(int signum,sighandler_t handler);

如果signal()调用成功,它返回最后一次为信号signum绑定的处理函数handler值,失败返回SIG_ERR。以下是一个使用信号实现异步通知的例子:

cpp 复制代码
#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;

    len = read(STDIN_FILENO,&data,MAX_LEN);
    data[len]=0;
    printf("input available:%s\n",data);
}
int main()
{
    int oflags;

    signal(SIGIO,input_handler);
    fcntl(STDIN_FILENO,F_SETOWN,getpid());//设置本进程为标准输入文件的拥有者,指示信号要发送给本进程
    //oflags = fcntl(STDIN_FILENO,F_GETFL);
    //fcntl(STDIN_FILENO,F_SETFL,oflags | FASYNC);
    while(1);
}

在用户空间中能处理一个设备释放的信号必须完成以下工作:

1、通过F_SETOWN IO控制命令设置设备文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进程接收到。

2、通过F_SETFL IO控制命令设置设备文件支持FASYNC,即异步通知模式。

3、通过signal()函数连接信号和信号处理函数。


信号的释放。在设备驱动和应用程序的异步通知交互中,仅仅在应用程序端捕获信号是不够的,应该在合适的时机让设备驱动释放信号,在设备驱动程序中增加信号释放的相关代码。

设备驱动中的异步通知编程比较简单,主要用到一项数据结构和两个函数。数据结构是fasync_struct结构体,两个函数如下:

处理FASYNC标志变更的函数:

cpp 复制代码
int fasync_helper(int fd,struct file *filp,int mode,struct fasync_struct **fa);

释放信号用的函数:

cpp 复制代码
void kill_fasync(struct fasync_struct **fa,int sig,int band);

和其他的设备驱动一样,fasync_struct结构体可以放在设备结构体中:

cpp 复制代码
struct xxx_dev{
    struct cdev cdev;
    ......
    struct fasync_struct *async_queue;//异步结构体指针
};

以下是支持异步通知的设备驱动程序fasync()函数的模板:

cpp 复制代码
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信号,可读时第3个参数设置为POLL_IN,可写时第3个参数设置为POLL_OUT,代码如下:

cpp 复制代码
ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
    struct globalfifo_dev *dev = filp->private_data;
    ...
    //产生异步信号
    if(dev->async_queue)
    {
        kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
    }
    ...
}

最后在文件关闭时,应在release函数中调用设备驱动的fasync函数将文件从异步通知的队列中删除:

cpp 复制代码
int globalfifo_release(struct inode *node, struct file *filp)
{
    struct globalfifo_dev *dev = filp->private_data;
    ...
    //将文件从异步通知队列中删除
    globalfifo_fasync(-1,filp,0);
    ...
    return 0;
}

支持异步通知的globalfifo的驱动。需要在file_operations中定义 .fasync函数。当应用程序使用fcntl设置FASYNC标志时,驱动程序就会调用定义好的fasync函数,注册异步通知队列。驱动程序在写完数据后,使用kill_fasync函数释放读信号。

cpp 复制代码
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/bits.h>
#include <linux/debugfs.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/pmbus.h>
#include <linux/util_macros.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#define GLOBALFIFO_SIZE 20
int globalfifo_major=0;
struct class *globalfifo_class;
struct globalfifo_dev{
    //struct cdev cdev;//cdev结构体
    unsigned int current_len;//fifo有效数据长度
    unsigned char mem[GLOBALFIFO_SIZE];//全局内存123
    struct semaphore sem;//并发控制用的信号量
    wait_queue_head_t r_wait;//阻塞读用的等待队列头
    wait_queue_head_t w_wait;//阻塞写用的等待队列头
    struct fasync_struct *async_queue;//异步结构指针,用于读
};
struct globalfifo_dev *globalfifo_devp;

int globalfifo_open(struct inode *node, struct file *filp)
{
    printk(KERN_INFO"globalfifo_open\n");
    filp->private_data = globalfifo_devp;
    printk(KERN_INFO"private_data\n");
    return 0;
}
int globalfifo_fasync(int fd, struct file *filp, int mode)
{
    struct globalfifo_dev *dev = filp->private_data;
    printk("globalfifo_fasync");
    return fasync_helper(fd,filp,mode,&dev->async_queue);
}
int globalfifo_release(struct inode *node, struct file *filp)
{
    printk(KERN_INFO"globalfifo_release\n");
    globalfifo_fasync(-1,filp,0);
    return 0;
}
ssize_t globalfifo_read(struct file *filp, char *buf, size_t size, loff_t *offset)
{
    printk(KERN_INFO"globalfifo_read\n");
    int ret;
    struct globalfifo_dev *dev = filp->private_data;
    printk(KERN_INFO"read try to declare queue\n");
    DECLARE_WAITQUEUE(wait,current);
    printk(KERN_INFO"read try to get sem\n");
    down(&dev->sem);
    printk(KERN_INFO"read have got sem\n");
    add_wait_queue(&dev->r_wait,&wait);

    if(dev->current_len==0)
    {
        if(filp->f_flags & O_NONBLOCK)
        {
            ret = -EAGAIN;
            goto out;
        }
        __set_current_state(TASK_INTERRUPTIBLE);
        up(&dev->sem);
        printk(KERN_INFO"read release sem,begin shcedule\n");
        schedule();
        if(signal_pending(current))
        {
            ret = -ERESTARTSYS;
            goto out2;
        }
        down(&dev->sem);
    }
    if(size > dev->current_len)
        size = dev->current_len;

    if(copy_to_user(buf,dev->mem,size))
    {
         ret = -EFAULT;
         goto out;
     }
     else
    {
        //printk("%s",kern_buf);
        memcpy(dev->mem,dev->mem+size,dev->current_len-size);
        dev->current_len-=size;
        printk(KERN_INFO"read %d bytes(s),current_len:%d\n",size,dev->current_len);
        wake_up_interruptible(&dev->w_wait);
        ret = size;
    }
    out:up(&dev->sem);
    out2:remove_wait_queue(&dev->r_wait,&wait);
    set_current_state(TASK_RUNNING);
    return size;
}
ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
    printk("global_fifo write");
    struct globalfifo_dev *dev = filp->private_data;
    int ret,i=0,j;

    DECLARE_WAITQUEUE(wait,current);
    down(&dev->sem);
    add_wait_queue(&dev->w_wait,&wait);

    if(dev->current_len == GLOBALFIFO_SIZE)
    {
        if(filp->f_flags & O_NONBLOCK)
        {
            ret = -EAGAIN;
            goto out;
        }
        __set_current_state(TASK_INTERRUPTIBLE);
        up(&dev->sem);
        schedule();
        if(signal_pending(current))
        {
            ret = -ERESTARTSYS;
            goto out2;
        }
        down(&dev->sem);
    }
    if(size>GLOBALFIFO_SIZE-dev->current_len)
    {
        size = GLOBALFIFO_SIZE-dev->current_len;
    }
    //for(i=dev->current_len,j=0;i<dev->current_len+size;i++,j++)dev->mem[i]=buf[j];
    if(copy_from_user(dev->mem + dev->current_len,buf,size))
    {
        ret = -EFAULT;
        goto out;
    }
    else
    {
        dev->current_len+=size;
        printk(KERN_INFO"written %d bytes(s),current_len:%d\n",size,dev->current_len);
        wake_up_interruptible(&dev->r_wait);

        //产生异步读信号
        if(dev->async_queue)
        {
            kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
        }
        ret = size;
    }
    out:up(&dev->sem);
    out2:remove_wait_queue(&dev->w_wait,&wait);
    set_current_state(TASK_RUNNING);
    return ret;

}
unsigned int globalfifo_poll(struct file *filp, struct poll_table_struct *wait)
{
    unsigned int mask = 0;
    struct globalfifo_dev *dev = filp->private_data;
    down(&dev->sem);
    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;
    }
    up(&dev->sem);
    return mask;
}

struct file_operations globalfifo_drv={
    .owner   = THIS_MODULE,
    .open    = globalfifo_open,
    .release = globalfifo_release,
    .read    = globalfifo_read,
    .write   = globalfifo_write,
    .poll    = globalfifo_poll,
    .fasync  = globalfifo_fasync,
};
int globalfifo_init(void)
{
    int ret;

    //申请设备号
    globalfifo_major = register_chrdev(0,"globalfifo",&globalfifo_drv);
    globalfifo_class = class_create("globalfifo_class");
    if (IS_ERR(globalfifo_class)) {
		printk(KERN_ERR "globalfifo_class: failed to allocate class\n");
		return PTR_ERR(globalfifo_class);
	}
    device_create(globalfifo_class,NULL,MKDEV(globalfifo_major,0),NULL,"globalfifo_device");
    

    globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev),GFP_KERNEL);
    if(!globalfifo_devp)
    {
        ret = -ENOMEM;
        goto fail_malloc;
    }
    memset(globalfifo_devp,0,sizeof(struct globalfifo_dev));
    //globalfifo_setup_cdev(globalfifo_devp,0);
    sema_init(&globalfifo_devp->sem,1);
    init_waitqueue_head(&globalfifo_devp->r_wait);
    init_waitqueue_head(&globalfifo_devp->w_wait);
    return 0;

    fail_malloc:device_destroy(globalfifo_class,MKDEV(globalfifo_major,0));
    class_destroy(globalfifo_class);
    unregister_chrdev(globalfifo_major,"globalfifo_chrdev");
    return ret;
}
void globalfifo_exit(void)
{
    device_destroy(globalfifo_class,MKDEV(globalfifo_major,0));
    class_destroy(globalfifo_class);
    unregister_chrdev(globalfifo_major,"globalfifo_chrdev");
    return;
}
module_init(globalfifo_init);
module_exit(globalfifo_exit);
MODULE_LICENSE("GPL"); 
相关推荐
-SGlow-5 分钟前
Linux相关概念和重要知识点(7)(git、冯诺依曼体系结构)
linux·运维·git
AIGC破防黑吗喽10 分钟前
Stable Diffusion零基础学习
gpt·学习·ai·stable diffusion·学习方法·ai绘画
江凡心16 分钟前
Qt 每日面试题 -5
服务器·数据库·qt·学习·面试
EterNity_TiMe_22 分钟前
【Linux基础IO】深入解析Linux基础IO缓冲区机制:提升文件操作效率的关键
linux·运维·服务器·开发语言·学习·性能优化·学习方法
0xwang1 小时前
Ubuntu24.04桌面版下的网络管理
linux·ubuntu
苏湘涵1 小时前
进程的那些事--实现shell
linux·运维·服务器
4647的码农历程1 小时前
Linux学习之路 -- 线程 -- 条件变量与生产消费模型
学习
Maxx Space1 小时前
828华为云征文|部署个人知识管理系统 SiyuanNote
linux·git·docker·华为云·github·notion
宝哈2 小时前
认识 Linux操作系统
linux·运维·服务器·开发语言·c++·算法
ulimpid2 小时前
Git | Dockerized GitLab 安装使用(简单实操版)
学习·docker·gitlab