Linux驱动:异步通讯
异步通讯的引入
还是以按键驱动为例,之前我们写的两个驱动( Linux驱动:POLL机制 和 Linux驱动:外部中断)都是应用程序主动去读按键值,如果没有按键就做休眠等操作。而现在我们使用异步通讯,当有按键值的时候,驱动程序会通知应用程序去读取按键值。这种更加符合系统中断的思想。
函数解析
sighandler_t signal(int signum, sighandler_t handler);
-
输入参数
- signum 设置的信号类型,他的一般取值可以看该文章 POSIX多线程笔记(5):信号 中的表格部分
-
handler 获得该信号以后执行的函数,函数原型为
typedef void (*sighandler_t)(int)
函数功能
- 设置该应用程序在获得输入参数 signum设置的信号时,执行 handler指向的函数。
有接收信号然后处理的函数,就有发送信号的函数,在驱动程序中我们使用kill_fasync发送信号
void kill_fasync(struct fasync_struct **fp, int sig, int band)
-
输入参数
- fp 传入结构体指针的地址,该结构体可以用fasync_helper函数初始化
- sig 发送的信号类型
- band 带宽,一般都是使用 POLL_IN,表示设备可读,如果设备可写,使用 POLL_OUT 函数功能
- 对 fp结构体中设置的进程发送变量 sig的信号。
启用异步通知的步骤
- signal(SIGIO, sig_handler);
调用signal函数,让指定的信号SIGIO与处理函数sig_handler对应。 - fcntl(fd, F_SET_OWNER, getpid());
指定一个进程作为文件的“属主(filp->owner)”,这样驱动程序才知道信号要发给哪个进程。 - f_flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, f_flags | FASYNC);
在设备文件中添加FASYNC标志,驱动中就会调用将要实现的test_fasync函数。
三个步骤执行后,一旦有信号产生,相应的进程就会收到。
测试
实验平台
内核版本:Linux-4.19.5
开发板:SAMSUNG JZ2440
实验程序
/* 驱动程序 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/interrupt.h>
int major; //主设备号
static struct class *buttons_class;
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
static unsigned char key_val; //返回给用户的键值
static struct fasync_struct *button_async;
const int t_s3c2440_devid[4] = {1, 2, 3, 4}; //键值数组
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
int i_pinselect = *((int *)dev_id);
int i_pinval = 0;
unsigned long ul_regval;
switch(i_pinselect)
{
case 1:
case 2:
ul_regval = *gpfdat; //读寄存器gpfdat的值
if (i_pinselect == 1)
i_pinval = (ul_regval & (1<<0)) ? 1 : 0;
else
i_pinval = (ul_regval & (1<<2)) ? 1 : 0;
break;
case 3:
case 4:
ul_regval = *gpgdat; //读寄存器gpgdat的值
if (i_pinselect == 3)
i_pinval = (ul_regval & (1<<3)) ? 1 : 0;
else
i_pinval = (ul_regval & (1<<11)) ? 1 : 0;
break;
}
if (i_pinval) //按下读回来的值为0,松开读回来的值为1
{
/* 松开 */
key_val = 0x80 | i_pinselect;
}
else
{
/* 按下 */
key_val = i_pinselect;
}
kill_fasync (&button_async, SIGIO, POLL_IN);
return IRQ_RETVAL(IRQ_HANDLED);
}
static int buttons_open(struct inode *inode, struct file *file)
{
int i_ret;
/* 注册一个名为S2的外部中断,上升沿和下降沿触发,中断执行函数为buttons_irq */
i_ret = request_irq(IRQ_EINT0, buttons_irq, IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING, "S2", (void *)&t_s3c2440_devid[0]);
i_ret = request_irq(IRQ_EINT2, buttons_irq, IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING, "S3", (void *)&t_s3c2440_devid[1]);
i_ret = request_irq(IRQ_EINT11, buttons_irq, IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING, "S4", (void *)&t_s3c2440_devid[2]);
i_ret = request_irq(IRQ_EINT19, buttons_irq, IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING, "S5", (void *)&t_s3c2440_devid[3]);
return 0;
}
ssize_t buttons_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
int i_ret;
if (size != 1)
return -EINVAL;
/* 返回键值 */
i_ret = copy_to_user(buf, &key_val, 1);
return 1;
}
static int buttons_drv_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT0, (void *)&t_s3c2440_devid[0]); //释放中断
free_irq(IRQ_EINT2, (void *)&t_s3c2440_devid[1]);
free_irq(IRQ_EINT11, (void *)&t_s3c2440_devid[2]);
free_irq(IRQ_EINT19, (void *)&t_s3c2440_devid[3]);
return 0;
}
static int buttons_drv_fasync (int fd, struct file *filp, int on)
{
//printk("driver: fifth_drv_fasync\n");
return fasync_helper (fd, filp, on, &button_async);
}
static struct file_operations buttons_fops=
{
.owner = THIS_MODULE,
.open = buttons_open,
.read = buttons_read,
.release = buttons_drv_close,
.fasync = buttons_drv_fasync,
};
static int buttons_init(void)
{
major = register_chrdev(0, "buttons", &buttons_fops); //注册一个字符设备
buttons_class = class_create(THIS_MODULE, "buttons"); //创建一个类
device_create(buttons_class, NULL, MKDEV(major,0), NULL, "buttons"); //在类下面创建一个设备
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); //寄存器指针重定位
gpfdat = gpfcon + 1;
gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16); //寄存器指针重定位
gpgdat = gpgcon + 1;
return 0;
}
static void buttons_exit(void)
{
device_destroy(buttons_class, MKDEV(major,0));
class_destroy(buttons_class);
unregister_chrdev(major, "buttons");
iounmap(gpfcon);
iounmap(gpgcon);
}
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
/* 测试程序 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>
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 i_ret;
int Oflags;
fd = open("/dev/buttons", O_RDWR);
if (fd < 0)
{
return 0;
printf("can't open!\n");
}
signal(SIGIO, my_signal_fun); //设置该测试程序收到SIGIO信号时执行的函数
fcntl(fd, F_SETOWN, getpid());
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC);
while (1)
{
sleep(1000);
}
return 0;
}