嵌入式学习笔记--嵌入式Linux内核定时器和中断

嵌入式Linux内核定时器和中断

**

一、内核定时器

**
Linux 内核使用 timer_list 结构体表示内核定时器, timer_list 定义在文件include/linux/timer.h 中

struct timer_list {
	struct list_head entry;
	unsigned long expires;/* 定时器超时时间(jiffies),单位是节拍数...比如我们现在需要定义一个周期为 2 秒的定时
器,那么这个定时器的超时时间就是 jiffies+(2*HZ),因此 expires=jiffies+(2*HZ)。 */
	struct tvec_base *base;
	void (*function)(unsigned long);/* 定时处理函数 */
	unsigned long data;/* 要传递给 function 函数的参数 */
	int slack;
	.
	.
	.
};

//实例化一个定时器
struct timer_list my_timer;

//和定时器相关的API
void init_timer(struct timer_list *timer);
void add_timer(struct timer_list *timer);//向 Linux 内核添加、注册定时器
int del_timer(struct timer_list * timer);//删除定时器
int del_timer_sync(struct timer_list *timer);//是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,del_timer_sync 不能使用在中断上下文中
int mod_timer(struct timer_list *timer, unsigned long expires);//修改定时值,如果定时器还没有激活的话, mod_timer 函数会激活定时器!

内核定时器使用流程

struct timer_list timer; /* 定义定时器 */
/* 定时器回调函数 */
void function(unsigned long arg)
{
	/*
	* 定时器处理代码
	*/
	
	/* 如果需要定时器周期性运行的话就使用 mod_timer
	* 函数重新设置超时值并且启动定时器。
	*/
	mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));
}

/* 初始化函数 */
void init(void)
{
	init_timer(&timer); /* 初始化定时器 */
	
	timer.function = function; /* 设置定时处理函数 */
	timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
	timer.data = (unsigned long)&dev; /* 将设备结构体作为参数 */
	
	add_timer(&timer); /* 启动定时器 */
}

/* 退出函数 */
void exit(void)
{
	del_timer(&timer); /* 删除定时器 */
	/* 或者使用 */
	del_timer_sync(&timer);
}

Linux内核短延时函数

void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs); 
void mdelay(unsigned long mseces);
/*以上三个函数实现原理本质上是忙等待,根据cpu频率进行一定次数的循环。

毫秒级延时对于内核讲已经很大了,不推荐直接使用mdelay(),对于毫秒级以上的延时,内核提供下述函数:*/
void msleep(unsigned int millisecs);
void ssleep(unsigned int millisecs);
unsigned long msleep(unsigned int millisecs);
/*上述函数会使调用该函数的进程睡眠,睡眠时间为millisecs,msleep(),ssleep()不能被打断。msleep_interruptible()可以被打断/


/*睡着延时,他会使进程睡眠,让出cpu资源。schedule_timeout();*/

sleep_on_timeout(wait_queue_head_t *q,unsigned long timeout);
interruptible_sleep_on_timeout(wait_queue_head_t *q,unsigned long timeout);
/*上述两个函数可将进程添加到等待队列,从而在等待队列上休眠,时间到了以后,进程将被唤醒*/

二、中断

Linux中断API函数

//1.申请中断。request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq 函数。request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断,
int request_irq(unsigned int irq,irq_handler_t handler,unsigned long flags,const char *name,void *dev);
/*
irq:要申请中断的中断号。
handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
flags:中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志,
name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
dev: 如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数
*/
//2.void free_irq(unsigned int irq,void *dev);
/*
irq: 要释放的中断。
dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。
*/
//3.
irqreturn_t (*irq_handler_t) (int, void *);

enum irqreturn {
	 IRQ_NONE = (0 << 0),
	 IRQ_HANDLED = (1 << 0),
	 IRQ_WAKE_THREAD = (1 << 1),
 };
