1. Linux Time .

Trong Linux kernel, thời gian được đo bằng một biến golbal tên là jiffies . Jiffies là biến kiểu usigned long và có giá trị bằng số lần xảy ra ngát của iterrrupt timer kể từ khi hệ thống được khởi động. Tần số của ngắt này được khai báo trong biến HZ và biến này có thể được cấu hình ở thời điểm biên dịch (CONFIG_HZ), tuy nhiên nếu như không có nhu cầu gì đặc biệt thì tốt nhất nên để nó ở giá trị mặc định (250HZ trên x86_64).

2. Linux Standard timer.

Có nhiều mục đích cần đến jiffies, tuy nhiên phổ biến nhất có lẽ là dùng để tính timeout cho timer. Trong Linux có 2 bộ timer là: Standard timer, hay còn gọi là timer wheel và một bộ timer có độ chính xác cao (đến nanosecond) gọi là high resolution timers (hrtimer). Mặc dụ timer wheel ít chính xác hơn so với hrtimer tuy nhiên nó là giải pháp hiệu quả trong trường hợp timer có thể bị hủy bỏ trước khi nó kịp timeout (thường dùng để kiểm tra timeout của peer trong networking).

struct timer_list {
	/*
	 * All fields that change during normal runtime grouped to the
	 * same cacheline
	 */
	struct hlist_node	entry;
	unsigned long		expires;
	void			(*function)(struct timer_list *);
	u32			flags;

#ifdef CONFIG_LOCKDEP
	struct lockdep_map	lockdep_map;
#endif
};

Standard Timer cung cấp các hàm để tạo, hủy bỏ và quản lý timer: Trong Linux kernel, mỗi Timer là một biến với kiểu struct timer_list, cấu trúc này có chứa một callback function được gọi khi timer hết hạn, cấu trúc này và các hàm liên quan được khai báo trong linux/timer.h. Dĩ nhiên là để sử dụng Timer chúng ta cần khởi tạo nó, bằng cách sử dụng init_timer hoặc setup_timer. (ngoài ra còn có 1 cái macro tên DEFINE_TIMER nữa).

void init_timer( struct timer_list *timer );
void setup_timer( struct timer_list *timer, 
                     void (*function)(unsigned long), unsigned long data );

Hàm setup_timer sẽ khởi tạo timer_list và gán hàm truyền vào (tham số thứ 2) cho callback function của timer_líst vừa được khởi tạo. Trong khi hàm init_timer thì chúng ta phải tự gán các giá trị cần thiết của timer sau đó gọi init_timer để đăng ký với kernel. (THật ra thì macro setup_timer sẽ gọi đến macro init_timer). Note Trong các version kernel mới(từ 4.15) thì setup_timer được đổi thành timer_setup

Sau khi đã khởi tạo timer, ta có thể đặt thời gian timeout cho nó thông qua hàm mod_timer hay xóa timer với del_timer.

3. Timer example.

Sau đây mình sẽ thử viết một module đơn giản để demo khả năng của timer api. Trong module này mình sẽ khai báo một timer trong hàm init_module và đặt timeout cho nó là 200ms. Và gán hàm oni_callback làm callback function cho nó. Hàm này sẽ được gọi sau 200ms. Trong code có giải thích cụ thể bằng comment.

//Kernel 4.15.0
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/timer.h>

MODULE_LICENSE("GPL");

static struct timer_list my_timer;					//Khai báo timer list.

/*Đây là hàm callback. Như đã nói ở trên
kể tử kernel 4.15 timer api đã có sự thay đổi. Và hàm callback nhận tham
số kiểu <i>struct timer_list *</i> thay vì <i>unsigned long</i>
*/
void oni_callback(struct timer_list* timer) {
	//jiffies ở đây chính là thời điểm timer timeout + delta nhỏ.
  printk("Oni callback function (%ld).\n", jiffies); 
}

