timespec_to_jiffies() 是用户空间到 jiffies 的转换。timespec 的时间结构是由 秒 和 纳秒组成:
[C++] 纯文本查看 复制代码 struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
最能直接想到的转换可能会依据以下的公式:(tv_sec * nsec_per_sec + tv_nsec) / nsec_per_jiffy 上面,nsec_per_sec 表示每秒的纳秒数,也就是 1,000,000,000 ;nsec_per_jiffy 表示每个 jiffy 里有多少个纳秒。但是,在内核里,为了提高运算的精度,却不是直接这么算的,会经过一道稍微复杂的转换工序。转换过程会通过一种称为 Scaled math (操作数尺寸调整运算法)的方法来实现,这个方法的原理可以先通过 include/linux/jiffies.h 里的一段注释获得:
Scaled math??? What is that?
Scaled math is a way to do integer math on values that would,otherwise, either overflow, underflow, or cause undesired div instructions to appear in the execution path. In short, we "scale" up the operands so they take more bits (more precision, less underflow), do the desired operation and then "scale" the result back by the same amount. If we do the scaling by shifting we avoid the costly mpy and the dastardly div instructions.
Suppose, for example, we want to convert from seconds to jiffies where jiffies is defined in nanoseconds as NSEC_PER_JIFFIE. The simple math is: jiff = (sec * NSEC_PER_SEC) / NSEC_PER_JIFFIE; We observe that (NSEC_PER_SEC / NSEC_PER_JIFFIE) is a constant which we might calculate at compile time, however, the result will only have about 3-4 bits of precision (less for smaller values of HZ). So, we scale as follows:
jiff = (sec) * (NSEC_PER_SEC / NSEC_PER_JIFFIE);
jiff = ((sec) * ((NSEC_PER_SEC * SCALE)/ NSEC_PER_JIFFIE)) / SCALE;
Then we make SCALE a power of two so:
jiff = ((sec) * ((NSEC_PER_SEC << SCALE)/ NSEC_PER_JIFFIE)) >> SCALE;
Now we define:
#define SEC_CONV = ((NSEC_PER_SEC << SCALE)/ NSEC_PER_JIFFIE))
jiff = (sec * SEC_CONV) >> SCALE;
中心思想是,为了提高运算精度,可以先将操作数的尺寸进行扩展,最后再将尺寸还原回去。关于具体的运算原理可以参考:http://www.groad.net/bbs/read.php?tid-3328.html
先来看 timespec_to_jiffies() 的代码:
[C++] 纯文本查看 复制代码
/*
* The TICK_NSEC - 1 rounds up the value to the next resolution. Note
* that a remainder subtract here would not do the right thing as the
* resolution values don't fall on second boundries. I.e. the line:
* nsec -= nsec % TICK_NSEC; is NOT a correct resolution rounding.
*
* Rather, we just shift the bits off the right.
*
* The >> (NSEC_JIFFIE_SC - SEC_JIFFIE_SC) converts the scaled nsec
* value to a scaled second value.
*/
unsigned long
timespec_to_jiffies(const struct timespec *value)
{
unsigned long sec = value->tv_sec;
long nsec = value->tv_nsec + TICK_NSEC - 1;
if (sec >= MAX_SEC_IN_JIFFIES){
sec = MAX_SEC_IN_JIFFIES;
nsec = 0;
}
return (((u64)sec * SEC_CONVERSION) +
(((u64)nsec * NSEC_CONVERSION) >>
(NSEC_JIFFIE_SC - SEC_JIFFIE_SC))) >> SEC_JIFFIE_SC;
}
上面代码中, TICK_NSEC - 1 是为了向上提高一个精度。它和 A/B 转换为 (A + (A%B)/2)/B 是一样的道理,比如 (115/31 = 3 余22),实际上我们更希望是得到的商为 4 (余数比除数的一半还大),这样会更精确些,所以可以 (115 + 31-1)/31 = 4 余 21,但这里只取商 4 ,余数省略。
现在再看 MAX_SEC_IN_JIFFIES 宏的定义:
[C++] 纯文本查看 复制代码 #if BITS_PER_LONG < 64
# define MAX_SEC_IN_JIFFIES \
(long)((u64)((u64)MAX_JIFFY_OFFSET * TICK_NSEC) / NSEC_PER_SEC)
#else /* take care of overflow on 64 bits machines */
# define MAX_SEC_IN_JIFFIES \
(SH_DIV((MAX_JIFFY_OFFSET >> SEC_JIFFIE_SC) * TICK_NSEC, NSEC_PER_SEC, 1) - 1)
#endif
上面定义中 BITS_PER_LONG 表示 32 位平台或 64 位平台。如果该值小于 64 ,那么自然是 32 位平台。我们这里也是分析在 32 位平台上的情况,所以 MAX_SEC_IN_JIFFIES 值取: (long)((u64)((u64)MAX_JIFFY_OFFSET * TICK_NSEC) / NSEC_PER_SEC) 上面,TICK_NSEC 的值是 999848 ,这是一个被系统初始化的值,它可以产生的时钟信号频率大约为 1000.15Hz 的时钟频率(该值的设置见:http://www.groad.net/bbs/read.php?tid-3329.html)。
NSEC_PER_SEC 表示每秒中的纳秒数,那自然是:
[C++] 纯文本查看 复制代码 #define NSEC_PER_SEC 1000000000L
接下来看 MAX_JIFFY_OFFSET 是怎么定义的:
[C++] 纯文本查看 复制代码 /*
* Change timeval to jiffies, trying to avoid the
* most obvious overflows..
*
* And some not so obvious.
*
* Note that we don't want to return LONG_MAX, because
* for various timeout reasons we often end up having
* to wait "jiffies+1" in order to guarantee that we wait
* at _least_ "jiffies" - so "jiffies+1" had better still
* be positive.
*/
#define MAX_JIFFY_OFFSET ((LONG_MAX >> 1)-1)
而 LONG_MAX 的定义如下:
[C++] 纯文本查看 复制代码 #define LONG_MAX ((long)(~0UL>>1))
也就是,LONG_MAX 的值为 0x7FFFFFFF ;那么 MAX_JIFFY_OFFSET 的值就是 0x3FFFFFFE 。这个值有什么特殊之处?我们先将此值代入到 MAX_SEC_IN_JIFFIES 的定义中去运算,最后算得的 MAX_SEC_IN_JIFFIES 的值为 1073578 。这个值的单位是秒,换算成天数即为 12.42 天,这个天数也就是 24.85 天的一半。关于 24.85 天的来历见 http://www.groad.net/bbs/read.php?tid-3067-fpage-2.html
这里可以想象,MAX_JIFFY_OFFSET 这个值是从 24.85 这个值倒推得来。
回到 timespec_to_jiffies() 函数中来,
[C++] 纯文本查看 复制代码 if (sec >= MAX_SEC_IN_JIFFIES){
sec = MAX_SEC_IN_JIFFIES;
nsec = 0;
}
上面的代码意思是,如果指定的秒数大于 1073578S 也就是大于 12.42 天,那么我们就将 sec 强制为 1073578 秒。
下面接下来看 SEC_CONVERSION 和 NSEC_CONVERSION 的定义:
[C++] 纯文本查看 复制代码 #define SEC_CONVERSION ((unsigned long)((((u64)NSEC_PER_SEC << SEC_JIFFIE_SC) +\
TICK_NSEC -1) / (u64)TICK_NSEC))
#define NSEC_CONVERSION ((unsigned long)((((u64)1 << NSEC_JIFFIE_SC) +\
TICK_NSEC -1) / (u64)TICK_NSEC))
上面又涉及到两个新的量:SEC_JIFFIE_SC 和 NSEC_JIFFIE_SC 。顺着看一下它们的定义:
[C++] 纯文本查看 复制代码 #define SEC_JIFFIE_SC (31 - SHIFT_HZ)
#if !((((NSEC_PER_SEC << 2) / TICK_NSEC) << (SEC_JIFFIE_SC - 2)) & 0x80000000)
#undef SEC_JIFFIE_SC
#define SEC_JIFFIE_SC (32 - SHIFT_HZ)
#endif
#define NSEC_JIFFIE_SC (SEC_JIFFIE_SC + 29)
#define USEC_JIFFIE_SC (SEC_JIFFIE_SC + 19)
上面,SHIFT_HZ 的定义为:
[C++] 纯文本查看 复制代码 /*
* The following defines establish the engineering parameters of the PLL
* model. The HZ variable establishes the timer interrupt frequency, 100 Hz
* for the SunOS kernel, 256 Hz for the Ultrix kernel and 1024 Hz for the
* OSF/1 kernel. The SHIFT_HZ define expresses the same value as the
* nearest power of two in order to avoid hardware multiply operations.
*/
#if HZ >= 12 && HZ < 24
# define SHIFT_HZ 4
#elif HZ >= 24 && HZ < 48
# define SHIFT_HZ 5
#elif HZ >= 48 && HZ < 96
# define SHIFT_HZ 6
#elif HZ >= 96 && HZ < 192
# define SHIFT_HZ 7
#elif HZ >= 192 && HZ < 384
# define SHIFT_HZ 8
#elif HZ >= 384 && HZ < 768
# define SHIFT_HZ 9
#elif HZ >= 768 && HZ < 1536
# define SHIFT_HZ 10
#elif HZ >= 1536 && HZ < 3072
# define SHIFT_HZ 11
#elif HZ >= 3072 && HZ < 6144
# define SHIFT_HZ 12
#elif HZ >= 6144 && HZ < 12288
# define SHIFT_HZ 13
#else
# error Invalid value of HZ.
#endif
SHIFT_HZ 是根据 HZ 的范围来定义的。一般的,个人桌面计算机上,PC 值为 1000 ,那么这里的 SHIFT_HZ 也就会定义为 10 。
首先会定义 SEC_JIFFIE_SC 的值为 (31 - SHIFT_HZ),也就是 21 。但接下来会进行判断:
[C++] 纯文本查看 复制代码 #if !((((NSEC_PER_SEC << 2) / TICK_NSEC) << (SEC_JIFFIE_SC - 2)) & 0x80000000)
上面 0x80000000 的十进制也就是 2147483648 ,如果以 jiffy 为单位,那么它就大约是 24.85 天。也就是说,如果先定义的 SEC_JIFFIE_SC 的值为 21,那么 ((((NSEC_PER_SEC << 2) / TICK_NSEC) << (SEC_JIFFIE_SC - 2)) & 0x80000000) 这个值就会为 0 ,也就是说它不够 24.85 天,所以 SEC_JIFFIE_SC 要重新定义。经过重新定义后,SEC_JIFFIE_SC 的值就会变成是 22 。另外,上面为什么先左移 2 又再左移 (SEC_JIFFIE_SC - 2) 呢?那么它和直接左移 SEC_JIFFIE_SC 又有什么区别的?这应该也是从提高精度上面进行考虑(见上面注释中的 操作数尺寸调整运算法 );移 2 而不是别的数,也是从溢出的角度来考虑选取的。
根据上面注释我们知道 SEC_CONVERSION 和 NSEC_CONVERSION 都只是转换过程中为了提高精度所需的一个中间数。
最后在 timespec_to_jiffies() 里通过 return 返回了所需要转换的值。 |