最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Linux內(nèi)核定時器使用及說明

 更新時間:2025年12月06日 16:33:17   作者:卿相訴七弦  
文章詳細介紹了Linux內(nèi)核定時器的特性、核心數(shù)據(jù)結(jié)構(gòu)、時間相關(guān)轉(zhuǎn)換函數(shù)以及操作API,通過示例展示了如何編寫和使用定時器,包括按鍵消抖的應用

1.Linux內(nèi)核定時器特征

  1. 依賴Linux系統(tǒng)心跳節(jié)拍發(fā)生器的定時器;
  2. 基于未來的時間點的一種定時方式;
  3. 支持多段定時,互不干擾:一段定時還沒有完成,可以再啟動新一段定時;
  4. 內(nèi)核定時器支持的是單次定時方式,不是循環(huán)定時;
  5. 內(nèi)核定時器定時精度不高,如果需要高精度定時需要使用硬件定時器或使用內(nèi)核高精度定時器;
  6. 定時器的超時處理函數(shù)只會在注冊它的CPU上運行,不會存在并發(fā)問題。

2.Linux內(nèi)核定時器核心數(shù)據(jù)結(jié)構(gòu)

內(nèi)核使用struct timer_list表示一個內(nèi)核定時器,定義如下:

struct timer_list {
       struct hlist_node entry;                                    //實現(xiàn)定時器結(jié)構(gòu)鏈表化管理
       unsigned long             expires;                          //到期時間點      不是時長
       void                     (*function)(unsigned long);        //超時處理函數(shù)
       unsigned long             data;
       u32               flags;
       int                 slack;
};

重要成員:

到期時間,基于未來時間點的時鐘節(jié)拍數(shù)。等于定時時刻當前的時鐘節(jié)拍計數(shù)(存儲在系統(tǒng)的全局expires變量jiffies)+定時時長對應的時鐘節(jié)拍數(shù)量。

示例:從現(xiàn)在開始定時1秒:expires = jiffies + 1*Hz

Hz:表示一秒對應的時鐘節(jié)拍數(shù)

  • function超時處理函數(shù),并不是硬件中斷服務程序。
  • data傳遞給超時處理函數(shù)的參數(shù),可以把一個變量的地址轉(zhuǎn)換成unsigned long

3.Linux內(nèi)核時間相關(guān)轉(zhuǎn)換函數(shù)

定時使用的不是時長,是基于未來時間點,單位是時鐘節(jié)拍。

HZ宏:1秒對應時鐘節(jié)拍。

unsigned long msecs_to_jiffies(const unsigned int m)             //把ms轉(zhuǎn)換為時鐘節(jié)拍
unsigned long usecs_to_jiffies(const unsigned int u)             //把us轉(zhuǎn)換為時鐘節(jié)拍

示例:定時2s+200ms+500us

到期時間:jiffies + 2*HZ+msecs_to_jiffies(200)+msecs_to_jiffies(500)

4.Linux內(nèi)核定時器操作相關(guān)API

4.1 靜態(tài)定義結(jié)構(gòu)體變量并且初始化(宏):DEFINE_TIMER

頭文件

#include <linux/timer.h>

原型

#define DEFINE_TIMER(_name, _function, _expires, _data)             \

struct timer_list _name = TIMER_INITIALIZER(_function, _expires, _data)

參數(shù)

_name變量名

_function超時處理函數(shù)

_expires到期時間,一般在啟動定時前需要重新初始化。

_data傳遞給超時處理函數(shù)的參數(shù)

功能

定義一個定時器結(jié)構(gòu)并且初始化function, expires, data成員。

4.2 動態(tài)初始化定時器(宏):setup_timer

頭文件

#include <linux/timer.h>

原型

#define setup_timer(timer, fn, data)      __setup_timer((timer), (fn), (data), 0)

參數(shù)

timer定時器結(jié)構(gòu)指針

n超時處理函數(shù)

data超時處理函數(shù)參數(shù)

功能

運行時初始化定時器

struct timer_list btn_timer;
setup_timer(&btn_timer, fn, data)
btn_timer.expires = jiffies+定時時長對應的時鐘節(jié)拍數(shù);
啟動定時器。。。

4.3 注冊定時器到內(nèi)核:add_timer

頭文件

#include <linux/timer.h>

原型

void add_timer(struct timer_list *timer)

