"copyright notice: respect for the original, reproduced please retain the source: Blog.csdn.net/shallnet, the article is only for learning exchange, do not use for commercial use "
The previous section says that the mechanism of putting the work forward to a time other than the present is the second half, but when you need to postpone the work to a certain period of time, using a timer is a good choice. The last section of kernel time management mentions that the kernel is executing the timer as a soft interrupt in the lower half of the interrupt. The clock interrupt handler executes the Update_process_times function and runs the run_local_timers () function in the function to mark a soft interrupt to process all expired timers. as follows:
void update_process_times (int user_tick) { struct task_struct *p = current; int cpu = SMP_PROCESSOR_ID (); /* Note:this Timer IRQ Context must is accounted for as well. * /Account_process_tick (P, user_tick); Run_local_timers (); Rcu_check_callbacks (CPU, user_tick); Printk_tick (); Scheduler_tick (); Run_posix_cpu_timers (P);} void Run_local_timers (void) { hrtimer_run_queues (); RAISE_SOFTIRQ (TIMER_SOFTIRQ); Softlockup_tick ();}
before we analyze the implementation of the timer, let's take a look at one instance of using the kernel timer, and use this article to see:http://blog.csdn.net/shallnet/article/details/17734571, examples are as follows:
#include <linux/module.h> #include <linux/init.h> #include <linux/version.h> #include <linux/ timer.h> #include <linux/delay.h>struct timer_list sln_timer;void sln_timer_do (unsigned long l) { Mod_timer (&sln_timer, jiffies + HZ); PRINTK (kern_alert "param:%ld, jiffies:%ld\n", L, jiffies);} void Sln_timer_set (void) { init_timer (&sln_timer); Sln_timer.expires = jiffies + HZ; 1s sln_timer.function = sln_timer_do; Sln_timer.data = 9527; Add_timer (&sln_timer);} static int __init sln_init (void) { printk (kern_alert "===%s===\n", __func__); Sln_timer_set (); return 0;} static void __exit sln_exit (void) { printk (kern_alert "===%s===\n", __func__); Del_timer (&sln_timer);} Module_init (Sln_init); Module_exit (Sln_exit); Module_license ("GPL"); Module_author ("Allen");
This example prints the value of the current system jiffies per second.
The kernel timer is represented by the structure timer_list, defined in file <include/linux/timer.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};
as an example, the kernel provides some operating interfaces to simplify the management timer,
The first step, define a timer:
struct Timer_list Sln_timer;
The second step, initialize the internal value of the timer data structure.
Init_timer (&sln_timer);//Initialize Timer
#define Init_timer (timer) Init_timer_key (timer), NULL, null) void Init_timer_key (struct timer_list *timer, const char *name, struct Lock_class_key *key) { debug_init (timer); __init_timer (timer, name, key);} static void __init_timer (struct timer_list *timer, const char *name, struct Lock_class_key *key) { timer- >entry.next = NULL; Timer->base = __raw_get_cpu_var (tvec_bases); #ifdef config_timer_stats timer->start_site = NULL; Timer->start_pid =-1; memset (Timer->start_comm, 0, Task_comm_len), #endif lockdep_init_map (&timer->lockdep_map, name, key, 0);}
The third step, populate the values required in the TIMER_LIST structure:
Sln_timer.expires = jiffies + HZ; 1s after execution sln_timer.function = Sln_timer_do; Execution function sln_timer.data = 9527;
Sln_timer.expires represents the timeout period, which is the absolute count value in beats. If the current jiffies count is equal to or greater than the value of Sln_timer.expires, then the handler Sln_timer_do that the sln_timer.function points to is executed, and the function uses the long integer parameter Sln_timer.dat.
void Sln_timer_do (unsigned long l);
fourth Step, activation timer:
Add_timer (&sln_timer); Registering timers with the kernel
This will allow the timer to run.
the implementation of Add_timer () is as follows:
void Add_timer (struct timer_list *timer) { bug_on (timer_pending (timer)); Mod_timer (timer, timer->expires);}
Add_timer () called Mod_timer (). Mod_timer () is used to modify the timer timeout period.
Mod_timer (&sln_timer, jiffies + HZ);
because Add_timer () activates the timer by calling Mod_timer (), the timer can also be activated directly using Mod_timer (), and if the timer is initialized but not activated, Mod_timer () activates it.
If you need to stop the timer before the timer times out, use the Del_timer () function to complete.
Del_timer (&sln_timer);
the function is implemented as follows:
int del_timer (struct timer_list *timer) {struct tvec_base *base; unsigned long flags; int ret = 0; Timer_stats_timer_clear_start_info (timer); if (timer_pending (timer)) {base = Lock_timer_base (timer, &flags); if (timer_pending (timer)) {Detach_timer (timer, 1); if (timer->expires = = Base->next_timer &&!tbase_get_deferrable (timer->base)) Base->next_timer = base->timer_jiffies; ret = 1; } spin_unlock_irqrestore (&base->lock, flags); } return ret;} static inline void Detach_timer (struct timer_list *timer, int clear_pending) {struct List_head *entry = &timer->entry; Debug_deactivate (timer); __list_del (Entry->prev, Entry->next); if (clear_pending) Entry->next = NULL; Entry->prev = List_poison2;}
when Del_timer () is returned, the timer is no longer activated, but the timer interrupt on the multiprocessor machine may already be running on the other processor, so it is necessary to wait for the timer processing I program that may run on the other processor to exit when the timer is removed. You will use the Del_timer_sync () function to perform the delete work:
Del_timer_sync (&sln_timer);
the function can no longer be used in an interrupt context.
the function is implemented in detail as follows:
int Del_timer_sync (struct timer_list *timer) {#ifdef CONFIG_LOCKDEP unsigned long flags; Local_irq_save (flags); Lock_map_acquire (&TIMER->LOCKDEP_MAP); Lock_map_release (&TIMER->LOCKDEP_MAP); Local_irq_restore (flags); #endif for (;;) {//Loop until delete timer succeeds and then exits int ret = Try_to_del_timer_sync (timer); if (Ret >= 0) return ret; Cpu_relax (); }}int try_to_del_timer_sync (struct timer_list *timer) {struct tvec_base *base; unsigned long flags; int ret =-1; Base = Lock_timer_base (timer, &flags); if (Base->running_timer = = timer) goto out; ret = 0; if (timer_pending (timer)) {Detach_timer (timer, 1); if (timer->expires = = Base->next_timer &&!tbase_get_deferrable (timer->base)) base- >next_timer = base->timer_jiffies; ret = 1; }out:spin_unlock_irqrestore (&base->lock, flags); return ret;}
In general, the Del_timer_sync () function should be used instead of the Del_timer () function because it is not possible to determine whether he is running on another processor when the timer is removed. To prevent this from happening, you should call the Del_timer_sync () function instead of the Del_timer () function. Otherwise, after performing a delete operation on the timer, the code will continue to execute, but it may be possible to operate the resource that is being used by the timer running on the other processor, resulting in concurrent access, all taking precedence over the synchronization method of the delete timer.
in addition to using timers to postpone a task to a specified time period, there are other ways to process the delay request. Some methods suspend the processor while delaying the task, while others do not. There is actually no way to guarantee that the actual delay time is exactly equal to the specified delay time.
1. The simplest method of delay is a busy wait, which is simple to implement, and only needs to be rotated in the loop until the desired number of ticks is exhausted. For example:
unsigned long delay = jiffies+10; Delay of 10 beats while (Time_before (jiffies,delay)) ;
this way when the code waits, the processor can only rotate in place to wait, and it will not handle any other tasks. It is best to allow the kernel to reschedule other task executions while the task is waiting. Modify the above code as follows:
unsigned long delay = jiffies+10; 10 beats while (Time_before (jiffies,delay)) cond_resched ();
Take a look at the cond_resched () function implementation code:
#define Cond_resched ({__might_sleep ( __file__, __line__, 0); _cond_resched (); }) int __sched _cond_resched (void) { if (should_resched ()) { __cond_resched (); return 1; } return 0;} static void __cond_resched (void) { add_preempt_count (preempt_active); Schedule (); Finally, call the schedule () function to reschedule other programs to run Sub_preempt_count (preempt_active);}
the function cond_resched () will reschedule a new program to run, but it can only take effect if the need_resched flag is set. In other words, there are more important tasks in the system that need to be run. Because the method needs to invoke the scheduler, it cannot be used in an interrupt context----can only be used in the context of a process. In fact, all the deferred methods are used in the context of the process because the interrupt handlers should be executed as quickly as possible. In addition, deferred execution should not occur at the time of holding the lock or when the interrupt is disabled, in either case.
2. Sometimes the kernel requires a shorter delay, even shorter than the beat interval. You can then use the MS, NS, and US-level delay functions provided by the kernel.
void Udelay (unsigned long usecs); Arch/x86/include/asm/delay.hvoid ndelay (unsigned long nsecs); Arch/x86/include/asm/delay.hvoid mdelay (unsigned long msecs);
Udelay () uses a busy loop to defer a task to the specified MS after execution, which relies on the execution of several loops to achieve the delay effect, the Mdelay () function is implemented by the Udelay () function, as follows:
#define MDELAY (n) (\ (__builtin_constant_p (n) && (n) <=max_udelay_ms) Udelay ((n) *1000): ({ unsigned long __ms= (n); while (__ms--) udelay (1000);})) #endif
The udelay () function can only be executed when the required delay time is short, while a long delay in the high-speed machine can cause overflow. For a longer delay, mdelay () works well.
3. The Schedule_timeout () function is a more desirable method of delaying execution. This method causes the task that needs to delay execution to sleep until the specified delay time runs out and then runs again. However, this method also does not guarantee that the sleep time is equal to the specified delay time, only as long as the sleep time is close to the specified delay time. When the specified time expires, the kernel wakes up the deferred task and re-puts it back into the run queue. Use the following:
Set_current_state (task_interruptible); Set the task to interruptible sleep state schedule_timeout (s*hz); NAP, wake up after "s" seconds
The only parameter is the relative time of the delay, in which the unit is jiffies, in which case the corresponding task is pushed into the interruptible sleep queue, sleeping s seconds. Before calling the function schedule_timeout, do not set the task to one that can be interrupted or broken, or the task will not hibernate. This function calls the scheduler, so the code that calls it must be able to sleep, in short, the calling code must be in the context of the process and cannot hold the lock.
in fact, the implementation of the Schedule_timeout () function is a simple application of the kernel timer.
Signed Long __sched Schedule_timeout (signed long timeout) {struct timer_list timer; unsigned long expire; Switch (timeout) {case Max_schedule_timeout:/* * These-special cases is useful to be comfortab Le * in the caller. Nothing more. We could take * max_schedule_timeout from one of the negative value * and I ' d like to return a valid offs ET (>=0) to allow * The caller to do everything it want with the retval. */Schedule (); Goto out; Default:/* * Another bit of PARANOID. Note that the retval would be * 0 since no piece of kernel are supposed to does a check * for a negative retva L of Schedule_timeout () (since it * should never happens anyway). You just has the PRINTK () * That would tell you if something is gone wrong and where. */if (Timeout < 0) {PRINTK (kern_err "Schedule_timeout:wrong Timeout" "ValUE%lx\n ", timeout); Dump_stack (); Current->state = task_running; Goto out; }} expire = timeout + jiffies;//The next line of code sets the timeout execution function process_timeout (). Setup_timer_on_stack (&timer, Process_timeout, (unsigned long) current); __mod_timer (&timer, expire, false, timer_not_pinned); Activation timer schedule (); Scheduling other new Tasks Del_singleshot_timer_sync (&timer); /* Remove the timer from the object Tracker */Destroy_timer_on_stack (&timer); Timeout = expire-jiffies; Out:return Timeout < 0? 0:timeout;}
when the timer expires, the Process_timeout () function is called:
static void Process_timeout (unsigned long __data) { wake_up_process (struct task_struct *) __data);}
When the task is re-dispatched, the return code will continue to be executed before it enters sleep, at exactly schedule ().
code for the process context to wait for a specific time to occur, you can put yourself in the waiting queue. However, waiting for a task on the queue may wait for a specific event to arrive, and wait for a specific time to expire, to see who comes faster. In this case, the code can simply use the scedule_timeout () function instead of the schedule () function, so that when you want the specified time to expire, the task will be woken up, of course, the code needs to check the cause of the wake, it may be awakened by the event, It is also possible that the delayed time expires, or because the signal is received, and then the appropriate action is taken.
This article source code download:http://download.csdn.net/detail/gentleliu/8944281
Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.
Mastering the Linux kernel Design (vii): Kernel Timer and timed execution