Linux-時間管理和內核定時器
- ■ 設置系統節拍率
- ■ 高節拍率和低節拍率的優缺點:
- ■ jiffies 系統節拍數
- ■ get_jiffies_64 這個函數可以獲取 jiffies_64 的值
- ■ 處理繞回
- ■ 使用 jiffies 判斷超時
- ■ jiffies 和 ms、 us、 ns 之間的轉換函數`在這里插入代碼片`
- ■ 內核定時器
- ■ 內核定時器API
- ■ 內核短延時函數
- ■ 示例一:使用范例
- ■ 示例二:Linux內核定時器實驗 內核定時器周期性的點亮和熄滅開發板上的 LED燈
- ■
- ■
■ 設置系統節拍率
默認情況下選擇 100Hz。設置好以后打開 Linux 內核源碼根目錄下的.config 文件,
打開文件 include/asm-generic/param.h,
■ 高節拍率和低節拍率的優缺點:
①、高節拍率會提高系統時間精度,
如果采用 100Hz 的節拍率,時間精度就是 10ms,
采用1000Hz 的話時間精度就是 1ms,精度提高了 10 倍。
高精度時鐘的好處有很多,對于那些對時間要求嚴格的函數來說,能夠以更高的精度運行,時間測量也更加準確。
②、高節拍率會導致中斷的產生更加頻繁,頻繁的中斷會加劇系統的負擔,
1000Hz 和 100Hz的系統節拍率相比,系統要花費 10 倍的“精力”去處理中斷。
中斷服務函數占用處理器的時間增加,但是現在的處理器性能都很強大,所以采用 1000Hz 的系統節拍率并不會增加太大的負載壓力。
根據自己的實際情況,選擇合適的系統節拍率,本教程我們全部采用默認的 100Hz 系統節拍率。
■ jiffies 系統節拍數
Linux 內核使用全局變量 jiffies 來記錄系統從啟動以來的系統節拍數,系統啟動的時候會將 jiffies 初始化為 0, jiffies 定義在文件 include/linux/jiffies.h 中
extern u64 __jiffy_data jiffies_64; //定義了一個 64 位的 jiffies_64。
extern unsigned long volatile __jiffy_data jiffies; //定義了一個 unsigned long 類型的 32 位的 jiffies。
jiffies_64用于 64 位系統,而 jiffies 用于 32 位系統.
當我們訪問 jiffies 的時候其實訪問的是 jiffies_64 的低 32 位,
■ get_jiffies_64 這個函數可以獲取 jiffies_64 的值
HZ 表示每秒的節拍數, jiffies 表示系統運行的 jiffies 節拍數,所以 jiffies/HZ 就是系統運行時間,單位為秒。
■ 處理繞回
假如 HZ 為最大值 1000 的時候,
對于 32 位的 jiffies 只需要 49.7 天就發生了繞回,
對于 64 位的 jiffies 來說大概需要5.8 億年才能繞回,因此 jiffies_64 的繞回忽略不計。處理 32 位 jiffies 的繞回顯得尤為重要
函數 | 描述 |
---|---|
time_after(unkown, known) | unkown 超過 known 的話, time_after 函數返回真,否則返回假 |
time_before(unkown, known) | unkown 沒有超過 known 的話 time_before 函數返回真,否則返回假 |
time_after_eq(unkown, known) | 和 time_after 函數類似 只是多了判斷等于這個條件 |
time_before_eq(unkown, known) | 和 time_before函數類似 只是多了判斷等于這個條件 |
■ 使用 jiffies 判斷超時
timeout 就是超時時間點,比如我們要判斷代碼執行時間是不是超過了 2 秒
■ jiffies 和 ms、 us、 ns 之間的轉換函數在這里插入代碼片
函數 | 描述 |
---|---|
int jiffies_to_msecs(const unsigned long j) | 將 jiffies 類型的參數 j 分別轉換為對應的毫秒 |
int jiffies_to_usecs(const unsigned long j) | 將 jiffies 類型的參數 j 分別轉換為對應的微秒 |
u64 jiffies_to_nsecs(const unsigned long j) | 將 jiffies 類型的參數 j 分別轉換為對應的納秒 |
long msecs_to_jiffies(const unsigned int m) | 將毫秒轉換為 jiffies 類型。 |
long usecs_to_jiffies(const unsigned int u) | 將微秒轉換為 jiffies 類型。 |
unsigned long nsecs_to_jiffies(u64 n) | 將納秒轉換為 jiffies 類型。 |
■ 內核定時器
Linux 內核定時器使用很簡單,只需要提供超時時間(相當于定時值)和定時處理函數即可。
Linux 內核使用 timer_list 結構體表示內核定時器, timer_list 定義在文件include/linux/timer.h 中
struct timer_list {struct list_head entry;unsigned long expires; /* 定時器超時時間,單位是節拍數 */struct tvec_base *base;void (*function)(unsigned long); /* 定時處理函數 */unsigned long data; /* 要傳遞給 function 函數的參數 */int slack;
};
■ 內核定時器API
函數 | 描述 |
---|---|
init_timer 函數 | 初始化 timer_list 類型變量, |
add_timer 函數 | 向 Linux 內核注冊定時器, |
del_timer 函數 | 刪除一個定時器 |
del_timer_sync 函數 | del_timer_sync 函數是 del_timer 函數的同步版,會等待其他處理器使用完定時器再刪除,del_timer_sync 不能使用在中斷上下文中。 |
mod_timer 函數 | 修改定時值 |
■ 內核短延時函數
Linux 內核提供了毫秒、微秒 、納秒 延時函數
■ 示例一:使用范例
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內核定時器實驗 內核定時器周期性的點亮和熄滅開發板上的 LED燈
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define TIMER_CNT 1 /* 設備號個數 */
#define TIMER_NAME "timer" /* 名字 */
#define CLOSE_CMD (_IO(0XEF, 0x1)) /* 關閉定時器 */
#define OPEN_CMD (_IO(0XEF, 0x2)) /* 打開定時器 */
#define SETPERIOD_CMD (_IO(0XEF, 0x3)) /* 設置定時器周期命令 */
#define LEDON 1 /* 開燈 */
#define LEDOFF 0 /* 關燈 *//* timer設備結構體 */
struct timer_dev{dev_t devid; /* 設備號 */struct cdev cdev; /* cdev */struct class *class; /* 類 */struct device *device; /* 設備 */int major; /* 主設備號 */int minor; /* 次設備號 */struct device_node *nd; /* 設備節點 */int led_gpio; /* key所使用的GPIO編號 */int timeperiod; /* 定時周期,單位為ms */struct timer_list timer;/* 定義一個定時器*/spinlock_t lock; /* 定義自旋鎖 */
};struct timer_dev timerdev; /* timer設備 *//** @description : 初始化LED燈IO,open函數打開驅動的時候* 初始化LED燈所使用的GPIO引腳。* @param : 無* @return : 無*/
static int led_init(void)
{int ret = 0;timerdev.nd = of_find_node_by_path("/gpioled");if (timerdev.nd== NULL) {return -EINVAL;}timerdev.led_gpio = of_get_named_gpio(timerdev.nd ,"led-gpio", 0);if (timerdev.led_gpio < 0) {printk("can't get led\r\n");return -EINVAL;}/* 初始化led所使用的IO */gpio_request(timerdev.led_gpio, "led"); /* 請求IO */ret = gpio_direction_output(timerdev.led_gpio, 1);if(ret < 0) {printk("can't set gpio!\r\n");}return 0;
}/** @description : 打開設備* @param - inode : 傳遞給驅動的inode* @param - filp : 設備文件,file結構體有個叫做private_data的成員變量* 一般在open的時候將private_data指向設備結構體。* @return : 0 成功;其他 失敗*/
static int timer_open(struct inode *inode, struct file *filp)
{int ret = 0;filp->private_data = &timerdev; /* 設置私有數據 */timerdev.timeperiod = 1000; /* 默認周期為1s */ret = led_init(); /* 初始化LED IO */if (ret < 0) {return ret;}return 0;
}/** @description : ioctl函數,* @param - filp : 要打開的設備文件(文件描述符)* @param - cmd : 應用程序發送過來的命令* @param - arg : 參數* @return : 0 成功;其他 失敗*/
static long timer_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{struct timer_dev *dev = (struct timer_dev *)filp->private_data;int timerperiod;unsigned long flags;switch (cmd) {case CLOSE_CMD: /* 關閉定時器 */del_timer_sync(&dev->timer);break;case OPEN_CMD: /* 打開定時器 */spin_lock_irqsave(&dev->lock, flags);timerperiod = dev->timeperiod;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod));break;case SETPERIOD_CMD: /* 設置定時器周期 */spin_lock_irqsave(&dev->lock, flags);dev->timeperiod = arg;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));break;default:break;}return 0;
}/* 設備操作函數 */
static struct file_operations timer_fops = {.owner = THIS_MODULE,.open = timer_open,.unlocked_ioctl = timer_unlocked_ioctl,
};/* 定時器回調函數 */
void timer_function(unsigned long arg)
{struct timer_dev *dev = (struct timer_dev *)arg;static int sta = 1;int timerperiod;unsigned long flags;sta = !sta; /* 每次都取反,實現LED燈反轉 */gpio_set_value(dev->led_gpio, sta);/* 重啟定時器 */spin_lock_irqsave(&dev->lock, flags);timerperiod = dev->timeperiod;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod)); }/** @description : 驅動入口函數* @param : 無* @return : 無*/
static int __init timer_init(void)
{/* 初始化自旋鎖 */spin_lock_init(&timerdev.lock);/* 注冊字符設備驅動 *//* 1、創建設備號 */if (timerdev.major) { /* 定義了設備號 */timerdev.devid = MKDEV(timerdev.major, 0);register_chrdev_region(timerdev.devid, TIMER_CNT, TIMER_NAME);} else { /* 沒有定義設備號 */alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT, TIMER_NAME); /* 申請設備號 */timerdev.major = MAJOR(timerdev.devid); /* 獲取分配號的主設備號 */timerdev.minor = MINOR(timerdev.devid); /* 獲取分配號的次設備號 */}/* 2、初始化cdev */timerdev.cdev.owner = THIS_MODULE;cdev_init(&timerdev.cdev, &timer_fops);/* 3、添加一個cdev */cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);/* 4、創建類 */timerdev.class = class_create(THIS_MODULE, TIMER_NAME);if (IS_ERR(timerdev.class)) {return PTR_ERR(timerdev.class);}/* 5、創建設備 */timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, TIMER_NAME);if (IS_ERR(timerdev.device)) {return PTR_ERR(timerdev.device);}/* 6、初始化timer,設置定時器處理函數,還未設置周期,所有不會激活定時器 */init_timer(&timerdev.timer);timerdev.timer.function = timer_function;timerdev.timer.data = (unsigned long)&timerdev;return 0;
}/** @description : 驅動出口函數* @param : 無* @return : 無*/
static void __exit timer_exit(void)
{gpio_set_value(timerdev.led_gpio, 1); /* 卸載驅動的時候關閉LED */del_timer_sync(&timerdev.timer); /* 刪除timer */
#if 0del_timer(&timerdev.tiemr);
#endif/* 注銷字符設備驅動 */gpio_free(timerdev.led_gpio); cdev_del(&timerdev.cdev);/* 刪除cdev */unregister_chrdev_region(timerdev.devid, TIMER_CNT); /* 注銷設備號 */device_destroy(timerdev.class, timerdev.devid);class_destroy(timerdev.class);
}module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");