嵌入式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;
      }
相关推荐
戴为沐1 天前
Linux内存扩容指南
linux
zylyehuo1 天前
Linux 彻底且安全地删除文件
linux
用户805533698032 天前
主线 U-Boot 上 RK3506:和闭源 rkbin 拔河的三个隐性契约
linux·嵌入式
用户034095297912 天前
linux fcitx 5 雾凇拼音 设置在中文输入法下仍然输入英文标点
linux
Web3探索者4 天前
可视化服务器管理和传统命令行区别是什么?新手教程:Linux 运维到底该用图形界面还是 SSH 命令行?
linux·ssh
zylyehuo4 天前
Linux系统中网线与USB网络共享冲突
linux
荣--5 天前
在 strip 二进制 + 基址随机化的栈里做崩溃去重 —— 三阶段算法与一行 Crash Flag
嵌入式·崩溃分析·栈指纹·去重算法
释然小师弟5 天前
Android开发十年:反思与回顾
android·后端·嵌入式
Sokach10155 天前
Linux Shell 脚本从零到能用:一个新手的一天学习总结
linux
FreakStudio6 天前
W55MH32L-EVB 上手测评:硬件 TCP/IP 加持的以太网单片机,MicroPython 零门槛开发
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机