21-异步IO

从内核中最简单的驱动程序入手,描述Linux驱动开发,主要文章目录如下(持续更新中):
01 - 第一个内核模块程序
02 - 注册字符设备驱动
03 - open & close 函数的应用
04 - read & write 函数的应用
05 - ioctl 的应用
06 - ioctl LED灯硬件分析
07 - ioctl 控制LED软件实现(寄存器操作)
08 - ioctl 控制LED软件实现(库函数操作)
09 - 注册字符设备的另一种方法(常用)
10 - 一个cdev实现对多个设备的支持
11 - 四个cdev控制四个LED设备
12 - 虚拟串口驱动
13 - I2C驱动
14 - SPI协议及驱动讲解
15 - SPI Linux驱动代码实现
16 - 非阻塞型I/O
17 - 阻塞型I/O
18 - I/O多路复用之 select
19 - I/O多路复用之 poll
20 - I/O多路复用之 epoll
21 - 异步通知

1. 异步IO简介

 Linux 应用程序可以通过阻塞或者非阻塞这两种方式来访问驱动设备,通过阻塞方式访问的话应用程序会处于休眠态,等待驱动设备可以使用,非阻塞方式的话会通过 select或者poll 函数来不断的轮询,查看驱动设备文件是否可以使用。这两种方式都需要应用程序主动的去查询设备的使用情况,如果能提供一种类似中断的机制,当驱动程序可以访问的时候主动告诉应用程序那就最好了,对此Linux提供了一种异步通信机制来实现这种操作。

 异步通知的核心是信号 ,当驱动程序可以访问时会给应用程序发送一信号通知应用程序可以访问,驱动给应用程序发送信号
 Linux中所支持的信号种类如下:(include\uapi\asm\signal.h)

#define SIGHUP		 1		// 终端挂起或进程终止
#define SIGINT		 2		// ctrl + C
#define SIGQUIT		 3		// ctrl + \
#define SIGILL		 4		// 非法指令
#define SIGTRAP		 5		// DEBUG使用,有断点时产生
#define SIGABRT		 6		// abort(3)发出的退出指令
#define SIGIOT		 6		// IOT指令
#define SIGSTKFLT	 7		// 栈异常
#define SIGFPE		 8		// 浮点运算错误
#define SIGKILL		 9		// 杀死进程(不可忽略)      常用的 kill -9中的9就是信号的编号9
#define SIGBUS		10		// 总线错误
#define SIGSEGV		11		// 无效的内存段
#define SIGXCPU		12		// 超过CPU资源限制
#define SIGPIPE		13		// 向非法管道写入数据
#define SIGALRM		14		// 闹钟
#define SIGTERM		15		// 软件终止
#define SIGUSR1		16		// 用户自定义信号1
#define SIGUSR2		17		// 用户自定义信号2
#define SIGCHLD		18		// 子进程结束
#define SIGPWR		19		// 断点重启
#define SIGVTALRM	20		// 虚拟时钟信号
#define SIGPROF		21		// 时钟信号描述
#define SIGIO		22		// 可以进行输入/输出操作
#define SIGPOLL		SIGIO
#define SIGWINCH	23		// 窗口大小改变
#define SIGSTOP		24		// 停止进程的执行,只是暂停(不可忽略)
#define SIGTSTP		25		// ctrl + z
#define SIGCONT		26		// 进程继续
#define SIGTTIN		27		// 后台进程需要从终端读取数据
#define SIGTTOU		28		// 后台进程需要向终端写数据
#define SIGURG		29		// 有晋级输局
#define SIGXFSZ		30		// 文件大小超额
#define SIGUNUSED	31		// 未使用信号

2. 信号的处理操作

2.1 应用层的信号处理

 应用层对异步通知的处理过程如下:

2.1.1 注册信号处理函数

  应用层注册信号处理函数使用 signal,定义如下:

原    型: sighandler_t sighandler_t signal(int signum, sighandler_t handler);
功    能: 注册信号的处理函数 
@param1: 指定的信号标号
@param2: 信号的处理方式 
		 a. 捕获信号 signal(SIGINT, fun)
		 b. 忽略信号 signal(SIGINT, SIG_IGN)
		 c. 执行默认操作 signal(SIGINT, SIG_DFL)
@return: 成功:以前的信号处理函数,失败: -1

  如果信号的处理方式设置为捕获信号,则需要实现信号的处理函数 ,信号的处理函数定义如下:

