初尝内核中断

每台计算机都连接有很多外设,那操作系统对这些外设进行管理时要如何通信呢?一般有轮询(Polling)和中断(Interrupt)两种,第一种是操作系统定时主动去查询这些外设,看有没有设备需要进行处理,而第二种则是外设需要处理时主动向操作系统发送请求信号,然后操作系统再做相应的处理。这第二种不需要定时去查询,只有在设备需要时才进行处理,节省了CPU的消耗,提高了效率。故而我们先尝试下如何去使用Linux内核的中断处理机制,本次采用共享中断的机制来学习我们的中断程序。 
一般来说,一个中断有这样的操作步骤: 1.注册中断及服务子程序; 2.使能中断; 3.中断到来时调用服务子程序进行处理,处理完成后退出继续等待下一个中断的到来。 
而在Linux内核源码include/linux/interrupt.h里有注册中断需要用到的如下函数:
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev) ;
接下来说说这5个参数吧:
1.irq:中断号/中断向量,在嵌入式里经常会用gpio来作为外部中断请求线,经常会用到gpio_to_irq函数来作转换;
2.handler:服务子程序/中断处理程序,指向一个处理本中断的回调函数,其函数原型如下:
typedef irqreturn_t (*irq_handler_t)(int, void *);
其两参数分别对应irq和dev;
3.flags:作为gpio请求中断时,该标志可设置触发方式:高电平、低电平、上升延、下降延,还可以设置该中断的处理模式是:屏蔽、共享等,具体可查阅interrupt.h文件里以IRQF_开头的宏;
4.name:中断请求的设备名称,在/proc/interrupts里可查看到中断号、中断次数、中断设备名称;
5.dev:传递给中断处理程序的参数,void*类型表示可以传递任意类型的数据,一般传递设备结构体指针。该参数在设置为共享模式(IRQF_SHARED)时用于标志共享中断的唯一性。 上面说到了中断注册函数,释放时对应的函数为:
extern void free_irq(unsigned int, void *);
第一个参数对应irq,第二个对应dev。
好了,理论的东西了解了部分,还是从实践出发吧,尝尝这次共享中断号实现中断的魅力吧:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>

static int irqn;
static char* devname;

module_param(irqn,int,0644);
module_param(devname,charp,0644);

struct shrirq
{
        int devid;
}shrdev={1119};

static irqreturn_t shrirq_handler(int irq,void * dev)
{
        static int count=0;
        struct shrirq shrdev=*(struct shrirq*)dev;

        printk("Enter shrirq_handler!\n");
        printk("ISR devid:%d\n",shrdev.devid);
        printk("count=%d\n",count);
        count++;
        if(count > 65536)
                count=0;
        printk("Leaving IRQ handler!\n");
        return IRQ_HANDLED;
}

static int __init shrirq_init(void)
{
        printk("Enter shrirq_init...\n");
        if(request_irq(irqn,shrirq_handler,IRQF_SHARED,devname,&shrdev) != 0)
        {
                printk("Request IRQ failed!\ndevname=%s,IRQ:%d\n",devname,irqn);
                return -1;
        }
        printk("Request IRQ success!\ndevname=%s,IRQ:%d\n",devname,irqn);
        printk("Exit shrirq_init...\n");
        return 0;
}

static void __exit shrirq_exit(void)
{
        printk("Enter shrirq_exit...\n");
        free_irq(irqn,&shrdev);
        printk("Release irq sucess!\ndevname=%s,IRQ:%d\n",devname,irqn);
        printk("Exit shrirq_exit...\n");
}

module_init(shrirq_init);
module_exit(shrirq_exit);

MODULE_LICENSE("GPL");
而相应的Makefile如下:
obj-m += shrirq.o

CUR_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/home/xinu/linux-3.13.6

all:
        make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules

clean:
        make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean
从源码看到,多了module_param来指定模块参数和MODULE_LICENSE来指定模块的使用的开源协议,这两部分在后期再详细说明。
此时相应的源码树如下:
/home/xinu/xinu/linux_kernel_driver_l1/shrirq/
 ├── Makefile
 └── shrirq.c
我们make后使用如下命令来共享键盘中断验证我们的中断驱动模块:
1.PS/2接口
我们使用cat /proc/interrupts可以查看到PS/2所使用的i8042键盘控制器对应的中断信息如下:
 1: 3 0 IO-APIC-edge i8042
即中断号为1,那么我们使用命令sudo insmod shrirq.ko irqn=1 devname=shrirq来加载模块,加载成功后我们再看下中断信息: 1: 3 0 IO-APIC-edge i8042, shrirq 共享的设备名shrirq出现在i8042后面了。
2.USB接口
对于USB键盘,我们先用lsusb确认下自己的键盘在哪条USB总线上,我这边是如下信息:
Bus 002 Device 005: ID 1241:1603 Belkin Keyboard
在BUS 002上,而cat /proc/interrupts对应的内容如下:
23: 15035 15044 IO-APIC-fasteoi ehci_hcd:usb2, uhci_hcd:usb6
即中断号为23,那我们使用命令sudo insmod shrirq.ko irqn=23 devname=shrirq来加载模块,加载成功后再查看中断信息如下:
23: 15627 15651 IO-APIC-fasteoi ehci_hcd:usb2, uhci_hcd:usb6, shrirq
当上面加载模块成功后,我们可以使用sudo rmmod shrirq命令来卸载模块。
如果都能正常运行,相应的dmesg信息如下:
Enter shrirq_init…
Request IRQ success! devname=shrirq,IRQ:18 Exit shrirq_init…
Enter shrirq_handler! ISR devid:1119 count=0 Leaving IRQ handler!
Enter shrirq_handler! ISR devid:1119 count=1 Leaving IRQ handler! ……
Enter shrirq_exit…
Release irq sucess! devname=shrirq,IRQ:23 Exit shrirq_exit…
至此,我们体验了一把中断,接下来会继续尝试与中断相关的深层内容,大家继续加油啊!
参考网址:
http://edsionte.com/techblog/archives/1521
https://www.ibm.com/developerworks/cn/linux/l-cn-linuxkernelint/

评论

此博客中的热门博文

I/O映射之I/O端口

通过Netlink检测网线插拔

使用seq_file