使用Linux驱动程序的fasync(文件异步通知机制)向用户空间发送SIGIO信号的学习记录

前言

本文学习使用Linux驱动程序的fasync(文件异步通知机制)向用户空间发送SIGIO信号。

fasync(文件异步通知机制)名字的来历

fasync“file asynchronous” 的缩写,意思是 文件异步通知
这里的文件是指文件结构体struct file *file ,关于文件结构体,我在前面的博文中已经详细介绍过了,说情请看 https://blog.youkuaiyun.com/wenhao_ir/article/details/144927750

重点是文件描述符(fd)记录的相关信息就存储于文件结构体struct file *file 中的。

由于整个异步通知机制是借助于设备文件的文件描述符(fd)进行的操作,所以这种异步通知称为叫做“文件异步通知”,在Linux中缩写为fasync

本文代码源自之前的哪篇博文

本文的代码是在博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/145228617 的基础上修改而来的,把其中的wait_event_interruptible异步通知机制去掉,然后加入本文要学习的fasync(文件异步通知机制)。

完整源代码

驱动程序代码(gpio_key_drv.c)

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>


struct gpio_key{
	int gpio;
	struct gpio_desc *gpiod;
	int flag;
	int irq;
} ;

static struct gpio_key *gpio_keys_100ask;

/* 主设备号                                                                 */
static int major = 0;
static struct class *gpio_key_class;

static int g_key = 0;

struct fasync_struct *button_fasync;


/* 环形缓冲区 */
#define BUF_LEN 128
static int g_keys[BUF_LEN];
static int r, w;

#define NEXT_POS(x) ((x+1) % BUF_LEN)

static int is_key_buf_empty(void)
{
	return (r == w);
}

static int is_key_buf_full(void)
{
	return (r == NEXT_POS(w));
}

static void put_key(int key_value)
{
	if (!is_key_buf_full())
	{
		g_keys[w] = key_value;
		w = NEXT_POS(w);
	}
}

static int get_key(void)
{
	int key_value = 0;
	if (!is_key_buf_empty())
	{
		key_value = g_keys[r];
		r = NEXT_POS(r);
	}
	return key_value;
}


/* 实现文件操作结构体中的read函数  */
static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	int err;
	int key_value;
	
	//从缓形缓冲区中取出数据
	key_value = get_key();
	err = copy_to_user(buf, &key_value, 4);

	// 返回值为4表明读到了4字节的数据
	return 4;
}

static int gpio_key_drv_fasync(int fd, struct file *file, int on)
{
	if (fasync_helper(fd, file, on, &button_fasync) >= 0)
		return 0;
	else
		return -EIO;
}


/* 定义自己的file_operations结构体                                              */
static struct file_operations gpio_key_drv = {
	.owner	 = THIS_MODULE,
	.read    = gpio_key_drv_read,
	.fasync  = gpio_key_drv_fasync,
};

static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
    struct gpio_key *gpio_key = dev_id;
    int val;

    // 返回引脚电平的逻辑值,注意:如果是低电平有效,则当物理电平为低电平时,其返回值为1;则当物理电平为高电平时,其返回值为0.
	// 如果要得到物理电平值,可以用函数gpiod_get_raw_value()得到
    val = gpiod_get_value(gpio_key->gpiod);

    // 打印中断号、GPIO引脚编号和电平值
    // printk("Interrupt number: %d; GPIO pin number: %d; Pin Logical value: %d\n", irq, gpio_key->gpio, val);

	// g_key的高8位中存储的是GPIO口的编号,低8位中存储的是按键按下时的逻辑值
	g_key = (gpio_key->gpio << 8) | val;
	//装按键值放入环形缓冲区
	put_key(g_key);

	kill_fasync(&button_fasync, SIGIO, POLL_IN);

    return IRQ_HANDLED;  // 表示中断已处理
}


/* 1. 从platform_device获得GPIO
 * 2. gpio=>irq
 * 3. request_irq
 */
