嵌入式linux学习记录十,定时器

定时器去抖:

复制代码
  #### 数据结构

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

  每个按键独立一个定时器,设计合理。
复制代码
  ##### 定时器初始化

      /* 原代码 */
      setup_timer(&gpio_keys_100ask[i].key_timer,
                  key_timer_expire,
                  &gpio_keys_100ask[i]);
      gpio_keys_100ask[i].key_timer.expires = ~0;
      add_timer(&gpio_keys_100ask[i].key_timer);

  `setup_timer` 是内核 4.15 之前的旧接口,新内核已废弃。回调函数参数是 `unsigned long`,类型不安全。

  **改进:**

      /* 使用新接口 */
      timer_setup(&gpio_keys_100ask[i].key_timer,
                  key_timer_expire, 0);
      gpio_keys_100ask[i].key_timer.expires = ~0;
      add_timer(&gpio_keys_100ask[i].key_timer);

      /* 回调函数改为 */
      static void key_timer_expire(struct timer_list *t)
      {
          struct gpio_key *gpio_key = from_timer(gpio_key, t, key_timer);
          ...
      }
复制代码
  ##### 中断处理

      /* 原代码 */
      static irqreturn_t gpio_key_isr(int irq, void *dev_id)
      {
          struct gpio_key *gpio_key = dev_id;
          printk("gpio_key_isr key %d irq happened\n", gpio_key->gpio);
          mod_timer(&gpio_key->key_timer, jiffies + HZ/5);
          return IRQ_HANDLED;
      }

  `HZ/5 = 200ms` 去抖时间太长,正常按键响应会有明显延迟。`printk` 在中断里频繁打印影响性能。

  **改进:**

      static irqreturn_t gpio_key_isr(int irq, void *dev_id)
      {
          struct gpio_key *gpio_key = dev_id;
          /* 去抖时间改为 20ms */
          mod_timer(&gpio_key->key_timer, jiffies + msecs_to_jiffies(20));
          return IRQ_HANDLED;
      }
复制代码
  ##### 定时器回调

      /* 原代码 */
      static void key_timer_expire(unsigned long data)
      {
          struct gpio_key *gpio_key = data;
          int val;
          int key;

          val = gpiod_get_value(gpio_key->gpiod);
          printk("key_timer_expire key %d %d\n", gpio_key->gpio, val);
          key = (gpio_key->gpio << 8) | val;
          put_key(key);
          wake_up_interruptible(&gpio_key_wait);
          kill_fasync(&button_fasync, SIGIO, POLL_IN);
      }

  使用旧接口 `unsigned long data` 接收参数,不安全。没有区分按下和松开,两个事件都会放入缓冲区,用户需要自己判断。

  **改进:**

      static void key_timer_expire(struct timer_list *t)
      {
          struct gpio_key *gpio_key = from_timer(gpio_key, t, key_timer);
          int val;
          int key;

          val = gpiod_get_value(gpio_key->gpiod);

          /* 低电平有效:val=0 按下,val=1 松开,更直观 */
          key = (gpio_key->gpio << 8) | (val ? 0 : 1);

          put_key(key);
          wake_up_interruptible(&gpio_key_wait);
          kill_fasync(&button_fasync, SIGIO, POLL_IN);
      }
复制代码
  ##### read 函数

      /* 原代码 */
      static ssize_t gpio_key_drv_read(struct file *file,
                                        char __user *buf,
                                        size_t size, loff_t *offset)
      {
          int err;
          int key;

          if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))
              return -EAGAIN;

          wait_event_interruptible(gpio_key_wait, !is_key_buf_empty());
          key = get_key();
          err = copy_to_user(buf, &key, 4);

          return 4;
      }

  `wait_event_interruptible` 返回值没有判断,被信号打断时应返回 `-EINTR`。`copy_to_user` 返回值没有判断,拷贝失败时应报错。`size` 参数没有检查,用户传入小于4字节时会越界。

  **改进:**

      static ssize_t gpio_key_drv_read(struct file *file,
                                        char __user *buf,
                                        size_t size, loff_t *offset)
      {
          int err;
          int key;

          /* 检查size */
          if (size < 4)
              return -EINVAL;

          if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))
              return -EAGAIN;

          /* 判断是否被信号打断 */
          err = wait_event_interruptible(gpio_key_wait, !is_key_buf_empty());
          if (err)
              return -EINTR;

          key = get_key();

          /* 判断拷贝是否成功 */
          if (copy_to_user(buf, &key, 4))
              return -EFAULT;

          return 4;
      }
复制代码
  ##### remove 函数

      /* 原代码 */
      static int gpio_key_remove(struct platform_device *pdev)
      {
          ...
          for (i = 0; i < count; i++) {
              free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
              del_timer(&gpio_keys_100ask[i].key_timer);  // 不安全
          }
          kfree(gpio_keys_100ask);
          return 0;
      }

  `del_timer` 不等待回调执行完就删除,可能导致回调还在执行时内存已被释放。

  **改进:**

      static int gpio_key_remove(struct platform_device *pdev)
      {
          ...
          for (i = 0; i < count; i++) {
              free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
              del_timer_sync(&gpio_keys_100ask[i].key_timer); // 等回调结束再删
          }
          kfree(gpio_keys_100ask);
          return 0;
      }
相关推荐
tianyuanwo6 分钟前
DevOps主机管理员视角:企业级Linux主机权限管理策略与实践
linux·服务器·devops
郭涤生8 分钟前
Windows + Ubuntu 双系统启动故障修复
linux·windows·ubuntu
三言老师11 分钟前
CentOS8 Firewalld 高级规则中级实操教程
linux·运维·服务器·网络·centos
tedcloud1232 小时前
Impeccable 部署指南:开源前端设计工具 Linux 环境搭建实践
linux·运维·服务器·前端·人工智能·开源
风曦Kisaki2 小时前
# Kubernetes(K8s)笔记Day12 :K8s 七层代理(Ingress 和 Ingress Controller)
linux·笔记·云原生·容器·kubernetes
大黄说说2 小时前
SQL Server 执行计划怎么看?快速定位 SQL 慢的根源
java·linux·数据库
yunwei375 小时前
eBPF 教程:精准隔离已建立的 TCP 连接
linux·安全·开源
weixin_445476685 小时前
linux下lims部署详细步骤参考
linux
燕卫博6 小时前
借助AI的力量,将 ZYNQ 开发板改造成一个虚拟专用网络网关
人工智能·串口·嵌入式·zynq·reasonix·ser2mcp
奇特認6 小时前
数据库MySQL 1.安装环境部署
linux·运维·服务器