int init_module(void) {
  int ret;
  printk("Oni Timer module installing\n");
  
  timer_setup(&my_timer, oni_callback, 0);						//Khởi tạo timer_list và gán callback function.

  //Ở đây mình sẽ in ra thời điểm timer bắt đầu chạy để đối chiếu.
  printk("Starting timer, current time (%ld)\n", jiffies);	
  ret = mod_timer(&my_timer, jiffies + msecs_to_jiffies(200));	//Đặt timeout cho timer (200ms).
  
  if (ret) {
    printk("Error in mod_timer\n");

    return 0;
  }
}

void cleanup_module(void) {
  int ret;
  //Xóa bỏ timer khi không sử dụng nữa
  ret = del_timer(&my_timer);
  if (ret) {
    printk("Oni timer could not be removed...\n");
  }
  printk("Bye bye timer\n");
  return;
}

Khi insert module này vào hệ thống, dmesg sẽ hiển thị log sau:

[12390.892122] Oni Timer module installing
[12390.892124] Starting timer, current time (4297990083)
[12391.092317] Oni callback function (4297990134)

Mình đang chạy thử nó trên x86_64, và như nói ở trên thì HZ của platform này là 250, tức là 1 jiffies sẽ tương đương với 4ms. Theo log trên ta có fired_time - started_time = 4297990134 - 4297990083 = 51 jiffies * 4ms = 204 ms. (4ms là delta nhỏ ở trên). Tức là timer của mình chạy đủ 200ms.

4. High-resolution timer API

Như đã nói ở phần đầu thì kernel còn một loại timer nữa là hrtimer - high resolution timer, tạm dịch là timer độ chính xác cao: hrtimer cho độ chính xác tới nano secs. Khác với standard timer được xây dụng dựa trên giá trị jiffer, thì hrtimer lại sử dụng giá trị ktime làm đơn vị đo, kernel cung cấp hàm ktime_set để chuyển đổi từ thời gian thông thường thành ktime. Hrtimer cũng được biểu diễn bằng một struct tên là hrtimer. Hrtimer sẽ dùng một cấu trúc cây (red-black tree) - struct rbtree_node của kernel để quản lý các timer, các timer được thêm vào cây theo thứ tự thời gian trước-sau để giảm thiệu chi phí xử lý. Để sử dụng được HRT thì kernl cần phải enable CONFIG_HIGH_RES_TIMERS. Để biết HRT có được enable trên thiết bị mà đang sử dụng hay không, ta có thể dùng cmd sau:

cat /proc/timer_list |grep resolution

Nếu kết quả trả về có ít nhất 1 entry ghi “.resolution: 1 nsecs” thì có nghĩa là HRT đã được enable. Vậy câu hỏi là: khi nào thì dùng HRT? tất nhiên là khi cần độ chính xác cao - như cái tên rồi :gach:. Thật ra nó thường được dùng trong multimedia là chính thì phải, có lần đọc được thế ở đâu quên rồi, ahihi.

//Kernel 4.19
//file: linux/hrtimer.h 
/**
 * struct hrtimer - the basic hrtimer structure
 * @node:	timerqueue node, which also manages node.expires,
 *		the absolute expiry time in the hrtimers internal
 *		representation. The time is related to the clock on
 *		which the timer is based. Is setup by adding
 *		slack to the _softexpires value. For non range timers
 *		identical to _softexpires.
 * @_softexpires: the absolute earliest expiry time of the hrtimer.
 *		The time which was given as expiry time when the timer
 *		was armed.
 * @function:	timer expiry callback function
 * @base:	pointer to the timer base (per cpu and per clock)
 * @state:	state information (See bit values above)
 * @is_rel:	Set if the timer was armed relative
 *
 * The hrtimer structure must be initialized by hrtimer_init()
 */
