当前位置: 首页 > news >正文

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中断和定时器的内容了,有什么疑问和建议欢迎在评论区提出来喔。 

 

相关文章:

Linux驱动中断和定时器

目录 中断 顶半部/底半部机制 软中断&#xff1a; Tasklet: 工作队列&#xff1a; 定时器 中断 中断是正在执行的程序被另一个程序打断&#xff0c;去执行另一个程序的处理函数&#xff0c;当执行完再返回执行被打断的程序。分为内中断(异常)和外中断(硬件中断)。 当cp…...

表达式和函数

表达式&#xff1a; 将数字和运算符连接起来的组合称为表达式。我们可以将数字称为操作数&#xff0c;单个操作数也可以被看作是一个表达式。 操作数&#xff1a;常数&#xff0c;列名&#xff0c;函数调用&#xff0c;其他表达式 运算符&#xff1a;算数运算符&#xff0c;…...

C#基础复习

目录 格式字符串 多重标记和值 预定义类型 用户定义类型 值类型和引用类型 存储引用类型对象的成员 C#类型的分类 静态类型和dynamic关键字 类的基本概念 类成员的类型 为数据分配内存 访问修饰符 格式字符串 多重标记和值 C#程序就是一组类型声明&#xff0c;学习C#就是学习…...

Windows服务器使用代码SSH免密登录并执行脚本

服务器操作系统 Window Server 2016 1、Windows服务器安装OpenSSH 有多种方式&#xff0c;本文介绍一种方式 下载页&#xff1a; https://github.com/PowerShell/Win32-OpenSSH/releases 在下载页下载文件OpenSSH-Win64.zip 本次实验解压至 D:\OpenSSH-Win64\OpenSSH-Win6…...

(Deep Learning)交叉验证(Cross Validation)

交叉验证&#xff08;Cross Validation&#xff09; 交叉验证&#xff08;Cross Validation&#xff09;是一种评估模型泛化性能的统计学方法&#xff0c;它比单次划分训练集和测试集的方法更加稳定、全面。 交叉验证不但可以解决数据集中数据量不够大的问题&#xff0c;也可以…...

通俗举例讲解动态链接】静态链接

参考动态链接 - 知乎 加上我自己的理解&#xff0c;比较好懂&#xff0c;但可能在细节方面有偏差,但总体是一致的 静态链接的背景 静态链接使得不同的程序开发者和部门能够相对独立的开发和测试自己的程序模块&#xff0c;从某种意义上来讲大大促进了程序开发的效率&#xf…...

K8S部署常见问题归纳