typedef enum irqreturn irqreturn_t;
//可以看出 irqreturn_t 是个枚举类型,一共有三种返回值。一般中断服务函数返回值使用如下形式:
return IRQ_RETVAL(IRQ_HANDLED);
//4.中断使能与禁止函数
void enable_irq(unsigned int irq);
void disable_irq(unsigned int irq);
/*
disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。在这种情况下,可以使用另外一个中断禁止函数:
*/
void disable_irq_nosync(unsigned int irq);//disable_irq_nosync 函数调用以后立即返回,不会等待当前中断处理程序执行完毕。

local_irq_enable()
local_irq_disable()
/*local_irq_enable 用于使能当前处理器中断系统, local_irq_disable 用于禁止当前处理器中断
系统。假如 A 任务调用 local_irq_disable 关闭全局中断 10S,当关闭了 2S 的时候 B 任务开始运
行, B 任务也调用 local_irq_disable 关闭全局中断 3S, 3 秒以后 B 任务调用 local_irq_enable 函
数将全局中断打开了。此时才过去 2+3=5 秒的时间,然后全局中断就被打开了,此时 A 任务要
关闭 10S 全局中断的愿望就破灭了,然后 A 任务就“生气了”,结果很严重,可能系统都要被
A 任务整崩溃。为了解决这个问题, B 任务不能直接简单粗暴的通过 local_irq_enable 函数来打
开全局中断,而是将中断状态恢复到以前的状态,要考虑到别的任务的感受,此时就要用到下
面两个函数:
*/
local_irq_save(flags)
local_irq_restore(flags)
/*这两个函数是一对, local_irq_save 函数用于禁止中断,并且将中断状态保存在 flags 中。
local_irq_restore 用于恢复中断,将中断到 flags 状态*/

中断上半部与下半部
上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。

使用:
①、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
②、如果要处理的任务对时间敏感,可以放到上半部。
③、如果要处理的任务与硬件有关,可以放到上半部
④、除了上述三点以外的其他任务,优先考虑放到下半部。

下半部实现的四种方式

tasklet、工作队列、软中断、threaded_irq…后续整理四者的特点

软中断的使用

struct softirq_action
{
	void (*action)(struct softirq_action *);
};

//1.注册
void open_softirq(int nr, void (*action)(struct softirq_action *));//nr:要开启的软中断,action:软中断对应的处理函数。
//2.触发
void raise_softirq(unsigned int nr);

tasklet的使用

struct tasklet_struct
{
	struct tasklet_struct *next; /* 下一个 tasklet */
	unsigned long state; /* tasklet 状态 */
	atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
	void (*func)(unsigned long); /* tasklet 执行的函数 */
	unsigned long data; /* 函数 func 的参数 */
};

//1.
struct tasklet_struct tasklet;
//2.初始化
void tasklet_init(struct tasklet_struct *t.void (*func)(unsigned long),unsigned long data);//data: 要传递给 func 函数的参数
/*
也 可 以 使 用 宏 DECLARE_TASKLET 来 一 次 性 完 成 tasklet 的 定 义 和 初 始 化 ,DECLARE_TASKLET 定义在 include/linux/interrupt.h 文件中,定义如下:
*/
DECLARE_TASKLET(name, func, data);
/*
其中 name 为要定义的 tasklet 名字,这个名字就是一个 tasklet_struct 类型的时候变量, func就是 tasklet 的处理函数, data 是传递给 func 函数的参数。
*/

//3.在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行, tasklet_schedule 函数原型如下:
void tasklet_schedule(struct tasklet_struct *t);

//用例:
/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
	/* tasklet 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
	......
	/* 调度 tasklet */
	tasklet_schedule(&testtasklet);
	......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
	......
	/* 初始化 tasklet */
	tasklet_init(&testtasklet, testtasklet_func, data);
	/* 注册中断处理函数 */
	request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
	......
}

工作队列的使用

struct work_struct {
	atomic_long_t data;
	struct list_head entry;
	work_func_t func; /* 工作队列处理函数 */
};