返回值

功能

向內(nèi)核注冊一個定時器,同步啟動定時。本質(zhì)上把定時器結(jié)構(gòu)添加到定時鏈表

注意:同一個結(jié)構(gòu)變量在啟動定時后,沒有超時前重復注冊沒有用的。

struct timer_list timer,
……
add_timer(&timer);
add_timer(&timer);
……

4.4 注銷內(nèi)核定時器:del_timer,del_timer_sync

頭文件

#include <linux/timer.h>

原型

int del_timer(struct timer_list *timer)

int del_timer_sync(struct timer_list *timer)

功能

從內(nèi)核中注銷一個定時器。(本質(zhì)上把定時器結(jié)構(gòu)從定時鏈表取走)

區(qū)別:  del_timer 單處理器系統(tǒng)安全刪除定時器函數(shù),del_timer_sync SMP系統(tǒng)安全刪除定時器的函數(shù)。

4.5 修改定時器定時時間:mod_timer

頭文件

#include <linux/timer.h>

原型

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

參數(shù)

timer定時器指針;

expires未來的到期時鐘節(jié)拍。

功能

修改定時器時間以及啟動定時。

5.Linux內(nèi)核定時器示例

5.1 內(nèi)核定時器編程步驟

  • 編寫一個定時器超時處理函數(shù)。
  • 定義struct timer_list結(jié)構(gòu)變量。
  • 初始化上一步定義的結(jié)構(gòu)變量。
  • 重新初始化到期時間,馬上啟動定時器(注冊定時器)。
  • 如果不需要定時器,可以刪除定時器(注銷定時器),這一步是可選。

5.2 定時2s

#include<linux/module.h>
#include<linux/init.h>
#include<linux/timer.h>

static void timer_func(unsigned long data);

//靜態(tài)定義:定義并且初始化
DEFINE_TIMER(my_timer,timer_func,0,(unsigned long)"LIU");

//定時器超時函數(shù)
static void timer_func(unsigned long data)
{
	printk("data:%s\r\n",(char *)data);
	//啟動新一次定時,實現(xiàn)循環(huán)定時
	mod_timer(&my_timer,jiffies + 2*HZ);
}

static int __init timer_list_init(void)
{
	//重新初始化到期時間
	my_timer.expires = jiffies + 2*HZ;	//2s
	
	//馬上啟動定時器
	printk("-----begin------\r\n");
	add_timer(&my_timer);
	printk("------end-------\r\n");
    return 0; 
}

static void __exit timer_list_exit(void)
{
	//刪除定時器
	del_timer_sync(&my_timer);
}

module_init(timer_list_init);			// 指定模塊初始化函數(shù)
module_exit(timer_list_exit);			// 指定模塊清理函數(shù)
MODULE_LICENSE("GPL");					// 聲明模塊許可證(GPL v2) 

5.3 循環(huán)定時

#include<linux/module.h>
#include<linux/init.h>
#include<linux/timer.h>

static void timer_func(unsigned long data);

//靜態(tài)定義:定義并且初始化
DEFINE_TIMER(my_timer,timer_func,0,(unsigned long)"Time out");

//定時器超時函數(shù)
static void timer_func(unsigned long data)
{
	printk("data:%s\r\n",(char *)data);
	//啟動新一次定時,實現(xiàn)循環(huán)定時
	mod_timer(&my_timer,jiffies + 2*HZ);
}

static int __init timer_list_init(void)
{
	//重新初始化到期時間
	my_timer.expires = jiffies + 2*HZ;	//2s
	
	//馬上啟動定時器
	printk("-----begin------\r\n");
	add_timer(&my_timer);
	printk("------end-------\r\n");
    return 0; 
}

static void __exit timer_list_exit(void)
{
	//刪除定時器
	del_timer_sync(&my_timer);
}

module_init(timer_list_init);			// 指定模塊初始化函數(shù)
module_exit(timer_list_exit);			// 指定模塊清理函數(shù)
MODULE_LICENSE("GPL");					// 聲明模塊許可證(GPL v2) 

5.4 全局驅(qū)動數(shù)據(jù)

#include <linux/module.h>
#include <linux/init.h>
#include <linux/timer.h>

//全局驅(qū)動數(shù)據(jù)
struct drv_data{
	struct timer_list timer;
	char *data;
	u64 expires;
};
static struct drv_data gvar;