目录一. 常用错误发现手段二、错误问题1. token 过期2. 时间同步问题3. docker Cgroup Driver 不是systemd4. Failed to create cgroup(未验证&#xff09;子节点误执行kubeadm reset一. 常用错误发现手段 我们在部署经常看到的提示是&#xff1a; [kubelet-check] It seems …...

Redis高可用

最近离职后还没开始找工作&#xff0c;在找工作前去学习一下Redis高可用方案。 目录Redis高可用高可用的概念实现方式持久化主从复制简单结构优化结构优缺点哨兵模式&#xff08;Sentinel&#xff09;哨兵进程的作用自动故障迁移(Automatic failover)优缺点集群优缺点Redis高可…...

Hyperledger Fabric 2.2版本环境搭建

前言 部署环境: CentOS7.9 提前安装好以下工具 git客户端golangdockerdocker-composecurl工具 以下是个人使用的版本 git: 2.39.2golang: 1.18.6docker: 23.0.3dockkekr-compose: v2.17.2curl: 7.29.0 官方文档参考链接&#xff1a;跳转链接&#xff0c;不同的版本对应的官…...

macOS Monterey 12.6.5 (21G531) Boot ISO 原版可引导镜像

本站下载的 macOS 软件包&#xff0c;既可以拖拽到 Applications&#xff08;应用程序&#xff09;下直接安装&#xff0c;也可以制作启动 U 盘安装&#xff0c;或者在虚拟机中启动安装。另外也支持在 Windows 和 Linux 中创建可引导介质。 2023 年 4 月 10 日&#xff08;北京…...

【软件设计师13】数据库设计

数据库设计 1. 数据库设计过程 2. E-R模型 3. E-R图向关系模型的转换 例如一对一联系&#xff0c;可以将联系单独做为关系模式&#xff0c;也可以存放到任意一个实体中 而一对多要合并只能合并到多这边&#xff0c;不能存放到1 多对多则联系必须单独转成一个关系模式 4. 案…...

SpringMVC的全注解开发

文章目录一、spring-mvc.xml 中组件转化为注解形式二、DispatcherServlet加载核心配置类三、消除web.xml一、spring-mvc.xml 中组件转化为注解形式 跟之前全注解开发思路一致&#xff0c; xml配置文件使用核心配置类替代&#xff0c;xml中的标签使用对应的注解替代 <!-- 组件…...

C# | 导出DataGridView中的数据到Excel、CSV、TXT

C# | 导出DataGridView中的数据到Excel、CSV、TXT 文章目录C# | 导出DataGridView中的数据到Excel、CSV、TXT前言DataGridView数据转存DataTableDataTable转Excel方法一、使用Microsoft.Office.Interop.Excel方法二、使用EPPlus库方法三、使用NPOI库DataTable转CSVDataTable转T…...

新规拉开中国生成式AI“百团大战”序幕?

AI将走向何方&#xff1f; ChatGPT在全球范围掀起的AI热潮正在引发越来越多的讨论&#xff0c;AI该如何管理&#xff1f;AI该如何发展&#xff1f;一系列问题都成为人们热议的焦点。此前&#xff0c;马斯克等海外名人就在网络上呼吁OpenAI暂停ChatGPT的模型训练和迭代&#xf…...

日撸 Java 三百行day31

文章目录day31 整数矩阵及其运算面向对象思想java异常处理java中的getter和setter方法代码day31 整数矩阵及其运算 面向对象思想 结合之前day7和day8面向过程开发&#xff0c;只关注了矩阵加法和矩阵乘法的功能。而day31是面向对象开发&#xff0c;一个矩阵类&#xff0c;在这…...

在线绘制思维导图

思维导图是一种可视化的思维工具&#xff0c;它可以将放射性思考具体化为可视的图像和图表。 思维导图利用图文并重的技巧&#xff0c;把各级主题的关系用相互隶属与相关的层级图表现出来&#xff0c;把主题关键词与图像、颜色等建立记忆链接。 它运用图像和颜色等多种元素&…...

月薪20k的性能测试必备技能:发现性能瓶颈掌握性能调优

背景 当下云计算、大数据盛行的背景下&#xff0c;大并发和大吞吐量的需求已经是摆在企业面前的问题了&#xff0c;其中网络的性能要求尤为关键&#xff0c;除了软件本身需要考虑到性能方面的要求&#xff0c;一些硬件上面的优化也是必不可少的。 作为一名测试工作者&#xf…...

3、Web前端学习规划:CSS - 学习规划系列文章

CSS作为Web前端开发的第2种重要的语言&#xff0c;笔者建议在学了HTML之后进行。CSS主要是对于HTML做一个渲染&#xff0c;其也带了一些语言语法函数&#xff0c;功能也非常强大。 1、 简介&#xff1b; CSS(层叠样式表)是一种用于描述网页样式的语言。它可以控制网页中的字体、…...

城市轨道交通列车时刻表优化问题【最优题解】

文章目录城市轨道交通列车时刻表优化问题思路文章底部城市轨道交通列车时刻表优化问题 最新进度在文章最下方卡片&#xff0c;加入获取思路数据代码论文&#xff1a;2023十三届MathorCup交流 (第一时间在CSDN分享&#xff0c;文章底部) 题目为数据分析类题目。列车时刻表优化…...

常年不卷,按时下班,工作能力强,同事求助知无不言,不扯皮,不拉帮结派,这样的职场清流竟然被裁掉了!...

在职场上&#xff0c;你永远想不到什么样的员工会被优化&#xff0c;比如下面这位&#xff1a;常年不卷&#xff0c;按时下班&#xff0c;工作很专业&#xff0c;同事问什么都回答&#xff0c;不扯皮&#xff0c;不拉帮结派&#xff0c;简直是职场清流。在上个月竟然被优化了&a…...

学校招生小程序源码介绍

基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码&#xff0c;专为学校招生场景量身打造&#xff0c;功能实用且操作便捷。 从技术架构来看&#xff0c;ThinkPHP提供稳定可靠的后台服务&#xff0c;FastAdmin加速开发流程&#xff0c;UniApp则保障小程序在多端有良好的兼…...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

基于数字孪生的水厂可视化平台建设:架构与实践

分享大纲&#xff1a; 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年&#xff0c;数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段&#xff0c;基于数字孪生的水厂可视化平台的…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

云原生玩法三问:构建自定义开发环境

云原生玩法三问&#xff1a;构建自定义开发环境 引言 临时运维一个古董项目&#xff0c;无文档&#xff0c;无环境&#xff0c;无交接人&#xff0c;俗称三无。 运行设备的环境老&#xff0c;本地环境版本高&#xff0c;ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...

论文笔记——相干体技术在裂缝预测中的应用研究

目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术&#xff1a;基于互相关的相干体技术&#xff08;Correlation&#xff09;第二代相干体技术&#xff1a;基于相似的相干体技术&#xff08;Semblance&#xff09;基于多道相似的相干体…...

给网站添加live2d看板娘

给网站添加live2d看板娘 参考文献&#xff1a; stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下&#xff0c;文章也主…...

WPF八大法则:告别模态窗口卡顿

⚙️ 核心问题&#xff1a;阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程&#xff0c;导致后续逻辑无法执行&#xff1a; var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题&#xff1a…...

【SpringBoot自动化部署】

SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一&#xff0c;能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时&#xff0c;需要添加Git仓库地址和凭证&#xff0c;设置构建触发器&#xff08;如GitHub…...

深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向

在人工智能技术呈指数级发展的当下&#xff0c;大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性&#xff0c;吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型&#xff0c;成为释放其巨大潜力的关键所在&…...