//使用 INIT_WORK 宏来初始化工作, INIT_WORK 宏定义如下:
#define INIT_WORK(_work, _func)
/*_work 表示要初始化的工作, _func 是工作对应的处理函数。
也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化,宏定义如下:
*/
#define DECLARE_WORK(n, f)
/*n 表示定义的工作(work_struct), f 表示工作对应的处理函数。
和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为 schedule_work,函数原
型如下所示:*/
bool schedule_work(struct work_struct *work)
//函数参数和返回值含义如下:work: 要调度的工作
*/

//用例
/* 01.定义工作(work) */
struct work_struct testwork;
/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
	/* work 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
	......
	/* 03.调度 work */
	schedule_work(&testwork);
	......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
	......
	/* 02.初始化 work */
	INIT_WORK(&testwork, testwork_func_t);
	/* 注册中断处理函数 */
	request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
	......
}

按键中断例程

设备树添加:

gpio_key {
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "atkalpha-key";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_key>;
	key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
	interrupt-parent = <&gpio1>;
	interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
	status = "okay";
};

key_drv_int.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define KEYVALUE 0X01   //按键值
#define INVAKEY  0XFF   //无效按键值
#define KEY_NUM 1
struct irq_keydesc {
	int gpio;
	int irqnum;//中断号
	unsigned char key_value;//按键对应的键值
	char name[10];//名字
	irqreturn_t (*handler)(int,void *);//中断服务函数
};

struct newchrkey_dev{

	struct device_node *dev_nod;
	int key_gpio;
	int major;
	int minor;
	dev_t devid;//设备号
	struct cdev cdev;
	struct device *device;//设备
	struct class *cls;
	struct irq_keydesc irq_key[KEY_NUM];//案件描述数组
	struct timer_list timer;//定义一个定时器
	unsigned char curkeynum;//当前按键号

	atomic_t my_atomic;//有效的按键键值
	atomic_t release_key;// 标记是否完成一次完成的按键,包括按下和释放
};

struct newchrkey_dev *newchrkey;

/* @description		: 中断服务函数,开启定时器,延时10ms,
 *				  	  定时器用于按键消抖。
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t key0_handler(int irq,void *dev_id){
	struct newchrkey_dev *dev = (struct newchrkey_dev *)dev_id;

	dev->curkeynum = 0;
	dev->timer.data = (volatile long)dev_id;
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));//10ms定时
	return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description	: 定时器服务函数,用于按键消抖,定时器到了以后
 *				  再次读取按键值,如果按键还是处于按下状态就表示按键有效。
 * @param - arg	: 设备结构变量
 * @return 		: 无
 */
