六、Linux驱动之异步通知

本文详细介绍了异步通知的概念,以及如何在Linux环境下利用信号机制实现设备与应用程序之间的异步通信。通过具体的代码示例,展示了信号的接收与释放过程,以及如何在设备驱动程序中实现异步通知。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 基本概念

    异步通知的意思是: 一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号驱动的异步I/O”。信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
    阻塞I/O意味着一直等待设备可访问后再访问,非阻塞I/O中使用poll()意味着查询设备是否可访问,而异步通知则意味着设备通知用户自身可访问,之后用户再进行I/O处理。由此可见,这几种I/O方式可以相互补充。

2. 使用异步通知

    使用信号进行进程间通信(IPC)是UNIX中的一种传统机制,Linux也支持这种机制。在Linux中,异步通知使用信号来实现, Linux中可用的信号及其定义如下: 

    除了SIGSTOPSIGKILL两个信号外,进程能够忽略或捕获其他的全部信号。一个信号被捕获的意思是当一个信号到达时有相应的代码处理它,如果一个信号没有被这个进程所捕获,内核将采用默认行为处理。

2.1 信号的接收

    在用户程序中, 为了捕获信号, 可以使用signal() 函数来设置对应信号的处理函数:

void (*signal(int signum, void (*handler))(int)))(int);

    第一个参数指定信号的值,第二个参数指定针对前面信号值的处理函数,若为SIG_IGN,表示忽略该信号;若为SIG_DFL,表示采用系统默认方式处理信号;若为用户自定义的函数,则信号被捕获到后,该函数将被执行。如果signal() 调用成功,它返回最后一次为信号signum绑定的处理函数的handler值,失败则返回SIG_ERR。
    信号接收部分完全在应用程序中,完整应用程序fasync_text.c代码如下:

#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>

/* fasync_text
  */
void my_signal_fun(int signum)
{
	printf("received signal SIGIO!\n");
}

int main(int argc, char **argv)
{
	int Oflags;
	int fd;
	
	signal(SIGIO, my_signal_fun);
	
	fd = open("/dev/test", O_RDWR);
	if (fd < 0)
	{
		printf("can't open /dev/test !\n");
	}

	fcntl(fd, F_SETOWN, getpid());    //定义该驱动要发送信号给哪个进程(PID)
	Oflags = fcntl(fd, F_GETFL); 
	fcntl(fd, F_SETFL, Oflags | FASYNC);

	while (1)
	{
		sleep(1000);
	}
	return 0;
}

    fcntl(fd, F_SETOWN, getpid());
    Oflags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, Oflags | FASYNC);

    这3行代码的作用是为了能在用户空间中处理一个设备释放的信号, 分别的作用是:
    (1) 通过F_SETOWN IO控制命令设置设备文件的拥有者为本进程, 这样从设备驱动发出的信号才能被本进程接收到。
    (2) 通过F_SETFL IO控制命令设置设备文件以支持FASYNC, 即异步通知模式。
    (3) 通过signal( ) 函数连接信号和信号处理函数。

    这样,当应用程序接收到SIGIO这个信号时,就会调用my_signal_fun()函数了,就可以实现设备驱动通知应用程序去执行某些工作了。

2.2 信号的释放

    应用程序端负责捕获信号,则设备驱动负责释放信号,需要在设备驱动程序中增加信号释放的相关代码。
    为了使设备支持异步通知机制, 驱动程序中涉及3项工作:
    (1) 支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID。不过此项工作已由内核完成,设备驱动无须处理。
    (2) 支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。因此,驱动中应该实现fasync() 函数。
    (3) 在设备资源可获得时,调用kill_fasync()函数激发相应的信号。

    上述的3项工作和应用程序中的3项是对应的,他们之间的关系,如下图:


完整驱动程序fasync_drv.c代码如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/cdev.h>


int major;
static struct cdev test_cdev;
static struct class *test_class;
 
struct timer_list timer;    //定义一个定时器
static struct fasync_struct *test_async;

/*每隔5秒发送一次SIGIO信号*/
void test_function(unsigned long arg)
{
	mod_timer(&timer, jiffies + (5*HZ));	//重新设置定时器,每隔5秒执行一次

	printk("send signal SIGIO to user!\n");
	kill_fasync(&test_async, SIGIO, POLL_IN);
}

static int test_open(struct inode *inode, struct file *file)
{
	init_timer(&timer);     		//初始化定时器
    timer.expires = jiffies+(5*HZ); //设定超时时间,5秒
    timer.data = 5;    				//传递给定时器超时函数的值
    timer.function = test_function; //设置定时器超时函数
    add_timer(&timer); 				//添加定时器,定时器开始生效

	return 0;
}

static int test_fasync(int fd, struct file * file, int on)
{
	printk("driver: test_fasync\n");
	return fasync_helper(fd, file, on, &test_async);
}

int test_close(struct inode *inode, struct file *file)
{	
	del_timer(&timer);		 //删除定时器
	test_fasync(-1, file, 0);	 //将文件从异步通知列表中删除
	return 0;
}

static struct file_operations test_fops = {
    .owner   =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open    =  test_open,     
    .fasync  =	test_fasync,
    .release =  test_close,
};

static int test_init(void)
{
	int result;
	dev_t devid = MKDEV(major, 0);	//从主设备号major,次设备号0得到dev_t类型
	if (major) 
	{
		result=register_chrdev_region(devid, 1, "test");	//注册字符设备
	} 
	else 
	{
		result=alloc_chrdev_region(&devid, 0, 1, "test");	//注册字符设备
		major = MAJOR(devid);		//从dev_t类型得到主设备
	}
	if(result<0)
		return result;
	
	cdev_init(&test_cdev, &test_fops);
	cdev_add(&test_cdev, devid, 1);
	test_class = class_create(THIS_MODULE, "test_drv");
	class_device_create(test_class, NULL, MKDEV(major, 0), NULL, "test"); /* /dev/buttons */

	return 0;
}

static void test_exit(void)
{
	class_device_destroy(test_class, MKDEV(major, 0));
	class_destroy(test_class);
	cdev_del(&test_cdev);
	unregister_chrdev_region(MKDEV(major, 0), 1);
}

module_init(test_init);
module_exit(test_exit);
MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");

Makefile如下:

KERN_DIR = /work/system/linux-2.6.22.6    //内核目录
 
all:
	make -C $(KERN_DIR) M=`pwd` modules 
 
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
 
obj-m	+= fasync_drv.o

3. 测试

内核:linux-2.6.22.6
编译器:arm-linux-gcc-3.4.5
环境:ubuntu9.10

fasync_drv.c、fasync_test.c、Makefile三个文件放入网络文件系统内,在ubuntu该目录下执行:
    make
    arm-linux-gcc -o fasync_test fasync_test.c

在挂载了网络文件系统的开发板上进入相同目录,执行“ls”查看:

装载驱动:
    insmod fasync_drv.ko
运行测试程序:
    ./fasync_test
结果如下:

每隔5秒都会打印:
    send signal SIGIO to user!
    received signal SIGIO!

4. 程序说明

应用程序open打开驱动,驱动里就会创建一个5秒循环启动的定时器,然后应用程序设置好异步通知模式后,直接进入while死循环里。驱动程序每5秒启动定时器函数发送SIGIO信号,打印send signal SIGIO to user!,应用程序就会接受到信号并打印received signal SIGIO!。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值