> 文章列表 > Linux驱动中断和定时器

Linux驱动中断和定时器

Linux驱动中断和定时器

目录

中断

顶半部/底半部机制

软中断:

Tasklet:

工作队列

定时器


中断

中断是正在执行的程序被另一个程序打断,去执行另一个程序的处理函数,当执行完再返回执行被打断的程序。分为内中断(异常)和外中断(硬件中断)。

当cpu收到一个中断会去中断向量表中查找该中断的处理函数(中断上下文)和地址,然后根据地址进入处理函数。注意中断过程中不允许阻塞睡眠和进程切换,且执行时间越快越好。

驱动中使用中断相当于使用系统资源,需要申请和释放。

需要使用的头文件为:

#include <linux/irq.h>

#include <linux/interrupt.h>

 申请函数为:

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev_id);

参数:

        irq:表示中断号(中断线),/proc/interrupts文件来查看系统已经使用的中断号

        handler:中断处理函数

        flags:中断标志可以为 0,也可能是下列一个或多个标志的位掩码

        name:系统中记录中断名称

        dev_id:用于共享中断,传递给中断处理函数的参数,非共享类型的中断,直接设置成为 NULL

返回0表示成功,非0表示失败

flags标志:

IRQF_DISABLED:此标志表明给定的中断处理程序是一个快速中断处理程序,除了时钟中断外,绝大多数中断都不使用标志

IRQF_SAMPLE_RANDOM:此标志表明这个设备产生的中断对内核熵池有贡献

IRQF_SHARED:此标志表明可以在多个中断处理程序之间共享中断线(中断号),在同一个中断线上注册的每个处理程序必须指定这个标志。其意思就是说没有这个标志那一条中断线上只能有一个处理函数,有这个标志可以多个中断可以共享同一条中断线

上面第5个参数dev_id就是用来区分不同的中断使用同一个中断号的标志,也可以给处理函数传参

释放函数为:

void free_irq(unsigned int irq, void *dev_id)

参数:

        irq:中断号

        dev_id:用于共享中断,传递给中断处理函数的参数,非共享类型的中断,直接设置成为 NULL

下面以按键为例使用混杂设备驱动框架,实现GPIO硬件中断。不了解混杂设备或者想知道更多实现字符设备的驱动框架请看上一篇文章(多种字符设备驱动实现方式)。

//通过GPIO引脚号,获取中断号

int gpio_to_irq(unsigned gpio)

 参数:

        gpio:GPIO引脚号

