day07

来源:互联网 发布:阿里云服务器搭建免流 编辑:程序博客网 时间:2024/05/29 05:03
回顾:
面试题:谈谈对中断的理解
1.谈为什么有中断
2.中断的硬件触发流程
  画图
3.中断的软件编程
  画图
4.linux内核中断编程
5.linux内核对中断处理函数的要求
6.linux内核中断编程的顶半部和底半部机制
7.顶半部特点
8.底半部特点
9.底半部实现方式
  tasklet特点
  工作队列特点
  软中断特点
 
2.linux内核软件定时器
  硬件定时器特点
  硬件定时器的中断处理函数
  HZ/jiffies
  struct timer_list
          .expires
          .function
          .data
  配套函数
  init_timer/add_timer/del_timer/mod_timer
  软件定时器基于软中断实现,不能进行休眠操作
 
3.linux内核并发和竞态
    3.1.掌握笔记中的两个经典案例
    3.2.概念
            并发
            竞态
            共享资源
            临界区
            互斥访问
            执行路径具有原子性
  3.3.竞态引起异常的四种情形
      多核
      同一CPU上的进程与进程的抢占
      中断和进程
      中断和中断
      画图
  3.4.解决竞态引起异常的四种方法
      中断屏蔽
      自旋锁
      信号量
      原子操作
 
4.解决竞态引起异常的方法之中断屏蔽
  中断屏蔽特点:
  能够解决进程与进程之前的抢占引起的异常(进程之前的抢占本身基于软中断实现)
  能够解决中断和进程的抢占引起的异常
  能够解决中断和中断引起的异常
  无法解决多核引起的异常
  中断屏蔽保护的临界区的代码执行速度要越快越好,更不能进行休眠操作
  因为linux系统的很多机制跟中断密切相关(tasklet,软件定时器,硬件定时器等)
  长时间的屏蔽中断非常危险
   
  利用中断屏蔽解决竞态引起的异常的编程步骤:
  1.明确驱动代码中哪些是共享资源
  2.明确驱动代码中哪些是临界区
  3.明确保护的临界区中是否有休眠操作
    如果有休眠操作,势必不考虑中断屏蔽
    如果没有,并且没有多核参与竞态,可以考虑使用中断屏蔽
  4.访问临界区之前屏蔽中断
      unsigned long flags;
      local_irq_save(flags);//屏蔽中断,保存中断标志到flags(内核来完成)
  5.任务踏踏实实的访问临界区
    此时会有其他进程的抢占吗?没有
    此时会有其他的中断来打断吗?没有
  6.访问临界区之后,记得将中断进行恢复
    local_irq_restore(flags);//恢复中断
  7.屏蔽中断和恢复中断务必要逻辑上成对使用
   
  案例:利用中断屏蔽来解决之前案例1中的代码漏洞
  实施步骤:同上
  案例:利用中断屏蔽解决LCD显示屏的问题
  void lcd_config(void) {
      unsigned long flags;
      local_irq_save(flags);
      //临界区
      gpio_set_value(..., 1);
      mdelay(5);
      gpio_set_value(..., 0);
      mdelay(5);
      local_irq_restore(flags);
  }
   