struct hrtimer {
	struct timerqueue_node		node;
	ktime_t				_softexpires;
	enum hrtimer_restart		(*function)(struct hrtimer *);
	struct hrtimer_clock_base	*base;
	u8				state;
	u8				is_rel;
};

Một HRT được khởi tạo thông qua hàm hrtimer_init, hàm này nhận 3 tham số: timer struct, clock được sử dụng và timer mode. Clock được định nghĩa trong file timer.h, đại diện cho các loại clock mà hệ thống hỗ trợ (ví dụ như CLOCK_MONOTONIC hay CLOCK_REALTIME), còn timer mode có hai dạng là thời gian tương đối (HRTIMER_MODE_RLL) và thời gian tuyệt đối (HRTIMER_MODE_ABS).

void hrtimer_init( struct hrtimer *time, clockid_t which_clock, 
            enum hrtimer_mode mode );
int hrtimer_start(struct hrtimer *timer, ktime_t time, const 
            enum hrtimer_mode mode);

Một khi đã khởi tạo timer, thì ta có thể bắt đầu nó bằng cách gọi hàm hrtimer_start, trong hàm này sẽ có thời gian timeout được truyền vào. Và tương tự như standard timer thì HRT cũng cung cấp khả năng hủy bọ timer bằng các hàm hrtimer_cancel hoặc hrtimer_try_to_cancal. Điểm khách nhau giữa hai hàm cancel này là nếu như chúng được gọi khi timer đã gọi hàm callback, thì hàm đầu tiên sẽ đợi hàm callback hoàn thành rồi, còn hàm thứ hai sẽ kệ nó và báo lỗi là không cancel được. Ngoài ra ta cũng có thể kiểm tra xem timer đã gọi hàm callback của nó chưa bằng cách sử dụng hrtimer_callback_running function. Hai hàm khác cũng có thể được dùng để kiểm tra trạng thái timer là : hrtimer_get_remaininghrtimer_cb_get_time, một hàm dùng để kiểm tra thời gian còn lại của timer, một hàm dùng để kiểm tra thời gian đã chạy của timer.

Kernel cũng cung cấp hai hàm hrtimer_forwardhrtimer_forward_now để thay đổi thời gian timeout của timer sau khi nó đã start.

u64 hrtimer_forward (struct hrtimer * timer, ktime_t now, ktime_t interval);
u64 hrtimer_forward_now(struct hrtimer *timer, ktime_t interval);

5. HRT example.

Một ví dụ tương tự phần trước, nhưng thay vì sử dụng timer_list, mình sẽ dùng hrtimer.

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>

MODULE_LICENSE("GPL");

static struct hrtimer oni_hrt;

//Chọn timer không restart.
enum hrtimer_restart oni_hrt_callback(struct hrtimer *timer){
  printk("oni_hrt_cb called (%ld). \n", jiffies);
  return HRTIMER_NORESTART;
}

int init_module(void) {
  printk("ONI: Module installing\n");
  ktime_t ktime = ktime_set(0, 200 * 1000000); //0s + 200ms
  //Sử dụng thời gian tương đối.
  hrtimer_init(&oni_hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
  oni_hrt.function = &oni_hrt_callback;
  printk("ONI: Timer started at (%ld) jiffies\n", jiffies);
  hrtimer_start(&oni_hrt, ktime, HRTIMER_MODE_REL); //Bắt đầu chạy timer.
  return 0;
}

void cleanup_module(void) {
  int ret;
  ret = hrtimer_cancel(&oni_hrt);
  if (ret) {
    printk("Cannot cancel hrtimer\n");
  }

  printk("HRtimer: bye bye\n");
  return;
}

Khi thêm module trên vào hệ thống, ta thu được log sau. Có thể thấy là nó chính xác đúng 200ms luôn 0o0.

[17185.811269] ONI: Module installing
[17185.811270] ONI: Timer started at (4299188837) jiffies
[17186.011273] oni_hrt_cb called (4299188887). 

Leave a Comment