嵌入式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·运维·服务器·信号处理
广州灵眸科技有限公司2 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) 开发(编译)方式说明
linux·服务器·单片机·嵌入式硬件·电脑
北山有鸟2 小时前
用开发板的.config替换ubuntu中内核源码目录的.config
linux·运维·ubuntu
jcbut2 小时前
离线安装dify 1.7
linux·运维·dify
云计算磊哥@3 小时前
运维开发宝典024-Linux云计算运维入门阶段总结
linux·运维·运维开发
江华森3 小时前
《Linux内核技术实战:从Page Cache到CPU调度的深度解构》博客大纲(26讲精编版)
linux
知无不研3 小时前
对套接字的深入理解
linux·服务器·网络·c++·socket·网络套接字
wuminyu5 小时前
Java锁机制之Java对象重量级锁源码剖析
java·linux·c语言·jvm·c++
deadbird6 小时前
Xbox 无线适配器 Linux 设置指南
linux