返回值为中断号

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <asm/gpio.h>
#include <linux/io.h>
#include <mach/platform.h>
#include <linux/miscdevice.h>struct btn_res{int gpio;//端口号char *name;//名称
};struct btn_res btn_info[] = {[0] = {.gpio = PAD_GPIO_B+9,.name = "Key0",},[1] = {.gpio = PAD_GPIO_A+28,.name = "Key1",},[2] = {.gpio = PAD_GPIO_B+30,.name = "Key2",},[3] = {.gpio = PAD_GPIO_B+31,.name = "Key3",}
};int btn_open(struct inode *inode, struct file *filp)
{printk("btn_open!\\n");return 0;
}int btn_release(struct inode *inode, struct file *filp)
{printk("btn_release!\\n");return 0;
}//声明操作函数集合
struct file_operations btn_fops = {.owner = THIS_MODULE,.open = btn_open,.release = btn_release,//对应用户close接口
};//分配初始化miscdevice
struct miscdevice btn_dev = {.minor = MISC_DYNAMIC_MINOR,//系统分配次设备号.name = "btn",//设备文件名.fops = &btn_fops,//操作函数集合
};//中断处理函数
//返回IRQ_HANDLED表示成功,IRQ_NONE表示失败
irqreturn_t btn_handler(int irq, void *dev_id)
{int state;//引脚状态struct btn_res *pdata = (struct btn_res *)dev_id;//引脚数据//区分哪个按键//区分按下松开state = gpio_get_value(pdata->gpio);printk("key %s %s!\\n",pdata->name,state?"released":"pressed");return IRQ_HANDLED;//处理成功
}//加载函数
int btnirq_init(void)
{int ret,i,j,irq;//注册miscdeviceret = misc_register(&btn_dev);if(ret<0){printk("misc_register failed!\\n");goto failure_misc_register;}/*ARRAY_SIZE求数组元素个数*/for(i=0;i<ARRAY_SIZE(btn_info);i++){//申请中断irq = gpio_to_irq(btn_info[i].gpio); ret = request_irq(irq, //中断号btn_handler, //中断处理函数IRQ_TYPE_EDGE_BOTH, //中断标志,表示上升下降沿触发都会触发中断btn_info[i].name, //中断名称&btn_info[i]);//传递给中断处理函数的参数if(!ret){   //注册失败printk("request_irq failed!\\n");goto failure_request_irq;}}return 0;failure_request_irq://第i次失败了//释放0 --- i-1中申请的中断for(j=0;j<i;j++){irq = gpio_to_irq(btn_info[j].gpio);free_irq(irq, &btn_info[j]);}	misc_deregister(&btn_dev);
failure_misc_register:return ret;
}//卸载函数
void btnirq_exit(void)
{int irq,i;//释放所有申请的中断for(i=0;i<ARRAY_SIZE(btn_info);i++){irq = gpio_to_irq(btn_info[i].gpio);free_irq(irq, &btn_info[i]);}misc_deregister(&btn_dev);
}//声明为模块的入口和出口
module_init(btnirq_init);
module_exit(btnirq_exit);MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("1.0");//版本
MODULE_DESCRIPTION("button interrupt module!");//描述信息

顶半部/底半部机制

我们知道中断处理函数的要求是越快越好,但是有一些场合做不到,它们需要使用中断,又不能很快的处理完。为了解决这个问题Linux将中断分为了两个部分topbottom

顶半部(top half):用来处理紧急,耗时比较短的事务,且顶半部不可被打断,底半部需要在顶半部中调度。

底半部(bottom half):用来处理不紧急,耗时较长的事务,且允许稍后完成。

底半部机制的实现有三种方式:软中断,Tasklet,工作队列。(本文重点讲解Tasklet和工作队列)

  • 软中断:

它是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。软中断一般是“可延迟函数”的总称,可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。Linux内核使用结构体softirq_action表示软中断,定义在include/linux/interrupt.h文件中。

/* 用于描述一个软中断 */
struct softirq_action
{/* 软中断的处理函数 */        void    (*action)(struct softirq_action *);
};//软中断描述符有10个
enum{HI_SOFTIRQ=0,TIMER_SOFTIRQ,NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,BLOCK_SOFTIRQ,BLOCK_IOPOLL_SOFTIRQ,TASKLET_SOFTIRQ,SCHED_SOFTIRQ,HRTIMER_SOFTIRQ,RCU_SOFTIRQ,  /* Preferable RCU should always be the last softirq */NR_SOFTIRQS};
  • Tasklet:

tasklet是利用软中断来实现的另外一种底半部机制,一个使用tasklet的中断程序首先会通过执行中断处理程序来快速完成顶半部的工作,接着通过调用tasklet使得底半部的工作得以完成,Linux内核使用结构体tasklet_struct来定义。

struct tasklet_struct
{struct tasklet_struct *next; //下一个taskletunsigned long state;    //tasklet状态atomic_t count;   //计数器,记录对 tasklet 的引用数void (*func)(unsigned long);//tasklet的处理函数(底半部)unsigned long data;//传递给tasklet处理函数的参数
};

使用流程为:

//定义初始化struct tasklet_struct myTasklet;   //定义//myTaskletFunc是处理函数,data是处理函数的参数tasklet_init(myTasklet,myTaskletFunc,data);   //初始化
//或者DECLARE_TASKLET(myTasklet,myTaskletFunc,data);  //定义初始化一步到位//调度tasklet//需要调度tasklet的时候引用一个tasklet_schedule()函数就能使系统在适当的时候进行调度tasklet_schedule(&myTasklet);    

