/** * Author:hasen * reference: "Linux device driver Development specific EXPLANATION" * Brief introduction: Android small Novice Linux * device Driver Development Learning Journey * Theme: Clock * date:2014-11-15 * *
First, the kernel timer
1. Kernel Timer programming
The software sense Timer finally relies on the hardware timer to be realized. The kernel runs a check to see if each timer expires after a clock interrupt occurs.
The timer handler function after expiration runs as a soft interrupt at the bottom half. Essentially, the clock interrupt handler evokes TIMER_SOFTIRQ
Soft interrupt to perform all timers that expire on the current processor.
Linux device driver programming. Ability to use a set of functions and data structures provided in the kernel to complete timed triggering or completion of a cycle
Sexual affairs. This set of functions and data allows the driver project master to take care of the kernel and hardware of the detailed software timer in most cases
Behavior.
The data structures and functions provided by the Linux kernel for manipulating timers are as follows:
(1) timer_list
In the Linux kernel, an instance of the Timer_list struct corresponds to a timer.
struct Timer_list {struct List_head entry;/* Timer list */unsigned long expires;/* Timer expiry time (jiffies) */void (*function) ( unsigned long);/* Timer handler function */unsigned Long data;/* As the input timer handler function */struct timer_base_s base;}
The following defines a my_timer timer
struct Timer_lsit my_timer;
(2) Initialize timer
void Init_timer (struct timer_list *timer);
The Init_timer () function above initializes the next null for the Timer_list entry and assigns a value to the base pointer.
The Timer_initializer (_FUNCTION,_EXPIRES,_DATA) macro is used to assign a timer structure to function, expires, data,
Base these members, the definition of this macro is:
The Define_timer (_NAME,_FUNCTION,_ESPIRES,_DATA) macro is a "shortcut" that defines and initializes the timer member. This one
Macro definitions such as the following:
In addition, the Setup_timer () function can be used to initialize the timer and assign values to its members. Its code such as the following:
static inline void Setup_timer (struct timer_list *timer,void (*function) (unsigned long), unsigned Long data) { timer- >function = function; Timer->data = data; Init_timer (timer);}
(3) Add timer
void Add_timer (struct timer_list *timer);
The above function is used to register the kernel timer and connect the timer to the kernel dynamic timer list.
(4) Delete timer
int Del_timer (struct timer_lsit *timer);
The above functions are used to remove timers.
Del_timer_sync () is a synchronous version of Del_timer () that waits for it to be processed when a timer is deleted, so the call to the function does not
Can occur in the interrupt context.
(5) Expire of changing timer
int Mod_timer (struct timer_list *timer,unsigned long expires);
The above function is used to change the timer expiry time. The timer function is not run until the new incoming expires arrives.
Demo Sample: Kernel Timer usage template
/*XXX Equipment Structure Body */struct xx_dev{ struct Cdev cdev; ... Timer_lsit xxx_timer;/* Device to use the timer * /}/*xxx driver in the module function */xxx_func1 (...) {struct Xxx_dev *dev = filp->private_data, .../* Initialize timer */init_timer (&dev->xxx_timer);d ev->xxx_ Timer.function = &xxx_do_timer;d ev->xxx_timer.data = (unsigned long) dev;/* device struct body pointer as timer handler function reference */dev->xxx_ Timer.expires = jiffies + delay;/* JOIN (Register) Timer */add_timer (&dev->xxx_timer); ...} A function */xxx_func2 (...) in the/*xxx driver. {.../* Delete timer */del_timer (&dev->xxx_timer); ...} /* Timer handler function */static void Xxx_do_timer (unsigned long arg) {struct Xxx_device *dev = (struct xxx_device*) (ARG); */* Schedule timer to run again */dev->xxx_timer.expires = jiffies + delay; Add_timer (&dev->xxx_timer); ...}
The expiry time of the timer is often based on the current jiffies of a delay, if it is Hz. Indicates a delay of 1 seconds.
Timer processing function, after doing the corresponding work, will often delay expires and add the timer to the core timer list again
In So that the timer can be triggered again.
2, the kernel of the delayed work Delayed_work
Note that for periodic tasks, Linux also provides a set of packaged quick mechanisms, essentially leveraging the work queue and timer
Now, this mechanism is the definition of a delayed_work,delayed_work structure such as the following:
struct delayed_work{ struct work_struct work; struct timer_list timer;}; struct Work_struct { atimic_long_t data; #define Work_struct_pending 0#define work_struct_flag_mask (3UL) #define Work_struct_wq_data_mask (~work_struct_flag_mask) STRUCT list_head entry; work_func_t func; #ifdef config_lockdep struct lockdep_map lockdep_map; #endif};
We can schedule a delayed_work to run after a specified delay, for example, by using the following function.
int schedule_delayed_work (struct delayed_work *work,unsigned long delay);
When the specified delay arrives, the work_func_t type member func () of the work member in the Delayed_work struct is run.
The work_func_t type is defined as:
typedef void (*work_func_t) (struct work_sturct *work);
The units of the delay parameter are jiffies, so a common use method such as the following:
Schedule_delayed_work (&work,msecs_to_jiffies (poll_interval));
The Msecs_to_jiffies () is used to convert milliseconds to jiffies.
Assume that you want to run the task periodically. Schedule_delayed_work () is typically called again in the Delayed_work () function. Zhouerfu
Began.
For example, the following function is used to cancel Delayed_work:
int cancel_delayed_work (struct delayed_work *work); int Cancel_delayed_work_sync (struct delayed_work *work);
instance: Seconds character device
The following is a driver for a character device "second" (that is, "seconds") that initializes a timer when it is opened and adds it to the
The list of kernel timers, output per second is the current jiffies (for this reason, the timer handler function changes the new expires each time).
#include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/errno.h > #include <linux/mm.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/ cdev.h> #include <asm/io.h> #include <asm/system.h> #include <asm/uaccess.h> #define Second_major 248/* Preset second main device number */static int second_major = secong_major;/*second device structure */struct second_dev{struct Cdev cdev;/* Cdev structure */atomic_t counter;/* Total number of seconds */struct timer_list s_timer; * * Device to use the timer */}struct Second_dev *SECOND_DEVP;/* device struct pointer */struct void second_timer_handle (unsigned long arg) {Mod_timer (&second_devp->s_timer,jiffies + Hz); Atomic_inc (&second_devp->counter);p rintk (kern_notice "Current jiffies is%d\n", jiffies);} /* File Open function */int second_open (struct inode *inode, struct file *filp) {/* Initialize timer */init_timer (&second_devp->s_timer) ; second_devp->s_timer.function = &second_timer_handle; second_devp->s_timer.expires = jiffies + Hz; Add_timer (&second_devp->s_timer);/* Add (Register) timer */atomic_set (&second_devp->count,0); Count clear 0return 0;} /* File release function */int second_release (struct inode *inode, struct file *filp) {Del_timer (&second_devp->s_timer); return 0 ;} /* Read function */static ssize_t second_read (struct file *filp, char __user *buf,size_t count,loff_t *ppos) {int counter; counter = at Omic_read (&second_devp->counter); if (Put_user (counter, (int *) BUF) Return-efault; else return sizeof (unsigned int);} /* File operation struct */static const struct File_operations second_fops = {. Owner = This_module,. Open = Second_open,. Release = Second _release,. Read = Second_read,};/* Initialize and register cdev*/static void Second_setup_cdev (struct Second_dev *dev,int index) {int err,d Evno = MKDEV (Second_major,index); Cdev_init (&dev->cdev,&second_fops);d ev->cdev.owner = THIS_MODULE; Err = Cdev_add (&dev->cdev,devno,1), if (err) PRINTK (Kern_notice, "Error%d adding led%d", err,index);} /* Device driver module load function */int second_init (void) {int ret;d ev_T devno = MKDEV (second_major,0);/* Request device number */if (second_major) ret = register_chrdev_region (devno,1, "second"); Else{ret = Alloc_chrdev_region (&devno,0,1, "second"); second_major = major (Devno);} if (Ret < 0) return ret;/* Dynamic request device structure Memory */SECOND_DEVP = kmalloc (sizeof (struct second_dev), gfp_kern); if (!SECOND_DEVP) { /* Application Failed */ret =-enomem; goto Fail_malloc;} memset (second_devp,0,sizeof (struct second_dev)); Second_setup_cdev (second_devp,0); fail_malloc:unregister_chrdev_ Region (devno,1); return ret;} /* Module unload function */void second_exit (void) {Cdev_del (&second_devp->cdev);/* Logout cdev*/kfree (SECOND_DEVP);/* release device structure BODY */ Unregister_chrdev_reigon (MKDEV (second_major,0), 1);} Module_author ("hasen<[email protected]>"); Module_license ("Dual BSD/GPL"); Module_param (Second_major,int,s_irugo); Module_init (Second_init); Module_exit ( Second_exit);
In the second open () function, the timer is started. The timer handler function is executed again every second thereafter. In Second's
The release () function. The timer is removed.
The atomic variable counter in the Second_dev struct is used for the second count, each time in the timer handler function is Atomic_inc ()
The read () function that calls the 1,second of an atom returns this value to the user space.
The following is a second test procedure second_test.c
#include ... main () { int fd; int counter = 0; int old_counter = 0; /* Open/dev/second Device file * /fd= open ("/dev/second", o_rdonly); if (FD! =-1) { while (1) { read (fd,&counter,sizeof (unsigned int));/* Reads the number of seconds currently experienced * /if (counter! = Old_ Counter) {printf ("seconds after Open/dev/second:%d\n", counter); old_counter = counter; } } else{ printf ("Device open failure\n");} }
After executing the second_test. The kernel will continue to output the current jiffies value. And the application will continue to output from the open
The number of seconds since/dev/second.
Second, kernel delay
1. Short delay
The Linux kernel provides 3 functions, such as the following, for Nanosecond, microsecond, and millisecond latencies, respectively:
void Ndelay (unsigned long nsecs), void Udelay (unsigned long usecs), void Mdelay (unsigned long msecs);
The implementation of the above delay principle is essentially busy waiting. It has a certain number of cycles depending on the CPU frequency, and this delay is performed in the software:
void delay (unsigned int time) { while (time--);}
The Ndelay (), Udelay (), and Mdelay () functions are implemented in such a way that the mechanism is similar. The kernel starts up yes, it performs a delay test
The program (Delay Looop calibration) calculates the LPJ (loop per jiffy). For example, for LDD6410 circuit boards. When the kernel starts, it
Print:
Calibrating delay loop ... 530.84 bogomips (lpj=1327104)
Millisecond latency (and greater second latency) is already larger in the kernel. It is best not to use the Mdelay () function directly. This will have no
CPU resources, the kernel provides the following functions for latency over the millisecond level:
void msleep (unsigned int millisecs) unsigned long msleep_interruptible (unsigned int millisecs); void ssleep (unsigned int seconds);
The above function will cause the process sleep parameters that call it to specify the time, Msleep (), ssleep () cannot be interrupted. and
Msleep_interruptible () can be interrupted.
2, long delay
A very intuitive way to delay in the kernel is to compare the current jiffies and Target jiffies (set to current Jiffies plus time
Interval of jiffies), until the next jiffies hits the target jiffies.
Demo Sample: Delay 100 jiffies before delay 2s with busy waiting
/* Delay of 100 jiffies*/unsigned long delay = jiffies + +, while (Time_before (Jiffies,delay));/* Delay 2s*/unsigned long delay = Jif Fies + 2*hz; while (Time_before (Jiffies,delay));
Another time_after () corresponding to Time_before (). They are defined in the kernel as (in fact, only the future of the incoming
A simple comparison between jiffies and the jiffies being called):
#define Time_after (b) (Typecheck (unsigned long, a)) &&typecheck (unsigned long, b) && ((long)-(long) (a) <0) #define Time_before (A, B) time_after (b,a)
To prevent Timer_before () and Timer_after () from being optimized by the compiler optimizer for jiffies, the kernel defines
is a volatile variable, which guarantees that it is read again every time.
3, Sleep delay
Sleep delay is undoubtedly a better way than busy waiting. Sleep delay the process is asleep between waiting time. CPU Resources
Used by other processes. Schedule_timeout () enables the jiffies of the current task to be scheduled to run again after the specified sleep, Msleep () and
Msleep_interruptible () essentially relies on schedule_timeout_uninterruptible () and Schedule_timeout (), which includes the
Implemented by Schedule_timeout_interruptible ().
void msleep (unsigned int msecs) { unsigned long timeout = msecs_to_jiffies (msecs) +1; while (timeout) timeout = schedule_timeout_uninterruptible (timeout);} unsigned long msleep_interruptible (unsigned int msecs) {unsigned long timeout = msecs_to_jiffies (msecs) + 1; while (timeout &&!signal_pending (current) timeout = schedule_timeout_interruptible (timeout); return Jiffies_to_msecs ( timeout);}
As a matter of fact. Schedule_timeout () is implemented by adding a timer to the system, which wakes up the parameter in the timer handler function.
The process that should be. The difference between the schedule_timeout_uninterruptible () and schedule_timeout_interruptible () functions is that the former call
Schedule_timeout () precedes a process state of task_uninterruptible, which has a process state of task_interruptible.
Signed Long __sched schedule_timeout_interruptible (signed long timeout) { __set_current_state (task_interruptible ) ; return Schedule_timeout (timeout);} Signed Long __sched schedule_timeout_uninterruptible (signed long timeout) { __set_current_state (task_ uninterruptible); return Schedule_timeout (timeout);}
Other than that. The following two functions can join the current process to the wait queue to sleep on the waiting queue. When the timeout occurs, the input
The process will be awakened (the latter can be interrupted before the timeout):
Summarize
Latency in the kernel can be used for busy waiting or sleep waiting. In order to make full use of CPU resources, the system has better throughput performance,
The delay time requirement is not very precise in the case. Sleep waits are generally recommended. While the Ndelay (), Udelay () busy wait mechanism is in the drive
is typically designed to accommodate short-term latency requirements on hardware.
Hasen Linux device Driver Development learning journey--clock