Linux异步通知 fasync

Linux异步通知 fasync

我们知道,驱动程序运行在内核空间中,应用程序运行在用户空间中,两者是不能直接通信的。

但在实际应用中,在设备已经准备好的时候,我们希望通知用户程序设备已经ok,用户程序可以读取了,这样应用程序就不需要一直查询该设备的状态,从而节约资源,这就是异步通知。

这个过程如何实现呢?两方面的工作。

1.驱动方面:

    1.在设备抽象的数据结构中增加一个struct fasync_struct的指针

    2.实现设备操作中的fasync函数,这个函数很简单,其主体就是调用内核的fasync_helper函数。

    3.在需要向用户空间通知的地方(例如中断中)调用内核的kill_fasync函数。

    4.在驱动的release方法中调用前面定义的fasync函数

其中fasync_helper和kill_fasync都是内核函数,我们只需要调用就可以了。

在设备抽象的数据结构中定义的指针是一个重要参数,fasync_helper和kill_fasync会使用这个参数。

2.应用层方面:

    1.利用signal或者sigaction设置SIGIO信号的处理函数

    2.fcntl的F_SETOWN指令设置当前进程为设备文件owner

    3.fcntl的F_SETFL指令设置FASYNC标志

完成了以上的工作的话,当内核执行到kill_fasync函数,用户空间SIGIO函数的处理函数就会被调用了。


让我们结合具体代码看看就更明白了。

-------------------------------------------------------------------------------------------

应用层代码

-------------------------------------------------------------------------------------------

void input_handler(int num) 

//处理函数,没什么好讲的,用户自己定义 

     char data[MAX_LEN]; 

     int len; 

     //读取并输出STDIN_FILENO上的输入 

     len = read(STDIN_FILENO, &data, MAX_LEN); 

     data[len] = 0; 

     printf("input available:%s\n", data); 

main() 

        int oflags; 

        //启动信号驱动机制 

        signal(SIGIO, input_handler); 


        //fcntl的F_SETOWN指令设置当前进程为设备文件owner

        fcntl(STDIN_FILENO, F_SETOWN, getpid()); 


        //fcntl的F_SETFL指令设置FASYNC标志

        oflags = fcntl(STDIN_FILENO, F_GETFL); 

        fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC); 


        //最后进入一个死循环,程序什么都不干了,只有信号能激发input_handler的运行 

        //如果程序中没有这个死循环,会立即执行完毕 

        while (1); 

-------------------------------------------------------------------------------------------

驱动层代码

-------------------------------------------------------------------------------------------

驱动层其他部分代码不变,就是增加了一个fasync方法的实现以及一些改动

//在设备抽象的数据结构中增加一个struct fasync_struct的指针

static struct fasync_struct *fasync_queue; 

static int my_fasync(int fd,  struct file * filp,  int on) 

        intretval; 

        retval=fasync_helper(fd,filp,on,&fasync_queue); 

        if(retval<0)  {

                return retval; 

        }

        return0; 

在驱动的release方法中我们再调用my_fasync方法 

int my_release(struct inode *inode, struct file *filp) 

      drm_fasync(-1, filp, 0); 

这样后我们在需要的地方(比如中断)调用下面的代码,就会向fasync_queue队列里的设备发送SIGIO信号,应用程序收到信号,执行处理程序

if(fasync_queue) {

        kill_fasync(&fasync_queue, SIGIO, POLL_IN);

}

好了,这下大家知道该怎么用异步通知机制了吧?

以下是几点说明:

1. 两个函数的原型

int fasync_helper(struct inode *inode, struct file *filp, int mode,struct fasync_struct **fa);

一个"帮忙者", 来实现 fasync 设备方法. mode 参数是传递给方法的相同的值, 而 fa指针指向一个设备特定的 fasync_struct*


void kill_fasync(structfasync_struct *fa, int sig, int band);

如果这个驱动支持异步通知, 这个函数可用来发送一个信号到登记在 fa 中的进程.


2. fcntl系统调用

int fcntl(int fd, int cmd, long arg);

