First, tick device concept Introduction
1. Data structure
In the kernel, use struct tick_device to abstract the tick device in the system, as follows:
struct Tick_device {
struct Clock_event_device *evtdev;
Enum Tick_device_mode mode;
};
As can be seen from the definition above: the so-called tick device is actually working in a certain mode of clock event devices. The work mode is reflected in the mode member of the Tick device, and evtdev points to the clock event device associated with the tick.
The working mode of the tick device is defined as follows:
Enum Tick_device_mode {
Tickdev_mode_periodic,
Tickdev_mode_oneshot,
};
Tick device can work in two modes, one is a cyclical tick mode, the other is one shot mode. The one shot mode is mainly related to the tickless system and the high-precision timer, which is described in another document, this article mainly introduces periodic mode.
2, the classification of tick device and the relationship with the CPU
(1) Local tick device. In a single-core system, the traditional UNIX is the Tick driven task scheduling, low-precision timer trigger, etc., in the multi-core architecture, the system for each CPU set up a tick device, as follows:
DEFINE_PER_CPU (struct tick_device, tick_cpu_device);
The clock event device for local tick device should have the following features:
(a) the HW timer corresponding to the clock event device must be associated with the CPU core (i.e., the interrupt of the HW timer can be delivered to the CPU core). struct Clock_event_device has a cpumask member that can indicate which or which CPU cores the clock event device is working on. If you use ARM generic timer hardware, its HW timer is always for a CPU core service, which we call per CPU timer.
(b) The clock event device supports one shot mode with the highest accuracy (rating max)
(2) Global tick device. It is defined as follows:
int tick_do_timer_cpu __read_mostly = tick_do_timer_boot;
Some tasks are not suitable for processing in local tick device, such as updating jiffies, updating the system's wall time, updating the system's average load (not the load of a single CPU core), which are system-level tasks that need to be done at the local tick Select one of the device as the global tick device is OK. TICK_DO_TIMER_CPU indicates which CPU has the local tick as the global tick.
(3) Broadcast tick device, defined as follows:
static struct Tick_device tick_broadcast_device;
We will describe it in a separate document, which is no longer described here.
Second, initialize the tick device
1. What does the tick device layer do when registering a new clock event device?
In the article of clock event device, we know: the underlying timer hardware driver registers the clock event device during initialization and calls Tick_check_new_ during the registration process. Device function to see if it is necessary to initialize the tick device if the tick device that has already initialized OK has the need to replace the higher precision clock event device. The code is as follows:
void Tick_check_new_device (struct clock_event_device *newdev)
{
struct Clock_event_device *curdev;
struct Tick_device *td;
int CPU;
CPU = smp_processor_id ();---------------------------(1)
if (!CPUMASK_TEST_CPU (CPU, newdev->cpumask)) goto OUT_BC;
td = &per_cpu (Tick_cpu_device, CPU);----Get the tick device for the current CPU
Curdev = td->evtdev; ---clock event device currently being used by tick device
if (!tick_check_percpu (Curdev, Newdev, CPU))-------------------(2)
Goto OUT_BC;
if (!tick_check_preferred (Curdev, Newdev))--------------------(3)
Goto OUT_BC;
if (!try_module_get (Newdev->owner))---increase the reference count of new devices
Return
if (Tick_is_broadcast_device (Curdev)) {----------------------(4)
Clockevents_shutdown (Curdev);
Curdev = NULL;
}
Clockevents_exchange_device (Curdev, Newdev); ---Notify clockevent layer
Tick_setup_device (TD, Newdev, CPU, cpumask_of (CPU)); ---------------(5)
if (Newdev->features & Clock_evt_feat_oneshot)----description in other documents
Tick_oneshot_notify ();
Return
OUT_BC:
Tick_install_broadcast_device (Newdev); ---Description in other documents
}
(1) is the clock event device for this CPU service? If not, you do not need to consider the initialization of the per CPU tick device or replace the clock event device of the CPU tick device. Of course, it is still possible to consider using the broadcast tick device.
(2) The second level is the per CPU check. If the check does not pass, then this new registered clock event device and the CPU does not call and cannot be used for the CPU's local tick. If the registered HW timer is CPU local (only one CPU, the clock event device cpumask only one bit is set), then things will be easier. However, things are often not so simple that a HW timer can serve multiple CPUs. What we say here is that the HW timer serves a CPU and the most important thing is whether the IRQ can be distributed to the specified CPU. We can look at the implementation of TICK_CHECK_PERCPU:
Static bool Tick_check_percpu (struct Clock_event_device *curdev,
& nbsp; struct clock_event_device *newdev, int cpu)
{
if (!cpumask_test_cpu (CPU, Newdev->cpumask))-------------------(a)
return false;
if (cpumask_equal (Newdev->cpumask, cpumask_of (CPU))---------------(b)
return true;
if (newdev->irq >= 0 &&!irq_can_set_affinity (NEWDEV->IRQ))--------------(c)
return false;
if (Curdev && cpumask_equal (Curdev->cpumask, cpumask_of (CPU)))----------(d)
return false;
return true;
}
(a) Determine if the newly registered clock event device can serve the CPU, and if it does not bird the CPU then it does not waste time.
(b) Determine whether this newly registered clock event device only serves the CPU. If this clock event device is to serve the CPU, then don't think three, this clock event device is your CPU person.
(c) If this clock event device is able to serve multiple CPUs, the specified CPU (passed in as a parameter) is only one of them, at which point the clock event can be set by setting the IRQ affinity The device's IRQ is directed to the CPU. Currently, the premise is that IRQ affinity can be set, and here is the check.
(d) walk here, stating that the newly registered clock event device is capable of IRQ affinity settings. We can let the HW timer serve this specified CPU by modifying the IRQ affinity. Well, that sounds like a bit of a hassle, indeed, if the clock event device that the current CPU's tick device is using is special for the current CPU (not the other CPU at all), there is such a special clock event device, The new registered equipment.
(3) The program came here, indicating that Tick_check_percpu returned to TRUE,CPU and the clock event device is already ogle, but whether it is possible to master, see if the CPU's original is strong enough (accuracy and characteristics). The tick_check_preferred code is as follows:
static bool Tick_check_preferred (struct Clock_event_device *curdev,
struct Clock_event_device *newdev)
{
if (! ( Newdev->features & Clock_evt_feat_oneshot) {--------------(a)
if (Curdev && (Curdev->features & Clock_evt_feat_oneshot))
return false;
if (Tick_oneshot_mode_active ())
return false;
}
return!curdev | |
newdev->rating > Curdev->rating | |
!cpumask_equal (Curdev->cpumask, Newdev->cpumask);-------------(b)
}
(a) First shot competency competition. If the new clock event device does not have one shot capability and the original is there, it fails. If none of the one shot is capable, see if the current system has a high-precision timer or tickless enabled. In essence, if the clock event device does not have the OneShot function, then the high-precision timer or tickless are in a state of compromise, if so, or to maintain the original state of compromise, the sweetheart failed
(b) If current is null, it becomes very simple, of course, the new clock event device wins (at this point, the comparison is meaningless). If the original exists, then you can see rating, if the new high precision, then also choose the new clock event device. Is the precision must not must not choose new? No, the new device still has the opportunity to tide over: if the new to the local timer, and the original is a non-local timer, this time, you can also consider the choice of new, after all, the new clock event device is a local timer, low precision is not related.
There are two cases when Tick_check_percpu returns true: The new device is the CPU's local timer (only for this CPU service), regardless of current state. Another scenario is that the new device is not a local timer for the CPU, and of course the original is not so exclusive.
Let's take a look at the first case: if Cpumask_equal returns True, then the original is also local timer, then there is no way, who rating choose Who. If Cpumask_equal returns False, then the original is not the local timer, so even if the new rating lower still prefer the local timer.
Let's look at the second situation: Here I have absolutely logic problems, do not know the code is the problem or I have not considered clearly, first todo it.
(4) OK, after a complex check, we finally decided to use this new registered clock event device to replace current (of course, it may not exist at all). Before replacing, we have to check if current is a broadcast tick device, and if so, you can't return it to the clockevents layer, just set its status to shutdown. Curdev = NULL is an important sentence, in the Clockevents_exchange_device function, if Curdev = = null, the old device will not be removed from the global list, and then be hung into the clockevents_ Released linked list.
(5) Setup tick device, refer to the next section.
2. How do I setup a tick device?
Setup is a tick device that sets the clock event devices that are right for tick, and points the evtdev of the tick device to the newly registered clock event, with the following code:
static void Tick_setup_device (struct tick_device *td,
struct Clock_event_device *newdev, int CPU,
const struct CPUMASK *cpumask)
{
ktime_t next_event;
void (*handler) (struct clock_event_device *) = NULL;
if (!td->evtdev) {
if (tick_do_timer_cpu = = tick_do_timer_boot) {--------------(1)
......
}
Td->mode = tickdev_mode_periodic;------------------(2)
} else {
Handler = td->evtdev->event_handler;
Next_event = td->evtdev->next_event;
Td->evtdev->event_handler = Clockevents_handle_noop; ------------(3)
}
Td->evtdev = Newdev; ----finally to the end, hehe
if (!cpumask_equal (Newdev->cpumask, cpumask))---------------(4)
Irq_set_affinity (NEWDEV->IRQ, cpumask);
if (Tick_device_uses_broadcast (Newdev, CPU))--Leave the broadcast tick document
Return
if (Td->mode = = tickdev_mode_periodic)
Tick_setup_periodic (Newdev, 0); ----------------------(5)
Else
Tick_setup_oneshot (Newdev, Handler, next_event); ---Other document description
}
(1) In the multi core environment, each CPU core has its own tick device (which can be called the local tick device), and one of these tick device is chosen to be the global tick device, Responsible for maintaining the jiffies of the whole system. If the tick device is set for the first time and there is currently no global tick device in the system, consider selecting the tick as the global device for system time and jiffies updates. For more details, please refer to the timekeeping documentation.
(2) When the tick device is initially set, the default is set to periodic tick. Of course, this is only the initial setting, in fact, under certain conditions, at the appropriate time, tick device can be switched to other modes, described below.
(3) The old Clockevent equipment will be relegated to the second-line, and its handler modified to Clockevents_handle_noop.
(4) If it is not a local timer, then you also need to call the Irq_set_affinity function, the clockevent interrupt, directed to this CPU.
(5) The Tick_setup_periodic code is as follows (note: Broadcast tick is not considered in the following code analysis):
void Tick_setup_periodic (struct clock_event_device *dev, int broadcast)
{
Tick_set_periodic_handler (Dev, broadcast); ---Set event handler to Tick_handle_periodic
if ((Dev->features & Clock_evt_feat_periodic) &&!tick_broadcast_oneshot_active ()) {
Clockevents_set_mode (Dev, clock_evt_mode_periodic);---------(a)
} else {
unsigned long seq;
Ktime_t Next;
do {
Seq = Read_seqbegin (&jiffies_lock);
Next = Tick_next_period; ---Get the time of the next recurring tick trigger
} while (Read_seqretry (&jiffies_lock, seq));
Clockevents_set_mode (Dev, clock_evt_mode_oneshot); ---Mode setting
for (;;) {
if (!clockevents_program_event (Dev, Next, false))----program Next Clock Event
Return
Next = Ktime_add (Next, Tick_period); ---Calculate the time of the next periodic tick trigger
}
}
}
(a) If the underlying clock event device supports periodic mode, then calling the Clockevents_set_mode setting mode directly is OK.
(b) If the underlying clock event device does not support the periodic mode, and the tick device is currently a periodic tick mode, it is slightly more complicated and requires the one of the clock event device Shot mode to achieve periodic ticks.
Third, the operation of periodic tick
1. From interrupt to clock event handler
In general, the underlying clock event chip driver will register the interrupt, we use ARM generic timer driver as an example, the registered code is as follows:
...
Err = REQUEST_PERCPU_IRQ (PPI, Arch_timer_handler_phys, "Arch_timer", arch_timer_evt);
......
The specific timer interrupt handler is as follows:
static irqreturn_t arch_timer_handler_phys_mem (int irq, void *dev_id)
{
......
Evt->event_handler (EVT);
......
}
That is, the event handler of the clock event device is called in the timer interrupt handler, and the event handler is set to Tick_handle_ in the periodic tick scenario. Periodic.
2, the periodic tick of clock event handler execution analysis
Since each CPU has its own tick device, every tick on each CPU will call the Tick_handle_periodic function to process the task in the periodic tick, as follows:
void Tick_handle_periodic (struct clock_event_device *dev)
{
int cpu = SMP_PROCESSOR_ID ();
Ktime_t Next;
Tick_periodic (CPU); ----the content to be processed in the periodic tick, refer to the following section to describe
if (dev->mode! = clock_evt_mode_oneshot)
Return
Next = Ktime_add (Dev->next_event, Tick_period);----Calculate the time of the next periodic tick trigger
for (;;) {
if (!clockevents_program_event (Dev, Next, false))---set the time of the next clock event trigger
Return
if (Timekeeping_valid_for_hres ())---described in other documents
Tick_periodic (CPU);
Next = Ktime_add (Next, Tick_period);
}
}
If the clock event device that the tick device is part of is working in one shot mode, you also need to do some extra processing to generate periodic ticks.
2, Periodic tick in the content to be processed
The code is as follows:
static void Tick_periodic (int cpu)
{
if (tick_do_timer_cpu = = CPU) {----global tick requires some extra processing
Write_seqlock (&jiffies_lock);
Tick_next_period = Ktime_add (Tick_next_period, tick_period);
Do_timer (1);-------------update jiffies, calculate average load
Write_sequnlock (&jiffies_lock);
Update_wall_time ();----------Update Wall time
}
Update_process_times (User_mode (Get_irq_regs ()))----Update and current process-related content
Profile_tick (cpu_profiling);--related to performance analysis, not detailed
}
Linux time Subsystem (12) Periodic tick