一 中断理解
中断的含义
所谓中断是指 CPU 在执行程序的过程中,出现了某些突发事件时 CPU 必须暂停
执行当前的程序,转去处理突发事件,处理完毕后 CPU 又返回原程序被中断的位置
并继续执行
中断的分类
内部中断 内部中断的中断源来自 CPU内部(软件中断指令、溢出、除法错误等,例如,操作系统从用户态切换到内核态需借助 CPU 内部的软件中断)
外部中断 外部中断的中断源来自 CPU 外部,由外设提出请求
可屏蔽中断 可屏蔽中断可以通过屏蔽字被屏蔽,屏蔽后,该中断不再得到响应
不可屏蔽中断
向量中断 采用向量中断的 CPU 通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行。不同中断号的中断有不同的入口地址
非向量中断 非向量中断的多个中断共享一个入口地址,进入该入口地址后再通过软件判断中断标志来识别具体是哪个中断
总结来说:
向量中断由硬件提供中断服务程序入口地址,
非向量中断由软件提供中断服务程序入口地址。
二 使用中断的设备需要申请和释放对应的中断,分别使用内核提供的 request_irq()和 free_irq()函数。
1.申请 IRQ
int request_irq(unsigned int irq, irq 是要申请的硬件中断号
void (*handler)(int irq, void *dev_id, struct pt_regs *regs), handler 是向系统登记的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数
unsigned long irqflags, irqflags 是中断处理的属性 SA_INTERRUPT则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽
const char * devname, SA_SHIRQ,则表示多个设备共享中断
void *dev_id); dev_id 是handler的参数
返回值
0 表示申请成功
-INVAL 中断号无效或处理函数指针为NULL
-EBUSY 中断已经被占用且不能共享
2.释放 IRQ
void free_irq(unsigned int irq,void *dev_id);
3.使能和屏蔽中断
屏蔽中断 void disable_irq(int irq); 此函数品屏蔽中断时,立即返回
void disable_irq_nosync(int irq); 而此函数会等待目前的中断处理完成
打开中断 void enable_irq(int irq);
屏蔽本CPU内的所有中断 void local_irq_save(unsigned long flags); 以上各 local_开头的方法的作用范围是本 CPU 内
void local_irq_disable(void);
恢复中断 void local_irq_restore(unsigned long flags);
void local_irq_enable(void);
三 中断处理机制
Linux 系统实现底半部的机制主要有 tasklet、工作队列和软中断
1. tasklet
void my_tasklet_func(unsigned long); /*定义一个处理函数*/
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data); /*定义一个 tasklet 结构 my_tasklet,与 my_tasklet_func(data)函数相关联*/
在需要调度 tasklet 的时候引用一个 tasklet_schedule()函数就能使系统在适当的时候进行调度运行
tasklet_schedule(&my_tasklet);
2. 工作队列
struct work_struct my_wq; /*定义一个工作队列*/
void my_wq_func(unsigned long); /*定义一个处理函数*/
INIT_WORK(&my_wq, (void (*)(void *)) my_wq_func, NULL); /*初始化工作队列并将其与处理函数绑定*/
schedule_work(&my_wq); /*调度工作队列执行*/
3. 软中断
软中断是用软件方式模拟硬件中断的概念,实现宏观上的异步执行效果,tasklet也是基于软中断实现的。
硬中断是外部设备对 CPU 的中断
软中断通常是硬中断服务程序对内核的中断
信号则是由内核(或其他进程)对某个进程的中断
四 内核定时器
1. 内核定时器介绍
struct timer_list {
struct list_head entry;
unsigned long expires;//定时时间,jiffies+delayn
void (*function)(unsigned long);//处理函数
unsigned long data;
struct tvec_base *base;
...
};//内核定时器结构体
2. 基本操作函数
struct timer_list my_timer; //声明一个定时器
void init_timer(struct timer_list *timer); //初始化定时器,在该定时器成员赋值前,先执行初始化
void add_timer(struct timer_list *timer); //向内核添加一个定时器,启动该定时器
int mod_timer(struct timer_list *timer,unsigned long expires);//修改定时的expire
int del_timer(struct timer_list *timer); //删除一个定时器
内核定时使用简介:
声明一个定时器,然后初始化,然后添加定时器到内核,定时器开始启动,时间到后,定时器处理程序会自动执行,在中断程序里需要重新设置定时值,并重新使用add_timer来开启定时
器。
3. 短延时
void ndelay(unsigned long nsecs); //忙等待延时nsecs纳秒
void udelay(unsigned long usecs); //忙等待延时usecs微秒
void mdelay(unsigned long msecs); //忙等待延时msecs毫秒
4. 长延时
例子1:延时100个jiffies
unsigned long delay = jiffes+100;
while(time_before( jiffes,delay) );
例子2: 延时2秒
unsigned long delay = jiffes+2*Hz;
while(time_before( jiffes,delay) );
此外还有time_after(a,b),用法类似。
5. 睡着延时
void msleep( unsigned int millisecs ); //睡眠millisecs个毫秒的延时
void ssleep( unsigned int seconds ); //睡眠seconds秒的延时
unsigned long msleep_interruptible( unsigned int millisecs );//睡眠millisecs个毫秒的延时,并且睡眠期间可被中断
中断的含义
所谓中断是指 CPU 在执行程序的过程中,出现了某些突发事件时 CPU 必须暂停
执行当前的程序,转去处理突发事件,处理完毕后 CPU 又返回原程序被中断的位置
并继续执行
中断的分类
内部中断 内部中断的中断源来自 CPU内部(软件中断指令、溢出、除法错误等,例如,操作系统从用户态切换到内核态需借助 CPU 内部的软件中断)
外部中断 外部中断的中断源来自 CPU 外部,由外设提出请求
可屏蔽中断 可屏蔽中断可以通过屏蔽字被屏蔽,屏蔽后,该中断不再得到响应
不可屏蔽中断
向量中断 采用向量中断的 CPU 通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行。不同中断号的中断有不同的入口地址
非向量中断 非向量中断的多个中断共享一个入口地址,进入该入口地址后再通过软件判断中断标志来识别具体是哪个中断
总结来说:
向量中断由硬件提供中断服务程序入口地址,
非向量中断由软件提供中断服务程序入口地址。
二 使用中断的设备需要申请和释放对应的中断,分别使用内核提供的 request_irq()和 free_irq()函数。
1.申请 IRQ
int request_irq(unsigned int irq, irq 是要申请的硬件中断号
void (*handler)(int irq, void *dev_id, struct pt_regs *regs), handler 是向系统登记的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数
unsigned long irqflags, irqflags 是中断处理的属性 SA_INTERRUPT则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽
const char * devname, SA_SHIRQ,则表示多个设备共享中断
void *dev_id); dev_id 是handler的参数
返回值
0 表示申请成功
-INVAL 中断号无效或处理函数指针为NULL
-EBUSY 中断已经被占用且不能共享
2.释放 IRQ
void free_irq(unsigned int irq,void *dev_id);
3.使能和屏蔽中断
屏蔽中断 void disable_irq(int irq); 此函数品屏蔽中断时,立即返回
void disable_irq_nosync(int irq); 而此函数会等待目前的中断处理完成
打开中断 void enable_irq(int irq);
屏蔽本CPU内的所有中断 void local_irq_save(unsigned long flags); 以上各 local_开头的方法的作用范围是本 CPU 内
void local_irq_disable(void);
恢复中断 void local_irq_restore(unsigned long flags);
void local_irq_enable(void);
三 中断处理机制
Linux 系统实现底半部的机制主要有 tasklet、工作队列和软中断
1. tasklet
void my_tasklet_func(unsigned long); /*定义一个处理函数*/
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data); /*定义一个 tasklet 结构 my_tasklet,与 my_tasklet_func(data)函数相关联*/
在需要调度 tasklet 的时候引用一个 tasklet_schedule()函数就能使系统在适当的时候进行调度运行
tasklet_schedule(&my_tasklet);
2. 工作队列
struct work_struct my_wq; /*定义一个工作队列*/
void my_wq_func(unsigned long); /*定义一个处理函数*/
INIT_WORK(&my_wq, (void (*)(void *)) my_wq_func, NULL); /*初始化工作队列并将其与处理函数绑定*/
schedule_work(&my_wq); /*调度工作队列执行*/
3. 软中断
软中断是用软件方式模拟硬件中断的概念,实现宏观上的异步执行效果,tasklet也是基于软中断实现的。
硬中断是外部设备对 CPU 的中断
软中断通常是硬中断服务程序对内核的中断
信号则是由内核(或其他进程)对某个进程的中断
四 内核定时器
1. 内核定时器介绍
struct timer_list {
struct list_head entry;
unsigned long expires;//定时时间,jiffies+delayn
void (*function)(unsigned long);//处理函数
unsigned long data;
struct tvec_base *base;
...
};//内核定时器结构体
2. 基本操作函数
struct timer_list my_timer; //声明一个定时器
void init_timer(struct timer_list *timer); //初始化定时器,在该定时器成员赋值前,先执行初始化
void add_timer(struct timer_list *timer); //向内核添加一个定时器,启动该定时器
int mod_timer(struct timer_list *timer,unsigned long expires);//修改定时的expire
int del_timer(struct timer_list *timer); //删除一个定时器
内核定时使用简介:
声明一个定时器,然后初始化,然后添加定时器到内核,定时器开始启动,时间到后,定时器处理程序会自动执行,在中断程序里需要重新设置定时值,并重新使用add_timer来开启定时
器。
3. 短延时
void ndelay(unsigned long nsecs); //忙等待延时nsecs纳秒
void udelay(unsigned long usecs); //忙等待延时usecs微秒
void mdelay(unsigned long msecs); //忙等待延时msecs毫秒
4. 长延时
例子1:延时100个jiffies
unsigned long delay = jiffes+100;
while(time_before( jiffes,delay) );
例子2: 延时2秒
unsigned long delay = jiffes+2*Hz;
while(time_before( jiffes,delay) );
此外还有time_after(a,b),用法类似。
5. 睡着延时
void msleep( unsigned int millisecs ); //睡眠millisecs个毫秒的延时
void ssleep( unsigned int seconds ); //睡眠seconds秒的延时
unsigned long msleep_interruptible( unsigned int millisecs );//睡眠millisecs个毫秒的延时,并且睡眠期间可被中断
五 定时器例程
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE > KERNEL_VERSION(3, 3, 0)
#include <asm/switch_to.h>
#else
#include <asm/system.h> //在2.6.x的内核版本中,文件操作结构体中,才会有ioctl的字段,高版本中使用unlocked_ioctl,
#endif
#include <asm/uaccess.h>
#include <linux/slab.h>
#define SECOND_MAJOR 248 /*预设的 second 的主设备号*/
static int second_major = SECOND_MAJOR;
/*second 设备结构体*/
struct second_dev {
struct cdev cdev; /*cdev 结构体*/
atomic_t counter; /* 一共经历了多少秒?*/
struct timer_list s_timer; /*设备要使用的定时器*/
};
struct second_dev *second_devp;
/*定时器处理函数*/
static void second_timer_handle (unsigned long arg)
{
//修改定时的expire,重新设定定时时间
mod_timer (&second_devp->s_timer, jiffies + HZ);
//原子计数,对当前记录时间更新
atomic_inc (&second_devp->counter);
printk(KERN_NOTICE "current jiffies is %lld\n", jiffies);
}
/*文件打开函数*/
int second_open (struct inode *inode, struct file *filp)
{
/*初始化定时器*/
init_timer (&second_devp->s_timer);
second_devp->s_timer.function = &second_timer_handle; //当定时器达到时,执行定时处理函数。
second_devp->s_timer.expires = jiffies + HZ; //定时时间,jiffies + HZ(HZ默认值为1000)
add_timer (&second_devp->s_timer);/*添加(注册)定时器*/
atomic_set (&second_devp->counter, 0); //计数清零
return 0;
}
/*文件释放函数*/
int second_release (struct inode *inode, struct file *filp)
{
//删除一个定时器
del_timer (&second_devp->s_timer);
return 0;
}
/*Second 读函数*/
static ssize_t second_read (struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
int counter;
counter = atomic_read (&second_devp->counter);
if (put_user (counter, (int *)buf))
return -EFAULT;
else
return sizeof (unsigned int);
}
/*文件操作结构体*/
static const struct file_operations second_fops = {
.owner = THIS_MODULE,
.open = second_open,
.release = second_release,
.read = second_read,
};
/*初始化并注册 cdev*/
static void second_setup_cdev (struct second_dev *dev, int index)
{
int err, devno = MKDEV (second_major, index);
cdev_init (&dev->cdev, &second_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add (&dev->cdev, devno, 1);
if (err)
printk (KERN_NOTICE "Error %d adding CDEV %d", err, index);
}
/*设备驱动模块加载函数*/
int second_init (void)
{
int ret;
dev_t devno = MKDEV (second_major, 0);
/* 申请设备号*/
if (second_major)
ret = register_chrdev_region (devno, 1, "second");
else {
/* 动态申请设备号 */
return alloc_chrdev_region (&devno, 0, 1, "second");
second_major = MAJOR (devno);
}
if (ret < 0)
return ret;
/* 动态申请设备结构体的内存*/
second_devp = kmalloc (sizeof (struct second_dev), GFP_KERNEL);
if (!second_devp) { /*申请失败*/
ret = -ENOMEM;
goto fail_malloc;
}
memset (second_devp, 0, sizeof (struct second_dev));
second_setup_cdev (second_devp, 0);
return 0;
fail_malloc:
unregister_chrdev_region (devno, 1);
return ret;
}
/*模块卸载函数*/
void second_exit (void)
{
cdev_del (&second_devp->cdev); /*注销 cdev*/
kfree (second_devp); /*释放设备结构体内存*/
unregister_chrdev_region (MKDEV (second_major, 0), 1); /* 释放设备号 */
}
MODULE_AUTHOR ("zhongming");
MODULE_LICENSE ("Dual BSD/GPL");
module_param (second_major, int, S_IRUGO);
module_init (second_init);
module_exit (second_exit);
Makefile文件
obj-m := Second.o
KERNEL_VERSION ?= $(shell uname -r)
PWD := $(shell pwd)
all:
make -C /lib/modules/$(KERNEL_VERSION)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(KERNEL_VERSION)/build M=$(PWD) clean
测试代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
int main (void)
{
int fd;
int counter = 0;
int old_counter = 0;
fd = open ("/dev/Second", O_RDONLY);
if (fd != -1) {
while (1) {
read (fd, &counter, sizeof (unsigned int));
if (counter != old_counter) {
printf ("seconds after open /dev/second: %d\n", counter);
old_counter = counter;
}
}
} else {
printf ("Device open failure\n");
}
return 0;
}