还是以按键为例使用混杂设备驱动框架,实现Tasklet。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <asm/gpio.h>
#include <linux/io.h>
#include <mach/platform.h>
#include <linux/miscdevice.h>struct btn_res{int gpio;//端口号char *name;//名称
};struct btn_res btn_info[] = {[0] = {.gpio = PAD_GPIO_B+9,.name = "Key0",},[1] = {.gpio = PAD_GPIO_A+28,.name = "Key1",},[2] = {.gpio = PAD_GPIO_B+30,.name = "Key2",},[3] = {.gpio = PAD_GPIO_B+31,.name = "Key3",}
};int btn_open(struct inode *inode, struct file *filp)
{printk("btn_open!\\n");return 0;
}int btn_release(struct inode *inode, struct file *filp)
{printk("btn_release!\\n");return 0;
}//声明操作函数集合
struct file_operations btn_fops = {.owner = THIS_MODULE,.open = btn_open,.release = btn_release,//对应用户close接口
};//分配初始化miscdevice
struct miscdevice btn_dev = {.minor = MISC_DYNAMIC_MINOR,//系统分配次设备号.name = "btn",//设备文件名.fops = &btn_fops,//操作函数集合
};//tasklet处理函数(底半部)
void btn_tasklet_func(unsigned long data)
{int state;//引脚状态struct btn_res *pdata = (struct btn_res *)data;//引脚数据printk("btn_tasklet_func\\n");//区分哪个按键//区分按下松开state = gpio_get_value(pdata->gpio);printk("key %s %s!\\n",pdata->name,state?"released":"pressed");
}//分配初始化tasklet
DECLARE_TASKLET(btn_tasklet, btn_tasklet_func, 0);//中断处理函数(顶半部)
//返回IRQ_HANDLED表示成功,IRQ_NONE表示失败
irqreturn_t btn_handler(int irq, void *dev_id)
{btn_tasklet.data = (unsigned long)dev_id;//调度tasklettasklet_schedule(&btn_tasklet);printk("left btn_handler\\n");return IRQ_HANDLED;//处理成功
}//加载函数
int btnirq_init(void)
{int ret,i,j,irq;//注册miscdeviceret = misc_register(&btn_dev);if(ret<0){printk("misc_register failed!\\n");goto failure_misc_register;}/*ARRAY_SIZE求数组元素个数*/for(i=0;i<ARRAY_SIZE(btn_info);i++){//申请中断irq = gpio_to_irq(btn_info[i].gpio); ret = request_irq(irq, //中断号btn_handler, //中断处理函数IRQ_TYPE_EDGE_BOTH, //中断标志,表示上升下降沿触发都会触发中断btn_info[i].name, //中断名称&btn_info[i]);//传递给中断处理函数的参数if(!ret){   //注册失败printk("request_irq failed!\\n");goto failure_request_irq;}}return 0;failure_request_irq://第i次失败了//释放0 --- i-1中申请的中断for(j=0;j<i;j++){irq = gpio_to_irq(btn_info[j].gpio);free_irq(irq, &btn_info[j]);}	misc_deregister(&btn_dev);
failure_misc_register:return ret;
}//卸载函数
void btnirq_exit(void)
{int irq,i;//释放所有申请的中断for(i=0;i<ARRAY_SIZE(btn_info);i++){irq = gpio_to_irq(btn_info[i].gpio);free_irq(irq, &btn_info[i]);}misc_deregister(&btn_dev);
}//声明为模块的入口和出口
module_init(btnirq_init);
module_exit(btnirq_exit);MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("2.0");//版本
MODULE_DESCRIPTION("button interrupt module!");//描述信息

tasklet本身运行在中断上下文,处理函数不能阻塞/睡眠。

  • 工作队列:

