Problem description
How do I use kernel timers?
Kernel timer
The Linux kernel timer is timer_list.
1. Introduction
A kernel timer is a mechanism used by the kernel to control the scheduled execution of a function at a future point in time (based on Jiffies). Its implementation is in the <Linux/timer.h> and kernel/timer.c files.
The scheduled function must be executed asynchronously, which is similar to a “software interrupt” and in a non-process context, so the scheduled function must obey the following rules:
-
- No current pointer, no access to user space is allowed. Because there is no process context, the code in question has no connection to the interrupted process.
-
- Hibernation (or functions that might cause hibernation) and scheduling cannot be performed.
-
-
Any data structures that are accessed should be protected against concurrent access to prevent race conditions.
-
The kernel timer’s scheduling function runs once and is not run again (equivalent to being automatically logged out), but can be run periodically by rescheduling itself within the scheduled function.
In SMP systems, the scheduling function always runs on the same CPU where it is registered to achieve as much cache locality as possible.
2. Data structure
(1) Data structure of kernel timer
struct timer_list {
struct list_head entry, /* Timer list */unsigned long expires, /* Timer expiration time */void (*function) (unsigned long), /* Timer handler */unsigned long data,/* is passed as an argument to the timer handler */struct timer_base_s *base. };
Copy the code
Where the Expires field represents the jiffies value that the timer is expected to execute. When the jiffies value is reached, the function function is called and data is passed as an argument.
jiffies
After a timer is registered with the kernel, the entry field is used to connect the timer to a kernel linked list.
The base field is used internally by the kernel implementation.
It’s important to note that expires is a 32-bit value because kernel timers are not good for long future points in time.
(2) Initialize the timer
Method one:
DEFINE_TIMER(timer_name, function_name, expires_value, data);
Copy the code
This macro defines a kernel timer named timer_name and initializes its function, Expires, name, and Base fields.
Method 2:
struct timer_list mytimer;
void init_timer(struct timer_list *timer);
Copy the code
The above init_timer function initializes the next of struct timer_list’s entry to NULL and assigns a value to the base pointer
To be effective, a timer must also be connected to a specific kernel linked list, which can be done by add_timer(struct timer_list *timer).
void add_timer (struct timer_list *timer);
Copy the code
To log out of a timer, run del_timer(struct timer_list *timer) or del_timer_sync(struct timer_list *timer).
int del_timer (struct timer_list *timer);
int del_timer_sync(struct timer_list *timer)
Copy the code
Where del_timer_sync is used on SMP systems (on non-SMP systems, it is equal to del_timer), del_timer_sync() waits for the unregistered timer function to complete while it is running on another CPU, so it sleeps.
It should also be avoided using the same lock as the scheduled function. For a timer that has already been run and has not re-registered itself, the logout function does nothing.
int timer_pending(const struct timer_list *timer);
Copy the code
This function is used to determine whether a timer has been added to the kernel list to be scheduled to run. Note that when a timer function is about to be run, the kernel removes the corresponding timer from the kernel list (equivalent to logging out).
To change the expire time of a timer, call mod_timer(struct timer_list *timer, unsigned long Expires). Mod_timer () re-registers the timer to the kernel, regardless of whether the timer function has been run.
int mod_timer (struct timer_list *timer, unsigned long expires);
Copy the code
(6) For periodic tasks, the Linux kernel also provides a delayed_work mechanism to complete them, essentially using work queues and timers.
3. For example
Example 1: Implement printing a message to the kernel log every second
/* Print a message to the kernel log every second */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/time.h>
#include <linux/timer.h>
static struct timer_list tm;
struct timeval oldtv;
void callback(unsigned long arg)
{
struct timeval tv;
char *strp = (char*)arg;
printk("%s: %lu, %s\n", __func__, jiffies, strp);
do_gettimeofday(&tv);
printk("%s: %ld, %ld\n", __func__,
tv.tv_sec - oldtv.tv_sec, // From the last interrupt interval s
tv.tv_usec- oldtv.tv_usec); // The interval from the last interrupt is ms
oldtv = tv;
tm.expires = jiffies+1*HZ;
add_timer(&tm); // Restart the timer
}
static int __init demo_init(void)
{
printk(KERN_INFO "%s : %s : %d - ok.\n", __FILE__, __func__, __LINE__);
init_timer(&tm); // Initialize the kernel timer
do_gettimeofday(&oldtv); // Get the current time
tm.function= callback; // Specifies the callback function after the specified time expires
tm.data = (unsigned long)"hello world"; // Callback function arguments
tm.expires = jiffies+1*HZ; // Timing time
add_timer(&tm); // Register the timer
return 0;
}
static void __exit demo_exit(void)
{
printk(KERN_INFO "%s : %s : %d - ok.\n", __FILE__, __func__, __LINE__);
del_timer(&tm); // Unregister the timer
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yikoupeng");
MODULE_DESCRIPTION("timerlist");
Copy the code
Example 2: a second character device
second_drv.c
1 #include <linux/module.h>
2 #include <linux/types.h>
3 #include <linux/fs.h>
4 #include <linux/errno.h>
5 #include <linux/mm.h>
6 #include <linux/sched.h>
7 #include <linux/init.h>
8 #include <linux/cdev.h>
9 #include <asm/io.h>
10 #include <asm/system.h>
11 #include <asm/uaccess.h>
12 #include <linux/slab.h>
13
14 #define SECOND_MAJOR 248
15
16 static int second_major = SECOND_MAJOR;
17
18 struct second_dev {
19 struct cdev cdev;
20 atomic_t counter;
21 struct timer_list s_timer;
22 };
23
24 struct second_dev *second_devp;
25 static void second_timer_handle (unsigned long arg)
26 {
27 mod_timer (&second_devp->s_timer, jiffies + HZ);
28 atomic_inc (&second_devp->counter);
29 printk (KERN_NOTICE "current jiffies is %ld\n", jiffies);
30 }
31 int second_open (struct inode *inode, struct file *filp)
32 {
33 init_timer (&second_devp->s_timer);
34 second_devp->s_timer.function = &second_timer_handle;
35 second_devp->s_timer.expires = jiffies + HZ;
36 add_timer (&second_devp->s_timer);
37 atomic_set (&second_devp->counter, 0);
38 return 0;
39 }
40 int second_release (struct inode *inode, struct file *filp)
41 {
42 del_timer (&second_devp->s_timer);
43 return 0;
44 }
45 static ssize_t second_read (struct file *filp, char __user *buf,
46 size_t count, loff_t *ppos)
47 {
48 int counter;
49 counter = atomic_read (&second_devp->counter);
50 if (put_user (counter, (int *)buf))
51 return -EFAULT;
52 else
53 return sizeof (unsigned int);
54 }
55 static const struct file_operations second_fops = {
56 .owner = THIS_MODULE,
57 .open = second_open,
58 .release = second_release,
59 .read = second_read,
60 };
61 static void second_setup_cdev (struct second_dev *dev, int index)
62 {
63 int err, devno = MKDEV (second_major, index);
64 cdev_init (&dev->cdev, &second_fops);
65 dev->cdev.owner = THIS_MODULE;
66 err = cdev_add (&dev->cdev, devno, 1);
67 if (err)
68 printk (KERN_NOTICE "Error %d adding CDEV %d", err, index);
69 }
70 int second_init (void)
71 {
72 int ret;
73 dev_t devno = MKDEV (second_major, 0);
74 if (second_major)
75 ret = register_chrdev_region (devno, 1."second");
76 else {
77 return alloc_chrdev_region (&devno, 0.1."second");
78 second_major = MAJOR (devno);
79 }
80 if (ret < 0)
81 return ret;
82 second_devp = kmalloc (sizeof (struct second_dev), GFP_KERNEL);
83 if(! second_devp) {84 ret = -ENOMEM;
85 goto fail_malloc;
86 }
87 memset (second_devp, 0.sizeof (struct second_dev));
88 second_setup_cdev (second_devp, 0);
89 return 0;
90 fail_malloc:
91 unregister_chrdev_region (devno, 1);
92 return ret;
93 }
94 void second_exit (void)
95 {
96 cdev_del (&second_devp->cdev);
97 kfree (second_devp);
98 unregister_chrdev_region (MKDEV (second_major, 0), 1);
99 }
100 MODULE_AUTHOR ("yikoupeng");
101 MODULE_LICENSE ("Dual BSD/GPL");
102 module_param (second_major, int, S_IRUGO);
103 module_init (second_init);
104 module_exit (second_exit);
Copy the code
second_test.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
int main (void)
{
int fd;
int counter = 0;
int old_counter = 0;
fd = open ("/dev/second", O_RDONLY);
if(fd ! =- 1) {
while (1) {
read (fd, &counter, sizeof (unsigned int));
if(counter ! = old_counter) {printf ("seconds after open /dev/second: %d\n", counter); old_counter = counter; }}}else {
printf ("Device open failure\n");
}
return 0;
}
Copy the code
4. Other time functions and macros
1) Beat rate
Frequency of system timer; Defined by static preprocessing — HZ; The hardware is set according to the HZ value when the system starts. The HZ value varies with the architecture. HZ variable.
// kernel time frequency
#define HZ 1000
Copy the code
The benefits of increasing the beat rate interrupt generation more frequently:
- Improved resolution of time-driven events;
- Improve the accuracy of time driven events;
- Kernel timer with higher frequency and accuracy;
- The system calls poll() and select() run with greater precision depending on the overhead;
- System time measurement is more precise;
- Improve the accuracy of process preemption;
Side effects of increasing the beat rate:
- As the interrupt frequency increases, the system burden increases.
- Interrupt handlers consume more processor time.
- Frequent interruptions to the processor cache;
So: The metronomic HZ value needs to be balanced among them.
2) jiffies
- concept
Jiffies: Global variable used to record the total number of ticks generated since system startup. The kernel initializes this variable to 0 at startup;
Each subsequent clock interrupt handler increments the value of this variable.
Interrupts HZ per second, jiffies increase HZ within a second.
System running time = Jiffie /HZ.
Jiffies uses: calculating elapsed time and time management
- The header file:
linux/jiffies.h
Copy the code
- Definition:
extern u64 jiffies_64;
extern unsigned long volatile jiffies; // Bit length is more systemically related to 32/64
Copy the code
32 bits: overflow after 497 days
64-bit: Astronomical number
- For example, the timeout duration is 0.5 seconds
// Time out after 0.5 seconds
unsigned long timeout = jiffies + HZ/2;
// Note that jiffies value overflow wrap uses the macro time_before instead of straight timeout > jiffies
if(time_before(jiffies,timeout)){
// There is no timeout
}else{
/ / timeout
}
Copy the code
3) Time function do_gettimeofday
- Brief introduction:
In Linux you can use the function do_gettimeofday() to get the exact time. It can be subtle and is the same function used by getTimeofday () in the C library. A function to get time in the Linux kernel.
- Function prototype:
linux/time.h
void do_gettimeofday(struct timeval *tv)
Copy the code
- Description:
Do_gettimeofday () returns the current time in the TV structure and puts the local time zone information in the tz structure. Structure: timeVal
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
Copy the code
- case
struct timeval tv_begin.tv_end;
do_gettimeofday(&tv_begin,NULL); ..................... do_gettimeofday(&tv_end,NULL); Printk (" tv_begin_sec: % d \ n ", tv_begin. Tv_sec); Printk (" tv_begin_usec: % d \ n ", tv_begin. Tv_usec); Printk (" tv_end_sec: % d \ n ", tv_end. Tv_sec); Printk (" tv_end_usec: % d \ n ", tv_end. Tv_usec);Copy the code