Time || Space? Generation of Time and Dates in C Language:Generation of Space
(The sums look off-by-one because of the sign bit. You can learn more
以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。
(The sums look off-by-one because of the sign bit. You can learn more about the IEEE754 floating-point formats that give rise to these numbers at [FP]. They originated on the VAXen that were the workhorse machines of Unix in the early 1980s and are now implemented in hardware on Intel and ARM architectures, among many other places.)
When the Unix time API first had to represent subsecond precision, microsecond resolution was required to represent times comparable to a machine cycle.
The problem was that a microsecond count requires 20 bits. A microsecond-precision time with 32 bits of integer part is on the far edge of what a double-precision float can hold:
seconds: 32 bits microseconds: 20 bits
total: 52 bits
That would barely have fit and seemed likely to be a bit flaky in actual use due to floating point rounding. Doing any math with it would lose precision quickly. Trying to go finer-grained to nanosecond resolution would have required 11 more bits that weren’t there in double precision.
Thus, quad-precision floating point would have been required for even 32-bit times. Given the high cost of FPU computation at the time and the near-waste of 64 bits of expensive storage, this took float representation out of the running.
This is why fractional times are normally represented by two-element structures in which the first member is seconds since the epoch and the second is an integral offset in sub-second units - originally microseconds.
The original subsecond-precision time structure was associated with the gettimeofday(2) system call in 4.2BSD Unix, dating from the 1980s. It looks like this:
struct timeval { time_t tv_sec; /* seconds / suseconds_t tv_usec; / microseconds */ };
Note the microsecond resolution. The newer POSIX time functions use this:
struct timespec { time_t tv_sec; /* seconds / long tv_nsec; / nanoseconds */ };
(No, that’s not a paste error. The struct timespec members really do have tv_ prefixes on their names. This seems to have been someone’s attempt to reduces required code changes. It was probably a bad idea.)
This has nanosecond resolution. The change is related to the tremendous increase in machine speeds since the 1980s, and the correspondingly increased resolution of hardware clocks. While it is conceivable that in the future we may see further generations of these structures in which the subsecond offset is in picoseconds or smaller units, some breakthrough in fundamental physics would be required first - at time of writing in 2014 processor cycle times seem to be topping out in the roughly 0.1ns range due to quantum-mechanical limits on the construction of electron logic.
Although the timeval and timespec structures are very useful for manipulating high-precision timestamps, there are unfortunately no standard functions for performing even the most basic arithmetic on them, so you’re often left to roll your own.
Another structure, used for interval timers and describing a time interval with nanosecond precision, looks like this:
struct itimerspec { struct timespec it_interval; struct timespec it_value; };
While the C time API tends to shape the time APIs presented by higher-level languages implemented in C, these subsecond-precision structures are one area where signs of revolt have long been visible. Python has chosen to instead accept the minor problems of using a floating-point scalar representation; Ruby uses integral nanoseconds since the Unix epoch. Perl uses a mixture, a BSD-like seconds/microseconds pair in some functions and floating-point time since the Unix epoch in others.
On today’s true 64-bit machines with relatively inexpensive floating point the natural float representation of time would look like this:
seconds: 64 bits fractional seconds: 48 bits
total: 112 bits
offering sub-picosecond resolution with plenty of headroom to avoid serious roundoff issues. So the scripting languages are heading in a direction that the C API could in theory eventually follow.
The Go language, designed by old Unix hands abd first released in 2009, provides a potential model for future language libraries under Unix. Describing it in detail is beyond the scope of this document (a reference can be found at [GO-TIME]) but its time representation makes an instructive contrast with C’s. Go uses 64-bit integers to count nanoseconds since January 1, year 1, 00:00:00.000000000 UTC with no leap-second correction. Every time includes a timezone field, intended to be used to compute a time zone offset through the IANA database.
The simplest portions of the Unix time API are clock and delay functions that only deal with the basic second- and fractional-second-oriented time structures.
First we’ll survey the delay and interval-timing functions, then the clock functions. If you are mainly interested in clock/calendar/timezone issues, you can safely skip this section.
#include <unistd.h>
unsigned int sleep(unsigned int seconds); int usleep(useconds_t usec);
#include <time.h>
int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);
These functions are the poster children for the mess created by having multiple time representations. All three simply delay execution of the calling program for some span of time; what differs is how that time is specified.
The oldest, sleep(3), takes an integer argument in seconds; usleep(3) takes microseconds, and nanosleep(3) takes a struct timespec with nanosecond resolution. All three may be interrupted by an unignored signal. The nanosleep(2) function takes a second timespec which is filled with time remaining if it was interrupted.
The usleep(3) call originated in BSD Unix and, though it was carried forward in POSIX, was deprecated in favor of nanosleep(2) in POSIX.1-2001/SUSv3 and then removed entirely in POSIX.1-2008/SUSv4. It’s probably best not to count on it being portable.
Of these three functions, only nanosleep(3) is guaranteed not to have any interaction with signals (the other two may be implemented using some signal and a hidden handler). It is is thread- and signal-safe.
The sleep(3) function is required by POSIX to be async-signal-safe and thread-safe. The thread-safe requirement means sleep(3) can’t be signal-based in multithreaded programs, but can be in single-threaded programs.
These functions should not be mixed with the ones in the next group.
#include <unistd.h>
unsigned alarm(unsigned seconds); useconds_t ualarm(useconds_t usecs, useconds_t interval);
#include <sys/time.h>
int getitimer(int which, struct itimerval *value); int setitimer(int which, const struct itimerval *restrict value, struct itimerval *restrict ovalue);
The alarm functions are another case of call proliferation due to multiple time units. The oldest, alarm(2|3), sends SIGALRM after a specified number of seconds; ualarm(3) sends SIGALRM after a specified number of microseconds (and a second argument, if nonzero, causes SIGALRM to be sent at regular intervals afterwards).
The getitimer() and setitimer() functions come from the Single UNIX Specification before 1996, when SUSv2 was merged with POSIX.1-1996 to develop POSIX.1-2001/SUSv3. They are now deprecated in favor of the POSIX timer_* calls in the next section.
These have three advantages over the traditional alarm calls: (1) they use timeval structures, so have microsecond resolution; (2) they can trigger the sending not just of SIGALRM but of more specialized profiling signals; and (3) they give you options about what derivative of the system hardware clock you want to use.
The gory details about the interval timer functions are best learned from their manual pages. For our purposes, the important thing to cover is how they fit with the rest of the time and calendar API. Bluntly, that is not very well.
The manual pages are full of ominous "unspecified behavior" warnings if you mix uses of the sleep group, the alarm group, and the itimer group in the same program. The reason for this is historical.
The sleep and alarm function groups developed by accretion in early Unixes. The sleep(3) group was often implemented with alarm(3) and a specialized SIGALRM handler; likewise for usleep(3) and ualarm(3).
The itimer functions were the result of a later effort to specify a more general facility that could subsume these, starting from a clean sheet of paper. They were declared obsolete in POSIX.1-2001/SUSv3 in favor of the timer_* group.
Thus, it is possible that your system’s implementation of sleep and alarm functions is a thin layer over POSIX itimer calls (or, possibly, the timer_* group documented next) using the ITIMER_REAL clock. When that isn’t the case, legacy implementations of the sleep and alarm calls won’t necessarily play well with interval timers - in particular SIGALRMs might be flying around when you don’t expect it, or setting an implied signal handler through the sleep functions might be interfered with by setitimer(2|3).
It’s best not to go there. Heed the warnings and don’t mix up these function groups.
#include <signal.h> #include <time.h>
int timer_create(clockid_t clockid, struct sigevent *restrict evp, timer_t *restrict timerid); int timer_delete(timer_t timerid); int timer_getoverrun(timer_t timerid); int timer_gettime(timer_t timerid, struct itimerspec *value); int timer_settime(timer_t timerid, int flags, const struct itimerspec *restrict value, struct itimerspec *restrict ovalue);
There is a group of POSIX functions timer_create(2|3), timer_delete(2|3), timer_settime(2|3), timer_gettime(2|3), and timer_getoverrun(2|3) that can be used to set per-process interval timers even more flexibly than the itimer group. In particular, they allow setting a function-call hook to be called directly at the end of an interval without going through either an explicit or implicit signal.
These functions were introduced in POSIX.1b-1993. After POSIX and SUSv2 merged in 2001 getitimer(2|3) and setitimer(2|3) were declared obsolete.
Neither the native Linux nor POSIX man pages document interactions with the older calls. A prudent programmer should assume that the alarm and sleep group is implemented in terms of one of the two groups of newer POSIX interval-timer functions, and not mix any of them.
If you want to avoid interval variability due to NTP frequency adjustments, you probably want to use these with CLOCK_MONOTONIC.
#include <sys/timerfd.h>
int timerfd_create(int clockid, int flags); int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value); int timerfd_gettime(int fd, struct itimerspec *curr_value);
These functions are Linux-specific (not standardized), offering a way to monitor timer expiration via selecting or polling on a file descriptor rather than via signal handler.
They are mentioned here for completeness, but should be avoided in code intended for portability. Consult the Linux manual pages for details.
A full discussion of the select(2) and pselect(2) system calls would be beyond the scope of this document, as they are not used for time/calendar programming. We mention them only to note two points:
select(2) takes a struct timeval argument, reflecting its origins in BSD Unix. The resolution to which you can specify a timeout is thus limited to a microsecond.
The later pselect(2) call takes a struct timespec.
These are functions for interacting with the system clock that avoid timezone issues by returning time in seconds since the Unix epoch.
#include <time.h>
time_t time(time_t *tloc);
The most basic function is time(3), which returns the current Unix second. All the previous caveats about leap-second discontinuities and tiny variabilities due to NTP correction apply.
#include <time.h>
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。