# 2022q1 Homework3 (quiz3)
contributed by < `jim12312321` >
> [測驗題目](https://hackmd.io/@sysprog/linux2022-quiz3)
## 測驗 1 : 連續的 bitmask
```cpp
#define GENMASK(h, l) \
(((~0UL) >> (LEFT)) & ((~0UL) >> (l) << (RIGHT)))
```
解析︰
先觀察程式碼,`~0UL` 等於 0xFFFFFFFFFFFFFFFF ,有出現 `shift` ,中間用 `AND` 相連,因此我們可以合理猜測本題中應該是想透過將 `~0UL` 經過特定的 `shift` 後再透過 `AND` 將不在 `[h,l]` 中的 1 消除。
> e.g.
> 要產生 `GENMASK(6,4)` ,要先產生 0x000000000000007F (`~0UL` 右移 57 個 bits) 和 0xFFFFFFFFFFFFFFF0 (`~0UL` 左移 4 個 bits),再藉由 `AND` 產生 0x0000000000000070 。
由上述例子進行延伸討論,我們可以知道若要透過 `~0UL` 產生 0x000000000000007F 需要右移,反之要產生 0xFFFFFFFFFFFFFFF0 則需要左移。
再與程式碼比較後可知,`(~0UL) >> (LEFT)` 僅有右移,`(~0UL) >> (l) << (RIGHT)` 包含左移操作。因此可知若對應上述例子,`(~0UL) >> (LEFT)` 目的在於產生 0x000000000000007F ,`(~0UL) >> (l) << (RIGHT)` 目的在於產生 0xFFFFFFFFFFFFFFF0 ,不過由於在 `(~0UL) >> (l) << (RIGHT)` 中還含有右移行為,因此產生出來的 bitmask 應該為 0x0FFFFFFFFFFFFFF0 。
==最後可以發現右移操作中的 57 來自於 64-1-h ,而左移操作中的 4 來自於 l。==
`LEFT`
$\rightarrow$ 63 - h
`RIGHT`
$\rightarrow$ l
### 延伸問題 2
> 比較 Linux 核心 GENMASK 巨集的實作,闡述其額外的考量
在 linux kernel v5.16.12 中的 [isst.h](https://elixir.bootlin.com/linux/latest/source/tools/power/x86/intel-speed-select/isst.h#L33) 中可以發現 GENMASK 的程式碼
```cpp
#define GENMASK(h, l) (((~0UL) << (l)) & (~0UL >> (sizeof(long) * 8 - 1 - (h))))
```
在這段程式碼中可以發現與本題中的 GENMASK 幾乎一致,僅差在 `(~0UL) << (l)` 的操作中沒有先左移 l ,但這對於答案是沒有差別的。
另外在 linux kernel v5.16.12 中的 [bits.h](https://elixir.bootlin.com/linux/latest/source/include/linux/bits.h#L37) 中也可以發現 GENMASK 的程式碼。
```cpp
#define GENMASK(h, l) \
(GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l))
```
接著我們分別來看 `GENMASK_INPUT_CHECK(h, l)` 和 `__GENMASK(h, l)`
- GENMASK_INPUT_CHECK
```cpp
#if !defined(__ASSEMBLY__)
#include <linux/build_bug.h>
#define GENMASK_INPUT_CHECK(h, l) \
(BUILD_BUG_ON_ZERO(__builtin_choose_expr( \
__is_constexpr((l) > (h)), (l) > (h), 0)))
#else
/*
* BUILD_BUG_ON_ZERO is not available in h files included from asm files,
* disable the input check if that is the case.
*/
#define GENMASK_INPUT_CHECK(h, l) 0
#endif
```
搭配 [BUILD_BUG_ON_ZERO](https://elixir.bootlin.com/linux/latest/source/tools/include/linux/build_bug.h#L8) 的說明
```cpp
#ifdef __CHECKER__
#define BUILD_BUG_ON_ZERO(e) (0)
#else /* __CHECKER__ */
/*
* Force a compilation error if condition is true, but also produce a
* result (of value 0 and type int), so the expression can be used
* e.g. in a structure initializer (or where-ever else comma expressions
* aren't permitted).
*/
#define BUILD_BUG_ON_ZERO(e) ((int)(sizeof(struct { int:(-!!(e)); })))
#endif /* __CHECKER__ */
```
我們可知在 `BUILD_BUG_ON_ZERO(e)`中當 e 這個情況為 0 時沒事,而不為 0 時 (condition is true) ,印出錯誤訊息。
接著來看 [`__builtin_choose_expr`](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) 的說明
>Built-in Function: type __builtin_choose_expr (const_exp, exp1, exp2)
>
>You can use the built-in function __builtin_choose_expr to evaluate code depending on the value of a constant expression. This built-in function returns exp1 if const_exp, which is an integer constant expression, is nonzero. Otherwise it returns exp2.
再來看 [`__is_constexpr`](https://elixir.bootlin.com/linux/latest/source/include/linux/const.h#L11) 的說明
```cpp
/*
* This returns a constant expression while determining if an argument is
* a constant expression, most importantly without evaluating the argument.
* Glory to Martin Uecker <Martin.Uecker@med.uni-goettingen.de>
*/
#define __is_constexpr(x) \
(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))
```
最後透過整理上述三個資訊可知,`GENMASK_INPUT_CHECK(h, l)` 的目的在於==確認 h > l== ,若是輸出 0;若否 (h <= l) ,則印出錯誤訊息。
- `__GENMASK`
```cpp
#define __GENMASK(h, l) \
(((~UL(0)) - (UL(1) << (l)) + 1) & \
(~UL(0) >> (BITS_PER_LONG - 1 - (h))))
```
我們一樣來舉個例子說明(不舉例給自己看我真的腦袋卡住)
> e.g.
> 要產生 `GENMASK(6,4)` ,要先產生 0x000000000000007F (`~0UL` 右移 57 個 bits) 和 0xFFFFFFFFFFFFFFF0 (`~0UL` 左移 4 個 bits),再藉由 `AND` 產生 0x0000000000000070 。
這跟上面舉的例子是完全相同的,而觀察程式碼可以發現, `~UL(0) >> (BITS_PER_LONG - 1 - (h))` 的作用等同 `(~0UL) >> (63 - h)` ,一樣是為了左移 4 個 bits 產生 0x000000000000007F 。
接著來看 `(((~UL(0)) - (UL(1) << (l)) + 1)` 想做什麼。
- `(~UL(0))` 產生 0xFFFFFFFFFFFFFFFF
- `(~UL(0)) - (UL(1) << (l))` 產生 0xFFFFFFFFFFFFFFEF
- `((~UL(0)) - (UL(1) << (l)) + 1)` 產生 0xFFFFFFFFFFFFFFF0
ya! 結果和 `((~0UL) << (l))` 是等價的!只是這裡的想法是先目標位置 (l) 的 bit 設為 0 ,再透過 `+1` 產生我們想要的 bitmask 。(雖然我覺得想法上沒有 `((~0UL) << (l))` 來的簡潔和直覺就是了)。
總結來說在 linux kernel 中的 `GENMASK` 並沒有什麼很特別的技術,僅是在 linux kernel v5.16.12 理的 [bits.h](https://elixir.bootlin.com/linux/latest/source/include/linux/bits.h#L37) 中的 `GENMASK` 多了要求 h 恆大於 l 的限制罷了。
### 延伸問題 3
>舉出 Linux 核心原始程式碼中二處 GENMASK 巨集和 include/linux/bitfield.h 的應用案例
在 linux 中有不少 GENMASK 巨集和 include/linux/bitfield.h 的應用案例(請看[這裡](https://github.com/torvalds/linux/search?q=%27%23include+%3Clinux%2Fbitfield.h%3E%27+%26%26+%27GENMASK%27&type=))。大部分跟裝置驅動程式或是網路挺有關係的,礙於能力有限,我會盡量理解後留下心得。
:::warning
device driver 請稱呼全名「裝置驅動程式」,不要簡稱「驅動」,不然日後涉及到編譯器和資料庫,都有對應的 compiler driver 和 database driver,必然會混淆。
:notes: jserv
:::
#### 應用案例 1
本段程式碼可以在 [linux/sound/soc/intel/keembay/kmb_platform.h ](https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/sound/soc/intel/keembay/kmb_platform.h) 的第 64 到 66 行中找到。
```cpp=
/*line 64 to 66*/
/* Interrupt Flag */
#define TX_INT_FLAG GENMASK(5, 4)
#define RX_INT_FLAG GENMASK(1, 0)
```
可以猜測這是用來設立 flag 用以判斷該發送數據或是接收數據。
> 參考:[What do ‘TX’ and ‘RX’ refer to in the Network Charts?](https://www.copperegg.com/what-do-tx-and-rx-refer-to-in-the-network-charts/)
接著繼續追蹤 `TX_INT_FLAG` 和 `RX_INT_FLAG`
```cpp
static inline void kmb_i2s_irq_trigger(struct kmb_i2s_info *kmb_i2s,
u32 stream, int chan_nr, bool trigger)
{
u32 i, irq;
u32 flag;
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
flag = TX_INT_FLAG;
else
flag = RX_INT_FLAG;
for (i = 0; i < chan_nr / 2; i++) {
irq = readl(kmb_i2s->i2s_base + IMR(i));
if (trigger)
irq = irq & ~flag;
else
irq = irq | flag;
writel(irq, kmb_i2s->i2s_base + IMR(i));
}
}
```
可發現當 stream 狀態為播放時,將 flag 設定為傳輸模式;否則將 flag 設定為接收模式。
#### 應用案例2
本段程式碼可以在 [ linux/net/dsa/tag_ar9331.c](https://github.com/torvalds/linux/blob/9e9fb7655ed585da8f468e29221f0ba194a5f613/net/dsa/tag_ar9331.c) 的第 13 到 15 行中找到。
```cpp=13
#define AR9331_HDR_VERSION 1
#define AR9331_HDR_VERSION_MASK GENMASK(15, 14)
```
接著追蹤 `AR9331_HDR_VERSION_MASK` ,可以在同一個檔案中的第 36 行找到他。(下面程式碼中的第 10 行)
```cpp=
static struct sk_buff *ar9331_tag_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct dsa_port *dp = dsa_slave_to_port(dev);
__le16 *phdr;
u16 hdr;
phdr = skb_push(skb, AR9331_HDR_LEN);
hdr = FIELD_PREP(AR9331_HDR_VERSION_MASK, AR9331_HDR_VERSION);
hdr |= AR9331_HDR_FROM_CPU | dp->index;
/* 0b10 for AR8216 and 0b11 for AR9331 */
hdr |= AR9331_HDR_RESERVED_MASK;
phdr[0] = cpu_to_le16(hdr);
return skb;
}
```
再來看看 [FIELD_PREP](https://github.com/torvalds/linux/blob/master/include/linux/bitfield.h#L99) 的說明。
```cpp
/**
* FIELD_PREP() - prepare a bitfield element
* @_mask: shifted mask defining the field's length and position
* @_val: value to put in the field
*
* FIELD_PREP() masks and shifts up the value. The result should
* be combined with other fields of the bitfield using logical OR.
*/
#define FIELD_PREP(_mask, _val) \
({ \
__BF_FIELD_CHECK(_mask, 0ULL, _val, "FIELD_PREP: "); \
((typeof(_mask))(_val) << __bf_shf(_mask)) & (_mask); \
})
```
結合上述的內容我們可以推斷在 [ linux/net/dsa/tag_ar9331.c](https://github.com/torvalds/linux/blob/9e9fb7655ed585da8f468e29221f0ba194a5f613/net/dsa/tag_ar9331.c) 中的 `GENMASK` 目的是為了結合 `FIELD_PREP` 將特定的值左移到特定位置。
---
## 測驗 2 : 記憶體向下對齊
在進入程式碼解析之前,先來複習什麼是記憶體向上對齊和向下對齊,在⟨[你所不知道的 C 語言:記憶體管理、對齊及硬體特性](https://hackmd.io/@sysprog/c-memory#data-alignment)⟩ 中的 [data alignment](https://hackmd.io/@sysprog/c-memory#data-alignment) 中有提到,記憶體對齊就是讓記憶體內的資料落在 4 個 bytes 或是 8 個 btyes 的倍數位置(當然也可以根據需求設置對齊任意位置) ,方便 cpu 抓取資料。
> 以下例子皆以對齊 4 個 bytes 為例。
- 向上對齊
- 將記憶體內的資料對齊到 4 bytes + 1 bytes 的位置上。<br>對應到記憶體定址中,就是最後 2 個 bits clear ,並對第 3 個 bit + 1 。
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
address[label ="<h>Memory address|<a1>0x0|<a2>0x1|<a3>0x2|<a4>0x3|<a1>0x4|<a2>0x5|<a3>0x6|<a4>0x7",width = 1.8]
space_before[label ="<h>Memory space before||<d1>data|<d2>data|<d3>data|<d4>data|||",width = 2.5]
space_after[label ="<h>Memory space after|||||<d1>data|<d2>data|<d3>data|<d4>data",width = 2.5]
address->space_before->space_after[weight = 10, style = invis]
space_before->space_after
}
```
> address (after align up) : $0001_2$
> address (before align up) : $0100_2$
- 向下對齊
- 將記憶體內的資料對齊到 4 bytes 的位置上。<br>對應到記憶體定址中,就是最後 2 個 bits clear。
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
address[label ="<h>Memory address|<a1>0x0|<a2>0x1|<a3>0x2|<a4>0x3|<a1>0x4|<a2>0x5|<a3>0x6|<a4>0x7",width = 1.8]
space_before[label ="<h>Memory space before||<d1>data|<d2>data|<d3>data|<d4>data|||",width = 2.5]
space_after[label ="<h>Memory space after|<d1>data|<d2>data|<d3>data|<d4>data||||",width = 2.5]
address->space_before->space_after[weight = 10, style = invis]
space_before->space_after
}
```
> address (after align up) : $0001_2$
> address (before align up) : $0000_2$
看完說明之後就可以來解析程式碼了。
```cpp=
struct fd {
struct foo *foo;
unsigned int flags;
};
static inline struct fd to_fd(unsigned long v)
{
return (struct fd){EXP1, v & 3};
}
```
由於本題要將結構體中第一個成員的地址向下對齊 4 個 bytes ,又因為 `forward declaration` 的關係,所以在這個回傳式中要加入對 (struct foo \*) 的操作。
`EXP1`
$\rightarrow$ (struct foo \*)(v & ~3)
### 延伸問題 2
>在 Linux 核心原始程式碼找出類似技巧的實作 (例如檔案系統) 並解說
在 [linux/include/uapi/linux/const.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/const.h) 中可以找到對齊相關的程式碼
> 以下程式碼節錄自 [const.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/const.h) 第 31,32 行
```cpp=
#define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1)
#define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask))
```
- `__ALIGN_KERNEL_MASK`
針對特定的 bitmask (e.g. $15_{10} = 1111_2$) 進行向上對齊
- `__ALIGN_KERNEL`
在 `__ALIGN_KERNEL_MASK` 的基礎上,對特定長度的數字(通常是 $2^n ,n \in N$) 進行向上對齊
接著在 [linux/include/linux/align.h](https://github.com/torvalds/linux/blob/master/include/linux/align.h) 中可以找到向上對齊/向下對齊的程式碼。
> 以下程式碼節錄自 [align.h](https://github.com/torvalds/linux/blob/master/include/linux/align.h) 第 7 至 9 行
```cpp=
/* @a is a power of 2 value */
#define ALIGN(x, a) __ALIGN_KERNEL((x), (a))
#define ALIGN_DOWN(x, a) __ALIGN_KERNEL((x) - ((a) - 1), (a))
```
- `ALIGN`
本來在 `__ALIGN_KERNEL` 中就是向上對齊了,所以他就是向上對齊。
- `ALIGN_DOWN`
在要對齊的地址中先 `- ((a) - 1)` ,達到把 `a - 1`以下的位元全部 clear 並且不 `+1` ,完成向下對齊。
---
## 測驗 3 ︰ reverse bits
```cpp=
#include <stdint.h>
uint8_t rev8(uint8_t x)
{
x = (x >> 4) | (x << 4);
x = ((x & 0xCC) >> 2) | (EXP2);
x = ((x & 0xAA) >> 1) | (EXP3);
return x;
}
```
觀察程式碼行為可以得知,這段程式碼正在逐步將位元交換,一開始先交換前 4 個 bits 和後 4 個 bits,接著將交換完的 4 個 bits 內的 bits 兩兩互換,最後在將兩個 bits 互換。(有點 mergesort 的感覺)
- origin
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
bits_0[label ="{7
|6|5|4|3|2|1|0}",width = 1.8]
}
```
- reverse (4 bits)
```
x = (x >> 4) | (x << 4);
```
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
bits_0[label ="{3|2|1|0|7|6|5|4}",width = 1.8]
}
```
- reverse (2 bits)
```
x = ((x & 0xCC) >> 2) | (EXP2);
```
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
bits_0[label ="{1|0|3|2|5|4|7|6}",width = 1.8]
}
```
- reverse (1 bit)
```
x = ((x & 0xAA) >> 1) | (EXP3);
```
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
bits_0[label ="{0|1|2|3|4|5|6|7}",width = 1.8]
}
```
`EXP2`
$\rightarrow$ (x & 0x33) << 2
`EXP3`
$\rightarrow$ (x & 0x55) << 1
### 延伸問題 2
> 在 Linux 核心原始程式碼找出類似技巧的實作並解說其應用場景
在 [linux/lib/bitrev.c ](https://github.com/torvalds/linux/blob/master/lib/bitrev.c) 中可以發現由於反轉 8 bits 僅有 256 種可能,因此開發者直接把 8 bits 反轉的查詢表內建在裡面了。
> 節錄自 [bitrev.c ](https://github.com/torvalds/linux/blob/master/lib/bitrev.c) 中第 11 至 44 行
```cpp=
const u8 byte_rev_table[256] = {
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
};
```
而在 [linux/include/linux/bitrev.h ](https://github.com/torvalds/linux/blob/master/include/linux/bitrev.h)中則可以看到基於 `byte_rev_table` 延伸出來的 16 位元、 32 位元版本。
```cpp=16
static inline u8 __bitrev8(u8 byte)
{
return byte_rev_table[byte];
}
static inline u16 __bitrev16(u16 x)
{
return (__bitrev8(x & 0xff) << 8) | __bitrev8(x >> 8);
}
static inline u32 __bitrev32(u32 x)
{
return (__bitrev16(x & 0xffff) << 16) | __bitrev16(x >> 16);
}
```
另外在 [bitrev.h](https://github.com/torvalds/linux/blob/master/include/linux/bitrev.h) 中有提供用於 reverse 編譯時常數的版本,作法就根本題是幾乎一模一樣了。
```cpp=65
#define __constant_bitrev8(x) \
({ \
u8 ___x = x; \
___x = (___x >> 4) | (___x << 4); \
___x = ((___x & (u8)0xCCU) >> 2) | ((___x & (u8)0x33U) << 2); \
___x = ((___x & (u8)0xAAU) >> 1) | ((___x & (u8)0x55U) << 1); \
___x; \
})
```
因此在 linux 中真正在使用 bit reverse 時也是要先考慮輸入值是否為編譯時常數。
```cpp=98
#define bitrev8(x) \
({ \
u8 __x = x; \
__builtin_constant_p(__x) ? \
__constant_bitrev8(__x) : \
__bitrev8(__x) ; \
})
```
#### 應用情境
在很多裝置驅動程式相關的程式碼中都能看到 bit reverse 的身影,以下程式碼雖不能完整解釋,就當放上來作為實際應用情境參考。
在 [linux/drivers/media/usb/cx231xx/cx231xx-input.c ](https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/drivers/media/usb/cx231xx/cx231xx-input.c) 的 `get_key_isdbt` 函式中可以看到以下程式碼。
```cpp=39
scancode = bitrev8(cmd);
dev_dbg(&ir->rc->dev, "cmd %02x, scan = %02x\n", cmd, scancode);
```
又或者在 [linux/drivers/media/rc/img-ir/img-ir-nec.c](https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/drivers/media/rc/img-ir/img-ir-nec.c) 中的 `img_ir_nec_scancode` 函式可以看到解析 `raw code` to `scan code` 時用到 `bitrev8` 。
```cpp=30
request->scancode = bitrev8(addr) << 24 |
bitrev8(addr_inv) << 16 |
bitrev8(data) << 8 |
bitrev8(data_inv);
```
---
## 測驗 4 : foreach macro
foreach 的概念很簡單,就是依序提取出特定 array 內的所有資料。
先來看程式碼。
```cpp=
#include <assert.h>
#define _foreach_no_nullval(i, p, arr) \
assert((i) >= sizeof(arr) / sizeof(arr[0]) || (p))
#define foreach_int(i, ...) \
for (unsigned _foreach_i = (((i) = ((int[]){__VA_ARGS__})[0]), 0); \
_foreach_i < sizeof((int[]){__VA_ARGS__}) / sizeof(int); \
(i) = ((int[]){__VA_ARGS__, 0})[EXP4])
#define foreach_ptr(i, ...) \
for (unsigned _foreach_i = \
(((i) = (void *) ((typeof(i)[]){__VA_ARGS__})[0]), 0); \
(i); (i) = (void *) ((typeof(i)[]){__VA_ARGS__, \
NULL})[EXP5], \
_foreach_no_nullval(_foreach_i, i, \
((const void *[]){__VA_ARGS__})))
```
無論是 `foreach_int` 或 `foreach_ptr` 目標都是為了走訪 array 中所有資料,對應到 c 中 for 的虛擬碼就會是:
```cpp=
for(take the first element and get first index;
assert(index < MAX_LEN_ARRAY);
update the element and index)
```
為求精簡,題目中在一行內就要更新 element (題目中的 `i`) 與 index (題目中的 `_foreach_i`) 因此使用 `++Variable` 的技巧。
`EXP4`
$\rightarrow$ ++_foreach_i
`EXP5`
$\rightarrow$ ++_foreach_i
### 延伸問題 2
>在 Linux 核心原始程式碼找出類似技巧的實作並解說其應用場景
在 [drivers/net/ethernet/mellanox/mlxfw/mlxfw_mfa2_tlv_multi.h](https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/drivers/net/ethernet/mellanox/mlxfw/mlxfw_mfa2_tlv_multi.h) 中可以找到類似的實作技巧。
```cpp=33
#define mlxfw_mfa2_tlv_foreach(mfa2_file, tlv, idx, from_tlv, count) \
for (idx = 0, tlv = from_tlv; idx < (count); \
idx++, tlv = mlxfw_mfa2_tlv_next(mfa2_file, tlv))
```
這邊的作法跟 [list.h](https://github.com/sysprog21/lab0-c/blob/master/list.h) 中的 `list_for_each` 之類的實作挺類似的,就不特別解說。
這類型的技巧可以用來檢查特定變數/結構體有沒有符合特定條件。在同個目錄中的 [mlxfw_mfa2.c](https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/drivers/net/ethernet/mellanox/mlxfw/mlxfw_mfa2.c) 可以看到應用的場景。
```cpp=232
/* check that all the devices exist */
mlxfw_mfa2_tlv_foreach(mfa2_file, tlv, idx, mfa2_file->first_dev,
mfa2_file->dev_count) {
if (!tlv) {
pr_err("Device TLV error\n");
return false;
}
/* Check each device */
if (!mlxfw_mfa2_file_dev_validate(mfa2_file, tlv, idx))
return false;
}
```
---
## 測驗 5 : LeetCode 29. Divide Two Integers
```c=
#include <limits.h>
int divide(int dividend, int divisor)
{
int signal = 1;
unsigned int dvd = dividend;
if (dividend < 0) {
signal *= -1;
dvd = ~dvd + 1;
}
unsigned int dvs = divisor;
if (divisor < 0) {
signal *= -1;
dvs = ~dvs + 1;
}
int shift = 0;
while (dvd > (EXP6))
shift++;
unsigned int res = 0;
while (dvd >= dvs) {
while (dvd < (dvs << shift))
shift--;
res |= (unsigned int) 1 << shift;
EXP7;
}
if (signal == 1 && res >= INT_MAX)
return INT_MAX;
return res * signal;
}
```
`EXP6`
$\rightarrow$ dvs << shift
`EXP7`
$\rightarrow$ dvd -= dvs << shift
---
## 測驗 6 : LeetCode 149. Max Points on a Line
本題程式的目的在於將可連成一條線的點($\dfrac{y}{x}$ 相同者,在本題中 `x` 與 `y` 都有先除以 `gcd(x,y)` ,以確保 hash 值相同),存在 hash table 同個 hash 值的 linked list 中。
```cpp=
static bool can_insert(struct list_head *head, int p1, int p2)
{
struct point_node *pn;
list_for_each_entry (pn, head, link)
return EXP8;
return true;
}
```
所以 `EXP8` 的目的在於判斷傳進來的 `(p1,p2)` 與原先 list 中的 `(p1,p2)` 是否一致。
`EXP8`
$\rightarrow$ `p1 == pn->p1` or `p2 == pn->p2`
```cpp=
if (can_insert(&heads[hash], i, j)) {
struct point_node *pn = malloc(sizeof(*pn));
pn->p1 = i;
pn->p2 = j;
EXP9;
}
```
而 `EXP9` 的目的就在於將新的節點放進 `heads` 中對應 hash 的位置。
`EXP9`
$\rightarrow$ list_add(&pn->link, &heads[hash])
---
## 測驗 7 : ilog32
考慮題目一開始的要求
>針對給定的 32 位元無號數,計算扣除開頭的 0,最少需要多少位元才可儲存
相當於對特定的數字進行 $log_2$ 的操作再取 floor 。因此這題便是對前述概念的實作。
```cpp=
int ilog32(uint32_t v)
{
int ret = v > 0;
int m = (v > 0xFFFFU) << 4;
v >>= m;
ret |= m;
m = (v > 0xFFU) << 3;
v >>= m;
ret |= m;
m = (v > 0xFU) << 2;
v >>= m;
ret |= m;
m = EXP10;
v >>= m;
ret |= m;
EXP11;
return ret;
}
```
觀察題目後可以發現有類似的模組重複發生,如下所示。
```cpp=
m = (v > 0xFFU) << 3;
v >>= m;
ret |= m;
```
每次對半檢查,判斷對半後較高位的那組中是否有值。若有則記錄判斷了幾個 bits ,反覆檢查直至全部檢查過。
到 `EXP10` 之前的程式碼檢查到 4 個 bits ,因此可知 `EXP10` 要檢查剩下未確定的 4 個 bits 中,較高位的 2 個 bits 是否為 0。
`EXP10`
$\rightarrow$ (v > 0x3) << 1
理論上還要再進行一次針對剩下 2 個 bits 的操作, `ret` 的結果才會是最終答案,如下所示。
```cpp=
m = (v > 0x1);
v >>= m;
ret |= m;
```
題目中顯然是希望最後這三道指令能濃縮成一道,首先 `v >>= m` ,與最後回傳值無關,不理他。剩下 `m = (v > 0x1)` 跟 `ret |= m` 。不過由於最初就是考慮到每次位移的 bits 都不會重複,因此在這個情況下 `|=` 等價於 `+=` 。
`EXP11`
$\rightarrow$ `ret |= v > 1` or `ret += v > 1`
---
## 測驗 8 : binary tree
以下解說只挑有填空的位置講解。
```cpp=
while (*p != 0 && (*p)->data != d) {
if (d < (*p)->data)
EXP12;
else
EXP13;
}
```
根據二元搜尋樹的特性,若在當前節點無法匹配到指定值且當前節點不為葉節點,匹配值較當前值大則向右搜尋,反之向左。
`EXP12`
$\rightarrow$ p = &(*p)->left
`EXP13`
$\rightarrow$ p = &(*p)->right
```cpp=
tnode **r = &q->right;
while ((*r)->left)
r = EXP14;
q->data = (*r)->data;
q = *r;
*r = q->right;
```
這段在處理欲移除節點擁有同時擁有左右子樹的情況,此時應拿全部小於該數值中最接近該數值的值取代(左子樹中最右邊的值),或全部大於該數值中最接近該數值的值取代(右子樹中最左邊的值),而在本題中使用的是後者。
`EXP14`
$\rightarrow$ &(*r)->left
---
## 測驗 9 : round up alignment
上面有解釋過何謂向上對齊,這邊簡單複習。
> 以下例子皆以對齊 4 個 bytes 為例。
- 向上對齊
- 將記憶體內的資料對齊到 4 bytes + 1 bytes 的位置上。<br>對應到記憶體定址中,就是最後 2 個 bits clear ,並對第 3 個 bit + 1 。
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
address[label ="<h>Memory address|<a1>0x0|<a2>0x1|<a3>0x2|<a4>0x3|<a1>0x4|<a2>0x5|<a3>0x6|<a4>0x7",width = 1.8]
space_before[label ="<h>Memory space before||<d1>data|<d2>data|<d3>data|<d4>data|||",width = 2.5]
space_after[label ="<h>Memory space after|||||<d1>data|<d2>data|<d3>data|<d4>data",width = 2.5]
address->space_before->space_after[weight = 10, style = invis]
space_before->space_after
}
```
> address (after align up) : $0001_2$
> address (before align up) : $0100_2$
接著來看程式碼。
```cpp=
/* maximum alignment needed for any type on this platform, rounded up to a
power of two */
#define MAX_ALIGNMENT 16
/* Given a size, round up to the next multiple of sizeof(void *) */
#define ROUND_UP_TO_ALIGNMENT_SIZE(x) \
(((x) + MAX_ALIGNMENT - MMM) & ~(NNN))
```
因此本題希望將 x 向上對齊到 4 的倍數的位置上 (e.g. $0000 0010_2 \rightarrow 00001 0000_2$) ,並且將最低 4 個 bits 清空。
`MMM`
$\rightarrow$ 1
`NNN`
$\rightarrow$ MAX_ALIGNMENT - 1
---
## 測驗 10 : DIVIDE_ROUND_CLOSEST
```cpp=
#define DIVIDE_ROUND_CLOSEST(x, divisor) \
({ \
typeof(x) __x = x; \
typeof(divisor) __d = divisor; \
(((typeof(x)) -1) > 0 || ((typeof(divisor)) -1) > 0 || \
(((__x) > 0) == ((__d) > 0))) \
? ((RRR) / (__d)) \
: ((SSS) / (__d)); \
})
```
用來進行判斷進入 `((RRR) / (__d))` 或 `((SSS) / (__d))` 的三個條件,主要目的便是拿來判別運算結果有沒有可能為負,若不可能就進 `((RRR) / (__d))` 否則進 `((SSS) / (__d))` 。
三個條件中須注意的是 `((typeof(x)) -1)` 這是用以將 -1 cast 成 `typeof(x)` ,假設 x 的 type 為 int ,則 `((typeof(x)) -1) > 0` 不成立,若 x 的 type 為 unsigned int ,則 `((typeof(x)) -1) > 0` 成立(之所以要檢查是否為 unsigned ,是因為跟 unsigned 運算的 signed 數值也會變 unsigned ,因此恆大於等於 0)。
考慮到在 c 中除法會無條件捨去,因此對於除完後餘數大於等於 0.5 的數值要補齊這一半,利用==加上==這一半的值讓他進位;而對於小於 0.5 的情況,有沒有補都不會造成進位,因此補上去也沒關係。而在負數的除法中情況相反,對於除完後餘數大於等於 0.5 的數值要補齊這一半,利用==減去==這一半的值讓他進位。
`RRR`
$\rightarrow$ (__x) + ((__d) >> 1)
`SSS`
$\rightarrow$ (__x) - ((__d) >> 1)
---
## 測驗 11 : floor sqrt
`fls(x)` 的目的在於找出最高位的 set bit 的位置。
>`fls(1) == 0`, `fls(2) == 1`, `fls(0x8000 0000 0000 0000) == 63`
```cpp=
unsigned long i_sqrt(unsigned long x)
{
unsigned long b, m, y = 0;
if (x <= 1)
return x;
m = 1UL << (fls(x) & ~1UL);
while (m) {
b = y + m;
XXX;
if (x >= b) {
YYY;
y += m;
}
ZZZ;
}
return y;
}
```
一直卡住沒什麼想法,在參考 [kdnvt](https://hackmd.io/@5ux520T8Sd-OoYfdxZR55Q/SkO7JHRgq#2022q1-Homework3-quiz3) 同學的解釋之後有了一點頭緒。
用 2 進位的邏輯 $N^2$ 可以被表示為 $(a_n+...+a_0)^2$ ,其中 $a_m$ ($0 \leq m \leq n$) 表示 $2^m$ 或 0 ,因此我們可以從 $a_n$ 找回 $a_0$ 並把結果記錄下來 $P_m = a_n + ... + a_0$ 。
為了決定 $P_m$ 中的 $a_m$ 屬於 $a^m$ 或 0 ,利用以下規則︰
>If $P_m^2 \leq N^2$, $P_{m+1} = P_m - 2^m$
>Else, $P_{m+1} = P_m$
:::info
對應的程式碼
```cpp=
if (x >= b) {
YYY;
y += m;
}
```
:::
並且為了每次都去計算 $P_m^2$ ,在每一次計算的時候,我們僅紀錄 $X_m = N^2-P_m^2$ ,並用 $X_{m+1} = X_m + Y_m$ 的方式更新 $X_m$ 。(其中 $Y_m = P_m^2 - P_{m+1}^2=2P_{m+1}a_m+a_m^2$)
接著要從最大的 n 開始往下找,也就是 `m = 1UL << (fls(x) & ~1UL)` 這行的作用,並令 $P_n = a_n$ 。然後我們用兩個新變數 $c_m$ 和 $d_m$ 分別儲存 $Y_m$ 中的兩個部份。
>$c_m=P_{m+1}2^{m+1}$ (本題的 `y`)
>$d_m=(2^m)^2$ (本題的 `m`)
最後 $c_m$ 和 $d_m$ 會利用以下方式更新。
>$c_{m-1} = P_m2^m = P_{m+1}2^m + a_m2^m$.
If $a_m = 2^m$, $c_{m-1} = \dfrac{c_m}{2}+d_m$. Else $c_{m-1} = \dfrac{c_m}{2}$
>$d_{m-1}=\dfrac{d_m}{4}$
`XXX`
$\rightarrow$ y >>= 1
`YYY`
$\rightarrow$ x -= b
`ZZZ`
$\rightarrow$ m >>= 2
---