本系列文章主要講Linux中的中斷和時間管理,文章機構如下:
01 - 驅動中的中斷處理
02 - 中斷下半部 tasklet
03 - 中斷的下半部 workqueue
04 - Linux中的延時操作函數
05 - Linux硬件定時 jiffies
06 - Linux 低分辨率定時器
07 - Linux高分辨率定時器
文章目錄
1. 低分辨率定時器對象的結構類型
上一節講了Linux系統中的時鐘節拍和 jiffies 變量,利用時鐘節拍和 jiffies 變量可以定義內核中的低分辨率定時器,在學習之前首先需要了解低分辨率定時器對象的結構類型,如下所示,各個參數含義在結構體中進行解釋。
struct timer_list {
struct hlist_node entry; // 雙向鏈表節點的對象,用於構成雙向鏈表
unsigned long expires; // 定時器到期的jiffiers值
void (*function)(unsigned long); // 定時器到期後執行的函數
unsigned long data; // 傳遞給定時器的參數,通常是一個指針
u32 flags;
...
};
要使用內核定時器首先要先定義一個 timer_list 變量表示定時器,tiemr_list 結構體的expires 成員變量表示超時時間,單位爲節拍數。比如我們現在需要定義一個週期爲 2 秒的定時器,那麼這個定時器的超時時間就是 jiffies+(2HZ),因此 expires=jiffies+(2HZ)。function 就是定時器超時以後的定時處理函數,我們要做的工作就放到這個函數裏面,需要我們編寫這個定時處理函數。
2. 低分辨率定時器操作相關函數
低分辨率操作相關的函數如下:
2.1 初始化定時器
該函數用於初始化一個定時器
@原 型: void init_timer(struct timer_list *timer)
@功 能: 初始化一個定時器
@param1: 要初始化定時器
@return: 無返回值
2.2 將定時器添加到內核中的定時器鏈表中
@原 型: void add_timer(struct timer_list * timer)
@功 能: 將定時器添加到內核中的定時器鏈表中
@param1: 初始化的定時器
@return: 無返回值
2.3 修改定時器expires成員的值
該函數用於重複使用定時器
@原 型: int mod_timer(struct timer_list * timer, unsigned long expires)
@功 能: 修改定時器expires成員的值,不管當前定時器的狀態,如果定時器還沒有激活的話,mod_timer 函數會激活定時器
@param1: 要修改的的定時器
@param2: 修改後的超時時間
@return: 0表示調用 mod_timer 函數前定時器未被激活,1表示調用 mod_timer 函數前定時器已被激活
2.4 刪除定時器
@原 型: int del_timer(struct timer_list * timer)
@功 能: 從內核鏈表中刪除該定時器,不管當前定時器的狀態
@param1: 要刪除的的定時器
@return: 0表示定時器還沒被激活,1表示定時器已經激活
2.5 使用低分辨率定時器的操作步驟
要在內核中添加一個定時器,通常需要下面幾步操作:
1. 構造並初始化一個定時器,並對定時器對象中的expires、function和data成員賦值
2. 將定時器添加到內核中的定時器鏈表中
3. 時間到了之後定時器會被自動調用,如果需要週期性定時那麼可以使用mod_timer來修改expires值
4. 在不需要定時器的時候,刪除定時器
需要注意的是:內核是在定時器中斷的軟中斷下半部來處理這些定時器的,內核將會遍歷鏈表中的定時器,如果當前的jiffies的值和定時器中的expires值相等那麼定時器函數將會被執行,所以定時器是在中斷上下文中執行的。另外內核爲了高效率的管理這些定時器,會將這些定時器按照超時時間進行分組所以內核只會遍歷快要到期的定時器。
3. 示例代碼
3.1 demo.c
在demo.c的21行增加了一個定時器對象 struct timer_list,在22行增加了一個變量 timeperiod 用於定義定時器的延時週期。
在 demo_init 函數中,109行對定義的定時器進行了初始化,在112~115行對初始化的定時器進行賦值,112行指定了定時器的回調函數,在118行將初始化完成的定時器加入到定時器的鏈表中。
在26~34行是定時器回調函數的額具體實現過程,當定時器啓用併到達定時時間時,會調用該函數,在該函數的最後34行調用了mod_timer對定時器中的 expires 進行修改,並再次開啓定時器。
#include <linux/module.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/atomic.h>
#include <linux/slab.h> // kzalloc和kfree的頭文件
#include <linux/interrupt.h> // 中斷相關的頭文件
#include <linux/timer.h> // 定時器相關的頭文件
#define DELAY_MS 1000
typedef struct
{
dev_t dev_no;
char devname[20];
char classname[20];
struct cdev demo_cdev;
struct class *cls;
struct device *dev;
struct mutex my_mutex; // 定義一個互斥體,在init函數中進行初始化
struct timer_list test_timer; // 定義一個定時器
unsigned int timeperiod; // 定義一個延時週期,ms
}demo_struct;
demo_struct my_demo_dev; // 定義一個設備結構體
static void timer_function(unsigned long data)
{
static int count = 1;
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
printk("count = %d.\n", count);
++count;
mod_timer(&my_demo_dev.test_timer, get_jiffies_64() + msecs_to_jiffies(my_demo_dev.timeperiod)); // 再次開啓定時器
}
static int demo_open(struct inode *inode, struct file *filp)
{
/* 首先判斷設備是否可用 */
if( mutex_lock_interruptible(&my_demo_dev.my_mutex) ) // 訪問共享資源之前獲取互斥體,成功獲取返回0
{
return -ERESTARTSYS; // 不能獲取返回錯誤碼
}
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
static int demo_release(struct inode *inode, struct file *filp)
{
/* 釋放互斥量 */
mutex_unlock(&my_demo_dev.my_mutex);
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
struct file_operations demo_ops = {
.open = demo_open,
.release= demo_release,
};
static int __init demo_init(void)
{
int ret;
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
strcpy(my_demo_dev.devname, "demo_chrdev"); // 給設備名字賦值
strcpy(my_demo_dev.classname, "demo_class"); // 給設備類的名字賦值
ret = alloc_chrdev_region(&my_demo_dev.dev_no, 0, 0, my_demo_dev.devname);
if (ret)
{
printk("alloc_chrdev_region failed.\n");
goto region_err;
}
cdev_init(&my_demo_dev.demo_cdev, &demo_ops);
ret = cdev_add(&my_demo_dev.demo_cdev, my_demo_dev.dev_no, 1);
if (ret < 0)
{
printk("cdev_add failed.\n");
goto add_err;
}
my_demo_dev.cls = class_create(THIS_MODULE, my_demo_dev.classname); /* 在目錄/sys/class/.. */
if ( IS_ERR(my_demo_dev.cls) )
{
ret = PTR_ERR(my_demo_dev.cls);
printk("class_create failed.\n");
goto cls_err;
}
my_demo_dev.dev = device_create(my_demo_dev.cls, NULL, my_demo_dev.dev_no, NULL, "chrdev%d", 0); /* 在目錄/dev/.. */
if ( IS_ERR(my_demo_dev.dev) )
{
ret = PTR_ERR(my_demo_dev.dev);
printk("device_create failed.\n");
goto dev_err;
}
// 初始化一個定時器
init_timer(&my_demo_dev.test_timer);
// 給定時器中的變量賦值
my_demo_dev.timeperiod = DELAY_MS;
my_demo_dev.test_timer.expires = get_jiffies_64() + msecs_to_jiffies(my_demo_dev.timeperiod);
my_demo_dev.test_timer.function = timer_function;
my_demo_dev.test_timer.data = (unsigned long)&my_demo_dev;
// 將定時器添加到內核的定時器鏈表中
add_timer(&my_demo_dev.test_timer);
mutex_init(&my_demo_dev.my_mutex); // 初始化互斥體
return 0;
dev_err:
class_destroy(my_demo_dev.cls);
cls_err:
cdev_del(&my_demo_dev.demo_cdev);
add_err:
unregister_chrdev_region(my_demo_dev.dev_no, 1);
region_err:
return ret;
}
static void __exit demo_exit(void)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
del_timer(&my_demo_dev.test_timer); // 刪除定時器
free_irq(123, &my_demo_dev); // 註銷中斷
device_destroy(my_demo_dev.cls, my_demo_dev.dev_no);
class_destroy(my_demo_dev.cls);
cdev_del(&my_demo_dev.demo_cdev);
unregister_chrdev_region(my_demo_dev.dev_no, 1);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
3.2 test.c
在本例中 test.c 並不能起太大作用,因爲本例主要是驗證低分辨率定時器的使用,所以在加載模塊後會自動開啓定時器,而不需要執行應用程序。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
int i, count = 1;
fd = open("/dev/chrdev0", O_RDWR, 0666);
if (fd < 0)
{
perror("open");
return -1;
}
sleep(5);
close(fd);
return 0;
}
3.3 Makefile
KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-04.00.00.04/board-support/linux-4.9.28/
PWD := $(shell pwd)
all:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
arm-linux-gnueabihf-gcc test.c -o app
install:
sudo cp *.ko app /tftpboot
clean:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
rm app
obj-m += demo.o
3.4 測試結果
從測試結果可以看出:在加載模塊後,就開啓了定時器,並執行了定時器的回調函數並打印出了 count 的值,在執行完回調函數之後又再次的開啓了定時器,並不斷的打印出 count 的值,驗證了定時器的使用效果。
root@am335x-evm:~# insmod demo.ko
[ 34.997516] demo_init -- 71.
root@am335x-evm:~#
[ 36.082140] timer_function -- 30.
[ 36.085527] count = 1.
[ 37.122079] timer_function -- 30.
[ 37.125467] count = 2.
[ 38.162075] timer_function -- 30.
[ 38.165461] count = 3.
[ 39.202071] timer_function -- 30.
[ 39.205458] count = 4.
[ 40.242084] timer_function -- 30.
[ 40.245470] count = 5.
[ 41.282077] timer_function -- 30.
[ 41.285463] count = 6.
... ...