void timer_function(unsigned long arg){
	unsigned char value;
	unsigned char num;
	struct irq_keydesc *irq_key;
	struct newchrkey_dev *dev = (struct newchrkey_dev *)arg;

	num = dev->curkeynum;
	irq_key = &dev->irq_key[num];

	value = gpio_get_value(irq_key->gpio);//读取IO值
	if(value == 0){//按下按键
		atomic_set(&dev->my_atomic,irq_key->key_value);
	}else{//按键松开
		atomic_set(&dev->my_atomic, 0x80|irq_key->key_value);
		atomic_set(&dev->release_key, 1);//标记松开按钮
	}
	
}
static int keyio_init(void){
	unsigned char i=0;
	char name[10];
	int ret = 0;

	//01.从设备树获取key的节点信息
	newchrkey->dev_nod = of_find_node_by_path("/gpio_key");
	if(newchrkey->dev_nod == NULL){
		printk("can not find gpio_key int dts!!\n");
		return -EINVAL;
	}else{
		printk("gpio_key have been found!!\n");
	}

	//02.提取GPIO
	for(i=0;i<KEY_NUM;i++){
		newchrkey->irq_key[i].gpio = of_get_named_gpio(newchrkey->dev_nod, "key-gpio", i);
		if(newchrkey->irq_key[i].gpio < 0){
			printk("can not get key%d \n");
		}
	}

	//02.获取gpio属性信息
	//newchrkey->key_gpio = of_get_named_gpio(newchrkey->dev_nod, "key-gpio", 0);
	//if(newchrkey->key_gpio < 0){
	//	printk("can not get key-gpio!!\n");
	//	return EINVAL;
	//}
	for(i=0;i<KEY_NUM;i++){
		printk("newchrkey->irq_key[%d].gpio = %d\n",i,newchrkey->irq_key[i].gpio);
	}

	//初始化key用到的所有io,设置为中断模式
	for(i=0;i<KEY_NUM;i++){
		memset(newchrkey->irq_key[i].name,0,sizeof(name));//缓冲区清零
		sprintf(newchrkey->irq_key[i].name,"key%d",i);//组合名字
		gpio_request(newchrkey->irq_key[i].gpio, name);
		gpio_direction_input(newchrkey->irq_key[i].gpio);
		newchrkey->irq_key[i].irqnum = irq_of_parse_and_map(newchrkey->dev_nod, i);

#if 0
		newchrkey->irq_key[i].irqnum = gpio_to_irq(newchrkey->irq_key[i].gpio);
#endif
		printk("key%d:gpio=%d,irq_num=%d \n",i,newchrkey->irq_key[i].gpio,newchrkey->irq_key[i].irqnum);
	}

	/* 初始化key所使用的IO */
	//注意!!!!
	//gpio_request()使用过后,不用gpio_free()释放,当第二次insmod时会出现段错误。。
	//本人就犯了这个错误
	//gpio_request(newchrkey->key_gpio, "--key");	/* 请求IO */

	//03.申请中断
	newchrkey->irq_key[0].handler = key0_handler;
	newchrkey->irq_key[0].key_value = KEYVALUE;

	for(i=0;i<KEY_NUM;i++){
		ret = request_irq(newchrkey->irq_key[i].irqnum, newchrkey->irq_key[i].handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, newchrkey->irq_key[i].name, newchrkey);
		if(ret<0){
			printk("irq %d request failed!!\n",newchrkey->irq_key[i].irqnum);
			return -EFAULT;
		}
	}

	//03.设置gpio为输入
	//ret = gpio_direction_input(newchrkey->key_gpio);
	///if(ret<0){
	///	printk("can not set gpio !!\n");
	//}
	//printk("gpio number have gotten!!\n");

	//创建定时器
	init_timer(&newchrkey->timer);
	newchrkey->timer.function = timer_function;
	return 0;
}

static int key_drv_open (struct inode *inod, struct file *filp){
	printk("----------------%s---------------\n",__FUNCTION__);
	filp->private_data = newchrkey;
	return 0;
}
static ssize_t key_drv_read (struct file *filp, char __user *buf, size_t cnt, loff_t *offt){
	//printk("----------------%s---------------\n",__FUNCTION__);

	int ret;
	unsigned char keyvalue = 0;
	unsigned char releasekey = 0;
	struct newchrkey_dev *dev = (struct newchrkey_dev *)filp->private_data;
	keyvalue = atomic_read(&dev->my_atomic);
	releasekey = atomic_read(&dev->release_key);

	if (releasekey) { /* 有按键按下 */	
		if (keyvalue & 0x80) {
			keyvalue &= ~0x80;
			ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
		} else {
			goto data_error;
		}
		atomic_set(&dev->release_key, 0);/* 按下标志清零 */
	} else {
		goto data_error;
	}
	return 0;
	
	/*
	if(gpio_get_value(dev->key_gpio) == 0){//按键按下
		while(!gpio_get_value(dev->key_gpio));
		atomic_set(&dev->my_atomic,KEYVALUE);
	}else{
		atomic_set(&dev->my_atomic,INVAKEY);
	}

	val = atomic_read(&dev->my_atomic);
	ret = copy_to_user(buf,&val,sizeof(val));
	return ret;
	*/
	
	data_error:
		return -EINVAL;
}
static ssize_t key_drv_write (struct file *filp, const char __user *buf, size_t cnt, loff_t *offt){
	printk("----------------%s---------------\n",__FUNCTION__);
	return 0;
}
static int key_drv_release (struct inode *inod, struct file *filp){
	printk("----------------%s---------------\n",__FUNCTION__);
	atomic_inc(&newchrkey->my_atomic);
	return 0;
}	

