日志标签 ‘定时器’

Linux内核中的时间

2012年6月6日

时间在内核中占有重要地位,操作系统必须随时都能获得当前时间,其次操作系统必须提供一种计时器可以通知内核某一段时间已经过去了。时间在内核中最常见的应用就是进程调度,内核不但要为每个进程分配时间片,而且要周期性的对可运行队列中的进程进行调整。Linux内核中的时间由两种设备同时进行计时:实时时钟和系统定时器。

实时时钟

实时时钟(Real Time Clock)用来永久存放系统时间,即便系统关闭也可以靠主板上的电池继续进行计时。由于RTC通常和CMOS被集成在一起,因此RTC也称为CMOS时钟。虽然可以通过操作/dev/rtc对RTC进行编程,但是一般Linux只用RTC来获取当前的时间和日期。当系统启动时,内核通过读取RTC来初始化墙上时间,该时间存放在xtime变量中。所谓墙上时间也就是当前的实际时间。

系统定时器

系统定时器是内核时间机制中最重要的一部分,它提供了一种周期性触发中断机制,即系统定时器以HZ(时钟节拍率)为频率自行触发时钟中断。当时钟中断发生时,内核就通过时钟中断处理程序timer_interrupt()对其进行处理。
系统定时器完全由操作系统管理,因此也成为系统时钟或者软件时钟。当系统启动时,内核通过RTC初始化系统定时器,系统定时器接着由操作系统共掌管,进行固定频率的定时。可以看到,系统时间并不是传统意义上的那种计时时钟,而是通过定时这种特殊的方式来表现时间。在x86架构下,系统时钟通过可编程间隔定时器(PIT)这种设备产生定时。

内核定时器

内核定时器也称为动态定时器,它可以使任务能在指定的时间点上执行。要使用定时器,必须先设置好定时器超时的时间,指定超时发生后应该执行的内核函数,最后激活这个定时器实例。当定时器超时的时候,该内核函数将被自动执行(但不周期执行),执行完毕后自行销毁,这也是内核定时器被称为动态定时器的原因。

获得时间

内核通过xtime变量保存墙上时间,该变量是timespec类型的,在linux/time.h中定义如下:

struct timespec {
        __kernel_time_t tv_sec;                 /* seconds */
        long            tv_nsec;                /* nanoseconds */
};

其中,tv_sec是以秒为单位时间,它保存着从1970年7月1日以来经过的时间,而tv_nsec记录自上一秒开始经过的纳秒数。

在最新的内核中,xtime未导出因此不能在内核模块中使用。不过内核提供了内核函数current_kernel_time()来获取当前时间,该函数返回timespec类型的时间。

内核中的动态定时器

2010年10月13日

内核中时间的相关概念

节拍率:系统定时器以某种频率触发时钟中断,这个频率就称为节拍率(tick rate)。节拍率是通过静态预处理定义的,被定义为HZ。对于x86体系结构来说,它的HZ为100。

节拍:两次时钟中断的间隔就称为节拍(tick)。可以看到,节拍等于节拍率分之一。

jiffies:全局变量jiffies用来记录系统自启动以来产生的节拍总数。通过jiffies/HZ就可获取系统自启动以来的秒数。

内核定时器

内核定时器,也称为动态定时器,是管理内核时间的基础,它是一种用来推迟执行程序的工具。前面中断的文章中我们说到内核将在稍候的时间执行下部分工作,具体是何时来执行就需要内核定时器。我们在使用关于定时器的相关函数时,先来看看内核是如何描述定时器的:

  10struct tvec_base;
  11
  12struct timer_list {
  13        /*
  14         * All fields that change during normal runtime grouped to the
  15         * same cacheline
  16         */
  17        struct list_head entry;//定时器链表入口
  18        unsigned long expires;//以jiffies为单位的定时值
  19        struct tvec_base *base;//
  20
  21        void (*function)(unsigned long);//定时器处理函数
  22        unsigned long data;//传给定时器处理函数的参数,无符号长整型
  23
  24        int slack;
  25
  26#ifdef CONFIG_TIMER_STATS
  27        void *start_site;
  28        char start_comm[16];
  29        int start_pid;
  30#endif
  31#ifdef CONFIG_LOCKDEP
  32        struct lockdep_map lockdep_map;
  33#endif
  34};

内核定时器使用起来也是相当简单的,基本的使用思路是:定义一个timer_list;初始化定时器,即对timer_list结构中的相关字段进行赋值;定义你自己的定时器处理函数;激活定时器;修改定时器,如果有必要的话;删除定时器。上述过程通过以下的API就可简单的完成。

秒字符驱动与内核定时器API

