Linux内核编程——内核定时器

在Linux UWB Stack的内核模块实现中,较多的使用了内核定时器,本文基于fake MCPS实现中的应用为背景,介绍了内核定时器的使用。

1. 内核定时器

  Linux内核用来控制在未来某个时间点【基于jiffies(节拍总数)】调度执行某个函数的一种机制,相关函数位于 文件中。
  当内核定时器定时时间到达时,会进入用户指定的函数,相当于软中断。内核定时器注册开启后,运行一次就不会再运行(相当于自动注销),我们可以重新设置定时器的超时时间,让定时器重复运行。
  每当时钟tick中断发生时,全局变量jiffies(一个32位的unsigned long 变量)就加1,因此jiffies记录了linux系统启动后时钟中断发生的次数,驱动程序常利用jiffies来计算不同事件间的时间间隔。内核每秒钟将jiffies变量增加HZ次。因此,对于HZ值为100的系统,jiffy+1等于隔了10ms,而对于HZ为1000的系统,jiffy+1仅为1ms。

内核定时器使用

首先需要创建一个struct timer_list实例,并通过timer_setup设置定时器的超时处理函数,并启动。

static struct timer_list g_timer;

void timer_setup(struct timer_list *timer, void (*callback)(struct timer_list *), unsigned int flags);

timer_setup函数的实现相对比较简单:

void timer_setup(struct timer_list *timer, void (*callback)(struct timer_list *), unsigned int flags)
{
    memset(timer, 0, sizeof(*timer));
    INIT_LIST_HEAD(&timer->entry);
    timer->function = callback;
    timer->flags = flags;
}

2)定时器设置好之后,配置超时时间启动定时器

// 设置定时器的超时时间,在全局jiffies变量基础上增加,jiffies与系统设置的HZ相关
void mod_timer(struct timer_list *timer, unsigned long expires);

这样,定时器在设置的超时时间到之后,运行用户设定的函数(callback),若要保证周期性的运行,则可以在周期函数中,重复设置定时器的超时时间。

void del_timer(struct timer_list * timer);

del_timer 函数用于删除一个已经激活的定时器。其中,timer 是一个指向定时器结构体的指针。如果定时器已经被取消或者还未被激活,则不会有任何操作。在删除定时器之后,定时器的定时操作也会被取消。

关于内核定时器时间转换

//将输入的微秒时间转换为jiffies单位
unsigned long usecs_to_jiffies(const unsigned int m);
//将输入的毫秒时间转换为jiffies单位
unsigned long msecs_to_jiffies(const unsigned int m);

相反的,也可以将jiffies单位的时间转换为struct timespec的时间单位。

void jiffies_to_timespec(const unsigned long jiffies, struct timespec *value)struct timespec {
    time_t tv_sec;  // 秒数
    long tv_nsec;   // 纳秒数
};

2. 内核定时器应用——fake MCPS实现

在uwb stack的mcps802154_fake.c的fake MCPS实现代码如下:

static struct timer_list g_timer;
// 1s = 1000 ms
static const int g_time_interval = 1000;

// 周期任务,在每次任务中,重新设定定时器的超时时间
static void periodic_task(struct timer_list *unused)
{
    // ...
	/*Restarting the timer...*/
	mod_timer(&g_timer, jiffies + msecs_to_jiffies(g_time_interval));
	if (rx_enabled) {
		rx_enabled = false;
		mcps802154_rx_frame(driver_llhw);
	}
	if (tx_queued) {
		tx_queued = false;
		mcps802154_tx_done(driver_llhw);
	}
}

static int __init fake_init(void)
{
	int r;

	pr_info("fake_mcps: init\n");
    // 分配底层硬件驱动
	driver_llhw = mcps802154_alloc_llhw(0, &fake_ops);
	if (driver_llhw == NULL) {
		return -ENOMEM;
	}

    // ...

    // mcps802154注册底层硬件驱动
	r = mcps802154_register_llhw(driver_llhw);
	if (r) {
		mcps802154_free_llhw(driver_llhw);
		driver_llhw = NULL;
		return r;
	}

    // 设置定时器,启动周期定时器
	timer_setup(&g_timer, periodic_task, 0);
	mod_timer(&g_timer, jiffies + msecs_to_jiffies(g_time_interval));
	return 0;
}

static void __exit fake_exit(void)
{
	pr_info("fake_mcps: Exit\n");
    // 注销及释放底层硬件驱动
	mcps802154_unregister_llhw(driver_llhw);
	mcps802154_free_llhw(driver_llhw);
	driver_llhw = NULL;
	/*Deleting the timer aka the periodic task.*/
	stop_timer = true;
	del_timer(&g_timer);
}

module_init(fake_init);
module_exit(fake_exit);

通过fake_init完成必要的mcps802154相关的注册,同时设置内核定时器,周期性的启动模拟的收发操作。
模块退出时,通过fake_exit函数进行注销,删除已经激活的定时器。

你可能感兴趣的:(Linux,UWB,Stack,Linux,linux,智能硬件,信息与通信)