原    型: void (*sighandler_t)(int);
功    能: 捕获信号后,信号的处理函数
@param1: 传递的参数,当设置多个信号对应相同的处理函数时,该参数可用作判断信号的类型 
@return: 无返回值

2.1.2 将本应用程序的进程号告诉给内核

  这一步使用的函数如下:

原    型: int fcntl(int fd, int cmd)
		  int fcntl(int fd, int cmd, long arg)
		  int fcntl(int fd, int cmd ,struct flock* lock)
功    能: 改变已打开的文件性质
@param1:  文件描述符
@param2:  命命令值,定义如下:
			F_DUPFD		// 复制文件描述符
			F_GETFD 	// 获取文件描述符标志
			F_SETFD 	// 设置文件描述符标志
			F_GETFL 	// 获取文件状态标志
			F_SETFL 	// 设置文件状态标志
			F_GETLK 	// 获取文件锁
			F_SETLK 	// 设置文件锁
 			F_SETLKW	// 类似于F_SETLK,但等待返回
			F_SETOWN	// 设置当前接收 SIGIO 和 SIGURG 信号的进程ID和进程组ID
			F_GETOWN	// 获取当前接收 SIGIO 和 SIGURG 信号的进程ID和进程组ID
@param3: 本例中用作指定一个进程作为文件的“属主(filp->owner)”,这样内核才知道信号要发给哪个进程。
@return: 返回值与命令值有关,失败返回-1,成功则返回某个其他值

使用 fcntl(fd, F_SETOWN, getpid()) 将本应用程序的进程号告诉给内核

2.1.3 开启异步通知

  使用下面的代码开启异步通知

int flags;
flags = fcntl(fd, F_GETFL);			 // 获取当前的进程状态
fcntl(fd, F_SETFL, flags | FASYNC);	 // 开启当前进程异步通知功能

2.2 驱动中的信号处理

 驱动中信号的结构体定义如下:

struct fasync_struct {
	spinlock_t fa_lock;
	int magic;
	int fa_fd;
	struct fasync_struct *fa_next; // singly linked list 
	struct file *fa_file;
	struct rcu_head fa_rcu;
};

2.2.1 .fasync函数

 相关的函数如下:
 如果要实现异步通知,首先应该实现 file_operations 中的fasync接口

static int vser_fasync(int fd, struct file *filp, int on)
{
	return 0;
}

struct file_operations vser_fops = {
	.fasync = vser_fasync,
};

2.2.2 fasync_helper 函数

 在fasync接口中通常调用 fasync_helper 函数对fasync_struct结构体进行初始化,fasync_helper 定义如下:

原    型: int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
功    能: 初始化 fasync_struct 结构体,设置 fasync 队列,将fd,filp和定义的结构体传给内核
@param1: 和fasync接口中参数一相同
@param2: 和fasync接口中参数二相同
@param3: 和fasync接口中参数三相同
@param4: 要初始化的fasync_struct结构体,注意要传的是二级指针,通常定义一个fasync_struct结构体指针,传它的地址
@return: 负数表示失败,0表示fasync队列未更改,正数表示添加或者删除条目

 在关闭驱动文件时需要释放 fasync_struct,函数fasync_helper在内核中的实现如下,从中可以看出如果on等于0时,会执行fasync_remove_entry函数,因此可以同样调用file_operations中的fasync接口,将传入的参数改为(-1, filp, 0)即可

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
	if (!on)
		return fasync_remove_entry(filp, fapp);
	return fasync_add_entry(fd, filp, fapp);
}

2.2.3 kill_fasync 函数

 当设备可以访问时,驱动程序需要向应用程序发送信号,使用的函数是 kill_fasync 该函数的定义如下:

原    型: void kill_fasync(struct fasync_struct **fapp, int sig, int band)
功    能: 当设备可以访问时,向应用程序发送信号 
@param1: 要操作的 fasync_struct 结构体
@param2: 要发送的信号,常用的信号如上介绍的各种宏
@param3: 可读时设置为 POLL_IN,可写时设置为 POLL_OUT
@return: 无返回值

3. 示例代码

3.1 demo.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/kfifo.h>
#include <linux/timer.h>

#define VSER_CHRDEV_NAME ("vser_chrdev")
#define VSER_CLASS_NAME  ("vser_class")
#define VSER_DEVICE_NAME ("device_chrdev")
#define DELAY_MS (5000)