static int gpio_key_probe(struct platform_device *pdev)
{
	int err;

	// 获取设备树节点指针
	struct device_node *node = pdev->dev.of_node;

	// count用于存储设备树中描述的GPIO口的数量
	int count;
	
	int i;
	enum of_gpio_flags flag;
	unsigned flags = GPIOF_IN;
		
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	count = of_gpio_count(node);
	if (!count)
	{
		printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
		return -1;
	}

	gpio_keys_100ask = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
	if (!gpio_keys_100ask) {
		printk("Memory allocation failed for gpio_keys_100ask\n");
		return -ENOMEM;
	}


	for (i = 0; i < count; i++)
	{
		//  获取GIPO的全局编号及其标志位信息的代码
		gpio_keys_100ask[i].gpio = of_get_gpio_flags(node, i, &flag);
		if (gpio_keys_100ask[i].gpio < 0)
		{
			printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
			return -1;
		}

		// 获取GPIO口的GPIO描述符的代码
		gpio_keys_100ask[i].gpiod = gpio_to_desc(gpio_keys_100ask[i].gpio);
		if (!gpio_keys_100ask[i].gpiod) {
			printk("Failed to get GPIO descriptor for GPIO %d\n", gpio_keys_100ask[i].gpio);
			return -EINVAL;
		}

		// 结构体gpio_key的成员flag用于存储对应的GPIO口是否是低电平有效,假如是低电平有效,成员flag的值为1,假如不是低电平有效,成员flag的值为0。
		// 后续代码实际上并没有用到成员flag,这里出现这句代码只是考虑到代码的可扩展性,所以在这里是可以删除的。
		gpio_keys_100ask[i].flag = flag & OF_GPIO_ACTIVE_LOW;

		// 每次循环都重新初始化flags
    	flags = GPIOF_IN;

		// 假如GPIO口是低电平有效,则把flags添加上低电平有效的信息
		if (flag & OF_GPIO_ACTIVE_LOW)
			flags |= GPIOF_ACTIVE_LOW;

		// 请求一个GPIO硬件资源与设备结构体`pdev->dev`进行绑定
		// 注意,这个绑定操作会在调用函数platform_driver_unregister()注销platform_driver时自动由内核解除绑定操作,所以gpio_key_remove函数中不需要显示去解除绑定
		// 由`devm`开头的函数通常都会内核自动管理资源,咱们在退出函数中不用人为的去释放资源或解除绑定。
		err = devm_gpio_request_one(&pdev->dev, gpio_keys_100ask[i].gpio, flags, NULL);

		// 获取GPIO口的中断请求号
		gpio_keys_100ask[i].irq  = gpio_to_irq(gpio_keys_100ask[i].gpio);
	}

	for (i = 0; i < count; i++)
	{
		char irq_name[32];  // 用于存储动态生成的中断名称

		//使用snprintf()函数将动态生成的中断名称写入irq_name数组
		snprintf(irq_name, sizeof(irq_name), "swh_gpio_irq_%d", i);  // 根据i生成名称

		//调用函数request_irq()来请求并设置一个中断
		err = request_irq(gpio_keys_100ask[i].irq, gpio_key_isr, IRQF_TRIGGER_FALLING, irq_name, &gpio_keys_100ask[i]);
	}

	/* 注册file_operations 	*/
	major = register_chrdev(0, "swh_read_keys_major", &gpio_key_drv);  

	gpio_key_class = class_create(THIS_MODULE, "swh_read_keys_class");
	if (IS_ERR(gpio_key_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "swh_read_keys_major");
		return PTR_ERR(gpio_key_class);
	}

	// 由于这里是把多个按键看成是一个设备,你可以想像一个键盘上对应多个按键,但键盘本身是一个设备,所以只有一个设备文件
	device_create(gpio_key_class, NULL, MKDEV(major, 0), NULL, "read_keys0"); /* /dev/read_keys0 */
        
   
    return 0;
    
}

static int gpio_key_remove(struct platform_device *pdev)
{
    struct device_node *node = pdev->dev.of_node;
    int count;
    int i;

	device_destroy(gpio_key_class, MKDEV(major, 0));
	class_destroy(gpio_key_class);
	unregister_chrdev(major, "swh_read_keys_major");

    count = of_gpio_count(node);
    for (i = 0; i < count; i++) 
	{
        // 只有在irq有效时才释放中断资源
        if (gpio_keys_100ask[i].irq >= 0) {
            // 释放GPIO中断资源,下面这句代码做了下面两件事:
			// 1、解除 `gpio_keys_100ask[i].irq` 中断号和 `gpio_key_isr` 中断处理函数的绑定。
			// 2、解除 `gpio_keys_100ask[i].irq` 中断号和中断处理函数与 `gpio_keys_100ask[i]` 数据结构的绑定。
            free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
        }

        // 释放GPIO描述符
        if (gpio_keys_100ask[i].gpiod) {
            gpiod_put(gpio_keys_100ask[i].gpiod);
        }
    }

    // 释放内存
    kfree(gpio_keys_100ask);
    return 0;
}


static const struct of_device_id irq_matach_table[] = {
    { .compatible = "swh-gpio_irq_key" },
    { },
};

