下面用一个具体的例子来展示设备驱动程序如何实现fasync方法,以及应用程序如何得到来自设备驱动程序的异步通知。这个例了同时也展示了sysfs文件系统在驱动程序中的用法,以及通过Linux设各模型来创建设备节点及其他一些特性(这个看起来很简单的内核模块其实体现了设备驱动程序中一些比较重要且典型的特征)。
首先是设备驱动程序的代码,在代码中,我们将Linux设备模型中的一些概念融入其中(本书第9章会详细讨论Linux的设备驱动模型,不过读者可以在这里先热热身),这样我们可以动态创建一个设备节点而无须再手动地使用mknod命令,同时代码中还创建了一个sysfs文件接口,这使得我们可以直接操控设备驱动程序中的一些数据而不必采用ioctl的方式·也许这就是设备驱动模型给我们带来的好处吧。
1.fasync_flag.c
#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_address.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/of_gpio.h> #include <linux/of_irq.h> #include <linux/irq.h> static unsigned long flag = 0; /*设备struct*/ struct led_struct { dev_t led_dev; struct cdev led_cdev; struct class *led_class; struct device *led_device; struct fasync_struct *sigio_list; struct device_node *led_nd; }; struct led_struct led_struct; ssize_t read_flag(struct device *dev,struct device_attribute *attr,char *buf){ size_t count = 0; count += sprintf(&buf[count],"%lu\n",flag); return count; } ssize_t write_flag(struct device * dev,struct device_attribute *attr,char *buf,size_t count){ flag = buf[0] - '0'; //给所有以FASYNC标志调用fcntl的应用程序发信号 kill_fasync(&led_struct.sigio_list,SIGIO,POLL_IN); return count; } struct device_attribute flag_attr = __ATTR(flag,S_IRUGO | S_IWUSR,read_flag,write_flag); int led_open(struct inode *inode, struct file *filp)//打开led { printk("_____%s_____\n",__FUNCTION__); return 0; } int led_fasync (int fd, struct file *filp, int onflag){ //将需要通知的迸程加入sigio_list链表或者从链表中移除 return fasync_helper(fd, filp, onflag, &led_struct.sigio_list); } static struct file_operations led_ops = { .owner = THIS_MODULE, .open = led_open, .fasync = led_fasync, }; static int __init led_init(void) { int ret = 0; // 1.分配设备号 alloc_chrdev_region(&led_struct.led_dev, 0, 1, "my_led"); //2.创造cdev对象和file_operation,然后cdev_init初始化 cdev_init(&led_struct.led_cdev, &led_ops); //3.cdev_add将cdev对象注册进入内核 cdev_add(&led_struct.led_cdev, led_struct.led_dev, 1); //4.创造class与device led_struct.led_class = class_create(THIS_MODULE, "led_class"); led_struct.led_device=device_create(led_struct.led_class, NULL, led_struct.led_dev, NULL, "led"); //在文件系统中创建一个名为"flag的文件 ret = device_create_file(led_struct.led_device,&flag_attr); printk("flag = %d\n",flag); return ret; } static void __exit led_exit(void) { printk("flag = %d\n",flag); device_destroy(led_struct.led_class, led_struct.led_dev); class_destroy(led_struct.led_class); cdev_del(&led_struct.led_cdev); unregister_chrdev_region(led_struct.led_dev, 1); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL");
2.app.c
应用程序:app.c
#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "sys/ioctl.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" #include <poll.h> #include <sys/select.h> #include <sys/time.h> #include <signal.h> #include <fcntl.h> int fd_tty,fd_led,fd_iic; static unsigned long eflag; static void sigio_handler(int sigio){ eflag = 0; } static int block_sigio(void){ sigset_t set,old; int ret; sigisemptyset(&set); sigaddset(&set,SIGIO); sigprocmask(SIG_BLOCK,&set,&old); ret = sigismember(&old, SIGIO); return ret; } static void unblock_sigio(int blocked){ sigset_t set; if(!blocked){ sigisemptyset(&set); sigaddset(&set,SIGIO); sigprocmask(SIG_UNBLOCK,&set,NULL); } } int main(int argc, char *argv[]) { int fd; struct sigaction sigact,oldact; int oflag; int blocked; blocked = block_sigio(); sigisemptyset(&sigact.sa_mask); sigaddset(&sigact.sa_mask,SIGIO); sigact.sa_flags = 0; sigact.sa_handler = sigio_handler; if(sigaction(SIGIO,&sigact,&oldact) < 0){ printf("sigaction failed!\n"); unblock_sigio(blocked); return -1; } unblock_sigio(blocked); fd = ("/dev/led",O_RDWR); if(fd>=0){ fcntl(fd_led,F_SETOWN,getpid()); oflag = fcntl(fd_led,F_GETFL); fcntl(fd_led,F_SETFL,oflag | FASYNC); printf("do everything you want until we get signal..\n"); while(eflag); close(fd); } return 0; }
3.Makefile
#make 编译项目 #make file 在存放.ko文件目录中创建对应项目的目录 #make install 将*.ko及其应用测试文件移动到根文件中 # 1. 使用不同的开发板内核时, 一定要修改KERN_DIR # 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量: # 2.1 ARCH, 比如: export ARCH=arm64 # 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu- # 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin # 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同, # 请参考各开发板的高级用户使用手册 # ROOTFS_DIR 根文件系统中存放 *.ko文件所在目录 # PROJECT_NAME 在存放.ko文件目录中创建对应项目的目录 # DRIVER_NAME 项目中需要编译出.ko来的驱动 # APP_NAME 项目中的应用测试文件 #make 编译项目 #make file 在存放.ko文件目录中创建对应项目的目录 #make install 将*.ko及其应用测试文件移动到根文件中 KERN_DIR = /home/alientek/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek ROOTFS_DIR = /home/alientek/linux/nfs/rootfs/experiment #项目名字 PROJECT_NAME = fasync #各驱动名字,ko DRIVER_NAME1 = fasync_flag DRIVER_NAME2 = #app名字 APP_NAME = app all: make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)arm-linux-gnueabihf-gcc -o $(APP_NAME) $(APP_NAME).c clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order rm -f $(APP_NAME) file: mkdir $(ROOTFS_DIR)/$(PROJECT_NAME) install: cp *.ko $(ROOTFS_DIR)/$(PROJECT_NAME) cp $(APP_NAME) $(ROOTFS_DIR)/$(PROJECT_NAME) # 参考内核源码drivers/char/ipmi/Makefile # 要想把a.c, b.c编译成ab.ko, 可以这样指定: # ab-y := a.o b.o # obj-m += ab.o obj-m += $(DRIVER_NAME1).o
结果
1.编译
make
2.在根文件系统下创建fasync目录
sudo make file
3.复制模块文件和应用程序到根文件目录下
sudo make install
4.安装模块
insmod fasync_flag.ko
5.安装后结果
变量flag此时为0,另外在目录**/sys/devices/virtual/led_class/led** 下有一个flag文件,我们可以通过修改这个flag文件来修改变量flag的值
6.通过文件修改变量flag的值
echo 5 > flag
然后卸载模块查看变量的大小
rmmod fasync_flag.ko
变量flag的值最终被成功修改