static struct file_operations newchrkey_fops = {
	.owner = THIS_MODULE,
	.open = key_drv_open,
	.read = key_drv_read,
	.write = key_drv_write,
	.release = key_drv_release,
};

static int __init key_drv_init(void){
	printk("----------------%s---------------\n",__FUNCTION__);

	newchrkey = (struct newchrkey_dev *)kzalloc(sizeof(struct newchrkey_dev), GFP_KERNEL);
	if(newchrkey == NULL){
		printk("newchrkey kzalloc failed!!!\n");
		return -1;
	}

	atomic_set(&newchrkey->my_atomic,INVAKEY);
	
	int ret;
	

	//注册字符设备驱动
	//1.向系统申请设备号
	if(newchrkey->major){
		newchrkey->devid = MKDEV(newchrkey->major, 0);
		register_chrdev_region(newchrkey->devid, 1, "Pf_key");
	}else{
		printk("register chrdev!!\n");
		/*犯过错误:一下函数的第一个参数没有取地址,在insmod时出现段错误*/
		alloc_chrdev_region(&newchrkey->devid, 0, 1, "Pf_key");
		
		newchrkey->major = MAJOR(newchrkey->devid);
		newchrkey->minor = MINOR(newchrkey->devid);
		printk(">>>>>>>>>>>register chrdev!!\n");
	}
	printk("major: %d ,minor: %d \n",newchrkey->major,newchrkey->minor);

	//2.初始化cdev
	newchrkey->cdev.owner = THIS_MODULE;
	cdev_init(&newchrkey->cdev, &newchrkey_fops);

	//3.添加一个cdev到系统
	cdev_add(&newchrkey->cdev, newchrkey->devid, 1);

	//4.创建一个设备
	newchrkey->cls = class_create(THIS_MODULE, "Pf_key");
	if(IS_ERR(newchrkey->cls)){
		return PTR_ERR(newchrkey->cls); 
	}
	newchrkey->device = device_create(newchrkey->cls, NULL, newchrkey->devid, NULL, "Pf_key");
	if(IS_ERR(newchrkey->device)){
		return PTR_ERR(newchrkey->device); 
	}
	//5.初始按键
	atomic_set(&newchrkey->my_atomic,INVAKEY);
	atomic_set(&newchrkey->release_key,0);
	keyio_init();
	return 0;
}


static void __exit key_drv_exit(void){
	printk("----------------%s---------------\n",__FUNCTION__);
	//注意!!!!
	//gpio_request()使用过后,不用gpio_free()释放,当第二次insmod时会出现段错误
	//gpio_free(newchrkey->key_gpio);
	unsigned int i=0;
	//删除定时器
	del_timer(&newchrkey->timer);

	//释放中断
	for(i=0;i<KEY_NUM;i++){
		free_irq(newchrkey->irq_key[i].irqnum, newchrkey);
	}

	cdev_del(&newchrkey->cdev);
	unregister_chrdev_region(newchrkey->devid, 1);
	device_destroy(newchrkey->cls, newchrkey->devid);
	class_destroy(newchrkey->cls);
	kfree(newchrkey);
}

module_init(key_drv_init);
module_exit(key_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("PEIFENG");

key_app.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

/* 定义按键值 */
#define KEYVALUE	0XF0
#define INVAKEY		0X00

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, ret;
	char *filename;
	int keyvalue;
	
	if(argc != 2){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	/* 打开key驱动 */
	fd = open(filename, O_RDWR);
	if(fd < 0){
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

	/* 循环读取按键值数据! */
	while(1) {
		ret = read(fd, &keyvalue, sizeof(keyvalue));
		if (ret < 0) {	
			
		}else{
			if(keyvalue) 
				printf("key value = %#X\r\n",keyvalue);
		}
	}

	ret= close(fd); /* 关闭文件 */
	if(ret < 0){
		printf("file %s close failed!\r\n", argv[1]);
		return -1;
	}
	return 0;
}

你可能感兴趣的:(嵌入式开发,Linux,嵌入式,中断,定时器)