/* 1. 定义platform_driver */
static struct platform_driver gpio_keys_driver = {
    .probe      = gpio_key_probe,
    .remove     = gpio_key_remove,
    .driver     = {
        .name   = "swh_irq_platform_dirver",
        .of_match_table = irq_matach_table,
    },
};

/* 2. 在入口函数注册platform_driver */
static int __init gpio_key_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    err = platform_driver_register(&gpio_keys_driver); 
	
	return err;
}

/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 *     卸载platform_driver
 */
static void __exit gpio_key_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&gpio_keys_driver);
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(gpio_key_init);
module_exit(gpio_key_exit);

MODULE_LICENSE("GPL");

测试程序代码


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

static int fd;
static void sig_func(int sig)
{
	int val;
	read(fd, &val, 4);

	/* 提取 GPIO 编号和逻辑值 */
	int gpio_number = (val >> 8) & 0xFF; // 高8位为 GPIO 编号
	int gpio_value = val & 0xFF;         // 低8位为逻辑值

	/* 打印读到的信息 */
	printf("GPIO Number: %d, Logical Value: %d\n", gpio_number, gpio_value);

}


int main(int argc, char **argv)
{
	int val;
	struct pollfd fds[1];
	int timeout_ms = 5000;
	int ret;
	int	flags;
	
	/* 1. 判断参数 */
	if (argc != 2) 
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}

	signal(SIGIO, sig_func);

	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

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

	while (1)
	{
		printf("https://blog.youkuaiyun.com/wenhao_ir \n");
		sleep(5);
	}
	
	close(fd);
	
	return 0;
}

使用fasync处理按键中断的缺陷说明

由于是在博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/145228617 的基础上修改而来的,而博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/145228617的功能是处理开发板上按键产生的中断,这就存在一个问题,当按键按得过快时,短时间内连续发送多次信号以致于用户空间的信号处理函数上一次还没执行完,下一次的信号又来时Linux内核会怎么处理?
答:在 Linux 内核中,当中断处理程序中使用 kill_fasync 向用户空间发送信号时,如果信号的接收方(用户空间进程)的信号处理函数还没有执行完,新的信号到来时,Linux 会将信号合并为一次信号,这意味着相同类型的信号不会排队执行。

假设用户空间注册了 SIGIO 的信号处理函数 sig_func,按键被快速按下,触发多个 kill_fasync(&button_fasync, SIGIO, POLL_IN) 调用:

  • 第一步:按键第一次按下,kill_fasync 发送 SIGIO,用户空间的 sig_func 开始运行。
  • 第二步:按键第二次按下,kill_fasync 再次发送 SIGIO,但内核不会排队多次 SIGIO 信号,而是仅标记目标进程仍然需要处理该信号。
  • 第三步:用户空间处理完 sig_func 后,如果按键已经触发了多次信号,用户空间进程感知到的只是有一次信号到达,这就会导致按键信息丢失。

所以,其实本文代码中实现的fasync(文件异步通知机制),在实际工作应用中,并不会用来处理按键中断,本文只是借助于它来学习这种异步通知机制,真的要处理按键中断的话还是博文https://blog.youkuaiyun.com/wenhao_ir/article/details/145228617 中运用的wait_event_interruptible异步通知机制不错。

相关函数fcntl()的介绍

fcntl 是 Linux 系统中的一个强大的文件控制函数,用于操作文件描述符(fd)。通过 fcntl,可以设置文件描述符(fd)的属性、锁定文件或获取文件状态。


函数原型

#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

参数说明

  1. fd:需要操作的文件描述符。
  2. cmd:控制命令,决定 fcntl 作什么操作。
  3. arg:可选参数,根据 cmd 的不同而不同,有时可以省略。

参数cmd的常用命令及功能

1. 设置文件描述符的属主(属于哪个进程)

  • 命令F_SETOWN
  • 作用:设置当前文件描述符的“属主”(owner),在本文的代码中,即设置到哪底由哪个进程或线程接收来自与设备文件相关的驱动程序发来的信号。
  • 信号类型
    • SIGIO:异步 I/O 事件(如套接字有可读/可写事件)。
    • SIGURG:紧急数据到达(如 TCP 的 out-of-band 数据)。

2. 获取/设置文件描述符标志

  • F_GETFD:获取文件描述符标志(如 FD_CLOEXEC)。
  • F_SETFD:设置文件描述符标志。

3. 获取/设置文件状态标志

  • F_GETFL:获取文件状态标志(如 O_RDONLY, O_NONBLOCK)。
  • F_SETFL:设置文件状态标志。
    • 常用标志:
      • O_APPEND:追加模式。
      • O_NONBLOCK:非阻塞模式。
      • O_SYNC:同步写入模式。

