Linux内核设计与实现——读书笔记(5)中断

1、注册中断处理函数

  如果设备使用中断,则相应的驱动程序就要注册一个是中断处理程序。
  驱动程序通过request_irq() 函数向内核注册一个中断处理程序。

int
request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *),
        unsigned long irqflags, const char * devname, void *dev_id)

  第一个参数irq 表示要分配的中断号。对于某些设备,对于传统PC设备上的系统时钟或键盘,这个值通常是预先确定的。对于大多数其他设备来说,可以使用irq_of_parse_and_map() 函数通过设备树获得,也可使用gpio_to_irq() 函数通过gpio号得到。
  第二个参数handler 是指向中断处理函数的指针。
  第三个flags 可以为0,也可以是一个或多个标志的位掩码。位掩码定义在 <linux/interrupt.h> 中,通常都是形如IRQF_XXX 的宏。
  第四个参数name 时候中断名字。会被 /proc/irq和/proc/interrupts 文件使用,以便于用户通信。
  第五个参数dev 用于共享中断线。当一个中断处理程序需要释放时,dev将提供唯一的标志信息(cookie),以便从共享中断线的诸多中断处理程序中删除指定的那一个。另外,内核每次调用中断处理程序时,这个指针会作为中断处理程序的第二个参数传递过去,通常用来传递驱动的设备结构。
  request_irq() 函数执行成功返回0,失败返回非0。
  注意:request_irq() 函数可能会引起睡眠。因此,不能在中断上下文或其他不允许阻塞的代码中调用该函数。在注册中断的过程中,内核需要在/proc/irq文件中创建一个与中断固定的项,函数proc_mkdir() 就是用来创建这个procfs项的。proc_mkdir() 通过调用函数proc_create() 对新的profs项进行设置,proc_create() 会调用函数kmalloc() 来请求分配内存。kmalloc() 是一个可以引起睡眠的函数。

2、释放中断处理函数

  卸载驱动时,需要注销相应的中断处理程序,并释放中断线:

void free_irq(unsigned int irq, void *dev_id)

  如果指定的中断线不是共享的,在删除中出处理函数的同时会禁用这条中断线。如果是共享的,那么仅删除dev参数对应的处理程序,只有当中断线上的最后一个处理程序被删除时,中断线才会被禁用。

3、中断处理函数

static irqreturn_t (*handler)(int irq, void * dev)

  第一个参数irq为中断处理函数要相应的中断号。没啥用,可能用于打印日志。
  第二个参数dev可作为一个标志,在共享同一处理程序时区分多个设备。或者在非共享情况下,用来将设备结构传递给处理函数。不能给共享的中断处理程序传递NULL。
  返回值irqreturn_t其实是一个int型,2.6之前内核的中断处理函数没有返回值。中断处理函数的返回值通常有两个:IRQ_NONEIRQ_HANDLEDIRQ_NONE表示该中断对应的设备并不是在注册处理函数期间指定的产生源。IRQ_HANDLED表示中断确实是对应的设备产生的。
  当一个中断处理程序正在执行时,相应中断线会被屏蔽以防止在同一中断线上接收另一个新的中断。其他所有中断都是打开的。

4、中断上下文

  当中断发生时,在处理中断的过程中,current 宏会指向被中断的进程。
  曾经,中断处理程序没有自己的栈,他们共享所中断进程的内核栈,大小是2页,8KB。后来,内核增加了一个选项,把内核栈大小从2页减少为1页,而每个处理器拥有1个专门提供给中断处理程序的栈的页。

5、中断处理机制实现

  中断处理系统依赖于处理器、中断控制器的类型、体系结构和机器本身。
  当设备产生中断,总线会把电信号发送给中断控制器,如果中断线是激活的,中断控制器会把中断信号发送给处理器(在大多数体系结构中,中断控制器是给处理器的特定管脚发送一个电信号)。除非处理器上禁用该中断,否则,处理器会立即停止它正在做的事,并且关闭中断系统,然后跳到内存预定义的位置(中断函数入口)执行代码。
在这里插入图片描述

  每条中断线都有对应的唯一的一个预定义位置(中断函数入口)。这样当处理器跳到一个预定义位置时,内核也就知道了所接受中断的中断号了。初始入口点只是在栈中保存中断号,并存放被中断任务的信息(这些信息会先保存在寄存器中),然后内核调用函数do_IRQ() .

unsigned int do_IRQ(struct pt_regs regs)

  在计算出中断号后,do_IRQ() 函数对所接受的中断做应答,禁用这条中断线,查找中断线上有效的中断处理函数,如果有,则调用handle_IRQ_event() 来运行注册的中断处理函数。当函数返回,回到do_IRQ() 后,do_IRQ() 在进行一些清理工作,然后返回初始入口点。初始入口点在跳转到ret_from_intr() 函数。
  ret_from_intr() 函数用汇编实现,会在恢复现场之前检测need_resched标志和perrmpt_count来决定是否要调用schedule() 函数进行调度。如果不需要调度或者调度返回后,则恢复原先进程的现场。

6、/proc/interrupt

  procfs 是一个虚拟的文件系统,它只存在于内核内存,一般安装于/proc目录。在procfs中读写文件都要调用内核函数,这些函数模拟从真实文件中读或写。
  /proc/interrupt 文件存放系统中与中断相关的统计信息。例如:

root@OpenWrt:~# cat /proc/interrupts 
           CPU0       
  5:    9695183      MIPS   5  10100000.ethernet
  7:  687053832      MIPS   7  systick
  9:          0      INTC   1  10000100.timer
 15:          0      INTC   7  Ralink_DMA
 20:         11      INTC  12  serial
 22:          0      INTC  14  10130000.sdhci
 25:          5      INTC  17  gsw
 26:          1      INTC  18  ehci_hcd:usb1, ohci_hcd:usb2
 40:       4496      GPIO   8  lirc_rpi
