网络设备的中断处理

由起

中断的使用是CPU对系统外围设备进行管理的一个自然的选择。通常来说,处理器的速度和外围硬件设备的速度通常不在一个数量级上。如果使用轮询的方式进行协同工作,整体性能通常会差强人意(仅仅是通常情况,某些情况下,轮询的效率会很高,尤其在高速网络情况下)。故采用中断方式。中断本质上是一种特殊的电信号,有硬件设备产生。处理器接收到中断后,会马上向操作系统反映次信号的到来,然后就由操作系统负责处理这些新到来的数据。

类型

1.同步中断(异常)

  由CPU自身产生,针对当前执行的程序。。异常可能由种种原因触发:如编程失误(除0),执行期间出现特殊情况(缺页),因为许多体系结构处理异常与处理中断的方法类似,因此,内核对它们的处理也很类似。

2.异步中断

  经典的中断类型,由外部设备产生,可能发生在任意时间。

每个中断都有一个编号。如果将中断号分配给不同的设备,那么内核即可区分两个设备,并在中断发生时调用对应的ISR来执行特定与设备的操作。

中断控制器,负责IRQ的信号的电工处理之外,还会对IRQ编号和中断号进行一个转换。

处理中断

image

     中断处理划分为3部分。首先,必须建立一个适当的环境,使得处理程序函数能够在其中执行,接下来调用处理程序自身,最后将系统复原(在当前任务看来)到中断之前的状态。进入和退出任务还负责确保处理器从用户态切换到核心态。进入路径的一个关键任务是,从用户态栈切换到核心态栈。在退出路径中,内核会检查下列事项:1,是否要进行新的调度。2,是否有信号投递到原进程。

中断处理程序的实现要求和组成划分

ISR必须满足:1,实现(尤其是禁用其他中断时)必须包含尽可能少的代码,以支持快速处理。2,可以在其他ISR执行期间调用的中断处理例程,不能彼此干扰。

ISR组成划分:1,关键操作必须在中断发生后立即执行,在此类操作期间,必须禁用其他中断。2.非关键操作也应该尽快执行,但允许启用中断。3,可延期操作不是特别重要,不必在中断处理程序中实现。

中断处理框架实现

   1:  struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
   2:      [0 ... NR_IRQS-1] = {
   3:          .handle_irq    = handle_bad_irq,
   4:          .depth        = 1,
   5:          .lock        = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
   6:      }
   7:  };

IRQ相关信息管理的关键点是一个全局数组,每个数组项对应于一个IRQ号。尽管数组使用的是一个体系结构无关的数据类型,但IRQ的最大可能数目是通过一个平台相关的常数NR_IRQ指定的。IA-32连同经典的8256A控制器,提供16个IRQ。如果使用IO-APIC扩展,中断数可以增加到224个。

   1:  struct irq_desc {
   2:      struct irq_data        irq_data;
   3:      struct timer_rand_state *timer_rand_state;
   4:      unsigned int __percpu    *kstat_irqs;
   5:      irq_flow_handler_t    handle_irq;
   6:  #ifdef CONFIG_IRQ_PREFLOW_FASTEOI
   7:      irq_preflow_handler_t    preflow_handler;
   8:  #endif
   9:      struct irqaction    *action;    /* IRQ action list */
  10:      unsigned int        status_use_accessors;
  11:      unsigned int        core_internal_state__do_not_mess_with_it;
  12:      unsigned int        depth;        /* nested irq disables */
  13:      unsigned int        wake_depth;    /* nested wake enables */
  14:      unsigned int        irq_count;    /* For detecting broken IRQs */
  15:      unsigned long        last_unhandled;    /* Aging timer for unhandled count */
  16:      unsigned int        irqs_unhandled;
  17:      raw_spinlock_t        lock;
  18:      struct cpumask        *percpu_enabled;
  19:  #ifdef CONFIG_SMP
  20:      const struct cpumask    *affinity_hint;
  21:      struct irq_affinity_notify *affinity_notify;
  22:  #ifdef CONFIG_GENERIC_PENDING_IRQ
  23:      cpumask_var_t        pending_mask;
  24:  #endif
  25:  #endif
  26:      unsigned long        threads_oneshot;
  27:      atomic_t        threads_active;
  28:      wait_queue_head_t       wait_for_threads;
  29:  #ifdef CONFIG_PROC_FS
  30:      struct proc_dir_entry    *dir;
  31:  #endif
  32:      struct module        *owner;
  33:      const char        *name;
  34:  } ____cacheline_internodealigned_in_smp;

由于中断处理是高度体系结构相关的,故该数据结构含有大量体系结构相关的内容。对此我们略过。只讨论上层ISR相关的部分。整个中断处理的框架如下图所示,其中irq_chip是IRQ控制器:

image

软件真正关心的是struct irqaction(中断动作描述符):

   1:  struct irqaction {
   2:      irq_handler_t        handler;//中断处理函数
   3:      unsigned long        flags;//标志位,request_irq函数介绍
   4:      void            *dev_id;//设备ID,中断共享时标示中断具体属于哪个设备
   5:      void __percpu        *percpu_dev_id;
   6:      struct irqaction    *next;//单链表
   7:      int            irq;//中断号
   8:      irq_handler_t        thread_fn;
   9:      struct task_struct    *thread;
  10:      unsigned long        thread_flags;
  11:      unsigned long        thread_mask;
  12:      const char        *name;//设备名称

13: struct proc_dir_entry *dir;//proc/irq/NN

  14:  } ____cacheline_internodealigned_in_smp;