下面要分析的这个程序实现了一个秒字符设备驱动,程序整体的框架与之前我们分析的字符设备驱动相同。内核模块加载函数和卸载函数的功能和之前的字符设备驱动完全相同,因此接下来主要分析这个设备的操作函数。我们使用下面的结构来描述我们当前所讨论的秒字符设备。

struct second_dev
{
	struct cdev cdev;
	atomic_t counter;
	struct timer_list s_timer;
};

cdev即为内核中描述字符设备的结构体;counter用来记录秒数,它在测试程序中用来显示当前程序运行的时间。同时也是此设备驱动进行读写的数据;s_timer是为此设备所定义的定时器。每次定时一个HZ,也就是1秒,当定时到后,通过定时器处理函数对其进行修改,每次增加一个HZ。

接下来,我们一边分析驱动程序中的设备文件操作函数,一边学习内核定时器的API。

1.打开函数

秒字符设备在文件打开函数中实现对定时器的初始化以及赋值。

static int second_open(struct inode*inode,struct file*filp)
{
	//initializing the timer
	init_timer(&second_devp->s_timer);
	second_devp->s_timer.function=&second_timer_handler;
	second_devp->s_timer.expires=jiffies+HZ;
	add_timer(&second_devp->s_timer);//add a timer
	atomic_set(&second_devp->counter,0);//clear the counter
	return 0;
}

可以看到,内核定时器函数通过:init_timer(&t);来进行定时器的初始化,其中t为timer_list类型的指针。expires是用来设置定时器的定时值,单位是节拍数。我们可以看到初始定时值为一秒,即在当前时钟节拍数上加上一个节拍率。function则是上面提到的定时器处理函数。

另外,在open函数中,还通过add_timer(&t);来进行定时器的激活,也就是把当前这个定时器加入到内核定时器链表当中。由于我们在该设备对应的街头体中将counter设置为atomic_t(关于atomic.h头文件,本博客稍候会专门来分析,请持续关注~ e91 ),因此我们使用atomic_set函数对其进行初始化,其值设置为0。

2.读函数

static ssize_t second_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
{
	int counter;

	counter=atomic_read(&second_devp->counter);
	if(put_user(counter,(int*)buf))
	{
		return -EFAULT;
	}
	else
	{
		return sizeof(unsigned int);
	}

}

这个读函数的意思是从内核中通过atomic_read函数读取counter,再通过put_user函数将其值返回至用户空间。

3.定时器处理函数

static void second_timer_handler(unsigned long arg)
{
	mod_timer(&second_devp->s_timer,jiffies+HZ);
	atomic_inc(&second_devp->counter);
	printk("Current jiffies is %ld\n",jiffies);
}

执行此处理函数时,说明先前定时器的时间已经到期。我们通过mod_timer(&t,jiffies+newdelay);来改变定时器的超时时间。这里我们每次增加一秒的时间。接着通过atomic_inc函数对counter加一,并且在内核中显示当前的节拍数。

4.文件释放函数

static int second_release(struct inode*inode,struct file*filp)
{
	del_timer(&second_devp->s_timer);
	return 0;
}

在文件释放函数中,通过del_timer(&t);就可以删除这个定时器

测试

1.make编译并将此模块插入内核:sudo insmod secondcdev.ko
2.查看/proc/devices文件中是否有主设备号为250的设备
3.创建该设备文件节点,使用命令:sudo mknod /dev/secondcdev  c  250  0
4.修改该设备文件权限:sudo chmod 777 /dev/secondcdev
5.编译测试文件:gcc -o test test.c
6.运行测试程序:./test;

关于测试程序代码如下:

#include < stdio.h >
#include < sys/stat.h >
#include < sys/types.h >
#include < fcntl.h >
#include < stdlib.h >
int main()
{
	int fd;
	int counter=0;
	int old_counter=0;

	//open the /dev/secondcdev device file
	if((fd=open("/dev/secondcdev",O_RDONLY))<0)
	{
		printf("Device open failed\n");
		return -1;
	}

	while(1)
	{
		read(fd,&counter,sizeof(unsigned int));//read the current second number
		if(counter!=old_counter)
		{
			printf("seconds after open /dev/second:%d\n",counter);
			old_counter=counter;
		}
	}

	return 0;

在测试程序中,首先创建一个设备文件;然后循环读取counter的值,如果前后两次读取的counter值不一样,那么就输出;由于我们在定时器处理函数中是每隔一秒修改一次定时器,因此测试程序将每个一秒输出一次counter值。可以看到,我们所编写的秒字符设备驱动使得数据(即counter)在内核空间和用户空间之间进行交互。

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key