ERR:          0

  其中,第一列是中断线。没有显示未注册中断处理函数的中断线。
  第二列是接收中断数目的计数器,存储着中断次数。
  第三列是处理这个中断的中断控制器。
  第四列是中断相关的设备名字。这个名字是在使用request_irq() 函数进行中断处理函数注册时,传递给devname参数的名字。
  procfs的代码位于fs/proc中。其中,提供/proc/interrupts的函数是与体系结构相关的,叫做show_interrupts()

7、中断控制

  在 <asm/system.h>和<asm/irq.h> 中可以找到与体系结构相关的中断控制例程。
  中断控制的实质的同步,通过禁止中断可以确保中断处理程序不会抢占当前代码,禁止中断还可以禁止内核抢占。然后,禁止中断缺不能防止SMP系统的并发访问,Linux中内核代码一般都要获取某种锁,用来防止其他处理器对共享数据的并发访问,而获得这些锁的同时意味着禁止本地中断

7.1、禁止和激活中断

local_irq_disable();
local_irq_enable();

  这两个函数依赖于体系结构,作用是无条件禁止和激活全局中断。一般成对出现,如果连续调用两次local_irq_disable() 随后再调用local_irq_enable() 会出现潜在危险,因为local_irq_enable() 会直接激活全局中断。因此,通常会用以下函数在禁用中断之前保存中断系统的状态,在激活的时候再把中断恢复为之前的状态即可:

unsigned long flags;
local_irq_save(flags);			//保存当前中断状态,随后禁止中断
local_irq_restore(flags);		//恢复中断前的状态

  其中flags 是以值传递的。local_irq_save(flags)local_irq_restore(flags) 的调用必须在同一个函数中进行。
  在旧版本的内核中:使用cli() 函数可以禁止系统中所有处理器上的中断,使用sti() 函数可以激活所有中断。这两个接口在2.5版本的内核之后被取消了,所以,对于中断同步现在必须结合使用本地中断控制和自旋锁。

7.2、禁止指定中断线

  Linux提供了四个接口用来禁止一条特定的中断线:

void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq);
void enable_irq(unsigned int irq);
void synchronize_irq(unsigned int irq);

  前两个函数禁止中断控制器上指定的中断线。disable_irq() 函数只有在当前正在执行的所有处理函数完成后才返回,因此,要保证指定中断线上没有新的中断,并且所有已经开始执行的处理程序全部退出。disable_irq_nosync() 函数不会等待当前中断处理程序执行完毕。
  synchronize_irq() 函数等待一个特定的中断处理程序退出,只有中断处理程序退出后synchronize_irq() 函数才能返回。
  对一条指定的中断线调用disable_irq()disable_irq_nosync() 函数,每次调用都需要相应地调用一次enable_irq(),只有最后一次enable_irq() 被调用时才真正重新激活中断线。这三个函数不会引起睡眠。

7.3、中断系统的状态

  如果想知道中断系统是禁止的还是激活的或者处于上下文的执行状态,可以使用以下函数:

irqs_disabled()
in_interrupt()
in_irq()

  如果本地处理器上的中断系统被禁用,irqs_disabled() 函数返回非0,否则返回0。
  如果内核处于任何的中断处理中(包括下半部处理),in_interrupt() 函数返回非0,否则返回0。
  如果内核处于中断处理函数中,in_irq() 函数返回非0,否则返回0。

8、中断控制函数(表)

在这里插入图片描述

  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是关于《Linux内核设计实现》的笔记: 1. 进程管理 - 进程控制块(Process Control Block, PCB):一个进程在内核中的表示,包含了进程的状态、各种计数器和指针,以及进程所需要的资源。 - 进程调度:内核必须在可能的情况下公平地分配CPU时间片给每个进程。Linux内核使用完全公平调度(Completely Fair Scheduler, CFS)来实现这一点。 - 进程同步:进程在访问共享资源时需要同步,以避免冲突。Linux内核提供了多种同步机制,如信号量、自旋锁和读写锁等。 2. 内存管理 - 虚拟内存:每个进程都拥有自己的虚拟内存空间,这使得每个进程都可以认为自己独占整个系统内存。 - 页面置换:当物理内存不足时,Linux内核会使用页面置换算法将一部分未使用的页面从物理内存中移出,以便为正在运行的进程腾出空间。 - 内存映射文件:Linux允许将磁盘上的文件映射到进程的虚拟地址空间中,这样就可以像访问内存一样访问文件。 3. 文件系统 - 虚拟文件系统(Virtual File System, VFS):Linux内核中的抽象层,它允许系统支持多种文件系统格式,如ext4、FAT32等。 - I/O管理:内核必须管理所有的I/O操作,包括磁盘读写和网络通信等。 - 文件描述符:Linux内核使用文件描述符来标识打开的文件,每个进程都有一个文件描述符表。 4. 网络协议栈 - TCP/IP协议栈:Linux内核支持多种网络协议,其中最常用的是TCP/IP协议栈。 - Socket:在Linux中,进程之间通信的主要方式是使用Socket。Socket是一种抽象的概念,它代表了一个网络连接。 5. 设备驱动程序 - 驱动程序开发:Linux内核的设备驱动程序通常是以模块的形式开发的,它们可以动态地加载和卸载。 - 设备文件:Linux内核将设备表示为文件,它们可以通过文件系统接口来访问。 以上是《Linux内核设计实现》的一些重点内容和笔记,希望对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr_zhangsq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值