# 彙整學員們的作業成果
contributed by <`gyes00205`>
###### tags: `linux2021`
## 題目
考慮函式 `func` 接受一個 16 位元無號整數 $N$ ,並回傳小於或等於 $N$ 的 power-of-2
```cpp
uint16_t func(uint16_t N) {
/* change all right side bits to 1 */
N |= N >> 1;
N |= N >> 2;
N |= N >> 4;
N |= N >> 8;
return (N + 1) >> 1;
}
```
## 解釋運作原理:
首先將遇到的第一個值為 1 的 bit 的右邊所有 bit 都設為 1,最後 +1 並往右移一個 bit 。
以 N = $32768_{10}$ = $1000000000000000_2$ 為例
* `N |= N >> 1;`
$N$ = $1100000000000000_2$
* `N |= N >> 2;`
$N$ = $1111000000000000_2$
* `N |= N >> 4;`
$N$ = $1111111100000000_2$
* `N |= N >> 8;`
$N$ = $1111111111111111_2$
* `(N + 1) >> 1;`
$N$ = $1000000000000000_2$
## 不會 overflow 的原因:
在 [C99 規格書](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) **6.3.1.1 Boolean,characters, and integers**
> If an **int** can represent all values of the original type, the value is converted to an **int**; otherwise, it is converted to an **unsigned int**. These are called the integer promotions. All other types are unchanged by the integer promotions.
此為 integer promotion ,因為 `(N + 1) >> 1` 在 `int` 的表示範圍內,所以會將 `N` 轉型成 32-bit 的 `int` ,回傳時再以 `usigned int` 表示。
## 改進方法
因為上面所提到的 integer promotion 的緣故,所以在 `(N + 1) >> 1` 並不會有 overflow 的問題。
那麼如果是在 `uint32_t N` 的情況下呢?
改進方法: 參考[你所不知道的 C 語言:數值系統](/@sysprog/c-numerics)
>* 比方說 `(x + y) / 2` 這樣的運算,有個致命問題在於 (x + y) 可能會導致 overflow (考慮到 x 和 y 都接近 [UINT32_MAX](https://msdn.microsoft.com/en-us/library/mt764276.aspx),亦即 32-bit 表示範圍的上限之際)
* [Binary search 的演算法提出之後十年才被驗證](https://www.comp.nus.edu.sg/~sma5503/recitations/01-crct.pdf)
* 於是我們可以改寫為以下:
```
(x & y) + ((x ^ y) >> 1)
```
>* 用加法器來思考: x & y 是進位, x ^ y 是位元和, / 2 是向右移一位
>* 位元相加不進位的總和: x ^ y; 位元相加產生的進位值: (x & y) << 1
>* x + y = x ^ y + ( x & y ) << 1
>* 所以 (x + y) / 2
= (x + y) >> 1
= (x ^ y + (x & y) << 1) >> 1
= (x & y) + ((x ^ y) >> 1)
從上述的方法我們可以將原本的方式改成下面,在 `N` 為 `uint32_t` 的情況下也不會 overflow 。
```diff=
- (N + 1) >> 1
+ (N & 1) + ((N ^ 1) >> 1)
```
## The Linux Kernel API:
在 [The Linux Kernel API](https://www.kernel.org/doc/html/latest/core-api/kernel-api.html#c.is_power_of_2) 頁面搜尋 “power of 2”,可見 `is_power_of_2` ,查閱 Linux 核心原始程式碼,舉例說明其作用和考量,特別是 round up/down power of 2 。特別留意 `__roundup_pow_of_two` 和 `__rounddown_pow_of_two` 的實作機制。[
linux/include/linux/log2.h ](https://github.com/torvalds/linux/blob/master/include/linux/log2.h)
### `is_power_of_2`
接受一個無號整數 $n$ ,如果該值為 2 的冪,則回傳 `true` ; 反之回傳 `false` 。 0 不是 2 的冪 。
```cpp
/*
* Determine whether some value is a power of two, where zero is
* *not* considered a power of two.
*/
static inline __attribute__((const))
bool is_power_of_2(unsigned long n)
{
return (n != 0 && ((n & (n - 1)) == 0));
}
```
首先判斷 $n$ 是否為 0 。如果 `(n & (n - 1))` 為 0 ,則 $n$ 為 2 的冪。
以 $n=1000_2$ 為例,則 $n-1= 0111_2$ , $n$ & $(n-1)$ $= 0$ ,最後回傳 `true` 。
### `__roundup_pow_of_two`
接受一個無號整數 $n$ ,並回傳大於等於 $n$ 且離 $n$ 最近的 2 的冪 。
以 $n=9$ 為例,則 $n-1=8=1000_2$ , `fls_long(n - 1) = 4` 最後回傳 `1UL << 4` 等於 16。如果 $n$ 為 2 的冪,則剛好回傳 $n$ 。
```cpp
/*
* round up to nearest power of two
*/
static inline __attribute__((const))
unsigned long __roundup_pow_of_two(unsigned long n)
{
return 1UL << fls_long(n - 1);
}
```
:::info
其中有提到 `fls_long` 用來找從 MSB 開始出現第一個 1 的位置。[linux/include/linux/bitops.h](https://github.com/torvalds/linux/blob/master/include/linux/bitops.h)
```cpp
static inline unsigned fls_long(unsigned long l)
{
if (sizeof(l) == 4)
return fls(l);
return fls64(l);
}
```
`fls` 用於 `unsigned long` 為 4 bytes ,而 `fls64` 用於 `unsigned long` 為 8 bytes 。
`fls` 和 `fls64` 在 Linux kernel 中會根據不同硬體而有不同實作。以下為 `fls` 在 [arch/arc/include/asm/bitops.h](https://github.com/torvalds/linux/blob/master/arch/arc/include/asm/bitops.h) 的實作。
```cpp
/*
* fls = Find Last Set in word
* @result: [1-32]
* fls(1) = 1, fls(0x80000000) = 32, fls(0) = 0
*/
static inline __attribute__ ((const)) int fls(unsigned int x)
{
if (__builtin_constant_p(x))
return constant_fls(x);
return 32 - clz(x);
}
```
其中 `clz` 和 `constant_fls` 實作如下
```cpp
/*
* Count the number of zeros, starting from MSB
* Helper for fls( ) friends
* This is a pure count, so (1-32) or (0-31) doesn't apply
* It could be 0 to 32, based on num of 0's in there
* clz(0x8000_0000) = 0, clz(0xFFFF_FFFF)=0, clz(0) = 32, clz(1) = 31
*/
static inline __attribute__ ((const)) int clz(unsigned int x)
{
unsigned int res;
__asm__ __volatile__(
" norm.f %0, %1 \n"
" mov.n %0, 0 \n"
" add.p %0, %0, 1 \n"
: "=r"(res)
: "r"(x)
: "cc");
return res;
}
```
```cpp
static inline int constant_fls(unsigned int x)
{
int r = 32;
if (!x)
return 0;
if (!(x & 0xffff0000u)) {
x <<= 16;
r -= 16;
}
if (!(x & 0xff000000u)) {
x <<= 8;
r -= 8;
}
if (!(x & 0xf0000000u)) {
x <<= 4;
r -= 4;
}
if (!(x & 0xc0000000u)) {
x <<= 2;
r -= 2;
}
if (!(x & 0x80000000u))
r -= 1;
return r;
}
```
:::
### `__rounddown_pow_of_two`
接受一個無號整數 $n$ ,並回傳小於等於 $n$ 且離 $n$ 最近的 2 的冪 。以 $n=9=1001_2$ 為例, `fls_long(n) - 1 = 3` 最後回傳 `1UL << 3` 等於 8。如果 $n$ 為 2 的冪,則剛好回傳 $n$ 。
```cpp
/*
* round down to nearest power of two
*/
static inline __attribute__((const))
unsigned long __rounddown_pow_of_two(unsigned long n)
{
return 1UL << (fls_long(n) - 1);
}
```
## slab allocator
研讀 [slab allocator](https://en.wikipedia.org/wiki/Slab_allocation),探索 Linux 核心的 slab 實作,找出運用 power-of-2 的程式碼和解讀