您的位置 首页 厂商

Linux中止(interrupt)子体系之一:中止体系基本原理

这个中断系列文章主要针对移动设备中的Linux进行讨论,文中的例子基本都是基于ARM这一体系架构,其他架构的原理其实也差不多,区别只是其中的硬件抽象层。内核版本基于3.3。虽然内核的版本不断地提升

  这个中止系列文章首要针对移动设备中的Linux进行评论,文中的比方根本都是依据ARM这一体系架构,其他架构的原理其实也差不多,差异仅仅其间的硬件笼统层。内核版别依据3.3。尽管内核的版别不断地提高,不过自从上一次变更到当时的通用中止子体系后,大的结构性的东西并没有太大的改动。

  1. 设备、中止操控器和CPU

  一个完好的设备中,与中止相关的硬件能够划分为3类,它们别离是:设备、中止操控器和CPU自身,下图展现了一个smp体系中的中止硬件的组成结构:

 

  图 1.1 中止体系的硬件组成

  设备 设备是建议中止的源,当设备需求恳求某种服务的时分,它会建议一个硬件中止信号,一般,该信号会衔接至中止操控器,由中止操控器做进一步的处理。在现代的移动设备中,建议中止的设备能够坐落soc(system-on-chip)芯片的外部,也能够坐落soc的内部,由于现在大多数soc都集成了很多的硬件IP,例如I2C、SPI、Display Controller等等。

  中止操控器 中止操控器担任搜集一切中止源建议的中止,现有的中止操控器简直都是可编程的,经过对中止操控器的编程,咱们能够操控每个中止源的优先级、中止的电器类型,还能够翻开和封闭某一个中止源,在smp体系中,乃至能够操控某个中止源发往哪一个CPU进行处理。关于ARM架构的soc,运用较多的中止操控器是VIC(Vector Interrupt Controller),进入多核年代今后,G%&&&&&%(General Interrupt Controller)的运用也开端逐步变多。

  CPU cpu是终究呼应中止的部件,它经过对可编程中止操控器的编程操作,操控和办理者体系中的每个中止,当中止操控器终究断定一个中止能够被处理时,他会依据事前的设定,告诉其间一个或许是某几个cpu对该中止进行处理,尽管中止操控器能够一起告诉数个cpu对某一个中止进行处理,实际上,终究只会有一个cpu相应这个中止恳求,但具体是哪个cpu进行呼应是或许是随机的,中止操控器在硬件上对这一特性进行了确保,不过这也依赖于操作体系对中止体系的软件完结。在smp体系中,cpu之间也经过IPI(inter processor interrupt)中止进行通讯。

  2. IRQ编号

  体系中每一个注册的中止源,都会分配一个仅有的编号用于辨认该中止,咱们称之为IRQ编号。IRQ编号贯穿在整个Linux的通用中止子体系中。在移动设备中,每个中止源的IRQ编号都会在arch相关的一些头文件中,例如arch/xxx/mach-xxx/include/irqs.h。驱动程序在恳求中止服务时,它会运用IRQ编号注册该中止,中止产生时,cpu一般会从中止操控器中获取相关信息,然后计算出相应的IRQ编号,然后把该IRQ编号传递到相应的驱动程序中。

  3. 在驱动程序中请求中止

  Linux中止子体系向驱动程序供给了一系列的API,其间的一个用于向体系请求中止:

  [cpp] view plain copyint request_threaded_irq(unsigned int irq, irq_handler_t handler,

  irq_handler_t thread_fn, unsigned long irqflags,

  const char *devname, void *dev_id)

  其间,

  irq是要请求的IRQ编号,

  handler是中止处理服务函数,该函数作业在中止上下文中,假如不需求,能够传入NULL,可是不能够和thread_fn一起为NULL;

  thread_fn是中止线程的回调函数,作业在内核进程上下文中,假如不需求,能够传入NULL,可是不能够和handler一起为NULL;

  irqflags是该中止的一些标志,能够指定该中止的电气类型,是否同享等信息;

  devname指定该中止的称号;

  dev_id用于同享中止时的cookie data,一般用于区别同享中止具体由哪个设备建议;

  关于该API的具体作业机理咱们后边再评论。

  4. 通用中止子体系(Generic irq)的软件笼统

  在通用中止子体系(generic irq)呈现之前,内核运用__do_IRQ处理一切的中止,这意味着__do_IRQ中要处理各种类型的中止,这会导致软件的复杂性添加,层次不清楚,并且代码的可重用性也欠好。事实上,到了内核版别2.6.38,__do_IRQ这种方法现已完全在内核的代码中消失了。通用中止子体系的原型开始呈现于ARM体系中,一开端内核的开发者们把3种中止类型区别出来,他们是:

  电平触发中止(level type)

  边际触发中止(edge type)

  简易的中止(simple type)

  后来又针对某些需求回应eoi(end of interrupt)的中止操控器,加入了fast eoi type,针对smp加入了per cpu type。把这些不同的中止类型笼统出来后,成为了中止子体系的流控层。要使一切的体系架构都能够重用这部分的代码,中止操控器也被进一步地封装起来,形成了中止子体系中的硬件封装层。咱们能够用下面的图示表明通用中止子体系的层次结构:

    

 

  图 4.1 通用中止子体系的层次结构

  硬件封装层 它包含了体系架构相关的一切代码,包含中止操控器的笼统封装,arch相关的中止初始化,以及各个IRQ的相关数据结构的初始化作业,cpu的中止进口也会在arch相关的代码中完结。中止通用逻辑层经过规范的封装接口(实际上便是struct irq_chip界说的接口)拜访并操控中止操控器的行为,体系相关的中止进口函数在获取IRQ编号后,经过中止通用逻辑层供给的规范函数,把中止调用传递到中止流控层中。咱们看看irq_chip的部分界说:

  [cpp] view plain copystruct irq_chip {

  const char *name;

  unsigned int (*irq_startup)(struct irq_data *data);

  void (*irq_shutdown)(struct irq_data *data);

  void (*irq_enable)(struct irq_data *data);

  void (*irq_disable)(struct irq_data *data);

  void (*irq_ack)(struct irq_data *data);

  void (*irq_mask)(struct irq_data *data);

  void (*irq_mask_ack)(struct irq_data *data);

  void (*irq_unmask)(struct irq_data *data);

  void (*irq_eoi)(struct irq_data *data);

  int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);

  int (*irq_retrigger)(struct irq_data *data);

  int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);

  int (*irq_set_wake)(struct irq_data *data, unsigned int on);

  ……

  };

  看到上面的结构界说,很明显,它实际上便是对中止操控器的接口笼统,咱们只要对每个中止操控器完结以上接口(不用悉数),并把它和相应的irq相关起来,上层的完结即可经过这些接口拜访中止操控器。并且,同一个中止操控器的代码能够方便地被不同的渠道所重用。

  中止流控层 所谓中止流控是指合理并正确地处理接连产生的中止,比方一个中止在处理中,同一个中止再次抵达时怎么处理,何时应该屏蔽中止,何时翻开中止,何时回应中止操控器等一系列的操作。该层完结了与体系和硬件无关的中止流控处理操作,它针对不同的中止电气类型(level,edge……),完结了对应的规范中止流控处理函数,在这些处理函数中,终究会把中止操控权传递到驱动程序注册中止时传入的处理函数或许是中止线程中。现在内核供给了以下几个首要的中止流控函数的完结(只列出部分):

  handle_simple_irq();

  handle_level_irq(); 电平中止流控处理程序

  handle_edge_irq(); 边缘触发中止流控处理程序

  handle_fasteoi_irq(); 需求eoi的中止处理器运用的中止流控处理程序

  handle_percpu_irq(); 该irq只要单个cpu呼应时运用的流控处理程序

  中止通用逻辑层 该层完结了对中止体系几个重要数据的办理,并供给了一系列的辅佐办理函数。一起,该层还完结了中止线程的完结和办理,同享中止和嵌套中止的完结和办理,别的它还供给了一些接口函数,它们将作为硬件封装层和中止流控层以及驱动程序API层之间的桥梁,例如以下API:

  generic_handle_irq();

  irq_to_desc();

  irq_set_chip();

  irq_set_chained_handler();

  驱动程序API 该部分向驱动程序供给了一系列的API,用于向体系请求/开释中止,翻开/封闭中止,设置中止类型和中止唤醒体系的特性等操作。驱动程序的开发者一般只会运用到这一层供给的这些API即可完结驱动程序的开发作业,其他的细节都由别的几个软件层较好地“躲藏”起来了,驱动程序开发者无需再重视底层的完结,这看起来确实是一件美好的工作,不过我以为,要想写出好的中止代码,仍是花点时刻了解一下其他几层的完结吧。其间的一些API如下:

  enable_irq();

  disable_irq();

  disable_irq_nosync();

  request_threaded_irq();

  irq_set_affinity();

  这儿不再对每一层做具体的介绍,我将会在本系列的其他几篇文章中做深化的讨论。

  5. irq描绘结构:struct irq_desc

  整个通用中止子体系简直都是围绕着irq_desc结构进行,体系中每一个irq都对应着一个irq_desc结构,一切的irq_desc结构的安排方法有两种:

  依据数组方法 渠道相关板级代码事前依据体系中的IRQ数量,界说常量:NR_IRQS,在kernel/irq/irqdesc.c中运用该常量界说irq_desc结构数组:

  [cpp] view plain copystruct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {

  [0 … NR_IRQS-1] = {

  .handle_irq = handle_bad_irq,

  .depth = 1,

  .lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),

  }

  };

  依据基数树方法 当内核的装备项CONFIG_SPARSE_IRQ被选中时,内核运用基数树(radix tree)来办理irq_desc结构,这一方法能够动态地分配irq_desc结构,关于那些具有很多IRQ数量或许IRQ编号不接连的体系,运用该方法办理irq_desc对内存的节约有优点,并且对那些自带中止操控器办理设备自身多个中止源的外部设备,它们能够在驱动程序中动态地请求这些中止源所对应的irq_desc结构,而不用在体系的编译阶段保存irq_desc结构所需的内存。

  下面咱们看一看irq_desc的部分界说:

  [cpp] view plain copystruct irq_data {

  unsigned int irq;

  unsigned long hwirq;

  unsigned int node;

  unsigned int state_use_accessors;

  struct irq_chip *chip;

  struct irq_domain *domain;

  void *handler_data;

  void *chip_data;

  struct msi_desc *msi_desc;

  #ifdef CONFIG_SMP

  cpumask_var_t affinity;

  #endif

  };

  [cpp] view plain copystruct irq_desc {

  struct irq_data irq_data;

  unsigned int __percpu *kstat_irqs;

  irq_flow_handler_t handle_irq;

  #ifdef CONFIG_IRQ_PREFLOW_FASTEOI

  irq_preflow_handler_t preflow_handler;

  #endif

  struct irqaction *action; /* IRQ action list */

  unsigned int status_use_accessors;

  unsigned int depth; /* nested irq disables */

  unsigned int wake_depth; /* nested wake enables */

  unsigned int irq_count; /* For detecting broken IRQs */

  raw_spinlock_t lock;

  struct cpumask *percpu_enabled;

  #ifdef CONFIG_SMP

  const struct cpumask *affinity_hint;

  struct irq_affinity_notify *affinity_notify;

  #ifdef CONFIG_GENER%&&&&&%_PENDING_IRQ

  cpumask_var_t pending_mask;

  #endif

  #endif

  wait_queue_head_t wait_for_threads;

  const char *name;

  } ____cacheline_internodealigned_in_smp;

  关于irq_desc中的首要字段做一个解说:

  irq_data 这个内嵌结构在2.6.37版别引进,之前的内核版别的做法是直接把这个结构中的字段直接放置在irq_desc结构体中,然后在调用硬件封装层的chip->xxx()回调中传入IRQ编号作为参数,可是底层的函数常常需求拜访->handler_data,->chip_data,->msi_desc等字段,这需求运用irq_to_desc(irq)来取得irq_desc结构的指针,然后才干拜访上述字段,者带来了功能的下降,尤其在装备为sparse irq的体系中更是如此,由于这意味着基数树的查找操作。为了处理这一问题,内核开发者把几个低层函数需求运用的字段独自封装为一个结构,调用时的参数则改为传入该结构的指针。完结相同的意图,那为什么不直接传入irq_desc结构指针?由于这会损坏层次的封装性,咱们不期望低层代码能够看到不应该看到的部分,仅此而已。

  kstat_irqs 用于irq的一些计算信息,这些计算信息能够从proc文件体系中查询。

  action 中止呼应链表,当一个irq被触发时,内核会遍历该链表,调用action结构中的回调handler或许激活其间的中止线程,之所以完结为一个链表,是为了完结中止的同享,多个设备同享同一个irq,这在外围设备中是普遍存在的。

  status_use_accessors 记载该irq的状况信息,内核供给了一系列irq_settings_xxx的辅佐函数拜访该字段,具体请查看kernel/irq/settings.h

  depth 用于办理enable_irq()/disable_irq()这两个API的嵌套深度办理,每次enable_irq时该值减去1,每次disable_irq时该值加1,只要depth==0时才真正向硬件封装层宣布封闭irq的调用,只要depth==1时才会向硬件封装层宣布翻开irq的调用。disable的嵌套次数能够比enable的次数多,此刻depth的值大于1,跟着enable的不断调用,当depth的值为1时,在向硬件封装层宣布翻开irq的调用后,depth减去1后,此刻depth为0,此刻处于一个平衡状况,咱们只能调用disable_irq,假如此刻enable_irq被调用,内核会陈述一个irq失衡的正告,提示驱动程序的开发人员查看自己的代码。

  lock 用于维护irq_desc结构自身的自旋锁。

  affinity_hit 用于提示用户空间,作为优化irq和cpu之间的亲缘联系的依据。

  pending_mask 用于调整irq在各个cpu之间的平衡。

  wait_for_threads 用于synchronize_irq(),等候该irq一切线程完结。

  irq_data结构中的各字段:

  irq 该结构所对应的IRQ编号。

  hwirq 硬件irq编号,它不同于上面的irq;

  node 一般用于hwirq和irq之间的映射操作;

  state_use_accessors 硬件封装层需求运用的状况信息,不要直接拜访该字段,内核界说了一组函数用于拜访该字段:irqd_xxxx(),拜见include/linux/irq.h。

  chip 指向该irq所属的中止操控器的irq_chip结构指针

  handler_data 每个irq的私有数据指针,该字段由硬件封转层运用,例如用作底层硬件的多路复用中止。

  chip_data 中止操控器的私有数据,该字段由硬件封转层运用。

  msi_desc 用于PCIe总线的MSI或MSI-X中止机制。

  affinity 记载该irq与cpu之间的亲缘联系,它其实是一个bit-mask,每一个bit代表一个cpu,置位后代表该cpu或许处理该irq。

  这是通用中止子体系系列文章的第一篇,这儿不会具体介绍各个软件层次的完结原理,可是有必要对整个架构做扼要的介绍:

  体系发动阶段,取决于内核的装备,内核会经过数组或基数树分配好足够多的irq_desc结构;

  依据不同的体系结构,初始化中止相关的硬件,尤其是中止操控器;

  为每个必要irq的irq_desc结构填充默许的字段,例如irq编号,irq_chip指针,依据不同的中止类型装备流控handler;

  设备驱动程序在初始化阶段,运用request_threaded_irq() api请求中止服务,两个重要的参数是handler和thread_fn;

  当设备触发一个中止后,cpu会进入事前设定好的中止进口,它归于底层体系相关的代码,它经过中止操控器取得irq编号,在对irq_data结构中的某些字段进行处理后,会将操控权传递到中止流控层(经过irq_desc->handle_irq);

  中止流控处理代码在作出必要的流控处理后,经过irq_desc->action链表,取出驱动程序请求中止时注册的handler和thread_fn,依据它们的赋值状况,或许仅仅调用handler回调,或许发动一个线程履行thread_fn,又或许两者都履行;

  至此,中止终究由驱动程序进行了呼应和处理。

  6. 中止子体系的proc文件接口

  在/proc目录下面,有两个与中止子体系相关的文件和子目录,它们是:

  /proc/interrupts:文件

  /proc/irq:子目录

  读取interrupts会顺次显现irq编号,每个cpu对该irq的处理次数,中止操控器的姓名,irq的姓名,以及驱动程序注册该irq时运用的姓名,以下是一个比方:

    

 

  /proc/irq目录下面会为每个注册的irq创立一个以irq编号为姓名的子目录,每个子目录下别离有以下条目:

  smp_affinity irq和cpu之间的亲缘绑定联系;

  smp_affinity_hint 只读条目,用于用户空间做irq平衡只用;

  spurious 能够取得该irq被处理和未被处理的次数的计算信息;

  handler_name 驱动程序注册该irq时传入的处理程序的姓名;

  依据irq的不同,以上条目纷歧定会悉数都呈现,以下是某个设备的比方:

  # cd /proc/irq

  # ls

  ls

  332

  248

  ……

  ……

  12

  11

  default_smp_affinity

  # ls 332

  bcmsdh_sdmmc

  spurious

  node

  affinity_hint

  smp_affinity

  # cat 332/smp_affinity

  可见,以上设备是一个运用双核cpu的设备,由于smp_affinity的值是3,体系默许每个中止能够由两个cpu进行处理。

  本章内容完毕。接下来的方案:

  Linux中止(interrupt)子体系之二:arch相关的硬件封装层

  Linux中止(interrupt)子体系之三:中止流控处理层

  Linux中止(interrupt)子体系之四:驱动程序接口层

  Linux中止(interrupt)子体系之五:软件中止(softirq)

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/changshang/234107.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部