工作队列是另外一种底半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,工作队列包括工作和延时工作两种,工作队列在Linux内核中是一个结构体。

需要的头文件为:

#include <linux/workqueue.h> 

//1、工作队列
struct work_struct {atomic_long_t data;struct list_head entry;//链表指针 把每个工作连接在一个链表上组成一个双向链表work_func_t func;//工作的处理函数
#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;
#endif
};//2、延时工作队列:延时队列是在调度时,需要等待指定时间才会调用工作函数
struct delayed_work {struct work_struct work;//工作struct timer_list timer;//内核定时器,在下文会有描述
};

 一般流程为:

工作队列:
//定义初始化//动态定义初始化struct work_struct mywork;    INIT_WORK(&mywork,mywork_func);//静态定义初始化DECLARE_WORK(mywork,mywork_func); //调度工作队列schedule_work(&mywork);//空闲时调用延时队列:
//定义初始化//动态定义初始化struct delayed_work mydelaywork; INIT_DELAYED_WORK(&mydelaywork,mywork_func);//静态定义初始化DECLARE_DELAYED_WORK(mydelaywork, mywork_func); //调度延时工作队列schedule_delayed_work(&mydelaywork,3*HZ);//延时指定时间调用,HZ==10ms

 还是以按键为例使用混杂设备驱动框架,实现延时工作队列(实现工作队列只要把函数换一换)。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <asm/gpio.h>
#include <linux/io.h>
#include <mach/platform.h>
#include <linux/miscdevice.h>
#include <linux/workqueue.h>struct btn_res{int gpio;//端口号char *name;//名称
};struct btn_res btn_info[] = {[0] = {.gpio = PAD_GPIO_B+9,.name = "Key0",},[1] = {.gpio = PAD_GPIO_A+28,.name = "Key1",},[2] = {.gpio = PAD_GPIO_B+30,.name = "Key2",},[3] = {.gpio = PAD_GPIO_B+31,.name = "Key3",}
};struct delayed_work mydelaywork; 
struct btn_res *pdata = NULL;int btn_open(struct inode *inode, struct file *filp)
{printk("btn_open!\\n");return 0;
}int btn_release(struct inode *inode, struct file *filp)
{printk("btn_release!\\n");return 0;
}//声明操作函数集合
struct file_operations btn_fops = {.owner = THIS_MODULE,.open = btn_open,.release = btn_release,//对应用户close接口
};//分配初始化miscdevice
struct miscdevice btn_dev = {.minor = MISC_DYNAMIC_MINOR,//系统分配次设备号.name = "btn",//设备文件名.fops = &btn_fops,//操作函数集合
};//工作处理函数(底半部)
void btn_work_func(struct work_struct *work)
{int state;//引脚状态//printk("enter btn_work_func!\\n");//区分哪个按键//区分按下松开state = gpio_get_value(pdata->gpio);printk("key %s %s!\\n",pdata->name,state?"released":"pressed");
}//中断处理函数(顶半部)
//返回IRQ_HANDLED表示成功,IRQ_NONE表示失败
irqreturn_t btn_handler(int irq, void *dev_id)
{pdata = (struct btn_res *)dev_id;//引脚数据//登记延时工作schedule_delayed_work(&mydelaywork, 3*HZ);//延时30ms处理printk("left btn_handler\\n");return IRQ_HANDLED;//处理成功
}//加载函数
int btnirq_init(void)
{int ret,i,j,irq;//注册miscdeviceret = misc_register(&btn_dev);if(ret<0){printk("misc_register failed!\\n");goto failure_misc_register;}/*ARRAY_SIZE求数组元素个数*/for(i=0;i<ARRAY_SIZE(btn_info);i++){//申请中断irq = gpio_to_irq(btn_info[i].gpio); ret = request_irq(irq, //中断号btn_handler, //中断处理函数IRQ_TYPE_EDGE_BOTH, //中断标志,表示上升下降沿触发都会触发中断btn_info[i].name, //中断名称&btn_info[i]);//传递给中断处理函数的参数if(!ret){   //注册失败printk("request_irq failed!\\n");goto failure_request_irq;}}//延时工作队列INIT_DELAYED_WORK(&mydelaywork,btn_work_func);return 0;failure_request_irq://第i次失败了//释放0 --- i-1中申请的中断for(j=0;j<i;j++){irq = gpio_to_irq(btn_info[j].gpio);free_irq(irq, &btn_info[j]);}	misc_deregister(&btn_dev);
failure_misc_register:return ret;
}//卸载函数
void btnirq_exit(void)
{int irq,i;//释放所有申请的中断for(i=0;i<ARRAY_SIZE(btn_info);i++){irq = gpio_to_irq(btn_info[i].gpio);free_irq(irq, &btn_info[i]);}misc_deregister(&btn_dev);
}//声明为模块的入口和出口
module_init(btnirq_init);
module_exit(btnirq_exit);MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("3.0");//版本
MODULE_DESCRIPTION("button interrupt module!");//描述信息

