信号类似于硬件上使用的"中断",只不过信号是软件层次上的,可以理解为软件层次上对中断的一种模拟,驱动通过主动向应用程序发送可访问的信号,应用程序获取到信号后即可从驱动设备中读取或写入数据了
1. 驱动和应用中的信号处理
驱动程序中需要定义fasync_struct结构体指针变量,一般在设备结构体中定义该变量,fasync_struct结构体原型如下:
struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next;
struct file *fa_file;
struct rcu_head fa_rcu;
};
⏩ fasync函数:file_operations操作集中要实现的函数
int (*fasync) (int fd, struct file *filp, int on)
⏩ fasync_helper函数:fasync函数里通过调用该函数来初始化前面定义的 fasync_struct结构体指针
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
//参数fapp:要初始化的fasync_struct 结构体指针变量
⏩ kill_fasync函数:当设备可访问时,驱动程序利用该函数向应用程序发出信号
void kill_fasync(struct fasync_struct **fp, int sig, int band)
//fp:要操作的 fasync_struct
//sig:要发送的信号
//band:可读时设置为 POLL_IN,可写时设置为 POLL_OUT
驱动中fasync函数使用示例,如下示:
//驱动中fasync函数示例
struct xxx_dev {
......
struct fasync_struct *async_queue; /* 异步相关结构体 */
};
static int xxx_fasync(int fd, struct file *filp, int on) {
struct xxx_dev *dev = (xxx_dev)filp->private_data;
if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
return -EIO;
return 0;
}
static int xxx_release(struct inode *inode, struct file *filp) {
return xxx_fasync(-1, filp, 0); /* 删除异步通知 */
} //关闭驱动文件时需要释放fasync_struct
static struct file_operations xxx_ops = {
......
.fasync = xxx_fasync,
.release = xxx_release,
......
};
应用程序中对信号的处理包括以下三步:
⏩ 注册信号处理函数:使用signal函数来设置信号的处理函数
void (int) * signal(int sig, void (int) * func)
⏩ 将本应用程序的进程号告诉给内核
//以下函数用于将本应用程序的进程号告诉给内核
fcntl(fd, F_SETOWN, getpid())
⏩ 开启异步通知:当应用程序通过fcntl()函数改变fasync标记时,驱动程序file_operations操作集中的fasync函数就会执行
flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */
2. 信号驱动I/O程序编写
在非阻塞IO实验代码基础上进行改编,设备树文件无需修改,只需修改应用程序和驱动程序里的部分代码即可。当按键按下后驱动程序向应用程序发送SIGIO信号,应用程序获取到SIGIO信号以后读取并且打印出按键值
⏩ 驱动程序中需要修改的部分
/* imx6uirq 设备结构体 */
struct imx6uirq_dev{
......
struct fasync_struct *async_queue; /* 异步相关结构体 */
};
......
......
void timer_function(unsigned long arg) {
......
......
if(atomic_read(&dev->releasekey)) { /* 一次完整的按键过程 */
if(dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
}
#if 0
/* 唤醒进程 */
if(atomic_read(&dev->releasekey)) { /* 完成一次按键过程 */
/* wake_up(&dev->r_wait); */
wake_up_interruptible(&dev->r_wait);
}
#endif
}
......
......
static int imx6uirq_fasync(int fd, struct file *filp, int on) {
struct imx6uirq_dev *dev = (struct imx6uirq_dev *) filp->private_data;
return fasync_helper(fd, filp, on, &dev->async_queue);
}
static int imx6uirq_release(struct inode *inode, struct file *filp) {
return imx6uirq_fasync(-1, filp, 0);
}
/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
......
.fasync = imx6uirq_fasync,
.release = imx6uirq_release,
};
......
⏩ 应用程序:获取到SIGIO信号后读取并且打印出按键值
/* SIGIO信号处理函数 */
static void sigio_signal_func(int signum) {
int err = 0;
unsigned int keyvalue = 0;
err = read(fd, &keyvalue, sizeof(keyvalue));
if(err < 0) {
/* 读取错误 */
} else {
printf("sigio signal! key value=%d\r\n", keyvalue);
}
}
/* 主程序 */
int main(int argc, char *argv[]) {
int flags = 0;
char *filename;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s\r\n", filename);
return -1;
}
/* 设置信号 SIGIO 的处理函数 */
signal(SIGIO, sigio_signal_func);
fcntl(fd, F_SETOWN, getpid()); /* 将当前进程的进程号告诉给内核 */
flags = fcntl(fd, F_GETFD); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC);/* 设置进程启用异步通知功能 */
while(1) {
sleep(2);
}
close(fd);
return 0;
}
3. 编译测试
⏩ 编译驱动程序:当前目录下创建Makefile文件,并make
编译
KERNELDIR := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxi
CURRENT_PATH := $(shell pwd)
obj-m := asyncnoti.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
⏩ 编译测试程序:无需内核参与,直接编译即可
arm-linux-gnueabihf-gcc asyncnotiApp.c -o asyncnotiApp
⏩ 将驱动文件和测试文件拷贝至rootfs/lib/modules/4.1.15后加载驱动
depmod #第一次加载驱动时,需使用“depmod”命令
modprobe asyncnoti.ko
⏩ 使用./asyncnotiApp /dev/asyncnoti &
命令,以后台模式运行应用程序,此时按下按键,应用程序会打印出按键值
⏩ 使用top
命令查看asyncnotiApp的CPU使用率:可见使用信号驱动I/O方式处理后,CPU的使用率也非常低