初尝内核中断

每台计算机都连接有很多外设,那操作系统对这些外设进行管理时要如何通信呢?一般有轮询(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
#include
#include
#include

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/

你可能感兴趣的:(初尝内核中断)