//定時器超時處理函數(shù)
static void timer_func(unsigned long data)
{
	struct drv_data *pdata = (struct drv_data *)data;
	printk("data:%s\r\n",pdata->data);
	//啟動新一次定時,實現(xiàn)循環(huán)定時
	mod_timer(&pdata->timer,jiffies + pdata->expires);
}

static int __init timer_list_init(void)
{
	//普通成員初始化
	gvar.data    = "Time out";
	gvar.expires = 2*HZ;
	
	//初始化定時器
	setup_timer(&gvar.timer,timer_func, (unsigned long)&gvar);
	
	//重新初始化到期時間
	gvar.timer.expires = jiffies + gvar.expires;	//2s
	
	//馬上啟動定時器
	printk("-----begin------\r\n");
	add_timer(&gvar.timer);
	printk("------end-------\r\n");
    return 0; 
}

static void __exit timer_list_exit(void)
{
	//刪除定時器
	del_timer_sync(&gvar.timer);
}
module_init(timer_list_init);			// 指定模塊初始化函數(shù)
module_exit(timer_list_exit);			// 指定模塊清理函數(shù)
MODULE_LICENSE("GPL");					// 聲明模塊許可證(GPL v2) 

5.5 Linux內(nèi)核定時器應用:實現(xiàn)按鍵消抖

5.5.1 按鍵的抖動分析

裸機按鍵:檢測,   延時,  再檢測,確認按鍵

傳統(tǒng)的防抖方法用戶體驗較差,因為延時時間難以精確設(shè)定,還會占用較多的CPU資源。

早期的按鍵驅(qū)動程序設(shè)計:當按鍵中斷觸發(fā)時,直接讀取按鍵狀態(tài)來判斷按鍵動作,這種方法容易受到機械抖動的影響。

引入內(nèi)核定時器機制后,可以在按鍵中斷觸發(fā)時啟動一個定時任務。待定時器超時時,在其回調(diào)函數(shù)中讀取按鍵的狀態(tài)值。利用這一機制,可以每次在未超時時重新設(shè)置定時器,從而確保每次從當前時刻重新開始計算延時,有效避免了由于抖動帶來的誤判。這種方式不僅提高了檢測的準確性,還優(yōu)化了系統(tǒng)資源的使用效率。

中斷程序啟動定時器,每次從當前時間開始定時10ms,中斷程序返回

  1. 當出現(xiàn)第一個下降沿,進入中斷,啟動10ms定時,退出中斷
  2. 定時10ms過程中,出現(xiàn)抖動,上升沿出現(xiàn),進入中斷,啟動10ms定時,退出中斷
  3. 定時10ms過程中,出現(xiàn)抖動,下降沿出現(xiàn),進入中斷,啟動10ms定時,退出中斷
  4. 重復2,3步驟……直到最后一次抖動邊沿,10ms后就有機會超時。
  5. 在超時函數(shù)讀取按鍵狀態(tài)

松開檢測方法相同。

5.5.2 按鍵驅(qū)動程序改進

drv-btn.c

//增加內(nèi)核定時器功能
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/miscdevice.h>
#include<asm/uaccess.h>
#include<linux/interrupt.h>
#include<linux/irq.h>
#include<linux/gpio.h>
#include<linux/slab.h>

#define LEDS_MINOR	255
#define DEVICE_NAME  "my-buttons" // 設(shè)備節(jié)點名稱,將在/dev目錄下創(chuàng)建 

#ifndef ARRAYSIZE
#define ARRAYSIZE(a)	(sizeof(a)/sizeof(a[0]))
#endif

//按鍵數(shù)量,在模塊初始化函數(shù)中進行計算
static int key_size;
//按鍵緩沖區(qū),一個元素存放一個按鍵值,'1'表示按下,'0'表示松開
//在模塊的初始化函數(shù)中分配緩沖區(qū)空間
static char *keys_buf;	

//使用面向?qū)ο笏枷朐O(shè)計按鍵,把一個按鍵信息進行封裝
struct key_info{
	int id;					//按鍵編號
	int gpio;				//統(tǒng)一的GPIO編號
	unsigned long flags;	//觸發(fā)方式
	char *name;				//按鍵名
	int irq;				//中斷編號
	struct timer_list timer;//增加一個定時器作消抖
};

