day06

回顾:
面试题:谈谈对中断的理解
1.谈谈计算机为什么有中断机制
  举例子说本质
2.谈谈中断的硬件触发流程
  举例子画图看图谈流程
3.谈谈中断软件编程
  画图说明四步骤
4.linux内核中断编程
  request_irq(中断号,中断处理函数,中断标志,中断名称,给中断处理函数传递的参数)
  irqreturn_t 中断处理函数名(中断号,接收传递的参数)
  free_irq(中断号,给中断处理函数传递的参数)
5.linux内核对于中断处理函数的要求
  执行速度越快越好,更不能休眠操作
6.顶半部和底半部机制
  并不是所有的中断处理函数都能满足,势必影响系统的
  并发和响应能力,对于这种情况要考虑使用中断编程的顶半部和底半部进行优化
    画图说明
7.顶半部特点
  本质还是中断处理函数
  紧急,耗时较短
  不允许CPU资源切换   
8.底半部特点
  本质就是将某个事件进行延后执行
  不紧急,耗时较长
  允许CPU资源切换
  实现方式:
      tasklet
      工作队列
      软中断
9.tasklet特点
    基于软中断实现
    延后处理函数不能休眠
    优先级高于进程低于硬件中断
    struct tasklet_struct
    配置函数:
        DECLARE_TASKLET //定义初始化
        tasklet_schedule //向内核登记,内核会在适当的时候执行
10.工作队列特点
  基于进程
  延后处理函数可以进行休眠操作
  优先级低于软中断低于硬件中断
  struct work_struct
  配套函数
      INIT_WORK //初始化
      work_schedule    //登记
11.底半部机制之软中断
   tasklet本身是基于软中断
   软中断的延后处理函数同样不能进行休眠操作
   软中断的延后处理函数可以同时运行在多个CPU核上
   而tasklet的延后处理函数同一时刻只能运行在一个CPU核上
   所以软中断的延后处理函数在设计的时候务必考虑可重入性
   软中断编程实现上不能以insmod/rmmod形式动态的安装和卸载
   必须和uImage写到一起,这样软件设计相对比较繁琐
   tasklet本质就是解决软中断设计的繁琐问题
   tasklet就是软中断的一个替代品
 
