My environment:
Fedora 14 kernel version is 2.6.38.1
Development board: ARM9 TQ2440
Ported kernel version: linux-2.6.30.4
The timer is mainly implemented in the Linux kernel using a structure. However, it should be noted that the timer is an object that only runs once, that is, when a timer ends, it is necessary to add a timer again. However, the mod_timer() function can be used to dynamically change the timer's arrival time.
This driver mainly implements the b
asic operation of kernel timer. Kernel timer is mainly implemented by the following structure struct timer_list. The required header file includes #include
, but it is not necessary to include this header file in the actual development process because it is included in sched.h.
struct timer_list {
struct list_head entry;
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
struct tvec_base *base;
#ifdef CONFIG_TIMER_STATS
void *start_site;
char start_comm[16];
int start_pid;
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
The implementation of the timer is mainly completed by filling the structure and coordinating some functions. The red parts are the main elements. 1. expires is mainly used to define the time when the timer expires. The value of this element is usually set by coordinating the global variables jiffies and HZ. For example, expires = jiffies + n*HZ, where jiffies is the number of ticks since startup and HZ is the number of ticks in one second.
2. Function is a function pointer. This function is the timer processing function, which is similar to the interrupt function in the interrupt. In fact, the timer and the interrupt have great similarities. The timer processing function is a self-defined function.
3. Data is usually used to implement parameter transfer. From the parameter type of function, we can know that data can be used as a parameter of the timer processing function.
Other elements can be initialized by kernel functions.
The initialization function is:
init_timer(struct timer_list * timer);
Or directly use the DEFINE_TIMER macro to implement definition and initialization operations.
#define DEFINE_TIMER(_name, _function, _expires, _data)
struct timer_list _name =
TIMER_INITIALIZER(_function, _expires, _data)
Function to add timer to kernel:
void add_timer(struct timer_list *timer)
{
BUG_ON(timer_pending(timer));
mod_timer(timer, timer->expires);
}
Delete the timer function. If the timer's timing has not arrived, the timer can be deleted:
int del_timer(struct timer_list *timer)
Modify the arrival time of the timer. The feature of this function is that no matter whether the timer reaches the time or not, it will re-add a timer to the kernel. Therefore, this function can be called in the timing processing function to modify the arrival time that needs to be redefined.
int mode_timer(struct timer_list *timer,unsigned long expires)
int mod_timer(struct timer_list *timer, unsigned long expires)
{
/*
* This is a common optimization triggered by the
* networking code - if the timer is re-modified
* to be the same thing then just return:
*/
if (timer->expires == expires && timer_pending(timer))
return 1;
/*Note the calling condition, which means the current timer is the last one in the linked list*/
return __mod_timer(timer, expires, false);
}
static inline int
__mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only)
{
struct tvec_base *base, *new_base;
unsigned long flags;
int ret;
ret = 0;
timer_stats_timer_set_start_info(timer);
BUG_ON(!timer->function);
base = lock_timer_base(timer, &flags);
if (timer_pending(timer)) {
detach_timer(timer, 0);
ret = 1;
} else {
if (pending_only)
goto out_unlock;
}
debug_timer_activate(timer);
new_base = __get_cpu_var(tvec_bases);
if (base != new_base) {
/*
* We are trying to schedule the timer on the local CPU.
* However we can't change timer's base while it is running,
* otherwise del_timer_sync() can't detect that the timer's
* handler yet has not finished. This also guarantees that
* the timer is serialized wrt itself.
*/
if (likely(base->running_timer != timer)) {
/* See the comment in lock_timer_base() */
timer_set_base(timer, NULL);
spin_unlock(&base->lock);
base = new_base;
spin_lock(&base->lock);
timer_set_base(timer, base);
}
}
timer->expires = expires;
internal_add_timer(base, timer);
out_unlock:
spin_unlock_irqrestore(&base->lock, flags);
return ret;
}
static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)
{
unsigned long expires = timer->expires;
unsigned long idx = expires - base->timer_jiffies;
struct list_head *vec;
if (idx < TVR_SIZE) {
int i = expires & TVR_MASK;
vec = base->tv1.vec + i;
} else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
int i = (expires >> TVR_BITS) & TVN_MASK;
vec = base->tv2.vec + i;
} else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
vec = base->tv3.vec + i;
} else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
vec = base->tv4.vec + i;
} else if ((signed long) idx < 0) {
/*
* Can happen if you add a timer with expires == jiffies,
* or you set a timer to go off in the past
*/
vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);
} else {
int i;
/* If the timeout is larger than 0xffffffff on 64-bit
* architectures then we use the maximum timeout:
*/
if (idx > 0xffffffffUL) {
idx = 0xffffffffUL;
expires = idx + base->timer_jiffies;
}
i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
vec = base->tv5.vec + i;
}
/*
* Timers are FIFO:
*/
/*Added to the end of the linked list, this shows that mod_timer implements the operation of re-registering a timer*/
list_add_tail(&timer->entry, vec);
}
From the above analysis, we can see that the implementation process of mod_timer is relatively complicated, but it basically explains the operation process of mod_timer function to re-register the timer.
Generally speaking, the basic operations of the timer are mainly the above functions.
My kernel timer-based driver function is as follows, referring to Song
Baohua's Linux Device Driver Development Detailed Explanation (Second Edition). [page]
driver:
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*Use macro definition to set the device's major device number*/
#define SECOND_MAJOR 0
/*Statically save the variables of static major device numbers*/
static int second_major = SECOND_MAJOR;
/*Device structure, usually contains the required devices in the device, such as character, block and other types*/
struct second_dev{
/*Add device type,
I think we can adopt a union.
Contains block devices or character devices, similar to the implementation method of inode,
This can improve the versatility of the structure
*/
struct cdev cdev;
/*Atomic variable, used for statistics*/
atomic_t counter;
/*Add kernel timer structure variable*/
struct timer_list s_timer;
/*Device class used to dynamically create device files*/
struct class *myclass;
};
/*Structure pointer or global variable can be used to directly define the structure*/
struct second_dev *second_devp;
/*If the timing time is reached, the timer processing function*/
static void second_timer_handler(unsigned long arg)
{
/*
Modify the expiration time in the timer and increase the time to 1s.
It should be noted that the mod_timer function re-registers the timer to the kernel
Regardless of whether the timer has been run
*/
mod_timer(&second_devp->s_timer,jiffies + HZ);
/*Increase atomic variable*/
atomic_inc(&second_devp->counter);
/*Output jiffies value*/
printk(KERN_NOTICE "Current jiffies is %d ",jiffies);
}
/*open function implementation*/
static int second_open(struct inode *inode,struct file *filp)
{
/* Initialize the defined kernel timer */
init_timer(&second_devp->s_timer);
/*Specify the kernel timer processing function to be the function defined above*/
second_devp->s_timer.function = second_timer_handler;
/*Specify the timing interval to be 1s*/
second_devp->s_timer.expires = jiffies + HZ;
/*Add timer to kernel*/
add_timer(&second_devp->s_timer);
/*At the same time, the device-related statistics value is 0*/
atomic_set(&second_devp->counter,0);
return 0;
}
/*Release function implementation*/
static int second_release(struct inode *inode,struct file *filp)
{
/*If the time is not reached, turn off the device and delete the timer directly*/
del_timer(&second_devp->s_timer);
return 0;
}
/*read function implementation*/
static ssize_t second_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
{
int counter;
/*Read the current value*/
counter = atomic_read(&second_devp->counter);
/*
Use put_user to transfer values
The put_user function checks the pointer variable.
Therefore, there is no need to check whether the pointer is correct.
*/
if(put_user(counter,(int *)buf))
return -EFAULT;
else
/*Return data size*/
return sizeof(unsigned int);
}
/*Specific file operation set*/
static const struct file_operations second_fops =
{
/*This is the owner*/
.owner = THIS_MODULE,
.open = second_open,
.release = second_release,
.read = second_read,
};
/* Initialization function */
static int __init second_init(void)
{
int ret;
/*Application and creation of device number*/
dev_t devno = MKDEV(second_major,0);
/*Statically apply for device number*/
if(second_major)
{
ret = register_chrdev_region(devno,1,"second");
}
/*Dynamically apply for device number*/
else
{
ret = alloc_chrdev_region(&devno,0,1,"second");
second_major = MAJOR(devno);
}
if(ret < 0)
{
return ret;
}
/* Allocate address space for device structure */
second_devp = kmalloc(sizeof(struct second_dev),GFP_KERNEL);
/* Check if the allocation is correct */
if(!second_devp)
{
ret = -ENOMEM;
goto fail_malloc;
}
/*Clear the allocated space*/
memset(second_devp,0,sizeof(struct second_dev));
/*Create a device class to automatically create device files*/
second_devp->myclass = class_create(THIS_MODULE,"second_timer_class");
/*Initialize character device and bind related operations to the device*/
cdev_init(&second_devp->cdev,&second_fops);
/*Owner of the device*/
second_devp->cdev.owner = THIS_MODULE,
/*Add device to kernel*/
ret = cdev_add(&second_devp->cdev,devno,1);
/* Error handling */
if(ret)
{
printk(KERN_NOTICE "ERROR %d ",ret);
goto fail_malloc;
}
/*Create a device based on the previously created device class*/
device_create(second_devp->myclass,NULL,devno,NULL,"second%d",0);
return 0;
/*Error operation*/
fail_malloc:
unregister_chrdev_region(devno,1);
return ret;
}
/*Exit function*/
static void __exit second_exit(void)
{
/*Release the device*/
device_destroy(second_devp->myclass,MKDEV(second_major,0));
/*Delete character device*/
cdev_del(&second_devp->cdev);
/*Release device class*/
class_destroy(second_devp->myclass);
/*Release the allocated memory size*/
kfree(second_devp);
/*Release device number*/
unregister_chrdev_region(MKDEV(second_major,0),1);
}
/*Unload and load*/
module_init(second_init);
module_exit(second_exit);
/*LICENSE and author information*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("GP-");
app:
#include
#include
#include
#include
#include
#include
#include
int main()
{
int fd;
int counter = 0;
int old_counter = 0;
fd = open("/dev/second0",O_RDONLY);
if(fd != -1)
{
while(1)
{
read(fd,&counter,sizeof(unsigned int));
if(counter != old_counter)
{
printf("second after open /dev/second0 : %d ",counter);
old_counter = counter;
}
}
}
else
{
printf("Device open failure ");
exit(1);
}
exit(0);
}
Experimental results:
[root@EmbedSky Test]# ./app-timer
Current jiffies is 2137721
second after open /dev/second0 : 1
Current jiffies is 2137921
second after open /dev/second0 : 2
Current jiffies is 2138121
second after open /dev/second0 : 3
Current jiffies is 2138321
second after open /dev/second0 : 4
Current jiffies is 2138521
second after open /dev/second0 : 5
Current jiffies is 2138721
second after open /dev/second0 : 6
The above results show that the kernel timer basically achieves the effect, but from the experimental results it seems to be displayed once every two seconds. The specific reason needs to be further analyzed, because the HZ in arm should be 100, not 200.