5.解决竞态引起异常的方法之自旋锁
  自旋锁特点:
  自旋锁能够解决多核引起的竞态问题
  自旋锁能够解决进程与进程之前的抢占引起的竞态问题
  自旋锁无法解决中断引起的竞态问题
  自旋锁保护的临界区的代码执行速度要快,更不能进行休眠操作
  没有获取自旋锁的任务将会原地忙等待(原地空转)
   
  linux内核描述自旋锁的数据类型:spinlock_t
   
  利用自旋锁解决竞态引起异常的编程步骤:
  1.明确驱动代码中哪些是临界区
  2.明确驱动代码中哪些是共享资源
  3.明确临界区中是否有休眠操作
    如果有,势必不考虑此方法
    如果没有,还要考虑是否有中断参与
    如果有中断参与,势必不考虑
    如果没有中断参与,可以考虑使用
  4.访问临界区之前先获取自旋锁
    //定义初始化一个自旋锁对象
    spinlock_t lock; //定义
    spin_lock_init(&lock); //初始化
    spin_lock(&lock);//获取自旋锁,如果获取成功,立马返回即可访问临界区
                                         如果获取失败,任务将在此函数中进行循环忙等待
                                         直到持有自旋锁的任务释放自旋锁
  5.任务获取自旋锁以后,即可踏踏实实的访问临界区
  6.访问临界区之后,记得要释放自旋锁
      spin_unlock(&lock);
  7.注意:获取自旋锁和释放自旋锁务必在逻辑上成对使用
   
  案例:利用自旋锁来解决案例1中的代码漏洞
  实验步骤同上
  案例:利用中断屏蔽解决LCD显示屏的问题
  void lcd_config(void) {
    spin_lock(&lock);
      //临界区
      gpio_set_value(..., 1);
      mdelay(5);
      gpio_set_value(..., 0);
      mdelay(5);
    spin_unlock(&lock);
  }
  //如果用自旋锁进行保护,发现LCD显示照样有问题,拿示波器抓取
    波形发现周期同样超过10ms,说明引起问题的原因在于中断参与抢占
    CPU资源
     
6.解决竞态引起异常的方法之衍生自旋锁
  衍生自旋锁特点:
  衍生自旋锁能够解决所有的竞态引起的异常问题
  衍生自旋锁=屏蔽中断+自旋锁
  衍生自旋锁保护的临界区的代码执行速度要快,更不能进行休眠操作
  没有获取衍生自旋锁的任务将会原地忙等待(原地空转)
   
  linux内核描述衍生自旋锁的数据类型:spinlock_t
   
  利用衍生自旋锁解决竞态引起异常的编程步骤:
  1.明确驱动代码中哪些是临界区
  2.明确驱动代码中哪些是共享资源
  3.明确临界区中是否有休眠操作
    如果有,势必不考虑此方法
    如果没有,可以考虑使用
  4.访问临界区之前先获取自旋锁
    //定义初始化一个衍生自旋锁对象
    spinlock_t lock; //定义
    spin_lock_init(&lock); //初始化
        unsigned long flags;
    spin_lock_irqsave(&lock, flags);//屏蔽中断,获取衍生自旋锁,如果获取成功,立马返回即可访问临界区
                                         如果获取失败,任务将在此函数中进行循环忙等待
                                         直到持有自旋锁的任务释放自旋锁
  5.任务获取衍生自旋锁以后,即可踏踏实实的访问临界区
  6.访问临界区之后,记得要释放衍生自旋锁,恢复中断
      spin_unlock_irqrestore(&lock);
  7.注意:获取衍生自旋锁和释放衍生自旋锁务必在逻辑上成对使用
   
  案例:利用衍生自旋锁来解决案例1中的代码漏洞
  实验步骤同上                                          
  案例:利用中断屏蔽解决LCD显示屏的问题
  void lcd_config(void) {
      unsigned long flags
    spin_lock_irqsave(&lock, flags);
      //临界区
      gpio_set_value(..., 1);
      mdelay(5);
      gpio_set_value(..., 0);
      mdelay(5);
    spin_unlock_irqrestore(&lock,flags);
  }
  利用衍生自旋锁能够解决异常问题!
 