2.linux内核软件定时器
    2.1.硬件定时器特点
       硬件定时器能够按照一定的频率周期性的有规律的给CPU发送中断信号
       发送中断的频率(周期)可以通过软件编程来设置
       硬件定时器产生的中断信号可以称之为时钟中断
    
    2.2.硬件定时器对应的中断处理函数
         硬件定时器产生的中断同样也会有中断处理函数,只是
         这个中断处理函数由内核已经帮你写好,此中断处理函数
         同样被内核按照一定的频率周期性的有规律的被内核调用
         硬件定时器对应的中断处理函数做如下内容:
         1.更新系统的运行时间
         2.更新实际时间(Wall time)
     3.检查当前进程的时间片是否用尽,是否决定进行调度
     4.检查是否有超时的内核软件定时器,如果有超时的软件定时器
       内核调用超时的软件定时器对应的超时处理函数
     5.统计系统的资源,数据信息(top命令查看CPU的利用率)
    
  2.3.linux内核跟时间相关的概念
       HZ:是内核的一个全局常量
               ARM架构:HZ=100
               X86架构:HZ=1000
               以ARM架构为例,HZ=100表示硬件定时器一秒钟给CPU发送
               100次硬件定时器中断,每发生一次硬件定时器中断的时间
               间隔为10ms
               
       jiffies_64:是内核的一个全局变量,64位(unsigned long long)
                                记录自开机以来,硬件定时器给CPU发送的硬件
                                定时器中断的次数,每发生一次,jiffies_64自动
                                加1(由硬件定时器的中断处理函数进行加1)
                                那也就代表时间上加10ms
       jiffies:也是内核的一个全局变量,32位(unsigned long)
                        它的值取的是jiffies_64的低32位,也就是每发生
                        一次定时器中断,jiffis_64加1,那也是jiffies加1
                        jiffies一般用来记录时间间隔(记录流失时间)
                        切记切记:将来只要在代码中看到jiffies,那就表示当前时刻的时间
        参考代码:
        unsinged long timeout = jiffies + 5*HZ;
        说明:
        jiffies:表示代码执行到这条语句对应的当前时刻的时间                  
       5*HZ=5*100=500:表示500次硬件定时器中断,一次为10ms
                      所以5*HZ表示5秒钟
       timeout:5秒以后的时间
        
       unsigned long timeout = jiffies + 2;
       说明:
       jiffies:表示代码执行到这条语句对应的当前时刻的时间                  
       2:2次硬件定时器中断,共20ms
       timeout:20ms以后的时间
        
       案例:分析一下代码的漏洞
       unsigned long timeout = jiffies + 5*HZ;
       //一堆代码
       ...
       ...
       ...
       //以上代码执行完毕,判断是否发生了超时现象
       if(jiffies > timeout)
                超时
         else
               没超时
         问:linux内核如何解决呢?
         答:只需查看内核大神的代码即可
             人家怎么写,咱就怎么写       
         unsigned long timeout = jiffies + 5*HZ;
       //一堆代码
       ...
       ...
       ...
       //以上代码执行完毕,判断是否发生了超时现象
       if(time_after(jiffies, timeout))
                超时
         else
               没超时
             
         作业:认真研读time_after如何解决溢出问题
                                
  2.4.linux内核软件定时器
  内核软件定时器特点:
          可以指定一个超时时间,一旦超时时间到期,内核就会调用
          定会器的超时处理函数,切记:linux内核软件定时器基于
          软中断实现,所以其超时处理函数不能进行休眠操作
   
  内核描述软件定时器的数据结构:
          struct timer_list {
              unsigned long expires;
              void (*function)(unsigned long data);
              unsigned long data;
              ...
          };     
          成员:
          expires:定时器的超时时间
                  例如:5秒以后超时,expires=jiffies+5*HZ;    
          function:定时器的超时处理函数,基于软中断实现
                   所以不能进行休眠操作
                   形参data:保存传递的参数信息
            data:就超时处理函数传递的参数
      
     配套函数:
     init_timer(&定时器对象);//初始化定时器对象
     注意:还需要额外自己初始化超时时刻的时间
                  超时处理函数和传递的参数信息(如果有必要)
     定时器对象.expires = jiffies+5*HZ; //指定超时时间
     定时器对象.function = xxxx_function;//指定超时处理函数
     定时器对象.data = (unsigned long)&xxx; //给超时处理函数传递参数
      
     add_timer(&定时器对象);//向内核注册添加一个定时器,
                                                   一旦添加完毕,定时器就开始倒计时
                                                   一旦时间到期,内核就会调用其超时
                                                   处理函数并且将定时器从内核中删除
                                                   所以内核定时器的超时处理函数只
                                                   执行一次
     del_timer(&定时器对象);//从内核中删除定时器
     mod_timer(&定时器对象,新的超时时刻的时间);//修改定时器的超时时刻的时间
     注意:mod_timer=先del_timer,然后expires=jiffies+20*HZ,最后add_timer
      
     案例1:利用定时器,实现每隔2秒钟打印一句话
     案例2:利用定时器,实现每隔2秒钟开关灯
     gpio_set_value(led_info[i].gpio,  
                                         !gpio_get_value(led_info[i].gpio))
     实施步骤:
 
