First, preface
Clock is the basis of a timer, and any timer needs to work on a given clock. The kernel maintains a number of clock, the second chapter of this article describes the basic concept of clock and some statically defined POSIX clock. According to the timing characteristics, clock is divided into two types: one is the real-world time concept, the other is to calculate the CPU execution time, the two clocks are described in the third and fourth chapters respectively. From the clock life cycle, can be divided into static and dynamic POSIX clock, static is always present in the kernel, and dynamic clock has the concept of creation and destruction, the fifth chapter of this article describes the dynamic POSIX clock.
Second. Basic Concepts
1. Core Data structure
The so-called clock, in fact, is a timing tool, may be hardware, or software, of course, for POSIX clock, of course, refers to software abstraction. Clock can record the passage of time, which may be real time on the wall or virtual time, such as CPU execution time based on a process or thread. In Linux kernel, the struct k_clock is used to abstract, defined as follows:
struct K_clock {
Int (*clock_getres) (const clockid_t which_clock, struct timespec *tp);
Int (*clock_set) (const clockid_t which_clock, const struct TIMESPEC *tp);
Int (*clock_get) (const clockid_t Which_clock, struct TIMESPEC * tp);
Int (*CLOCK_ADJ) (const clockid_t Which_clock, struct Timex *tx);
Int (*timer_create) (struct k_itimer *timer);
Int (*nsleep) (const clockid_t Which_clock, int flags, struct timespec *, struct timespec __user *);
Long (*nsleep_restart) (struct restart_block *restart_block);
Int (*timer_set) (struct K_itimer * TIMR, int flags, struct Itimerspec * new_setting,
struct Itimerspec * old_setting);
Int (*timer_del) (struct k_itimer * timr);
void (*timer_get) (struct K_itimer * timr, struct itimerspec * cur_setting);
};
Clock as a timing tool of course has timing accuracy, through the Clock_getres function can obtain the clock time accuracy, it should be noted that this accuracy is related to the timer, to the user set timer time-out to the value of the clock accuracy allowed. The Clock_get and Clock_set functions can get and set the current time separately, which is an absolute time value (the absolute time is relative to the time axis, which is relative to the epoch of the timeline) and marks the current point in time. Clock timing can be inaccurate, such as clock based on the system crystal oscillator. On the one hand, the precision of crystal oscillator is limited, and the time accumulated long will cause large error. In addition, the crystal oscillator will be with the use of time, temperature changes and other factors resulting in errors. The CLOCK_ADJ function allows the system to adjust the clock based on the external precise time information. The two member functions of Nsleep and Nsleep_restart allow the process to sleep for a period of time. The TIMER_XXX series functions are related to POSIX interval timer and are described in the POSIX timer documentation.
2. Statically defined Clock
static struct K_clock posix_clocks[max_clocks];
The posix_clocks array defines all the clock supported by the system and is defined as follows:
#define CLOCK_REALTIME 0
#define CLOCK_MONOTONIC 1
#define CLOCK_PROCESS_CPUTIME_ID 2
#define CLOCK_THREAD_CPUTIME_ID 3
#define CLOCK_MONOTONIC_RAW 4
#define CLOCK_REALTIME_COARSE 5
#define Clock_monotonic_coarse 6
#define CLOCK_BOOTTIME 7
#define CLOCK_REALTIME_ALARM 8
#define CLOCK_BOOTTIME_ALARM 9
#define CLOCK_SGI_CYCLE */Hardware specific */
#define Clock_tai 11
#define Max_clocks 16
The POSIX standard defines 4 types of clock,clock_realtime, Clock_monotonic, clock_process_cputime_id, and clock_thread_cputime_id, and the others are Linux Specific. If a clock's timeline is based on CPU uptime, then we call it cpu-time clock. Cpu-time clock is primarily used to timing the execution time of a process or thread, and once the thread (process) is switched, the clock is stopped until the next time the scheduler switches back to that thread (process) execution.
Each specific operating system implementation can define its own clock, for Linux kernel, we define a number of clock. The Clock_monotonic_raw start point is set to 0, and has since been accumulated, and can be set, and will not be adjusted with NTP. The concept of Clock_realtime_coarse and Clock_monotonic_coarse is similar to the concept of clock_realtime and clock_monotonic, except that the accuracy is a rather coarse version. Sometimes, the timer doesn't have to require that much precision, so we can use this clock to get better performance. Clock_boottime and Clock_monotonic are similar, but also monotonous above, when the system initializes the baseline value is 0, but the Clock_boottime calculation system suspend time, that is, Whether it's running or suspend (these are start-up times), Clock_boottime will accumulate timings until the system reset or shutdown.
Clock_realtime_alarm and Clock_boottime_alarm are mainly used for Alarmtimer, this timer is based on RTC, more details please refer to this Site Alarmtimer documentation. Clock_tai is a time of atomic clocks, similar to UTC-based clock_realtime, but without leap second.
The CLOCK_XXX function of the user space passes the parameter of the clock ID, in the kernel state, according to the ID as index in the posix_clocks array can be indexed to the corresponding clock, and then call clock corresponding to the callback function is OK. Of course the basic meaning is this, the concrete implementation is as follows:
static struct K_clock *clockid_to_kclock (const clockid_t ID)
{
if (ID < 0)
Return (ID & clockfd_mask) = = CLOCKFD?
&clock_posix_dynamic: &clock_posix_cpu;
if (id >= max_clocks | |!posix_clocks[id].clock_getres)
return NULL;
Return &posix_clocks[id];
}
The Clockid_to_kclock function is used to correspond the clock ID to the K_CLOCK data structure of the specific POSIX clock. On the Linux platform, Clockid is a data of type int, a total of 32 bits, a height of 29 bits to hold a PID (for cpu-time clock) or FD (dynamically assigned clock), bit 2 to illustrate the Cpu-time Clock is a process clock or thread clock. Bit 1 and bit 0 are used to indicate the type of clock ID: prof=0, virt=1, sched=2, or fd=3.
When the clock ID is less than 0, either the Cpu-time clock, or the dynamically allocated clock, can be judged by the type of clock ID. The Cpu-time clock and dynamically assigned clock are described later.
Third. definitions of various real timeclock
When the system is initialized, the Init_posix_timers function is called to register the various statically defined real time clock. Note: monotonic clock is also a real time clock, the full name is monotonic real Time clock.
1. The definition of real time clock is as follows (the timer related content is not described in this article):
struct K_clock clock_realtime = {
. Clock_getres = Hrtimer_get_res,
. Clock_get = Posix_clock_realtime_get,
. Clock_set = Posix_clock_realtime_set,
. Clock_adj = Posix_clock_realtime_adj,
. Nsleep = Common_nsleep,
. Nsleep_restart = Hrtimer_nanosleep_restart,
};
Real time clock needs to invoke the interface of the timekeeping module to get and set the current value. For a function posix_clock_realtime_get that gets the current time value, it is called the KTIME_GET_REAL_TS function, which is an interface function of the timekeeping module, returning to real times in TIMESPEC format The current value of the clock. The Posix_clock_realtime_set function is primarily called the interface function of the Do_settimeofday timekeeping module. Posix_clock_realtime_adj is to invoke the Do_adjtimex interface function to implement the specific function.
The nanosecond level of sleep is achieved through a high-precision timer, real time clock precision and hrtimer correlation, in particular, can refer to Hrtimer related documents.
2. monotonic clock is defined as follows:
struct K_clock clock_monotonic = {
. Clock_getres = Hrtimer_get_res,
. Clock_get = Posix_ktime_get_ts,
. Nsleep = Common_nsleep,
. Nsleep_restart = Hrtimer_nanosleep_restart,
};
Monotonic clock has no clock_set function and cannot be set. The current value of monotonic clock can be obtained by ktime_get_ts the interface of this timekeeping module. The nanosecond level sleep and the precision get function are the same as the real time clock.
3. monotonic raw clock is defined as follows:
struct K_clock Clock_monotonic_raw = {
. Clock_getres = Hrtimer_get_res,
. Clock_get = Posix_get_monotonic_raw,
};
The Posix_get_monotonic_raw function is to invoke the Timekeeping Module Getrawmonotonic interface function Implementation to get the current time value of the monotonic raw clock. As with monotonic clock, cannot be set. Unlike monotonic clock, the clock does not have a timer-related callback function.
4. Coarse clock
struct K_clock clock_realtime_coarse = {
. Clock_getres = Posix_get_coarse_res,
. Clock_get = Posix_get_realtime_coarse,
};
struct K_clock clock_monotonic_coarse = {
. Clock_getres = Posix_get_coarse_res,
. Clock_get = Posix_get_monotonic_coarse,
};
The accuracy of the two clock is related to tick, and the ktime_low_res definition is the nanosecond value of the tick. The Clock_get function calls Current_kernel_time and Get_monotonic_coarse, respectively, to get the value of the current point in time.
Clock_boottime and Clock_tai clock implementation is very simple, you can read the code on their own OK.
Fourth, Cpu-time clock
1. Overview
From the perspective of user space, there are two applications of Cpu-time clock:
(1) Call the CLOCK_XXX function and pass clock_process_cputime_id or clock_thread_cputime_id to the function
(2) Call the Clock_getcpuclockid or Pthread_getcpuclockid function to get the clock ID of the specified process or thread, and then call the CLOCK_XXX function and pass the clock ID parameter
For the first scenario, the Init_posix_cpu_timers function is called to register the statically defined Cpu-time clock when the system initializes. For the second scenario, the kernel statically defines a clock_posix_cpu clock to address this requirement.
2. Cpu-time clock for the specified process or thread
The kernel statically defines a clock as follows (minus the timer's callback function):
struct K_clock clock_posix_cpu = {
. Clock_getres = Posix_cpu_clock_getres,
. Clock_set = Posix_cpu_clock_set,
. Clock_get = Posix_cpu_clock_get,
. Nsleep = Posix_cpu_nsleep,
. Nsleep_restart = Posix_cpu_nsleep_restart,
};
(1) Obtaining accuracy information
static int posix_cpu_clock_getres (const clockid_t which_clock, struct timespec *tp)
{
int error = Check_clock (which_clock);--------parameter check
if (!error) {
tp->tv_sec = 0;
Tp->tv_nsec = ((nsec_per_sec + HZ-1)/HZ);
if (Cpuclock_which (which_clock) = = cpuclock_sched) {
Tp->tv_nsec = 1;
}
}
return error;
}
The execution logic of the function is divided into two parts, part is the parameter check, and part is the return precision. The parameter check needs to be checked including:
(a) The high 29 bits in the clock ID contain the PID, and the code to get the PID is as follows:
#define CPUCLOCK_PID (Clock) ((pid_t) ~ ((clock) >> 3))
From the code, it is not actually to put the PID to a height of 29 bit, but to save the inverse code to a high of 29 bit. Why save anti-code? To make sure that the clock ID is a negative number (the MSB is 1), do you remember the implementation of Clockid_to_kclock? To get the precision of the clock ID, make sure that the PID's task exists
(b) If the clock ID is a process-dependent (called Clock_getcpuclockid), then the process ID should be a real process ID. In Linux kernel, the PID is actually the process ID of the thread id,posix standard, that is, the PID is a thread group ID in the kernel. Therefore, the so-called "real process id" means that the thread's ID (PID) and Tgid, the PID identifies the thread is the thread group leader. Of course, is to obtain the accuracy, in fact, the requirement is not so strict, perhaps the PID identified by the thread leader will exit, so it is actually required that the PID identification of the task has the thread group leader OK. (It may be wrong to understand, TODO)
(c) If the clock ID is a thread-dependent (called Pthread_getcpuclockid), then the caller must be part of a process (the thread group) with the thread (the one indicated in the clock ID).
The code logic that returns the precision section is simple, for Prof and virt types of cpu-time clock, whose accuracy is tick, for the sched type, the accuracy is 1ns.
(2) Get the current time value
Similarly, the first need to get the value of the PID from the clock ID, and then according to the PID value to get the corresponding task STURCT, if the PID equals 0, then do not need to bother to find. After you get the task struct, you can call the Posix_cpu_clock_get_task function to get the time value:
static int posix_cpu_clock_get_task (struct task_struct *tsk, const clockid_t Which_clock,
struct Timespec *tp)
{
int err =-einval;
unsigned long long RTN;
if (Cpuclock_perthread (Which_clock)) {---per thread CPU clock
if (Same_thread_group (tsk, current))---must be the same thread group as the caller, that is, the same process
Err = Cpu_clock_sample (Which_clock, tsk, &RTN);
} else {
if (tsk = = Current | | Thread_group_leader (TSK))---CPU clock of the process
Err = Cpu_clock_sample_group (Which_clock, tsk, &RTN);
}
if (!err)
Sample_to_timespec (Which_clock, RTN, TP); ---Assign a value to the return value
return err;
}
There is still a checksum problem, that is, whether the caller is allowed to get the Cpu-time clock for that task. For a process, only the caller process is allowed to get its own cpu-time clock, in a multithreaded environment, the main thread (threaded group leader) can get the Cpu-time clock information for the entire process. For a per-thread operation, the same thread group, that is the same process, must be the same as the caller.
(a) Get the clock information for a thread
static int cpu_clock_sample (const clockid_t which_clock, struct task_struct *p,
unsigned long long *sample)
{
Switch (Cpuclock_which (which_clock)) {
Default
Return-einval;
Case CPUCLOCK_PROF:
*sample = Prof_ticks (p);-----Get the task in user space plus the execution time in kernel spaces
Break
Case Cpuclock_virt:
*sample = Virt_ticks (p);-----get the task's execution time in user space
Break
Case cpuclock_sched:
*sample = Task_sched_runtime (p);----CPU clock associated with the scheduler
Break
}
return 0;
}
Calculating the execution time of a process or thread on the CPU is a very annoying thing, on the one hand want high precision, on the other hand do not want to calculate a large amount. So, in fact, there are three kinds of cpu-time clock, cpuclock_prof and cpuclock_virt are relatively rough estimates of CPU execution time clock, it works on the periodic tick in the process of CPU timing statistics, If the tick is user-state (the timer interrupts the execution of the user-state program), the entire tick time is the user-state execution time of the process. If the tick is a kernel state, and the user program makes a system call and falls into the kernel, the entire tick time is the system state execution time of the process.
Cpuclock_sched clock and the above method is not the same, its precision is nanosecond level, is the scheduler on the calculation process time. The specific calculation method is left to the scheduler article to describe it again.
(b) The concept of the Cpu_clock_sample_group function is similar, but it is just the time to count all threads on a process.
3, clock_process_cputime_id type of CLOCK
struct K_clock process = {
. Clock_getres = Process_cpu_clock_getres,
. Clock_get = Process_cpu_clock_get,
. Nsleep = Process_cpu_nsleep,
. Nsleep_restart = Process_cpu_nsleep_restart,
};
Process_cpu_clock_getres is used to obtain the time precision, which is actually called Posix_cpu_clock_getres (Process_clock, TP) to complete. Process_cpu_clock_get is used to get the current time value, which is actually done by calling Posix_cpu_clock_get. The POSIX_CPU_CLOCK_XXX function is described in the previous section.
4, clock_thread_cputime_id type of CLOCK
Very simple, we learn it by ourselves.
Fifth. Dynamic allocation of clock
1, source by
Some hardware provides the ability to timing, can be implemented as a POSIX clock, and these hardware, like a USB device can be hot-pluggable, which means that the POSIX clock cannot be statically defined. In addition, in addition to the standard timer and clock-related operations, these clocking-capable hardware also requires some other similar character-device interface control interfaces, which, driven by such requirements, provide the dynamic POSIX clock to the kernel.
2. Dynamic POSIX clock
Each dynamic POSIX clock in the system is abstracted with a struct posix_clock, as follows:
struct Posix_clock {
struct posix_clock_operations ops;--------------(1)
struct Cdev cdev;----------------------(2)
struct Kref kref;
struct Rw_semaphore Rwsem;
BOOL zombie;------------------------(3)
void (*release) (struct posix_clock *clk);-------------(4)
};
(1) OPS is the set of operating functions of the dynamic POSIX clock, divided into two groups, one timer (e.g. timer_create, timer_delete, etc.) and clock operations (e.g. Clock_gettime, Clock_settime, etc.), the other is the normal character device operation function (for example: Open, read, write, etc.).
(2) The dynamic POSIX clock corresponds to the CDEV data structure. There is an owner in the struct posix_clock_operations, and in fact there is an owner member in Cdev that points to Moudle, which seems to be repeating the definition. The same question also exists with KREF members, because there are kobject members in Cdev, Kobject abstracts the kernel's most basic object categories, including names, reference counts, and so on, so I think as long as struct Posix_clock includes Cdev members, struct The owner in Posix_clock_operations and the kref in struct posix_clock should not exist.
(3) Zombie record the status of the underlying hardware, for hotplug peripherals, it is possible that the hardware is removed. Rwsem used to protect this state information
(4) The release function is called when reference count equals 0 to release the resource occupied by the dynamic POSIX clock.
3. Registration and cancellation
The underlying clocking-capable hardware driver can call Posix_clock_register and posix_clock_unregister to register or unregister a POSIX clock with the following registration code:
int Posix_clock_register (struct posix_clock *clk, dev_t devid)
{
int err;
Kref_init (&CLK->KREF);
Init_rwsem (&clk->rwsem);
Cdev_init (&clk->cdev, &posix_clock_file_operations); a collection of operation functions for the-----VFS interface
Clk->cdev.owner = clk->ops.owner;
Err = Cdev_add (&clk->cdev, Devid, 1);
return err;
}
The set of operating functions of the VFS interface is very simple, essentially a set of character device manipulation functions on a struct posix_clock_operations. In this way, user-space programs can perform device operations with standard file descriptors.
4. Clock and Timer interface
The clock ID can be specified through the clock_xxx or Timer_xxx function, and for dynamic POSIX clock you can generate a dynamic POSIX clock ID by doing the following:
#define FD_TO_CLOCKID (FD) ((~ (clockid_t) (FD) << 3) | CLOCKFD)
Where FD is the clock-capable hardware that is opened through the device node. In the kernel state, the clock ID is converted to a clockid_to_kclock operation.
static struct K_clock *clockid_to_kclock (const clockid_t ID)
{
if (ID < 0)
Return (ID & clockfd_mask) = = CLOCKFD?
&clock_posix_dynamic: &clock_posix_cpu;
......
}
Clock_posix_dynamic can convert the dynamic POSIX clock ID to the corresponding posix_clock, and then call time and clock-related functions on the struct posix_clock_operations.
Linux time Subsystem (v) POSIX Clock