我本仁慈,奈何苍天不许
分类: LINUX
2013-12-26 16:52:59
Linux中断机制
{//?为何要用到中断机制
如果没有中断,CPU就只有用轮询的方式,那样会增加CPU负担,且轮询有时间间隔,
而中断马上打断CPU当前运行的任务,先进行中断处理后再返回原来的任务执行。响应快。
}
{//?linux的中断机制是如何实现的,它和别的系统的中断机制有何区别
相同的: 中断上下文切换,如保留现场,中断向量表跳转,调用中断处理程序,恢复现场等。
linux 特有的: 上半部,下半部
中断号
| | |
---------------
| 中断控制器 |
---------------
|
---------
|上半部 | //如: 用 request_irq申请的 中断处理程序
---------
|
-----
| | -- HI_SOFTIRQ
|下 | -- TIMER_SOFTIRQ
| | -- NET_TX_SOFTIRQ
|半 | -- NET_RX_SOFTIRQ
| |
|部 | -- TASKLET_SOFTIRQ //用于中断推迟执行,运行于中断的上下文,不能睡眠
| |
|---|
|
-----------------
| | -- task (时间片调度任务 对应应用中的进程)
| 内核 调度 | -- workqueue //工作队列,可用于任务的推迟执行,运行于进程上下文, 可睡眠
| | -- ksoftirq
-----------------
{//--中断在linux内核源码的实现
1. 中断号定义
{
见 arch/arm/mach-s5pc100/include/mach/irqs.h 和arch/arm/plat-s5p/include/plat/irqs.h
它是在移植时结合电路图和cpu手册获得的
}
2. 申请中断(注册中断号和中断处理程序)
{
typedef irqreturn_t (*irq_handler_t)(int, void *);
int request_irq(
unsigned int irq, //要申请的硬件中断号
irq_handler_t handler, //是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数
unsigned long flags, //中断属性 如IRQF_DISABLED 处理时屏蔽所有中断 IRQF_SHARED 表示共享中断 见include/linux/interrupt.h
const char *name, //设置中断名称,在cat /proc/interrupts中可以看到此名称
void *dev_id) //中断共享时会用到, 一般为NULL
}
3. 中断号和中断处理程序 关联起来 //request_irq 后 系统自动完成,开发者很少修改到,仅做了解
{
中断号 --> 中断描述符 irq_desc [NR_IRQS] --> 中断服务例程 irqaction --> handler 中断处理程序
... ...
--> 中断服务例程 irqaction --> handler 中断处理程序
//----include/linux/irq.h
struct irq_desc { // 中断例程描述符表
unsigned int irq; //该中断描述符的中断号
struct irqaction *action; /* IRQ action list */ //指向该中断号对应的irqaction结构体链表
unsigned int status; /* IRQ status */
}
extern struct irq_desc irq_desc[NR_IRQS]; //NR_IRQS 是中断源数目
//----include/linux/interrupt.h
struct irqaction {
irq_handler_t handler; //指向中断服务程序
unsigned long flags; //中断标志
const char *name; //I/O设备名
void *dev_id; //设备标识
struct irqaction *next;// 指向下一个描述符
int irq; // IRQ线
struct proc_dir_entry *dir;//指向IRQn相关的/proc/irq/n目录的描述符
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned long thread_flags;
};
}
4. 使能和屏蔽中断
disable_irq(int irq); //屏蔽指定中断号的中断
enable_irq(int irq); //使能某中断号的中断
5. 释放中断
void free_irq(unsigned int irq,void *dev_id); //释放指定的中断, irq表示中断号 dev_id中断共享时用,一般为NULL
6. 中断中耗时部分的推迟处理 (可选)
推迟处理我们一般用tasklet 和workqueue
DECLARE_TASKLET (xxx_tasklet, xxx_do_tasklet, 0) 注册tasklet
tasklet_schedule(&xxx_tasklet) 触发tasklet
}
}
{//?如何使用中断
{//---按键驱动
# cd s5pc100_irq
# insmod s5pc100_irq.ko
按k1按键后, 终端显示 irq: interrupt 33
//---s5pc100_irq
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("farsight");
static int irq_major = 250;
static int irq_minor = 0;
static struct cdev irq_cdev;
void my_delay_work(unsigned long);
static DECLARE_TASKLET(tlet, my_delay_work,0); //静态创建一个tasklet , my_delay_work(函数用于处理推迟执行的事情
struct work_struct my_wq;
static struct file_operations s5pc100_irq_ops = {
.owner = THIS_MODULE,
};
static int irq_setup_cdev(struct cdev *cdev,
struct file_operations *fops)
{
int result;
dev_t devno = MKDEV(irq_major, irq_minor);
cdev_init(cdev, fops);
cdev->owner = THIS_MODULE;
result = cdev_add(cdev, devno, 1);
if(result)
{
printk("irq: cdev add faiirq\n");
return result;
}
return 0;
}
// 中断处理程序 (对应中断的上半部)
static irqreturn_t irqhandler(int irqno, void *dev_id) //注意里面不能用会引起睡眠的函数 如信号量
{
printk("irq: interrupt %d\n", irqno);
tasklet_schedule(&tlet); //触发tasklet调度执行
//schedule_work(&my_wq);
printk("triger delay work schedule jiffies=%ld\n",jiffies); //每发生一次timer interrupt,Jiffies (unsigned long)变数会被加一 ,用来纪录系统 自开机以来 的 timer interrupts
return IRQ_HANDLED;
}
void my_delay_work(unsigned long arg)
{
printk(" used to do need more time things jiffies=%d",jiffies);
}
static int __init s5pc100_irq_init(void)
{
int result;
dev_t devno = MKDEV(irq_major, irq_minor);
result = register_chrdev_region(devno, 1, "s5pc100_irq");
if(result)
{
printk("irq: unable to get major %d\n", irq_major);
return result;
}
result = irq_setup_cdev(&irq_cdev, &s5pc100_irq_ops);
if(result)
return result;
//申请中断
result = request_irq(IRQ_EINT(1), //硬件中断号 IRQ_EINT(1) = 1+ S5P_EINT_BASE1 = 1 + S5P_IRQ_OFFSET = 1+32 = 33
irqhandler, //中断处理函数
IRQF_DISABLED|IRQF_TRIGGER_FALLING, //IRQF_DISABLED 处理时屏蔽所有中断 见include/linux/interrupt.h
//IRQF_TRIGGER_FALLING 中断下降沿触发
// 其它 如IRQF_SHARED 表示共享中断
"EINT 1", //设置中断名称,在cat /proc/interrupts
NULL);
if(result)
printk("irq: request irq %d failed!\n", IRQ_EINT(1));
printk("irq: driver instalirq, with major %d!\n", irq_major);
//INIT_WORK(&my_wq,(void *)my_delay_work);//for 2.6.20
return 0;
}
static void __exit s5pc100_irq_exit(void)
{
dev_t devno = MKDEV(irq_major, irq_minor);
free_irq(IRQ_EINT(1), NULL); //释放中断
cdev_del(&irq_cdev);
unregister_chrdev_region(devno, 1);
printk("irq: driver uninstalirq!\n");
}
module_init(s5pc100_irq_init);
module_exit(s5pc100_irq_exit);
}
}
{//---时间相关
jiffies:
全局变量jiffies用来记录自系统启动以来产生的节拍的总数 // Linux核心每隔固定周期会发出一次timer interrupt (IRQ 0),Jiffies会相应的加一
启动时,内核将该变量初始化为0,此后,每次时钟中断处理程序都会增加该变量的值。
一秒内时钟中断的次数等于Hz,所以jiffies一秒内增加的值也就是HZ
短延时: //接近或短于一个jiffies
udelay mdelay 是软循环忙等待,消耗CPU时间, 这里测下来1个jiffies = 4ms 所以,使用时延时小于4ms的可用它们。
长延时:
用查询jiffies的方法 如 time_after
内核调度超时 schedule_timeout(signed long timeout)
基于等待队列的睡眠超时 sleep_on_timeout(wait_queue_head_t *q, long timeout) //最后也是调用内核schedule
{//-----定时器例子
#cd time
#make
#insmod hello.ko
#gcc test.c
#./a.out
递增显示
seconds after open /dev/hello:1
seconds after open /dev/hello:2
seconds after open /dev/hello:3
{//---time/hello.c
atomic_t sec_count;
struct timer_list s_timer; /*设备要使用的定时器*/
/*定时器处理函数*/
static void second_timer_handle(unsigned long arg)
{
mod_timer(&s_timer,jiffies + HZ); //modify a timer's timeout
atomic_inc(&sec_count);
printk("current jiffies is %ld\n", jiffies);
}
static int hello_open (struct inode *inode, struct file *file)
{
/*初始化定时器*/
init_timer(&s_timer);
s_timer.function = &second_timer_handle;
s_timer.expires = jiffies + HZ; //超时值是一个jiffies值,当jiffies值大于超时值timer->expires时,timer->function函数就会被运行
add_timer(&s_timer); /*添加(注册)定时器*/
atomic_set(&sec_count,0);//计数清0
return 0;
}
ssize_t hello_read (struct file *filp, char *buff, size_t count, loff_t *offp)
{
int counter;
counter = atomic_read(&sec_count);
if (put_user(counter,(int *)buff)) //Write a simple value into user space.
return -EFAULT;
return sizeof(unsigned int);
}
}
}
}
}