当前位置:首页>Linux>腾讯笔试必刷题:嵌入式Linux下的GPIO中断驱动与应用层交互实现

腾讯笔试必刷题:嵌入式Linux下的GPIO中断驱动与应用层交互实现

  • 2026-09-02 19:39:49
腾讯笔试必刷题:嵌入式Linux下的GPIO中断驱动与应用层交互实现

大家好,我是一个爱分享的牛马程序员,工作中碰到,加上自己理解,很高兴给大家分享。

-begin-

题目:在嵌入式Linux系统中,编写一个GPIO中断驱动及应用程序,实现以下功能:

1.驱动层:注册GPIO中断(假设按键连接在GPIO5,下降沿触发),实现中断处理函数,记录按键按下次数;
2.应用层:通过sysfs接口读取按键按下次数,并在次数超过5次时打印提示信息“按键已按下5次以上”;
3.驱动需支持动态加载和卸载,且能正确释放资源。

详细分析过程:

一、核心考点拆解

本题是腾讯嵌入式Linux驱动开发岗位的常考题型,主要考察:

•GPIO中断的注册与处理(request_irq、中断处理函数编写);
•sysfs接口的创建(device_create_file、sysfs_ops),实现用户态与内核态的数据交互;
•驱动的动态加载(module_init)与卸载(module_exit),以及资源释放(free_irq、销毁sysfs文件);
•中断上下文的注意事项(避免阻塞操作、使用原子变量)。

二、关键设计思路

4.驱动层设计:
◦设备结构体:包含GPIO编号、中断号、按键次数(用atomic_t确保原子操作)、设备对象等;
◦中断注册:在驱动初始化时,通过gpio_to_irq获取GPIO对应的中断号,调用request_irq注册中断(下降沿触发,IRQF_TRIGGER_FALLING);
◦中断处理函数:在中断发生时(按键按下),通过atomic_inc递增按键次数,注意函数需返回IRQ_HANDLED;
◦sysfs接口:创建count属性文件,通过show方法向应用层返回按键次数,实现用户态读取。
5.应用层设计:
◦打开sysfs下的count文件(如/sys/class/key_drv/key_device/count);
◦循环读取文件内容(按键次数),转换为整数后判断是否超过5次,若超过则打印提示信息;
◦读取间隔可设为1秒,避免频繁访问。
6.资源管理:
◦驱动卸载时,需调用free_irq释放中断,device_remove_file删除sysfs文件,class_destroy销毁设备类,避免资源泄漏;
◦中断处理函数中禁止使用printk等可能导致阻塞的操作(本题简化处理,实际应减少打印)。

三、代码实现

1. 驱动代码(key_drv.c)

#include <linux/init.h>

#include <linux/module.h>

#include <linux/gpio.h>

#include <linux/interrupt.h>

#include <linux/device.h>

#include <linux/sysfs.h>

#include <linux/atomic.h>

#define GPIO_KEY 5                  // 按键连接的GPIO编号

#define DEV_NAME "key_device"       // 设备名

#define CLASS_NAME "key_drv"        // 设备类名

// 设备结构体

struct key_dev {

   int gpio;                       // GPIO编号

   int irq;                        // 中断号

   atomic_t count;                 // 按键次数(原子变量)

   struct device *dev;             // 设备对象

};

static struct key_dev *key_device;

static struct class *key_class;

// sysfs的show方法:向应用层返回按键次数

static ssize_t count_show(struct device *dev, struct device_attribute *attr, char *buf) {

   struct key_dev *kdev = dev_get_drvdata(dev);

   return sprintf(buf, "%d\n", atomic_read(&kdev->count));

}

// 定义sysfs属性

static DEVICE_ATTR(count, S_IRUGO, count_show, NULL);

// 中断处理函数:按键按下时调用

static irqreturn_t key_irq_handler(int irq, void *dev_id) {

   struct key_dev *kdev = (struct key_dev *)dev_id;

   atomic_inc(&kdev->count);       // 按键次数+1

   return IRQ_HANDLED;

}

// 驱动初始化函数