工作队列和延时工作队列工作于进程上下文,使用内核线程来执行,参与任务调度,可以睡眠 。

以上我们使用了三种方式分别介绍了Linux中的中断,小伙伴需要想清楚他之间的区别和联系,接下来介绍一下定时器。

定时器

先来了解一下tick,HZ,jiffies。

  • tick:内核心跳时钟,周期性产生时钟中断,每一次时钟中断中完成系统相关的工作,HZ是tick的倒数(心跳时钟的频率)。
  • HZ:系统硬件定时器的工作频率,ARM中HZ一般都等于100,所以频率100Hz,一般有100,250,500,1000。
  •  jiffies:内核中用来表示时间的32位(unsigned long)全局变量,记录了开机以来产生了多少次时钟中断。

详细了解请看(对linux内核中jiffies+Hz表示一秒钟的理解)。

与单片机的定时器不同,Linux定时器是在当前时间上加上需要定时的时间,它会在未来到达设置的时间时产生中断。例如现在17:47,我想定时半个小时,那就把定时时间调到18:17,他会在设置好的中断,就很像闹钟。内核定时器的精度不高,不能作为高精度定时器使用,其内核定时器不是周期性运行的,超时以后就会自动关闭,因此要想实现周期性的定时,就需要在定时处理函数中重新开启定时器。

那怎么实现呢?因为jiffies记录了开机以来产生了多少次时钟中断,每次中断都是固定时间,所以相当于jiffies就是当前时间,只需要在当前时间上加上想定时的时间就可以了。如果确切的知道当前Linux系统的tick,例如HZ为100,是10ms,那可以使用jiffies+HZ表示定时1秒钟,jiffies加一表示过了10ms,加一百就是过了1秒钟,这就是前面延时工作队列中说HZ表示1秒钟。不过不推荐这样使用,有函数用来转换jiffies与时间。

//jiffies转ms
unsigned int jiffies_to_msecs(const unsigned long j);
//jiffies转us
unsigned int jiffies_to_usecs(const unsigned long j);//ms转jiffies
unsigned long msecs_to_jiffies(const unsigned int m);
//us转jiffies
unsigned long usecs_to_jiffies(const unsigned int u);

 使用定时器需要的头文件为:

#include <linux/timer.h>

定时器在Linux内核中用一个结构体来表示。

struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/struct list_head entry;unsigned long expires;//定时时间,定时时间点的jiffies值struct tvec_base *base;void (*function)(unsigned long);//超时处理函数unsigned long data;//传递给定时处理函数的参数
}

初始化定时器

//动态初始化
struct timer_list mytimer;  
init_timer(&mytimer);
//重要的三个成员初始化
mytimer.expires = jiffies+msecs_to_jiffies(2000); //jiffies+定时时间
mytimer.function = mytimer_function;              //超时处理函数
mytimer.data = data;                              //定时处理函数参数//静态初始化
DEFINE_TIMER(_name,_function,_expires,_data);
//_name:定时器结构体名称
//_function:定时处理函数
//_expires:定时时间
//_data:定时处理函数参数