4. 文件锁操作

  • F_GETLK:获取文件锁的状态。

  • F_SETLK:设置文件锁(非阻塞)。

  • F_SETLKW:设置文件锁(阻塞)。

    锁类型:

    • F_RDLCK:共享读锁。
    • F_WRLCK:独占写锁。
    • F_UNLCK:解锁。

5. 文件描述符复制

  • F_DUPFD:复制文件描述符,返回新的文件描述符(从指定起点 arg 开始选择最小的可用描述符)。
  • F_DUPFD_CLOEXEC:类似于 F_DUPFD,但自动设置 FD_CLOEXEC 标志。

返回值

  • 成功:返回与命令相关的值。
  • 失败:返回 -1,并设置 errno

代码详解:与fasync(文件异步通知机制)有关的代码

首先在用户空间的测试程序中用函数signal()为当前进程(注意是进程,信号的接收对象是进程)设置一个信号类型为SIGIO的信号处理函数sig_func(),这样,当进程收到类型为SIGIO的信号,就会去调用函数sig_func(),代码如下:

static void sig_func(int sig)
{
	int val;
	read(fd, &val, 4);

	/* 提取 GPIO 编号和逻辑值 */
	int gpio_number = (val >> 8) & 0xFF; // 高8位为 GPIO 编号
	int gpio_value = val & 0xFF;         // 低8位为逻辑值

	/* 打印读到的信息 */
	printf("GPIO Number: %d, Logical Value: %d\n", gpio_number, gpio_value);

}


signal(SIGIO, sig_func);

然后测试程序打开驱动程序创建的设备文件,在这里设备文件的路径为:

/dev/read_keys0

相关的代码如下:

	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

获得设备文件描述符fd后,就通过函数fcntl()把进程号写入到设备文件描述符fd对应的文件结构体struct file *file中,当然,你也可以认为是把进程号写入到了文件描述符fd中。
关于文件结构体struct file *file的详细介绍,见我的另一篇博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/144927750
文件描述符fd对应的文件结构体struct file *file最终会以输入参数的形式传递给驱动程序中的文件操作结构结构体中的操作函数。
相关代码如下:

fcntl(fd, F_SETOWN, getpid());

这里请先了解下函数fcntl(),我在上面已经介绍过了,这里就不再介绍了。
这句代码的具体作用是设置当前进程号为接收来自设备文件描述符fd关联的驱动程序发来的信号的进程。

接下来设置文件描述中的标志,相关代码如下:

	flags = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, flags | FASYNC);

先用F_GETFL命令取出文件描述符fd的标志,然后再利用命令F_SETFL添加标志FASYNC

通过在文件描述符 fd 上添加标志 FASYNC,文件描述符被配置为支持异步通知功能。

当设备发生事件时,驱动程序可以通过调用 kill_fasync() 函数,向设置了异步通知的文件描述符所属进程发送信号(默认是 SIGIO),比如下面的在驱动程序中的代码:

fasync_helper(fd, file, on, &button_fasync) //将文件描述符及文件结构体中的相关信息放入button_fasync中
......
kill_fasync(&button_fasync, SIGIO, POLL_IN); // 向设置了异步通知的文件描述符所属进程发送信号

另外还要特别注意代码fcntl(fd, F_SETFL, flags | FASYNC);会触发驱动程序中文件操作结构体中的成员fasync指向的函数gpio_key_drv_fasync()的调用,就像用户空间中的read函数会调用驱动程序中的read操作函数一样:

在这里插入图片描述
用户空间主要与fasync(文件异步通知机制)的代码就介绍完毕了,由于上面介绍的最后一句代码fcntl(fd, F_SETFL, flags | FASYNC);会触发驱动程序中函数gpio_key_drv_fasync的调用,所以我们就直接来看驱动程序中的函数gpio_key_drv_fasync

函数gpio_key_drv_fasync的代码如下:

struct fasync_struct *button_fasync;

static int gpio_key_drv_fasync(int fd, struct file *file, int on)
{
	if (fasync_helper(fd, file, on, &button_fasync) >= 0)
		return 0;
	else
		return -EIO;
}

这段代码定义了一个 fasync 文件操作函数,用于处理文件描述符的异步通知配置(FASYNC 标志的启用和禁用)。以下是详细解释:


代码逐步解析

  1. button_fasync

    struct fasync_struct *button_fasync;
    
    • 定义:这是一个指向 struct fasync_struct 的指针,代表一个链表,用于存储启用了异步通知的文件描述符信息。
    • 作用:当设备有事件需要通知用户空间时,驱动会使用这个链表,通过 kill_fasync() 函数向所有启用了异步通知的进程发送信号。
  2. 函数定义

    static int gpio_key_drv_fasync(int fd, struct file *file, int on)
    
    • 函数名gpio_key_drv_fasync,是驱动程序的 fasync 文件操作函数。
    • 参数
      • fd:文件描述符,由用户空间程序传递。
      • file:指向与文件描述符关联的 struct file 结构体。
      • on:一个布尔值,表示是否启用(非零值)或禁用(零值)异步通知。
    • 返回值
      • 成功返回 0
      • 失败返回 -EIO
  3. 调用 fasync_helper

    if (fasync_helper(fd, file, on, &button_fasync) >= 0)
        return 0;
    else
        return -EIO;
    
    • fasync_helper() 的作用
      • 这是内核提供的一个辅助函数,用于管理异步通知链表。
      • 根据 on 的值:
        • 如果 on != 0,将 file 添加到 button_fasync 链表。
        • 如果 on == 0,从 button_fasync 链表中移除 file
      • 如果成功操作链表,返回非负值;否则返回负值。
    • 代码逻辑
      • 如果 fasync_helper() 成功(返回值 ≥ 0),函数返回 0
      • 如果失败(返回值 < 0),函数返回错误码 -EIO

工作原理

  1. 启用异步通知

    • 当用户空间调用 fcntl(fd, F_SETFL, flags | FASYNC) 时:
      • 内核会调用驱动的 gpio_key_drv_fasync() 函数,并将 on 参数设为非零值。
      • 通过 fasync_helper(),将文件描述符关联的 file 结构体加入到 button_fasync 链表。
    • 当设备事件发生时,驱动可以调用:
      kill_fasync(&button_fasync, SIGIO, POLL_IN);
      
      • 向用户空间的目标进程发送 SIGIO 信号,通知事件发生。
  2. 禁用异步通知

    • 当用户空间调用 fcntl(fd, F_SETFL, flags & ~FASYNC) 时:
      • 内核会调用 gpio_key_drv_fasync() 函数,并将 on 参数设为零。
      • 通过 fasync_helper(),将文件描述符关联的 filebutton_fasync 链表中移除。
    • 此后,设备事件将不再触发异步通知。

总结

  • gpio_key_drv_fasync 是驱动中负责管理异步通知的关键函数
  • 它通过 fasync_helper()
    • 添加或移除文件描述符到异步通知链表。
  • 应用场景
    • 异步事件通知:设备(如按键)触发事件后,通过 SIGIO 信号通知用户空间程序。
  • 核心机制
    • button_fasync 链表存储启用了异步通知的文件描述符。
    • 驱动调用 kill_fasync() 遍历链表并发送信号。

上面这段介绍把驱动程序中的中断处理函数gpio_key_isr中的代码:

kill_fasync(&button_fasync, SIGIO, POLL_IN);

也介绍了,这里就不多赘述了,只是要注意下第3个参数POLL_IN实际上没有啥实际作用,它的主要作用就是使代码增加了语义信息,使开发者可以快速理解该信号与“可读事件”相关。

至些,整个工程中与“fasync(文件异步通知机制)发送信号”的部分就解析完毕,从整个流程来看,还是比较简单和简洁的。

设备文件的编写

和下面这篇博文一样:
https://blog.youkuaiyun.com/wenhao_ir/article/details/145225508

Makfile文件内容

# 使用不同的Linux内核时, 一定要修改KERN_DIR,KERN_DIR代表已经配置、编译好的Linux源码的根目录

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules
	$(CROSS_COMPILE)gcc -o button_test_03 button_test.c

clean:
	make -C $(KERN_DIR) M=`pwd` clean
	rm -rf modules.order
	rm -f button_test_02

obj-m += gpio_key_drv.o

交叉编译

工程目录复制到Ubuntu中。
在这里插入图片描述

make

把生成的目录文件复制到NFS网络文件目录中,备用。
在这里插入图片描述

加载驱动程序模块

打开串口终端→打开开发板→挂载网络文件系统

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
insmod  /mnt/read_key_irq_signal/gpio_key_drv.ko

在这里插入图片描述

查看有没有相应的设备文件生成

ls /dev/

有了,如下图所示:
在这里插入图片描述

运行测试程序

cd /mnt/read_key_irq_signal/
./button_test_03 /dev/read_keys0

运行后,按下按键,结果如下图所示:
在这里插入图片描述
测试结果表明,虽然测试程序的主程序中在不断循环运行打印信息的语句时,当它收到信号时,仍能去执行信号处理函数。

这就说明程序测试通过。

附完整工程文件

https://pan.baidu.com/s/11vGSz0AknsHGlVx8YfY4Hw?pwd=j2xs

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值