fcntl的作用是改变一个已打开文件的属性,fd是要改变的文件的描述符,cmd是命令罗列如下:

F_DUPFD, F_GETFD, F_SETFD, F_GETFL, F_SETFL, F_SETLK, F_SETLKW,F_GETLK, F_GETOWN, F_SETOWN

本节只关心F_SETOWN(设置异步IO所有权),F_GETFL(获取文件flags),F_SETFL(设置文件flags)

arg是要改变的属性内容


3. 用户进程启用异步通知机制

首先,设置一个进程作为一个文件的属主(owner),这样内核就知道该把文件的信号发送给哪个进程

fcntl(fd, F_SETOWN, getpid()); // getpid()就是当前进程咯

然后,给文件设置FASYNC标志,以启用异步通知机制

fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | FASYNC);


4. 缺陷

当有多个文件发送异步通知信号给一个进程时,进程无法知道是哪个文件发送的信号,这时候还是要借助poll的帮助完成IO


一个例子

-------------------------------------------------------------------------------------------

应用程序

-------------------------------------------------------------------------------------------

#include "sys/types.h"

#include "sys/stat.h"

#include "fcntl.h"

#include "stdio.h" 

#include "poll.h"

#include "signal.h"

#include "sys/types.h"

#include "unistd.h"

#include "fcntl.h"


/* fifthdrvtest 

  */ 

int fd; 

//信号处理函数 

void my_signal_fun(int signum) 

        unsigned char key_val; 

        read(fd, &key_val, 1); 

        printf("key_val: 0x%x\n", key_val); 


int main(int argc, char **argv) 

        unsigned char key_val; 

        int ret; 

        int Oflags; 


        //在应用程序中捕捉SIGIO信号(由驱动程序发送) 

        signal(SIGIO, my_signal_fun); 


        fd = open("/dev/buttons", O_RDWR); 

        if (fd < 0) 

        { 

                printf("can't open!\n"); 

        } 


        //将当前进程PID设置为fd文件所对应驱动程序将要发送SIGIO,SIGUSR信号进程PID 

        fcntl(fd, F_SETOWN, getpid()); 

        //获取fd的打开方式 

        Oflags = fcntl(fd, F_GETFL); 


        //将fd的打开方式设置为FASYNC --- 即 支持异步通知 

        //该行代码执行会触发 驱动程序中 file_operations->fasync 函数

        //------fasync函数调用fasync_helper初始化一个fasync_struct结构体

       //该结构体描述了将要发送信号的进程PID (fasync_struct->fa_file->f_owner->pid) 

        fcntl(fd, F_SETFL, Oflags | FASYNC); 



        while (1) 

        { 

                sleep(1000); 

        } 

        return 0; 

-------------------------------------------------------------------------------------------

驱动

-------------------------------------------------------------------------------------------

#include "linux/module.h"

#include "linux/kernel.h"

#include "linux/fs.h" 

#include "linux/init.h" 

#include "linux/delay.h" 

#include "linux/irq.h" 

#include "asm/uaccess.h" 

#include "asm/irq.h" 

#include "asm/io.h" 

#include "asm/arch/regs-gpio.h" 

#include "asm/hardware.h" 

#include "linux/poll.h" 


static struct class *fifthdrv_class; 

static struct class_device  *fifthdrv_class_dev; 


//volatile unsigned long *gpfcon; 

//volatile unsigned long *gpfdat; 


static DECLARE_WAIT_QUEUE_HEAD(button_waitq); 


/* 中断事件标志, 中断服务程序将它置1,fifth_drv_read将它清0 */ 

static volatile int ev_press = 0; 


static struct fasync_struct *button_async; 



struct pin_desc{ 

        unsigned int pin; 

        unsigned int key_val; 

}; 



/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */ 

/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */ 

static unsigned char key_val; 


/*

* K1,K2,K3,K4对应GPG0,GPG3,GPG5,GPG6

*/ 


struct pin_desc pins_desc[4] = { 

        {S3C2410_GPG0, 0x01}, 

        {S3C2410_GPG3, 0x02}, 

        {S3C2410_GPG5, 0x03}, 