// 虚拟串口驱动相关结构体
struct vser_dev{
	dev_t devno;
	int major;
	int minor;
	struct cdev cdev;
	struct class *cls;
	struct device *dev;
	unsigned int timeperiod;			// 延时周期,ms
	struct timer_list timer_t;			// 低分辨率定时器
	struct fasync_struct *vser_fasync;	// 异步相关的结构体
};
struct vser_dev test_vser_dev;

DEFINE_KFIFO(virtual_serial_fifo, char, 32);

void timer_function(unsigned long arg)
{	
	printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);
	
	printk("before kill_fasync.\n");
	kill_fasync(&test_vser_dev.vser_fasync, SIGIO, POLL_IN);
	printk("after kill_fasync.\n");
}

static int vser_fasync(int fd, struct file *filp, int on)
{
	int ret;
	struct vser_dev *test_vser_dev = filp->private_data;
	
	printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);
	
	ret = fasync_helper(fd, filp, on, &test_vser_dev->vser_fasync);		// 初始化前面定义的 fasync_struct 结构体指针
	if (ret < 0)
	{
		printk("fasync_helper failed.\n");
		ret = EIO;
	}
	
	return 0;
}

static int vser_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &test_vser_dev;
	
	printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);

	return 0;
}

static int vser_release(struct inode *inode, struct file *filp)
{
	printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);

	vser_fasync(-1, filp, 0);	// 在release函数中同样调用fasync_helper释放fasync_struct
	
	return 0;
}

static ssize_t vser_read(struct file *filp, char __user *userbuf, size_t size, loff_t *offset)
{
	unsigned int copied_num, ret;	
	
	printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);

	if ( !kfifo_is_empty(&virtual_serial_fifo) )	// fifo为空返回true
	{
		ret = kfifo_to_user(&virtual_serial_fifo, userbuf, size, &copied_num);	
		if (ret < 0)
		{
			printk("kfifo_to_user failed.\n");
			return ret;
		}
		printk("%s -- %s -- copied_num = %d.\n", __FILE__, __FUNCTION__, copied_num);
	}
	
	return size;
}

static ssize_t vser_write(struct file *filp, const char __user *userbuf, size_t size, loff_t *offset)
{
	int retval, copied_num;
	
	printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);
	
	retval = kfifo_from_user(&virtual_serial_fifo, userbuf, size, &copied_num);  
	if (retval == -EFAULT)  
	{   
		printk("kfifo_from_user failed.\n");   
	}  
	printk("%s -- %s -- copied_num = %d.\n", __FILE__, __FUNCTION__, copied_num);

	return 0;
}



struct file_operations vser_fops = {
	.owner = THIS_MODULE,
	.open = vser_open,
	.release = vser_release,
	.read = vser_read,
	.write = vser_write,
	.fasync = vser_fasync,
};

static int __init vser_init(void)
{
	int ret;

	printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);
	
	if ( test_vser_dev.major )
	{
		test_vser_dev.devno = MKDEV(test_vser_dev.major, 0);
		ret = register_chrdev_region(test_vser_dev.devno, 1, VSER_CHRDEV_NAME);
		if ( ret < 0 )
		{
			printk("register_chrdev_region failed.\n");
			goto register_chrdev_region_err;
		}
	}
	else
	{
		ret = alloc_chrdev_region(&test_vser_dev.devno, 0, 1, VSER_CHRDEV_NAME);
		test_vser_dev.major = MAJOR(test_vser_dev.devno);
		test_vser_dev.minor = MINOR(test_vser_dev.devno);
		if ( ret < 0 )
		{
			printk("alloc_chrdev_region failed.\n");
			goto alloc_chrdev_region_err;
		}
	}

	cdev_init(&test_vser_dev.cdev, &vser_fops);
	ret = cdev_add(&test_vser_dev.cdev, test_vser_dev.devno, 1);
	{
		if ( ret < 0 )
		{
			printk("cdev_add failed.\n");
			goto cdev_add_err;
		}
	}

	test_vser_dev.cls = class_create(THIS_MODULE, VSER_CLASS_NAME);
	if ( IS_ERR(test_vser_dev.cls) )
	{
		printk("class_create failed.\n");
		ret = PTR_ERR(test_vser_dev.cls);
		goto class_create_err;
	}

	test_vser_dev.dev = device_create(test_vser_dev.cls, NULL, test_vser_dev.devno, NULL, VSER_DEVICE_NAME);
	if ( IS_ERR(test_vser_dev.dev) )
	{
		printk("device_create failed.\n");
		ret = PTR_ERR(test_vser_dev.dev);
		goto device_create_err;
	}

	init_timer(&test_vser_dev.timer_t);		// 初始化低分辨率定时器
	test_vser_dev.timeperiod = DELAY_MS;
	test_vser_dev.timer_t.expires = get_jiffies_64() + msecs_to_jiffies(test_vser_dev.timeperiod);
	test_vser_dev.timer_t.function = timer_function;
	test_vser_dev.timer_t.data = (unsigned long)&test_vser_dev;
	add_timer(&test_vser_dev.timer_t);
	
	return 0;