static int __init key_drv_init(void) {

   int ret;

   // 分配设备结构体内存

   key_device = kmalloc(sizeof(struct key_dev), GFP_KERNEL);

   if (!key_device) {

       return -ENOMEM;

   }

   // 初始化设备结构体

   key_device->gpio = GPIO_KEY;

   atomic_set(&key_device->count, 0);  // 初始次数为0

   // 申请GPIO

   ret = gpio_request(key_device->gpio, DEV_NAME);

   if (ret) {

       printk("gpio_request failed: %d\n", ret);

       kfree(key_device);

       return ret;

   }

   // 设置GPIO为输入

   gpio_direction_input(key_device->gpio);

   // 获取中断号

   key_device->irq = gpio_to_irq(key_device->gpio);

   if (key_device->irq < 0) {

       printk("gpio_to_irq failed: %d\n", key_device->irq);

       gpio_free(key_device->gpio);

       kfree(key_device);

       return key_device->irq;

   }

   // 注册中断(下降沿触发,传递设备结构体作为参数)

   ret = request_irq(key_device->irq, key_irq_handler,

                    IRQF_TRIGGER_FALLING | IRQF_SHARED,

                    DEV_NAME, key_device);

   if (ret) {

       printk("request_irq failed: %d\n", ret);

       gpio_free(key_device->gpio);

       kfree(key_device);

       return ret;

   }

   // 创建设备类

   key_class = class_create(THIS_MODULE, CLASS_NAME);

   if (IS_ERR(key_class)) {

       printk("class_create failed\n");

       free_irq(key_device->irq, key_device);

       gpio_free(key_device->gpio);

       kfree(key_device);

       return PTR_ERR(key_class);

   }

   // 创建设备节点

   key_device->dev = device_create(key_class, NULL, MKDEV(0, 0), key_device, DEV_NAME);

   if (IS_ERR(key_device->dev)) {

       printk("device_create failed\n");

       class_destroy(key_class);

       free_irq(key_device->irq, key_device);

       gpio_free(key_device->gpio);

       kfree(key_device);

       return PTR_ERR(key_device->dev);

   }

   // 创建sysfs属性文件

   ret = device_create_file(key_device->dev, &dev_attr_count);

   if (ret) {

       printk("device_create_file failed: %d\n", ret);

       device_destroy(key_class, MKDEV(0, 0));

       class_destroy(key_class);

       free_irq(key_device->irq, key_device);

       gpio_free(key_device->gpio);

       kfree(key_device);

       return ret;

   }

   printk("key driver init success\n");

   return 0;

}

// 驱动卸载函数

static void __exit key_drv_exit(void) {

   // 移除sysfs文件

   device_remove_file(key_device->dev, &dev_attr_count);

   // 销毁设备

   device_destroy(key_class, MKDEV(0, 0));

   // 销毁设备类

   class_destroy(key_class);

   // 释放中断

   free_irq(key_device->irq, key_device);

   // 释放GPIO

   gpio_free(key_device->gpio);

   // 释放设备结构体内存

   kfree(key_device);

   printk("key driver exit success\n");

}

module_init(key_drv_init);

module_exit(key_drv_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("牛马程序员");

MODULE_DESCRIPTION("GPIO Key Interrupt Driver");

2. 应用层代码(key_app.c)

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <fcntl.h>

#define COUNT_PATH "/sys/class/key_drv/key_device/count"  // sysfs文件路径

int main() {

   int fd;

   char buf[32];

   int count;

   // 打开count文件

   fd = open(COUNT_PATH, O_RDONLY);

   if (fd == -1) {

       perror("open failed");

       return -1;

   }

   printf("开始读取按键次数(按Ctrl+C退出)...\n");

   while (1) {

       // 移动文件指针到开头

       lseek(fd, 0, SEEK_SET);

       // 读取按键次数

       ssize_t len = read(fd, buf, sizeof(buf) - 1);

       if (len == -1) {

           perror("read failed");

           break;

       }

       buf[len] = '\0';

       // 转换为整数

       count = atoi(buf);

       printf("当前按键次数:%d\n", count);

       // 判断是否超过5次

       if (count > 5) {

           printf("提示:按键已按下5次以上\n");

       }

       sleep(1);  // 1秒读取一次

   }

   close(fd);

   return 0;

}

四、代码解析与考点总结

7.驱动层核心:
◦原子变量atomic_t确保在中断上下文和sysfs访问时,按键次数的修改和读取不会冲突(避免竞态);
◦中断处理函数必须简洁,仅执行计数递增操作,不可调用msleep等阻塞函数;
◦sysfs的device_attribute通过show方法向应用层暴露数据,实现内核态与用户态的安全交互。
8.应用层逻辑:
◦通过sysfs接口读取数据,无需了解驱动内部实现,符合Linux的“一切皆文件”哲学;
◦循环读取时使用lseek重置文件指针,避免每次读取从上次位置开始(sysfs文件通常大小为0,需重新定位)。
9.易错点:
◦中断注册时IRQF_SHARED标志允许多个设备共享中断,但需确保dev_id唯一;
◦驱动卸载时必须按创建的逆序释放资源(先删除文件,再销毁设备,最后释放中断和GPIO),否则可能导致内核崩溃;
◦应用层需确保sysfs文件路径正确,驱动加载后可通过ls /sys/class/key_drv/查看设备是否创建。
10.腾讯考点延伸:面试中可能追问“如何在中断中实现按键消抖?”——可在中断处理函数中启动定时器,延迟几毫秒后再计数,过滤抖动导致的多次触发;或问“如何通过ioctl实现应用层控制中断使能/禁用?”——需在驱动中实现file_operations的unlocked_ioctl方法,添加相应命令处理逻辑。

GPIO中断是嵌入式设备与外部交互的基础,腾讯等大厂注重驱动的稳定性和资源管理能力,本题涵盖了从中断注册到用户态交互的全流程,是驱动开发的必掌握知识点。

-end-

如果文章对你有提升,帮忙点赞,分享,关注。十分感谢

最新文章

随机文章