//實例化對象
static struct key_info keys[]={
	[0]={
		.id 	= 0,
		.gpio 	= 5,
		.flags 	= IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
		.name 	= "key-0",
	},
	[1]={
		.id 	= 0,
		.gpio 	= 54,
		.flags 	= IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
		.name 	= "key-1",
	},	
};

static int key_open (struct inode *pinode, struct file *pfile)
{
    printk("line:%d,%s is call\n",__LINE__,__FUNCTION__);
    return 0;    
}

loff_t key_llseek (struct file *pfile, loff_t offset, int whence)
{
    return 0;    
}

static ssize_t key_read (struct file *pfile, char __user *buf, size_t count, loff_t *offset)    
{
    int ret;
	if(count > key_size)	count = key_size;
    if(count == 0)			return 0;
	
	//準備數(shù)據(jù),但是按鍵數(shù)據(jù)在中斷中實時更新,不需要在這里讀取
	//復制數(shù)據(jù)到用戶空間
	ret = copy_to_user(buf, keys_buf, count);
	if(ret){
		printk("error:copy_to_user\r\n");
		ret = -EFAULT;
		goto errot_copy_to_user;
	}
	return count;  // 返回成功讀取的字節(jié)數(shù)  
errot_copy_to_user:
	return ret;
}

int key_release (struct inode *pinode, struct file *pfile)
{
    return 0;
}

// 文件操作結(jié)構(gòu)體(關(guān)聯(lián)實現(xiàn)的操作函數(shù)) 
static const struct file_operations dev_fops = {
    .open = key_open,      // 打開設(shè)備 
    .read = key_read,      // 讀取設(shè)備狀態(tài) 
	.owner = THIS_MODULE,
};

// 雜項設(shè)備定義 
static struct miscdevice key_device = {
    .name = DEVICE_NAME,	// 設(shè)備名稱(出現(xiàn)在/dev目錄下) 
    .minor = LEDS_MINOR, 	// 次設(shè)備號(建議使用動態(tài)分配) 
    .fops = &dev_fops,		// 關(guān)聯(lián)文件操作函數(shù)集 
};

//超時處理函數(shù)
void btn_timer(unsigned long data)
{

	int s;
	struct key_info *pdata = (struct key_info *)data;

	//檢測當前的電平狀態(tài)
	s = !gpio_get_value(pdata->gpio);
	keys_buf[pdata->id]='0'+s;	//保存狀態(tài)
}

//按鍵中斷函數(shù)
//設(shè)置了雙邊觸發(fā),按下和松開都會進入這個函數(shù)
irqreturn_t btns_irq_handler(int irq,void *devid)
{
	struct key_info *pdata = (struct key_info *)devid;

	//啟動新一次定時
	mod_timer(&pdata->timer,jiffies + msecs_to_jiffies(20));
	return IRQ_HANDLED;
}

static int __init xyd_btn_init(void)
{
    int ret,i;

	key_size = ARRAY_SIZE(keys);	//計算按鍵數(shù)量

	//分配按鍵緩沖區(qū)
	keys_buf = kzalloc(key_size,GFP_KERNEL);
	if(keys_buf == NULL)
		return -EFAULT;
	
	//循環(huán)注冊中斷
	for(i = 0;i < key_size;i++){
		keys[i].irq = gpio_to_irq(keys[i].gpio);
		if(keys[i].irq < 0)
		{
			printk("error:gpio_to_irq\r\n");
			goto error_gpio_to_irq;  
		}
		printk("irq:%d\r\n",keys[i].irq);
		
		//傳遞每個按鍵結(jié)構(gòu)變量地址,發(fā)生中斷時可以通過參數(shù)取得
		ret = request_irq(keys[i].irq, btns_irq_handler, keys[i].flags, keys[i].name, (void *)&keys[i]);
		if(ret < 0)
		{
			printk("error:request_irq\r\n");
			goto error_request_irq;  
		}

		//初始化按鍵的定時器,參數(shù)是按鍵自身的結(jié)構(gòu)內(nèi)存地址
		setup_timer(&keys[i].timer,btn_timer, (unsigned long)&keys[i]);
	}
    ret = misc_register(&key_device);
    if(ret < 0){
        printk("error:misc_register\r\n");
        goto error_misc_register;  
    }
    return 0;  								// 初始化成功 

error_misc_register:
error_request_irq:
	while(--i >= 0){
		free_irq(keys[i].irq, (void *)&keys[i]);	//注銷中斷
	}
	
error_gpio_to_irq:
	kfree(keys_buf);		//釋放按鍵緩沖區(qū)空間
    return ret; 
}