device_create_err:
	class_destroy(test_vser_dev.cls);
class_create_err:
	cdev_del(&test_vser_dev.cdev);
cdev_add_err:
	unregister_chrdev_region(test_vser_dev.devno, 1);
alloc_chrdev_region_err:
register_chrdev_region_err:
	return ret;
}

static void __exit vser_exit(void)
{
	printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);

	del_timer(&test_vser_dev.timer_t);
	device_destroy(test_vser_dev.cls, test_vser_dev.devno);
	class_destroy(test_vser_dev.cls);	
	cdev_del(&test_vser_dev.cdev);
	unregister_chrdev_region(test_vser_dev.devno, 1);
}

module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");

3.2 test.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>

const char *pathname = "/dev/device_chrdev";
int fd;
char write_buf[32] = "hello";
char read_buf[32]  = "\0";

static void sigio_signal_func(int signum)
{
	int err = 0;

	err = read(fd, read_buf, sizeof(read_buf));
	if(err < 0) 
	{
		printf("read error\n");	// 读取错误;
		strcpy(write_buf, "linux linux");
		write(fd, write_buf, sizeof(write_buf));
	} 
	else 
	{
		printf("sigio signal! buf = %s\r\n", read_buf);
	}
}

int main(int argc, const char *argv[])
{
	int flags = 0;
	
	fd = open(pathname, O_RDWR, 0666);
	if (fd < 0)
	{
		printf("open failed\n");
		return fd;
	}

	write(fd, write_buf, sizeof(write_buf));

	signal(SIGIO, sigio_signal_func);	// 设置信号SIGIO的处理函数
	
	fcntl(fd, F_SETOWN, getpid());		// 设置当前进程接收SIGIO信号

	flags = fcntl(fd, F_GETFL);			// 获取当前的进程状态
	// 通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函数就会执行
	fcntl(fd, F_SETFL, flags | FASYNC);	// 设置进程启用异步通知功能

	while(1) 
	{
	
	}

	close(fd);

	return 0;
}

3.3 Makefile

KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-04.00.00.04/board-support/linux-4.9.28/
PWD := $(shell pwd)

EXEC = app
OBJS = test.o
CC   = arm-linux-gnueabihf-gcc

$(EXEC):$(OBJS)
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
	$(CC) $^ -o $@
.o:.c
	$(CC) -c $<

install:
	sudo cp *.ko  app /tftpboot
	sudo cp *.ko app /media/linux/rootfs1/home/root/
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app
clean:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app

obj-m += demo.o

3.4 测试结果

root@am335x-evm:~# insmod demo.ko 
[	68.640752] demo.c -- vser_init -- 127.
root@am335x-evm:~# ./app
[	70.200820] demo.c -- vser_open -- 62.
[	70.207501] demo.c -- vser_write -- 100.
[	70.219652] demo.c -- vser_write -- copied_num = 32.
[	70.230214] demo.c -- vser_fasync -- 46.
[	73.681855] demo.c -- timer_function -- 34.
[	73.688605] before kill_fasync.
[	73.691770] after kill_fasync.
[	73.698877] demo.c -- vser_read -- 80.
[	73.706815] demo.c -- vser_read -- copied_num = 32.
sigio signal! buf = hello
^C
[   86.483844] demo.c -- vser_fasync -- 46.
[	86.490340] demo.c -- vser_release -- 69.
[	86.500087] demo.c -- vser_fasync -- 46.

root@am335x-evm:~# rmmod demo.ko 
[	94.961113] demo.c -- vser_exit -- 199.
发布了57 篇原创文章 · 获赞 64 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_36310253/article/details/103146287