关于中断大家应该很熟悉,无论是操作系统的学习也好还是微机原理也好都会接触到中断。以前进行单片机单板开发的时候,会有外部中断,定时器中断以及串口中断,就是有一个事件触发(外部中断则是外部有按键触发(边缘触发和水平触发),串口中断则是串口有数据输入,定时器中断则是内部设置一个定时器,时间到了就触发),然后就去调用对应的中断处理函数。在此之前,需要保护现场,保存中断发生时的状态,比如寄存器,堆栈,各种变量等等,一旦中断程序执行完毕,程序又会回到当初中断发生的地方重新执行。
概念性的东西就不费笔墨了。
当然,linux内核的中断当然要比上面复杂的多,上面只是大致阐述下中断的概念。
具体到一个操作系统,需要对连接到计算机上的硬件设备进行有效管理,就需要硬件对操作系统做出响应(不要问我为什么不是操作系统去向硬件发出请求)。就是让硬件在需要的时候向内核发出信号,这就是中断机制。
不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标志。(参见大学微机原理课程)这样操作系统就知道是谁发出的中断请求,从而给不同的中断提供对应的中断处理程序。
先看代码:
#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
#include <linux/semaphore.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
MODULE_LICENSE("Dual BSD/GPL");
#define DEV_SIZE 20
#define WQ_MAJOR 230
#define DEBUG_SWITCH 1
#if DEBUG_SWITCH
#define P_DEBUG(fmt, args...) printk("<1>" "<kernel>[%s]"fmt,__FUNCTION__, ##args)
#else
#define P_DEBUG(fmt, args...) printk("<7>" "<kernel>[%s]"fmt,__FUNCTION__, ##args)
#endif
static int irq;
static char *devname;
//模块参数,允许在运行insmod加载模块的时候对下面参数赋值
module_param(irq, int, S_IRUGO);
module_param(devname, charp, S_IRUGO);
struct wq_dev{
char kbuf[DEV_SIZE];//缓冲区
dev_t devno;//设备号
unsigned int major;
struct cdev wq_cdev;
unsigned int cur_size;//可读可写的数据量
struct semaphore sem;//信号量
wait_queue_head_t r_wait;//读等待队列
wait_queue_head_t w_wait;//写等待队列
struct fasync_struct *async_queue;//异步通知队列
};
//struct wq_dev *wq_devp;
//异步通知机制驱动函数
static int wq_fasync(int fd, struct file *filp, int mode)
{
struct wq_dev *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);//调用内核提供的函数
}
//中断处理程序
static irqreturn_t wq_irq_handler(int irq, void *dev)
{
struct wq_dev mydev;
static int count = 1;
mydev = *(struct wq_dev*)dev;
printk("key:%d\n",count);
printk("ISR is working...\n");
count++;
return IRQ_HANDLED;
}
int wq_open(struct inode *inodep, struct file *filp)
{
struct wq_dev *dev;
dev = container_of(inodep->i_cdev, struct wq_dev, wq_cdev);
filp->private_data = dev;
//文件打开的时候,注册中断处理程序,激活给定的中断线
if(request_irq(irq, wq_irq_handler, IRQF_SHARED, devname, dev) != 0)
{
printk("%s request IRQ:%d failed...\n", devname, irq);
return -1;
}
printk("%s request IRQ:%d sucess...\n", devname, irq);
printk(KERN_ALERT "open is ok!\n");
return 0;
}
int wq_release(struct inode *inodep, struct file *filp)
{
printk(KERN_ALERT "release is ok!\n");
wq_fasync(-1, filp, 0);//从异步通知队列中删除该filp
return 0;
}
static ssize_t wq_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)
{
struct wq_dev *dev = filp->private_data;
P_DEBUG("read data...\n");
if(down_interruptible(&dev->sem))//获取信号量
{
P_DEBUG("enter read down_interruptible\n");
return -ERESTARTSYS;
}
P_DEBUG("read first down\n");
while(dev->cur_size == 0){//无数据可读,进入休眠lon
up(&dev->sem);//释放信号量,不然写进程没有机会来唤醒(没有获得锁)
if(filp->f_flags & O_NONBLOCK)//检查是否是阻塞型I/O
return -EAGAIN;
P_DEBUG("%s reading:going to sleep\n", current->comm);
if(wait_event_interruptible(dev->r_wait, dev->cur_size != 0))//休眠等待被唤醒
{
P_DEBUG("read wait interruptible\n");
return -ERESTARTSYS;
}
P_DEBUG("wake up r_wait\n");
if(down_interruptible(&dev->sem))//获取信号量
return -ERESTARTSYS;
}
//数据已就绪
P_DEBUG("[2]dev->cur_size is %d\n", dev->cur_size);
if(dev->cur_size > 0)
count = min(count, dev->cur_size);
//从内核缓冲区赋值数据到用户空间,复制成功返回0
if(copy_to_user(buf, dev->kbuf, count))
{
up(&dev->sem);
return -EFAULT;
}
dev->cur_size -= count;//可读数据量更新
up(&dev->sem);
wake_up_interruptible(&dev->w_wait);//唤醒写进程
P_DEBUG("%s did read %d bytes\n", current->comm, (unsigned int)count);
return count;
}
static ssize_t wq_write(struct file *filp,const char __user *buf,size_t count, loff_t *offset)
{
struct wq_dev *dev = filp->private_data;
//wait_queue_t my_wait;
P_DEBUG("write is doing\n");
if(down_interruptible(&dev->sem))//获取信号量
{
P_DEBUG("enter write down_interruptible\n");
return -ERESTARTSYS;
}
P_DEBUG("write first down\n");
while(dev->cur_size == DEV_SIZE){//判断空间是否已满
up(&dev->sem);//释放信号量
if(filp->f_flags & O_NONBLOCK)
return -EAGAIN;
P_DEBUG("writing going to sleep\n");
if(wait_event_interruptible(dev->w_wait, dev->cur_size < DEV_SIZE))
return -ERESTARTSYS;
if(down_interruptible(&dev->sem))//获取信号量
return -ERESTARTSYS;
}
if(count > DEV_SIZE - dev->cur_size)
count = DEV_SIZE - dev->cur_size;
if(copy_from_user(dev->kbuf, buf, count))//数据复制
return -EFAULT;
dev->cur_size += count;//更新数据量
P_DEBUG("write %d bytes , cur_size:[%d]\n", count, dev->cur_size);
P_DEBUG("kbuf is [%s]\n", dev->kbuf);
up(&dev->sem);
wake_up_interruptible(&dev->r_wait);//唤醒读进程队列
if(dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);//可写时发送信号
return count;
}
static unsigned int wq_poll(struct file *filp, poll_table *wait)
{
struct wq_dev *dev = filp->private_data;
unsigned int mask = 0;
if(down_interruptible(&dev->sem))//获取信号量
return -ERESTARTSYS;
poll_wait(filp, &dev->w_wait, wait);//添加写等待队列
poll_wait(filp, &dev->r_wait, wait);//添加读等待队列
if(dev->cur_size != 0)//判断是否可读取
mask |= POLLIN | POLLRDNORM;
if(dev->cur_size != DEV_SIZE)//判断是否可写入
mask |= POLLOUT | POLLWRNORM;
up(&dev->sem);//释放信号量
return mask;
}
struct file_operations wq_fops = {
.open = wq_open,
.release = wq_release,
.write = wq_write,
.read = wq_read,
.poll = wq_poll,
.fasync = wq_fasync,//函数注册
};
struct wq_dev my_dev;
static int __init wq_init(void)
{
int result = 0;
my_dev.cur_size = 0;
my_dev.devno = MKDEV(WQ_MAJOR, 0);
//设备号分配
if(WQ_MAJOR)
result = register_chrdev_region(my_dev.devno, 1, "wqlkp");
else
{
result = alloc_chrdev_region(&my_dev.devno, 0, 1, "wqlkp");
my_dev.major = MAJOR(my_dev.devno);
}
if(result < 0)
return result;
cdev_init(&my_dev.wq_cdev, &wq_fops);//设备初始化
my_dev.wq_cdev.owner = THIS_MODULE;
sema_init(&my_dev.sem, 1);//信号量初始化
init_waitqueue_head(&my_dev.r_wait);//等待队列初始化
init_waitqueue_head(&my_dev.w_wait);
result = cdev_add(&my_dev.wq_cdev, my_dev.devno, 1);//设备注册
if(result < 0)
{
P_DEBUG("cdev_add error!\n");
goto err;
}
printk(KERN_ALERT "hello kernel\n");
return 0;
err:
unregister_chrdev_region(my_dev.devno,1);
return result;
}
static void __exit wq_exit(void)
{
cdev_del(&my_dev.wq_cdev);
unregister_chrdev_region(my_dev.devno, 1);
free_irq(irq, &my_dev);//卸载驱动的时候,注销相应的中断处理程序,并释放中断线(其释放机制类似于C++中的引用,linux中的描述符的引用...)
}
module_init(wq_init);
module_exit(wq_exit);
编译后,insmod wqlko.ko irq=1 devname=myirq
然后./app_read(./app_write)。
dmesg看打印信息。
cat /proc/interrupts 查看共享的中断线
尤其这里指定irq=1,是与键盘共享同一根中断线,所以一旦键盘发出中断请求,系统响应后也会执行我们的中断处理程序。
上面只是一个简单的中断处理程序样本,还没有涉及到下半部tasklet等,也不涉及到具体硬件(直接在PC下)。
主要注册中断处理程序和释放中断处理程序。
调用request_irq的正确位置应该是在设备第一次打开,硬件被告知产生中断之前;
调用free_irq的位置是最后一次关闭设备,硬件被告知不再使用中断处理器之后。
对于request_irq函数,内核维护了一个中断信号线的注册表,模块使用中断之前要先请求一个中断通道(或者中断请求IRQ),然后在使用后释放该通道。
我们这里是和其他驱动程序共享中断信号线。
同样的,我们这里依旧跟踪下linux 内核源码,不过与前面不同的是
中断处理系统在linux中的实现是非常依赖体系结构的,实现依赖于处理器,所使用的终端处理器的类型,体系结构的设计及机器本身。
设备产生中断,通过总线把电信号发送给中断控制器,如果中断是激活的(没被屏蔽),那么中断控制器就会把中断发往处理器,如果处理器没禁止该中断,处理器会停止当前的事,跳到内存中预定义的位置开始执行哪里的代码。这个预定义的位置是由内核设置的,是中断处理程序的入口处。
在内核中,中断的旅程开始于预定义入口点,这类似于系统调用通过预定义的异常句柄进入内核。对于每条中断线,处理器都会跳到对应的唯一的位置,这样内核就知道所接收中断的IRQ号了。初始入口点只是在栈中保存这个号,并存放当前寄存器的只;然后,内核调用函数do_IRQ。
先看request_irq函数:
/**其实看下面的英文注释就知道这个函数的功能了 * request_irq - allocate an interrupt line * @irq: Interrupt line to allocate * @handler: Function to be called when the IRQ occurs * @irqflags: Interrupt type flags * @devname: An ascii name for the claiming device * @dev_id: A cookie passed back to the handler function * * This call allocates interrupt resources and enables the * interrupt line and IRQ handling. From the point this * call is made your handler function may be invoked. Since * your handler function must clear any interrupt the board * raises, you must take care both to initialise your hardware * and to set up the interrupt handler in the right order. * * Dev_id must be globally unique. Normally the address of the * device data structure is used as the cookie. Since the handler * receives this value it makes sense to use it. * 这里写到Dev_id必须是全局唯一的变量,因为对于共享的中断线,需要一个唯一的信息 * 来区分其上面的多个处理程序,并让free_irq()仅仅删除指定的处理程序。 * * If your interrupt is shared you must pass a non NULL dev_id * as this is required when freeing the interrupt. * * Flags: * * IRQF_SHARED Interrupt is shared * * IRQF_DISABLED Disable local interrupts while processing * * IRQF_SAMPLE_RANDOM The interrupt can be used for entropy * */
//FIXME - handler used to return void - whats the significance of the change?
int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *),
unsigned long irq_flags, const char * devname, void *dev_id)
{
unsigned long retval;
struct irqaction *action;//irqaction结构体
if (irq >= NR_IRQS || !irq_desc[irq].valid || !handler ||
(irq_flags & IRQF_SHARED && !dev_id))
return -EINVAL;
action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL);//新建一个irqaction结构体
if (!action)
return -ENOMEM;
action->handler = handler;//中断处理程序
action->flags = irq_flags;//标识
cpus_clear(action->mask);
action->name = devname;//设备名
action->next = NULL;
action->dev_id = dev_id;//标识设备本身
retval = setup_irq(irq, action);//调用setup_irq注册中断处理程序
if (retval)
kfree(action);
return retval;
}
/* struct irqaction { irqreturn_t (*handler)(int, void *, struct pt_regs *);//中断处理程序 unsigned long flags;//标志 cpumask_t mask;//未使用 const char *name;//设备名 void *dev_id;//标识设备本身 struct irqaction *next;//下一个元素,链表中的元素指向共享同一IRQ的硬件设备 int irq;//IRQ线 struct proc_dir_entry *dir;//指向与TRQn相关的/proc/irq/n目录的描述符 }; */
//下面相继就是调用setup_irq();
struct irq_desc *desc = irq_desc + irq;//该语句就是根据中断号搜索到对应的中断描述符结构。
//下面的不追溯了,就是负责整个的注册事宜。
不管引起中断的电路种类如何,所有的I/O中断处理程序都执行四个相通的基本步骤:
在内核态堆栈中保存IRQ的值和寄存器的内容;
为正在给IRQ线服务的PIC发送一个应答,这将允许PIC进一步发出中断;
执行共享这个IRQ的所有设备的中断服务里程(ISR);
跳到ret_from_intr()的地址后终止(这部分是硬件控制的)。
ok,看do_IRQ()
/* * do_IRQ handles all normal device IRQ's (the special * SMP cross-CPU interrupts have their own specific * handlers). */
fastcall unsigned int do_IRQ(struct pt_regs *regs)
{
/* high bit used in ret_from_ code */
int irq = ~regs->orig_eax;//保存IRQ值
……
//这部分代码略,请参考arch/i386/kernel/Irq.c
//保存寄存器的内容
……
__do_IRQ(irq, regs);
irq_exit();
return 1;
}
/** * __do_IRQ - original all in one highlevel IRQ handler * @irq: the interrupt number * @regs: pointer to a register structure * * __do_IRQ handles all normal device IRQ's (the special * SMP cross-CPU interrupts have their own specific * handlers). * * This is the original x86 implementation which is used for every * interrupt type. */
//上面的英文注释已经大致解释了一番...如果英文好,这条路会好走很多
fastcall unsigned int __do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct irq_desc *desc = irq_desc + irq;//找到中断描述符
struct irqaction *action;
unsigned int status;
kstat_this_cpu.irqs[irq]++;
if (CHECK_IRQ_PER_CPU(desc->status)) {//该线上有中断处理程序
irqreturn_t action_ret;
/* * No locking required for CPU-local interrupts: */
if (desc->chip->ack)
desc->chip->ack(irq);
action_ret = handle_IRQ_event(irq, regs, desc->action);//转调用该函数
desc->chip->end(irq);//结束中断
return 1;
}
……
}
irqreturn_t handle_IRQ_event(unsigned int irq, struct pt_regs *regs,
struct irqaction *action)
{
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = 0;
handle_dynamic_tick(action);
if (!(action->flags & IRQF_DISABLED))
local_irq_enable_in_hardirq();
//下面通过循环,调用同一共享中断线上的所有中断服务程序
//这也就告诉我们上面的例程与键盘共享同一信号线,一旦键盘有中断请求,处理器响应之后,
//也会调用我们自定的中断服务程序
do {
ret = action->handler(irq, action->dev_id, regs);//调用我们前面注册的中断服务程序
if (ret == IRQ_HANDLED)
status |= action->flags;
retval |= ret;
action = action->next;//链表的下一个元素,也就是共享同一中断线
} while (action);
if (status & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();
return retval;
}
中断返回以后,处理器会控制执行ret_from_intr(),返回内核执行中断的代码。
2
限于篇幅,未完,待续…