************************************************
3.linux内核并发和竞态(高级部分)
    3.1.分析案例1:要求一个LED设备只能被一个进程打开访问
        分析实现过程,有两种方案:
        在应用层实现:利用进程间通信的机制实现这个需求
                            这种实现方式可以实现但是极其之繁琐
        在驱动层实现:不管有多少个进程来打开设备,他们都会
                      调用open函数进行打开,最终他们的open
                      都会调用到底层驱动的led_open,此时
                      只需在底层驱动的led_open中做相关一点点
                      代码限定即可实现需求(一夫当关万夫莫开)
        实施步骤:
        上位机执行:
        mkdir /opt/drivers/day06/2.0  
        cd /opt/drivers/day06/2.0
        vim led_drv.c
        vim led_test.c
        vim Makefile
        make
        arm...gcc -o led_test led_test.c
        cp led_drv.ko led_test /opt/rootfs/home/drivers
         
        下位机测试:
        cd /home/drivers/
        insmod led_drv.ko
        ./led_test & //启动一个A进程,A进程打开设备以后
                       先不关闭设备,让A进程后台运行
        ./led_test //启动B进程去打开设备,能否打开成功呢?
                                          
        结论:虽然此代码能够实现需求,但是此驱动代码中存在
              一个严重的漏洞,分析漏洞
              分析驱动代码,发现驱动中有一条关键语句:"--open_cnt"
              重点来分析"--open_cnt"
        分析"--open_cnt"过程:
        明确:C语言--open_cnt在汇编语言里对应的是三条语句:
        ldr r0, [open_cnt变量对应的内存地址]
        sub r0, r0, #1
        str r0, [open_cnt变量对应的内存地址]  
         
        正常情况:
        A进程先打开设备,A进程先执行--open_cnt,汇编看:
        先ldr读取:open_cnt=1
        然后sub/str修改写回:open_cnt=0
        结果:A进程打开成功
         
        然后B进程打开设备,B进程执行--open_cnt,汇编看:
        先ldr读取:open_cnt=0
        然后sub/str修改写回:open_cnt=-1
        结果:B进程打开失败
         
        异常情况(漏洞引起):
        A进程先打开设备,A进程先执行--open_cnt,汇编看:
        先ldr读取:open_cnt=1   
        就在此时此刻,由于linux内核支持进程与进程之间的抢占
        也就是高优先级的进程会抢占低优先级进程的CPU资源,此时
        B进程的优先级高于A进程,CPU资源会从A进程撤下来跑到
        B进程去执行,CPU执行B进程之前,先保护A进程当前所有的
        ARM寄存器的值到栈中(open_cnt=1压栈),然后B进程将CPU资源归还给A进程时
        A进程再从栈中恢复原先的数据
         
        B进程接着执行:
        先ldr读取:open_cnt=1
        然后sub/str修改写回:open_cnt=0
        结果:B进程打开成功
        B执行完毕,然后将CPU资源归还给A,A进程接着执行:
        A从栈中恢复原先保存的open_cnt=1
        然后sub/str修改写回:open_cnt=0
        结果:A进程打开成功
         
        总结:造成以上漏洞的根本原因是由于linux内核支持
              进程的优先级,支持进程之间抢占CPU资源
      
     3.2.分析案例2:CPU通过某个GPIO给LCD显示屏发送一个固定
         周期(10ms)的高低电平(高电平5ms,低电平5ms)
         实现过程,软件编程如下:
         void lcd_config(void)
         {
          //拉高
             gpio_set_value(PAD_GPIO_B+8, 1);
             //让高电平持续5ms
             mdelay(5);
             //拉低
             gpio_set_value(PAG_GPIO_B+8, 0);
             //让低电平持续5ms
             mdelay(5);
         }   
         此代码运行以后,通过示波器抓取波形,测量这周的时间要大于10ms
          
         分析问题产生的原因:
         假如是某个进程来调用此函数配置高低电平,如果当这个
         进程刚拉高电平(还没有执行mdelay),此时此刻来了一个
         高优先级的进程,或者来一个中断,会将这个进程的CPU资源
         抢走,CPU资源进行切换执行处理别的高优先级的进程或者中断
         而这个执行过程中,GPIOB8还是持续为高电平,当CPU处理完
         高优先级的进程或者中断返回以后在执行mdelay,最终
         造成高电平的持续时间势必大于5ms,整个周期势必超时10ms
          
     3.3.通过以上两个案例,得到结论:
         在linux内核中,产生以上类似漏洞的四种情形:
         1.多核(多个CPU,简称SMP)   
           多核他们是共享内存,闪存,GPIO等硬件资源
         2.同一个CPU上的进程与进程之前的抢占
         3.中断和进程(中断的优先级高于进程)
           硬件中断和进程
           软中断和进程          
         4.中断和中断(硬件中断优先级高于软中断)
           硬件中断和软中断
           软中断和软中断
                   例如:
                           tasklet_schedule(&tasklet对象);
                           或者
                           tasklet_hi_schedule(&tasklet对象);
         5.此时此刻务必画出一个简要的四种示意图
       
      3.4.理清相关的概念
      并发:多个执行单元(进程和中断)同时发生
      竞态:多个执行单元对共享资源的同时访问形成竞争的状态
            必须具备一下三个条件:
            1.必须有多个执行单元
            2.必须有共享资源
            3.必须同时访问
      共享资源:软件上的全局变量(例如:open_cnt)
                          或者是硬件资源(例如:GPIOB8,各种控制器的寄存器)
      临界区:对共享资源访问的代码区域
                       //临界区
                       if(--open_cnt != 0) {
                                printk("设备已被打开!\n");
                        open_cnt++;
                                return -EBUSY;//返回设备忙错误码
                         }
                         或者:
                         //此函数本身就是一个临界区
                         void lcd_config(void)
                      {
                      //拉高
                       gpio_set_value(PAD_GPIO_B+8, 1);
                         //让高电平持续5ms
                         mdelay(5);
                         //拉低
                         gpio_set_value(PAG_GPIO_B+8, 0);
                         //让低电平持续5ms
                         mdelay(5);
                      }   
       
      互斥访问:当一个执行单元在访问临界区时,其他执行单元禁止
                访问临界区,直到访问临界区任务访问完毕
                 
      执行路径具有原子性:当某个任务获取到CPU资源踏踏实实访问
                                            临界区时(共享资源),不允许发生CPU资源的
                                            切换,要保证这个任务能够顺利访问临界区
                                            而其他任务等待
       
      3.5.linux内核解决竞态引起的异常(漏洞)的方法
              中断屏蔽
              自旋锁
              信号量
              原子操作
       
      3.6.解决竞态引起异常方法之中断屏蔽
       
           
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值