The current time is frequently used in the program. For example, when an HTTP request is processed, two calls to gettimeofday calculate the difference value to calculate the number of seconds consumed by the request. This call is everywhere, so we need to know in detail what the gettimeofday function has done? Does the One-MS clock interrupt processing in the kernel support the TV _usec field to achieve microsecond precision? Does it cost the same in the i386/x86_64 architecture? If the system is busy and frequently called, is there a problem?
Gettimeofday is a function provided by the C library (not called by the system). It encapsulates the sys_gettimeofday system call in the kernel.
However, in addition to common system calls in the x86_64 architecture, the kernel also provides the sysenter and vsyscall methods to obtain kernel-state data. Currently, most of the operating systems we use are x86_64 systems. If we use strace command tracking, we will find that the gettimeofday command does not actually execute the system call (i386 system will have), because: in the x86_64 system, the gettimeofday system call is implemented using vsyscall. Specifically, a shared memory page is created. It is in the kernel state and its data is maintained by the kernel. However, the user State also has the permission to access this kernel page, if gettimeofday is not interrupted, the system time is obtained.
Next, I will answer the above four questions in detail.
1. What does gettimeofday do?
It combines the wall time stored in the kernel with jiffies and then returns it to the user. What are the wall time and jiffies? 1. The wall time is the actual time (since January 1 ), it is powered by the battery of our motherboard (anyone who has installed a PC knows) RTC unit storage, so that even if the machine is powered off, there is no need to reset it. When the operating system is started, the RTC will be used to initialize the wall time. Then, the kernel will maintain the wall time according to jiffies within a certain precision. 2. jiffies is the time after the operating system is started. Its unit is the number of beats. In some architecture, one cycle is 10 ms, but in our commonly used x86 system, one cycle is 1 ms. That is to say, the global variable jiffies stores the total number of milliseconds since the operating system was started. Let's take a look at how gettimeofday works. First, it calls the sys_gettimeofday system call.
asmlinkage long sys_gettimeofday(struct timeval __user *tv, struct timezone __user *tz){if (likely(tv != NULL)) {struct timeval ktv;do_gettimeofday(&ktv);if (copy_to_user(tv, &ktv, sizeof(ktv)))return -EFAULT;}if (unlikely(tz != NULL)) {if (copy_to_user(tz, &sys_tz, sizeof(sys_tz)))return -EFAULT;}return 0;}
As you can see, it calls the do_gettimeofday function to get the current time and store it on the local variable KTV, and then calls copy_to_user to copy the result to the user space. Each system has its own implementation. Here I will briefly list the implementation of do_gettimeofday IN THE x86_64 system:
void do_gettimeofday(struct timeval *tv){unsigned long seq, t; unsigned int sec, usec;do {seq = read_seqbegin(&xtime_lock);sec = xtime.tv_sec;usec = xtime.tv_nsec / 1000;/* i386 does some correction here to keep the clock monotonous even when ntpd is fixing drift. But they didn't work for me, there is a non monotonic clock anyways with ntp. I dropped all corrections now until a real solution can be found. Note when you fix it here you need to do the same in arch/x86_64/kernel/vsyscall.c and export all needed variables in vmlinux.lds. -AK */ t = (jiffies - wall_jiffies) * (1000000L / HZ) +do_gettimeoffset();usec += t;} while (read_seqretry(&xtime_lock, seq));tv->tv_sec = sec + usec / 1000000;tv->tv_usec = usec % 1000000;}
As you can see, the xtime is corrected by jiffies and then returned to the user. The maintenance update frequency of the xtime variable and jiffies determines the time accuracy. As mentioned above, the clock interruption is handled every 10 or 1 ms. Is the accuracy only 1 ms? Continue.
2. Does a 1 ms clock interruption in the kernel support the TV _usec field to achieve microsecond precision?
Yes, because this time will also be maintained by the high precision Event timer. This module will handle microsecond-level interruptions and update the xtime and jiffies variables. Let's take a look at the maintenance code in the x86_64 architecture:
static struct irqaction irq0 = {timer_interrupt, SA_INTERRUPT, CPU_MASK_NONE, "timer", NULL, NULL};
This timer_interrupt function will handle hpet time interruptions to update the xtime variable.
3. Is the call cost the same for all operating systems? If the system is busy, is there a problem with multiple calls within 1 millisecond?
As mentioned above, for x86_64 systems, this is a virtual system that calls vsyscall! Therefore, it does not need to be interrupted! The speed is fast and the cost is low. The call cost is less than one microsecond!
For the i386 system, this is the system call! The simplest system call has an unavoidable cost: falling into the kernel state. When we call gettimeofday, it will send soft interruptions to the kernel and then fall into the kernel state. At this time, the kernel should at least do the following: process Soft Interrupt, save all register values, copy function parameters from user State to kernel state, execute, and copy results to user State. These costs must be at least 1 microsecond!
4. Two points worth mentioning about jiffies
Let's take a look at its definition:
volatile unsigned long __jiffies;
Only two points.
1. It uses a rare keyword volatile in C language, which is used to solve the concurrency problem. C language compilers like optimization. It is unclear that a variable may be modified concurrently. For example, the preceding jiffies variable is 0 first, if one CPU changes its value to 1 first, and the other CPU reads its value, for example, _ jiffies = 0; while (_ jiffies = 1 ), in the C code of the kernel, if the volatile field is not added, the loop body in the second CPU may not be executed, because when the C compiler optimizes the code, the generated assembly code may not read the memory every time! It sets the Variable _ jiffies to 0 based on the Code and keeps using it! After the volatile field is added, the compiler is required to read the value in the memory each time _ jiffies is used.
2. It is of the unsigned long type. In a 32-bit system, the maximum value is less than 4.3 billion, and the maximum value is reached within 49 days after the system is started. Then, it is cleared and started again. How does one solve the Rotation Problem When jiffies reaches the maximum value? Or in other words, we need to ensure that when the jiffies is switched to a small positive number, for example, 1, it is larger than the big positive number dozens of seconds ago, such as 4294967290, to reach jiffies (1)> jiffies (4294967290.
The kernel is solved by defining two macros:
#define time_after(a,b)\(typecheck(unsigned long, a) && \ typecheck(unsigned long, b) && \ ((long)(b) - (long)(a) < 0))#define time_before(a,b)time_after(b,a)
Clever design! After converting unsigned long to the long type, the result is reduced to jiffies (1)> jiffies (4294967290), which solves the rotary problem of jiffies and likes one.