向内核添加一个定时器。

void add_timer(struct timer_list *timer);

向内核删除一个定时器。 

int del_timer(struct timer_list * timer);

//返回0表明定时器没有被激活,返回1表示定时器已经激活。

修改定时器时间。

int mod_timer (struct timer_list *timer,unsigned long expires);

//用于修改定时值,如果定时器还没有被激活,该函数可以激活定时器。

//返回0表明调用mod_timer函数前定时器没有被激活,返回1表明调用mod_timer函数前定时器已经激活。

//mod_timer = del_timer + 修改expires + add_timer

下面以LED2秒为周期闪烁为例使用混杂设备驱动框架,实现定时器。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <asm/gpio.h>
#include <linux/io.h>
#include <mach/soc.h>
#include <mach/platform.h>
#include <linux/timer.h>
#include <linux/miscdevice.h>struct led_dest{int gpio;//gpio端口号char *name;//名称
};//定义led的硬件信息
struct led_dest led_info[] = {[0] = {.gpio = PAD_GPIO_E+13,.name = "LED0",},[1] = {.gpio = PAD_GPIO_C+17,.name = "LED1",},[2] = {.gpio = PAD_GPIO_C+8,.name = "LED2",},[3] = {.gpio = PAD_GPIO_C+7,.name = "LED3",}
};//分配内核定时器
struct timer_list mytimer;int led_open(struct inode *inode, struct file *filp)
{printk("led_open!\\n");return 0;
}int led_release(struct inode *inode, struct file *filp)
{printk("led_release!\\n");return 0;
}//声明操作函数集合
struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.release = led_release,//对应用户close接口
};//分配初始化miscdevice
struct miscdevice led_dev = {.minor = MISC_DYNAMIC_MINOR,//系统分配次设备号.name = "led",//设备文件名.fops = &led_fops,//操作函数集合
};//超时处理函数
void mytimer_function(unsigned long data)
{//将GPIO电平取反gpio_set_value(led_info[0].gpio,!gpio_get_value(led_info[0].gpio));mod_timer(&mytimer, jiffies+2*HZ);//重置定时器
}//加载函数
int mytimer_init(void)
{int ret,i;//注册miscdeviceret = misc_register(&led_dev);if(ret<0){printk("misc_register failed!\\n");goto failure_misc_register;}//申请gpio资源并初始化for(i=0;i<ARRAY_SIZE(led_info);i++){//申请gpioret = gpio_request(led_info[i].gpio, led_info[i].name);//设置复用功能if(i==0)nxp_soc_gpio_set_io_func(led_info[i].gpio,NX_GPIO_PADFUNC_0);elsenxp_soc_gpio_set_io_func(led_info[i].gpio,NX_GPIO_PADFUNC_1);//设置输出模式,默认高电平---灭gpio_direction_output(led_info[i].gpio, 1);}//初始化内核定时器init_timer(&mytimer);mytimer.function = mytimer_function;//超时处理函数mytimer.data = 0;//超时处理函数的参数mytimer.expires = jiffies+msecs_to_jiffies(2000);// 2s后超时//添加启动定时器add_timer(&mytimer);printk("add_timer successed!\\n");failure_misc_register:return 0;
}//卸载函数
void mytimer_exit(void)
{int i;//删除定时器del_timer(&mytimer);//释放GPIOfor(i=0;i<ARRAY_SIZE(led_info);i++){//LED熄灭gpio_set_value(led_info[i].gpio,1);gpio_free(led_info[i].gpio);}misc_deregister(&led_dev);
}//声明为模块的入口和出口
module_init(mytimer_init);
module_exit(mytimer_exit);MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("1.0");//版本
MODULE_DESCRIPTION("timer_list module!");//描述信息

好了,以上就是Linux中断和定时器的内容了,有什么疑问和建议欢迎在评论区提出来喔。