嵌入式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;
      }
相关推荐
Cx330❀3 小时前
【MySQL基础】一文吃透“表的约束”:从 Null/Default 到主外键的终极安全法则
linux·服务器·数据库·c++·mysql·安全
程序员JerrySUN3 小时前
Jetson 刷机深度解析:flash.sh vs l4t_initrd_flash.sh(含安全与磁盘加密对比)
linux·网络·arm开发·安全·系统安全
六点_dn4 小时前
Linux学习笔记-printf命令
linux·运维·算法
蜡台4 小时前
Linux Python 安装使用
linux·运维·服务器
欧神附体1234 小时前
在虚拟机中添加网卡,修改网卡名并关闭SElinux
linux·服务器·网络
brave_zhao6 小时前
openEuler下安装tar包解压工具
linux·服务器·数据库
小宏运维有点菜6 小时前
JumpServer
linux·运维
是个西兰花7 小时前
Linux:死锁与生产者消费者模型解析
linux·运维·服务器·c++·死锁·生产者消费者模型
RisunJan7 小时前
Linux命令-rsync(远程/本地文件同步 —— 增量传输的备份与镜像神器)
linux·运维·服务器
爆浇牛肉面8 小时前
Linux进阶命令:测开必学的curl和jq
linux