static void __exit key_btn_exit(void)
{
	int i = key_size;
	while(--i >= 0){
		free_irq(keys[i].irq, (void *)&keys[i]);	//注銷中斷
	}
	misc_deregister(&key_device);	//注銷雜項設(shè)備
	kfree(keys_buf);				//釋放按鍵緩沖區(qū)空間
}

module_init(xyd_btn_init);		  								// 指定模塊初始化函數(shù) 
module_exit(key_btn_exit);  									// 指定模塊清理函數(shù) 
MODULE_LICENSE("GPL");											// 聲明模塊許可證(GPL v2) 
MODULE_AUTHOR("LIU");    										// 模塊作者 
MODULE_DESCRIPTION("RK3399 GPIO KEY Control Driver"); 			// 模塊描述 

app.c

#include<stdio.h>              // 標準輸入輸出庫(printf, perror等)
#include<stdlib.h> 
#include<string.h> 
#include<sys/types.h>          // 系統(tǒng)數(shù)據(jù)類型定義(如dev_t)
#include<sys/stat.h>           // 文件狀態(tài)信息(文件模式等)
#include<fcntl.h>              // 文件控制選項(open等)
#include<unistd.h>             // 系統(tǒng)調(diào)用封裝(lseek, read, write, sleep等)
#include<sys/ioctl.h>          // I/O控制操作(ioctl)
#include<errno.h> 

#define BTN_SIZE	1					// 按鍵數(shù)量
#define DEV_NAME 	"/dev/my-buttons"  	// 默認設(shè)備名

int main(int argc, char **argv)
{
    int fd,ret, i;                
    const char *devname; 				// 設(shè)備路徑指針(初始化為默認路徑)
    unsigned char pre_buf[BTN_SIZE+1],recv_buf[BTN_SIZE+1];

	memset(pre_buf,'0',BTN_SIZE);
	memset(recv_buf,'0',BTN_SIZE);
	
    if(argc == 1) 
        devname = DEV_NAME;
    else if(argc == 2)
        devname= argv[1];
    else {
        printf("Usage:%s [/dev/devname]\r\n", argv[0]);
        return 0;
    }

    fd = open(devname, O_RDWR);  			// O_RDWR:以讀寫模式打開
    if(fd < 0) {
        perror("open");   					// 打印系統(tǒng)錯誤信息
        printf("fd=%d\r\n", fd);
        return -1;            				// 打開失敗退出程序
    }
    printf("fd=%d\r\n", fd);  				// 成功打開后輸出fd值

    while(1) {
		ret = read(fd,recv_buf,BTN_SIZE);	// 讀取按鍵數(shù)據(jù)
		if(ret < 0){
			if(errno != EAGAIN){
				perror("read");
				exit(-1);
			}else	continue;
		}

		//只在狀態(tài)發(fā)生變化時候才輸出
		for(i = 0;i < BTN_SIZE;i++){
			//分別判斷每一個按鍵狀態(tài)是否發(fā)生變化
			if(recv_buf[i] != pre_buf[i]){
				//更新當前狀態(tài)為上一次狀態(tài)
				pre_buf[i] = recv_buf[i];

				//判斷這次變化是按下還是松開
				if(pre_buf[i] == '1')	printf("KEY%d is press!\r\n",i+1);
				else					printf("KEY%d is up!\r\n",i+1);
			}
		}
    }    
    return 0;
}

現(xiàn)象

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論

大港区| 江阴市| 隆回县| 桐城市| 当雄县| 武胜县| 板桥市| 桐庐县| 六枝特区| 城固县| 万年县| 乌兰县| 海宁市| 揭阳市| 宣城市| 文成县| 通化市| 门头沟区| 辽源市| 佛教| 锡林郭勒盟| 平陆县| 玛沁县| 宝丰县| 巴彦淖尔市| 平阳县| 万山特区| 青神县| 平昌县| 沂水县| 杂多县| 平潭县| 上林县| 定陶县| 长顺县| 邮箱| 博白县| 宁武县| 亳州市| 衡阳市| 仲巴县|