        {S3C2410_GPG6, 0x04}, 

}; 



/*

  * 确定按键值

  */ 

static irqreturn_t buttons_irq(int irq, void *dev_id) 

        struct pin_desc * pindesc = (struct pin_desc *)dev_id; 

        unsigned int pinval; 


        pinval = s3c2410_gpio_getpin(pindesc->pin); 


        if (pinval) 

        { 

                /* 松开 */ 

                key_val = 0x80 | pindesc->key_val; 

        } 

       else 

        { 

                /* 按下 */ 

                key_val = pindesc->key_val; 

        } 


        ev_press = 1;                  /* 表示中断发生了 */ 

        wake_up_interruptible(&button_waitq);  /* 唤醒休眠的进程 */ 


        //发送信号SIGIO信号给fasync_struct 结构体所描述的PID,触发应用程序的SIGIO信号处理函数 

        kill_fasync (&button_async, SIGIO, POLL_IN); 


        return IRQ_RETVAL(IRQ_HANDLED); 


static int fifth_drv_open(struct inode *inode, struct file *file) 

        /* GPG0,GPG3,GPG5,GPG6为中断引脚: EINT8,EINT11,EINT13,EINT14 */ 

        request_irq(IRQ_EINT8,  buttons_irq, IRQT_BOTHEDGE, "K1", &pins_desc[0]); 

        request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "K2", &pins_desc[1]); 

        request_irq(IRQ_EINT13, buttons_irq, IRQT_BOTHEDGE, "K3", &pins_desc[2]); 

        request_irq(IRQ_EINT14, buttons_irq, IRQT_BOTHEDGE, "K4", &pins_desc[3]);   


        return 0; 


ssize_t fifth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) 

        if (size != 1) 

                return -EINVAL; 


        /* 如果没有按键动作, 休眠 */ 

        wait_event_interruptible(button_waitq, ev_press); 


        /* 如果有按键动作, 返回键值 */ 

        copy_to_user(buf, &key_val, 1); 

        ev_press = 0; 


        return 1; 



int fifth_drv_close(struct inode *inode, struct file *file) 

        free_irq(IRQ_EINT8,  &pins_desc[0]); 

        free_irq(IRQ_EINT11, &pins_desc[1]); 

        free_irq(IRQ_EINT13, &pins_desc[2]); 

        free_irq(IRQ_EINT14, &pins_desc[3]); 

        return 0; 


static unsigned fifth_drv_poll(struct file *file, poll_table *wait) 

        unsigned int mask = 0; 

        poll_wait(file, &button_waitq, wait); // 不会立即休眠 


        if (ev_press) 

            mask |= POLLIN | POLLRDNORM; 


        return mask; 


static int fifth_drv_fasync (int fd, struct file *filp, int on) 

        printk("driver: fifth_drv_fasync\n"); 

        //初始化/释放 fasync_struct 结构体 (fasync_struct->fa_file->f_owner->pid) 

        return fasync_helper (fd, filp, on, &button_async); 



static struct file_operations sencod_drv_fops = { 

        .owner  =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ 

        .open    =  fifth_drv_open,     

        .read    =  fifth_drv_read,     

    .    release =  fifth_drv_close, 

        .poll    =  fifth_drv_poll, 

        .fasync  =  fifth_drv_fasync, 

}; 



int major; 

static int fifth_drv_init(void) 

        major = register_chrdev(0, "fifth_drv", &sencod_drv_fops); 

        fifthdrv_class = class_create(THIS_MODULE, "fifth_drv"); 

        fifthdrv_class_dev = class_device_create(fifthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */ 

    //  gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); 

    //  gpfdat = gpfcon + 1; 


        return 0; 


static void fifth_drv_exit(void) 

        unregister_chrdev(major, "fifth_drv"); 

        class_device_unregister(fifthdrv_class_dev); 

        class_destroy(fifthdrv_class); 

    //  iounmap(gpfcon); 

        return 0; 

module_init(fifth_drv_init); 

module_exit(fifth_drv_exit); 

MODULE_LICENSE("GPL"); 

推荐阅读更多精彩内容