7.解决竞态引起异常的方法之信号量
  内核信号量和用户的信号量一模一样
  信号量特点:
  信号量又称睡眠锁,基于自旋锁实现的
  信号量就是解决自旋锁保护的临界区不能休眠问题,有些场合
  临界区中需要进行休眠操作,此时此刻只能用信号量
  "休眠操作"仅仅存在于进程的世界中,进程休眠是指
  当前进程会释放占用的CPU资源给他们进程使用,信号量仅用于进程
  如果进程获取信号量,在访问临界区时,是可以进行休眠操作
  如果进程获取信号量失败,那么进程将进行休眠操作
   
  linux内核描述信号量的数据结构:struct semaphore
   
  利用信号量来解决竞态引起异常的编程步骤:
  1.明确驱动代码中哪些是共享资源
  2.明确驱动代码中哪些是临界区
  3.明确临界区中是否有休眠
    如果有,必须使用信号量
    如果没有,可以考虑使用信号量
  4.访问临界区之前,先获取信号量
    //定义初始化信号量对象
    struct seamphore sema; //定义信号量对象
    sema_init(&sema, 1); //初始化信号量对象
     
    //获取信号量
    down(&sema);
    说明:获取信号量,如果获取信号量成功,进程从此函数中立马返回
          然后可以踏踏实实的访问临界区
          如果获取信号量失败,进程将进入此函数中进入不可中断的
          休眠状态(释放CPU资源,在休眠期间接收到信号不会立即处理信号)
          直到持有信号量的进程释放了信号量并且唤醒这个休眠的等待进程
          "不可中断的休眠状态":进程在休眠期间,如果接收到了一个kill信号
                                                      进程不会立即处理接收到的信号,而是获取信号量的
                                                      任务释放信号量以后唤醒这个休眠的进程,进程一旦
                                                      被唤醒以后会处理之前接收到的信号
          “可中断的休眠状态”:进程在休眠期间,如果接收到了一个信号
                             进程会被立即唤醒并且处理接收到的信号
                                                                         
    或者
    down_interruptible(&sema);//获取信号量,如果获取信号量成功,进程从此函数中立马
                              返回,然后去访问临界区
                              如果获取信号量失败,进程将进入可中断的休眠状态
                              (休眠期间会立即处理接收到的信号)
                              直到获取信号被唤醒或者持有信号量的任务
                              释放信号量唤醒之前休眠的进程
   5.一旦获取信号量成功,进程可以踏踏实实的访问临界区
   6.访问临界区之后,记得释放信号量并且唤醒休眠的进程
     up(&sema);   
   7.获取信号量和释放信号量一定要在逻辑上成对使用
    
   案例:利用信号量down,实现一个设备只能被打开一次
   上位机实施步骤:同上
   下位机测试步骤:
   cd /home/drivers/
   insmod led_drv.ko
   ./led_test & //启动A进程,A进程打开成功,A进程调用sleep进行休眠
                                 不关闭
   ./led_test & //启动B进程,B进程获取信号量失败,进入不可中断的休眠状态
                                   等待A进程来唤醒
   ps //查看A,B的PID
   top //查看A,B的进程状态
           S:进程进入可中断的休眠状态
           D:进程进入不可中断的休眠状态
   按Q键退出top命令
   kill B的PID //给B进程发送kill信号
   ps
   top
   kill A的PID //杀死A进程,A进程关闭设备,释放信号量并且唤醒
                 B进程,B进程一旦被唤醒,B还要去处理之前接收到
                 的信号
   ps
   top
                                        
   ./led_test & //启动A进程,A进程打开成功,A进程调用sleep进行休眠
                                 不关闭
   ./led_test & //启动B进程,B进程获取信号量失败,进入不可中断的休眠状态
                                   等待A进程来唤醒
   ps //查看A,B的PID
   top //查看A,B的进程状态
           S:进程进入可中断的休眠状态
           D:进程进入不可中断的休眠状态
   按Q键退出top命令
   kill A的PID //给A进程发送kill信号
   ps
   top  //查看B进程的状态是否还是D,不是,变成了S,因为应用程序调用sleep
   kill B的PID  
    
    
   问题:内核吐核问题:
   INFO: task led_test:1032 blocked for more than 10 seconds.
   "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message
    
   解决办法:下位机执行:
   echo 0 > /proc/sys/kernel/hung_task_timeout_secs