中断处理程序是管理硬件的驱动程序的组成部分。如果设备使用中断,那么相应的驱动程序就要注册一个中断处理程序,通过request_irq()函数进行注册。

   1:  static inline int __must_check
   2:  request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
   3:          const char *name, void *dev)
   4:  typedef irqreturn_t (*irq_handler_t)(int, void *);

第一个参数表示要分配的中断号。对于某些设备,这个值通常是预先确定的。而对于大多数其他设备而言,这个值要么是可以通过探测获取,要么可以通过编程动态确定(PCI总线)。第二个参数指向处理这个中断的实际中断处理程序,只要操作系统已接收到中断,该函数就调用。第三个标志参数(~/include/linux/interrupt.h),重要的有如下几个:

IRQF_DISABLED,该标志设置后,在中断处理工程中,禁用其他的所用中断。

IRQF_SAMPLE_RANDOM,表明该设备产生的中断对于内核熵值有贡献

IRQF_TIMER,表明这是一个定时器中断

IRQF_SHARED,表明在多个中断程序之间共享中断线。

第四个参数,用于/proc/irq和/proc/interrupts文件中显示名称。第五个参数,共享中断线时使用。函数成功时返回0,非0为函数未注册成功,中断处理程序未注册。

   1:  void free_irq(unsigned int, void *);

卸载驱动程序时,需要注销相应的中断处理程序,并释放中断线。如果指定的中断线不是共享的,那么,该函数删除处理程序的同时将禁用这条中断线。如果中断线时共享的,则仅仅删除dev所对应的处理程序,而这条中断线本身只用在删除了最后一个处理程序时才禁用。

Linux中的中断处理程序是无须重入的。当一个给定的中断处理程序正在执行时,相应的中断线在所有处理器上都会被屏蔽掉,以防止在同一中断线上接收另一个新的中断。通常情况下,所有其他的中断都是打开的,所以这些不同中断线上的其他中断都能处理,但当前中断线总是被禁止的。由此可以看出,同一个中断处理程序绝对不会被同时调用以处理嵌套的中断。(通过irq_des的status字段进行处理,当有中断在进行处理时,该值被设置为IRQ_INPROGRESS,当发现被设置为该标志是,等待)

在实现中断处理例程时,主要的问题是它们在所谓的中断上下文中执行。中断上下文与普通上下文的不同之处主要有3点:

1),中断是异步执行的。因而从用户空间来看,处理程序例程并不是在一个明确定义的环境中执行(禁止访问用户空间)。

2),中断上下文中不能调用调度器

3),中断处理程序不能进入睡眠状态。

中断控制

Linux内核提供了一组接口用于操作机器上的中断状态,控制中断系统的原因归根结底是需要提供同步。

local_irq_save() //禁止本地中断

local_irq_restore() //激活中断

disable_irq() //中断指定的一条中断线

enable_irq() //激活指定的中断线

实例()

   1:  static int e100_up(struct nic *nic)
   2:  {
   3:      int err;
   4:   
   5:      if ((err = e100_rx_alloc_list(nic)))
   6:          return err;
   7:      if ((err = e100_alloc_cbs(nic)))
   8:          goto err_rx_clean_list;
   9:      if ((err = e100_hw_init(nic)))
  10:          goto err_clean_cbs;
  11:      e100_set_multicast_list(nic->netdev);
  12:      e100_start_receiver(nic, NULL);
  13:      mod_timer(&nic->watchdog, jiffies);
  14:      if ((err = 
  16:          goto err_no_irq;
  17:      netif_wake_queue(nic->netdev);
  18:      napi_enable(&nic->napi);
  19:      /* enable ints _after_ enabling poll, preventing a race between
  20:       * disable ints+schedule */
  21:      e100_enable_irq(nic);
  22:      return 0;
  23:   
  24:  err_no_irq:
  25:      del_timer_sync(&nic->watchdog);
  26:  err_clean_cbs:
  27:      e100_clean_cbs(nic);
  28:  err_rx_clean_list:
  29:      e100_rx_clean_list(nic);
  30:      return err;

31:

 

   1:  static void e100_down(struct nic *nic)
   2:  {
   3:      /* wait here for poll to complete */
   4:      napi_disable(&nic->napi);
   5:      netif_stop_queue(nic->netdev);
   6:      e100_hw_reset(nic);
   7:      
   8:      del_timer_sync(&nic->watchdog);
   9:      netif_carrier_off(nic->netdev);
  10:      e100_clean_cbs(nic);
  11:      e100_rx_clean_list(nic);
  12:  }
 
 
   1:  static irqreturn_t e100_intr(int irq, void *dev_id)
   2:  {
   3:      struct net_device *netdev = dev_id;
   4:      struct nic *nic = netdev_priv(netdev);
   5:      u8 stat_ack = ioread8(&nic->csr->scb.stat_ack);
   6:   
   7:      netif_printk(nic, intr, KERN_DEBUG, nic->netdev,
   8:               "stat_ack = 0x%02X\n", stat_ack);
   9:   
  10:      if (stat_ack == stat_ack_not_ours ||    /* Not our interrupt */
  11:         stat_ack == stat_ack_not_present)    /* Hardware is ejected */
  12:          
  13:   
  14:      /* Ack interrupt(s) */
  15:      iowrite8(stat_ack, &nic->csr->scb.stat_ack);
  16:   
  17:      /* We hit Receive No Resource (RNR); restart RU after cleaning */
  18:      if (stat_ack & stat_ack_rnr)
  19:          nic->ru_running = RU_SUSPENDED;
  20:   
  21:      if (likely(napi_schedule_prep(&nic->napi))) {
  22:          
  24:      }
  25:   
  26:      return IRQ_HANDLED;
  27:  }

转载于:https://my.oschina.net/longscu/blog/56736

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值