---
tags: linux2022
---
# 2022q1 Homework2 (quiz2)
contributed by < `hsuedw` >
* [作業需求](https://hackmd.io/@sysprog/BybKYf5xc)
* [測驗題目](https://hackmd.io/@sysprog/linux2022-quiz2#2022q1-%E7%AC%AC-2-%E9%80%B1%E6%B8%AC%E9%A9%97%E9%A1%8C)
* [作業繳交區](https://hackmd.io/@sysprog/linux2022-homework2)
---
# 測驗 1
## 題目
* 以 bitwise 操作對兩個無號整數取平均值。
* 也就是撰寫程式碼 2 中的 `EXP1` 使其功能等價於程式碼 1。
* 也就是撰寫程式碼 3 中的 `EXP2` 與 `EXP3` 使其功能等價於程式碼 1。
* 程式碼 1
```c
#include <stdint.h>
uint32_t average(uint32_t a, uint32_t b)
{
return (a + b) / 2;
}
```
* 程式碼 2
```c
#include <stdint.h>
uint32_t average(uint32_t a, uint32_t b)
{
return (a >> 1) + (b >> 1) + (EXP1);
}
```
* 程式碼 3
```c
uint32_t average(uint32_t a, uint32_t b)
{
return (EXP2) + ((EXP3) >> 1);
}
```
## 作答
* `EXP1` = `a & b & 1`
* 假設 $b \ge a$
* `EXP2` = `a & b`
* `EXP3` = `a ^ b`
## 延伸問題 1. 解釋上述程式碼運作的原理
* 程式碼 2 運作的原理
* 在 bitwise 運算中,向左位移一個 bit 就相當於 $\lfloor x \rfloor$,其中 $x = \dfrac{a}{2}$
* 雖然在數學上 $\dfrac{a+b}{2}$ = $\dfrac{a}{2}$ + $\dfrac{b}{2}$,但是在 C 語言中,當 a 與 b 皆為奇數時,因為 a 與 b 皆個別先除以 2 再相加,所以平均值會少 1。假設 a = 3,b = 5。
* (a + b) / 2 = 4
* a / 2 + b / 2 = 3
* 若 a 與 b 為一奇一偶或皆為偶數,則沒有這個現象。
* 一個奇數的二進為編碼中,LSB 必為 1。若 `a & b` 的 LSB 為 1,表示 a 與 b 皆為奇數。所以將 EXP1 實作為 `a & b & 1` 可補上當 a 與 b 皆為奇數時,各別先除二再相加所少掉的那個 1。
* 程式碼 3 運作的原理
* AND 與 XOR 的真值表如下所示。
| | | AND | XOR |
| --- | --- | --- | --- |
| 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 0 |
* 一個位元的加法運算如下所示。觀察後可發現進位 (carry) 那一欄就是 AND 運算。 bit sum 那一欄就是 XOR 運算。所以可以用 AND 與 XOR 進行加法運算。
| | carry | bit sum |
| ------ | ----- | ------- |
| 0 + 0 | 0 | 0 |
| 0 + 1 | 0 | 1 |
| 1 + 0 | 0 | 1 |
| 1 + 1 | 1 | 0 |
* 對兩個整數 a 與 b 做加法。 `a ^ b` 就是每個位元的各別位元和。 `(a & b) << 1` 就是兩數相加後,各別位元的進位狀況。所以,C 語言程式碼中, `a + b` 可改寫為 `(a ^ b) + ((a & b) << 1)`。
* `(a + b) / 2` 就是 `(a + b) >> 1`。
* 所以, `(a + b) >> 1` 就是 `((a ^ b) + ((a & b) << 1)) >> 1`。展開後就可以得到 `(a & b) + (a ^ b) >> 1`。
* 所以,`EXP2` 可實作為 `a & b` , `EXP3` 可實作為 `a ^ b`。
* 參考資料:
* [解讀計算機編碼](https://hackmd.io/vOjbtew3Tn2aA0uDrmUL4Q?view#%E8%A7%A3%E8%AE%80%E8%A8%88%E7%AE%97%E6%A9%9F%E7%B7%A8%E7%A2%BC)
* [你所不知道的 C 語言:數值系統篇, 運用 bit-wise operator](https://hackmd.io/@sysprog/c-numerics#%E9%81%8B%E7%94%A8-bit-wise-operator)
* [以 C 語言實作二進位加法 (Binary Addition)](https://kopu.chat/%e4%bb%a5c%e5%af%a6%e4%bd%9c%e4%ba%8c%e9%80%b2%e4%bd%8d%e5%8a%a0%e6%b3%95/)
## 延伸問題 2. 比較上述實作在編譯器最佳化開啟的狀況,對應的組合語言輸出,並嘗試解讀 (可研讀 CS:APP 第 3 章)
* working
## 延伸問題 3. 研讀 Linux 核心原始程式碼 include/linux/average.h,探討其 Exponentially weighted moving average (EWMA) 實作,以及在 Linux 核心的應用
> 移動平均(Moving average),又稱滾動平均值、滑動平均,在統計學中是種藉由建立整個資料集合中不同子集的一系列平均數,來分析資料點的計算方法。
> 移動平均通常與時間序列資料一起使用,以消除短期波動,突出長期趨勢或周期。短期和長期之間的閾值取決於應用,移動平均的參數將相應地設置。例如,它通常用於對財務數據進行技術分析,如股票價格、收益率或交易量。它也用於經濟學中研究國內生產總值、就業或其他宏觀經濟時間序列。
* working
---
# 測驗 2
## 題目
```c
#include <stdint.h>
uint32_t max(uint32_t a, uint32_t b)
{
return a ^ ((EXP4) & -(EXP5));
}
```
## 作答
* `EXP4`: `a ^ b`
* `EXP5`: `a < b`
## 延伸問題 1. 解釋上述程式碼運作的原理
* 列舉部分 AND 與 XOR 運算的特性。
* a ^ a = 0
* a ^ 0 = a
* a ^ 0xffffffff = 0 (假設 a 為 32 位元整數)
* a & a = a
* a & 0 = 0
* a & 0xffffffff = a
* 為了說明方便,把程式碼補齊,如下所示。
```c
#include <stdint.h>
uint32_t max(uint32_t a, uint32_t b)
{
return a ^ ((a ^ b) & -(a < b));
}
```
* 假設 a > b
* `a ^ ((a ^ b) & -(a < b))`
* ⇒ `a ^ ((a ^ b) & -(0))`
[C99 Standard](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf)
> 6.5.8 Relational operators
> Each of the operators < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to) shall yield 1 if the specified relation is true and 0 if it is false.) The result has type int.
* ⇒ `a ^ ((a ^ b) & 0)`
* ⇒ `a ^ (0)`
* ⇒ `a`
* 假設 a < b
* `a ^ ((a ^ b) & -(a < b))`
* ⇒ `a ^ ((a ^ b) & -(1))`
* ⇒ `a ^ ((a ^ b) & 0xffffffff)`
* ⇒ `a ^ (a ^ b)`
* ⇒ `(a ^ a) ^ b`
* ⇒ `(0) ^ b`
* ⇒ `b`
* 假設 a = b
* `a ^ ((a ^ b) & -(a < b))`
* ⇒ `a ^ ((a ^ a) & -(a < a))`
* ⇒ `a ^ ((0) & -(1))`
* ⇒ `a ^ (0x00000000 & 0xffffffff)`
* ⇒ `a ^ (0)`
* ⇒ `a`
* 參考資料:
* [Compute the minimum or maximum of two integers without branching](https://www.geeksforgeeks.org/compute-the-minimum-or-maximum-max-of-two-integers-without-branching/)
## 延伸問題 2. 針對 32 位元無號/有號整數,撰寫同樣 branchless 的實作
* working
## 延伸問題 3. Linux 核心也有若干 branchless / branch-free 的實作,例如 lib/sort.c。
```c
/*
* Logically, we're doing "if (i & lsbit) i -= size;", but since the
* branch is unpredictable, it's done with a bit of clever branch-free
* code instead.
*/
__attribute_const__ __always_inline
static size_t parent(size_t i, unsigned int lsbit, size_t size)
{
i -= size;
i -= size & -(i & lsbit);
return i / 2;
}
```
請在 Linux 核心原始程式碼中,找出更多類似的實作手法。請善用 git log 檢索。
---
# 測驗 3
## 題目
考慮以下 64 位元 GCD ([greatest common divisor](https://en.wikipedia.org/wiki/Greatest_common_divisor), 最大公因數) 求值函式:
```c
#include <stdint.h>
uint64_t gcd64(uint64_t u, uint64_t v)
{
if (!u || !v) return u | v;
while (v) {
uint64_t t = v;
v = u % v;
u = t;
}
return u;
}
```
註: GCD 演算法
1. If both x and y are 0, gcd is zero $gcd(0, 0)= 0$.
2. $gcd(x, 0)= x$ and $gcd(0, y) = y$ because everything divides 0.
3. If x and y are both even, $gcd(x, y) = 2 ∗ gcd(\dfrac{x}{2},\dfrac{y}{2})$ because 2 is a common divisor. Multiplication with 2 can be done with bitwise shift operator.
4. If x is even and y is odd, $gcd(x, y) = gcd(\dfrac{x}{2}, y)$.
5. On similar lines, if x is odd and y is even, then $gcd(x, y) = gcd(x, \dfrac{y}{2})$. It is because 2 is not a common divisor.
改寫為以下等價實作:
```c
#include <stdint.h>
uint64_t gcd64(uint64_t u, uint64_t v)
{
if (!u || !v) return u | v;
int shift;
for (shift = 0; !((u | v) & 1); shift++) {
u /= 2, v /= 2;
}
while (!(u & 1))
u /= 2;
do {
while (!(v & 1))
v /= 2;
if (u < v) {
v -= u;
} else {
uint64_t t = u - v;
u = v;
v = t;
}
} while (COND);
return RET;
}
```
## 作答
* `COND` = `v`
* `RET` = `u << shift`
## 延伸問題 1. 解釋上述程式運作原理
* 就是題目附註裡寫的 GCD 演算法。
## 延伸問題 2. 在 x86_64 上透過 __builtin_ctz 改寫 GCD,分析對效能的提升
* working
## 延伸問題 3. Linux 核心中也內建 GCD (而且還不只一種實作),例如 lib/math/gcd.c,請解釋其實作手法和探討在核心內的應用場景。
* working
---
# 測驗 4
## 題目
在影像處理中,[bit array](https://en.wikipedia.org/wiki/Bit_array) (也稱 bitset) 廣泛使用,考慮以下程式碼:
```c
#include <stddef.h>
size_t naive(uint64_t *bitmap, size_t bitmapsize, uint32_t *out)
{
size_t pos = 0;
for (size_t k = 0; k < bitmapsize; ++k) {
uint64_t bitset = bitmap[k];
size_t p = k * 64;
for (int i = 0; i < 64; i++) {
if ((bitset >> i) & 0x1)
out[pos++] = p + i;
}
}
return pos;
}
```
考慮 GNU extenstion 的 [__builtin_ctzll](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)的行為是回傳由低位往高位遇上連續多少個 0 才碰到 1。
> 範例: 當 a = 16
16 這個十進位數值的二進位表示法為 00000000 00000000 00000000 00010000
從低位元 (即右側) 往高位元,我們可發現 0 → 0 → 0 → 0 → 1 ,於是 ctz 就為 4,表示最低位元往高位元有 4 個 0
用以改寫的程式碼如下:
```c=
#include <stddef.h>
size_t improved(uint64_t *bitmap, size_t bitmapsize, uint32_t *out)
{
size_t pos = 0;
uint64_t bitset;
for (size_t k = 0; k < bitmapsize; ++k) {
bitset = bitmap[k];
while (bitset != 0) {
uint64_t t = EXP6;
int r = __builtin_ctzll(bitset);
out[pos++] = k * 64 + r;
bitset ^= t;
}
}
return pos;
}
```
其中第 9 行的作用是找出目前最低位元的 `1`,並紀錄到 `t` 變數。舉例來說,若目前 `bitset` 為 000101~b~,那 `t` 就會變成 000001~b~,接著就可以透過 XOR 把該位的 `1` 清掉,其他保留 (此為 XOR 的特性)。
若 bitmap 越鬆散 (即 `1` 越少),於是 `improved` 的效益就更高。
請補完程式碼。書寫規範:
* bitwise 運算子和運算元之間用一個半形空白區隔,如: bitset | 0x1
* 考慮到 -bitwise 的特性
* 不要包含任何小括號,即 ( 和 )
* 以最精簡的形式撰寫程式碼
## 作答
* `EXP6` = `-bitset & bitset`
## 延伸問題 1. 解釋上述程式運作原理,並舉出這樣的程式碼用在哪些真實案例中
* 運作原理
* 假設 `bitset` 的二進位表示法中,由 LSB 往 MSB 數,遇到第一個 `1` 前,有 x 個 `0`。目標就是保留位元 x 為 1。其餘位元皆設為 `0`。
* 假設 `bitset` = 1312~10~ = 0000010100100000~b~
* 則 `-bitset` = -1312~10~ = 1111101011100000~b~
* 所以, `-bitset & bitset` 結果如下。
```
1111101011100000
& 0000010100100000
---------------------
0000000000100000
```
* 用在哪些真實案例中
* working
## 延伸問題 2. 設計實驗,檢驗 ctz/clz 改寫的程式碼相較原本的實作有多少改進?應考慮到不同的 bitmap density
* working
## 延伸問題 3. 思考進一步的改進空間
* working
## 延伸問題 4. 閱讀 Data Structures in the Linux Kernel 並舉出 Linux 核心使用 bitmap 的案例
* working
---
# 測驗 5
## 題目
以下是 LeetCode [166. Fraction to Recurring Decimal](https://leetcode.com/problems/fraction-to-recurring-decimal/) 的可能實作:
```c=
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"
struct rem_node {
int key;
int index;
struct list_head link;
};
static int find(struct list_head *heads, int size, int key)
{
struct rem_node *node;
int hash = key % size;
list_for_each_entry (node, &heads[hash], link) {
if (key == node->key)
return node->index;
}
return -1;
}
char *fractionToDecimal(int numerator, int denominator)
{
int size = 1024;
char *result = malloc(size);
char *p = result;
if (denominator == 0) {
result[0] = '\0';
return result;
}
if (numerator == 0) {
result[0] = '0';
result[1] = '\0';
return result;
}
/* using long long type make sure there has no integer overflow */
long long n = numerator;
long long d = denominator;
/* deal with negtive cases */
if (n < 0)
n = -n;
if (d < 0)
d = -d;
bool sign = (float) numerator / denominator >= 0;
if (!sign)
*p++ = '-';
long long remainder = n % d;
long long division = n / d;
sprintf(p, "%ld", division > 0 ? (long) division : (long) -division);
if (remainder == 0)
return result;
p = result + strlen(result);
*p++ = '.';
/* Using a map to record all of reminders and their position.
* if the reminder appeared before, which means the repeated loop begin,
*/
char *decimal = malloc(size);
memset(decimal, 0, size);
char *q = decimal;
size = 1333;
struct list_head *heads = malloc(size * sizeof(*heads));
for (int i = 0; i < size; i++)
INIT_LIST_HEAD(&heads[i]);
for (int i = 0; remainder; i++) {
int pos = find(heads, size, remainder);
if (pos >= 0) {
while (PPP > 0)
*p++ = *decimal++;
*p++ = '(';
while (*decimal != '\0')
*p++ = *decimal++;
*p++ = ')';
*p = '\0';
return result;
}
struct rem_node *node = malloc(sizeof(*node));
node->key = remainder;
node->index = i;
MMM(&node->link, EEE);
*q++ = (remainder * 10) / d + '0';
remainder = (remainder * 10) % d;
}
strcpy(p, decimal);
return result;
}
```
請補完,使得程式碼符合預期,儘量以最簡短且符合一致排版的形式來撰寫。
PPP = ? (表示式)
MMM = ? (巨集)
EEE = ? (表示式)
## 作答
* `PPP` = `pos--`
* `MMM` = `list_add`
* `EEE` = `&heads[remainder % size]`
## 延伸問題 1. 解釋上述程式碼運作原理,指出其中不足,並予以改進
> 例如,判斷負號只要寫作 bool isNegative = numerator < 0 ^ denominator < 0;
> 搭配研讀 [The simple math behind decimal-binary conversion algorithms](https://indepth.dev/posts/1019/the-simple-math-behind-decimal-binary-conversion-algorithms)
* 第 26~24 行。
* 用 `malloc` 動態宣告一個長度為 1024 bytes 的記憶體空間。用這個記憶體空間存字串。並用 `result` 這個指標指向這個記憶體空間的起始位址。然後再宣告一個指標 `p` 用來操作 `result` 所指向的記憶體空間。原則上,就是用 `result` 所指向的記憶體空間做出一個 C sytle string。
* 根據題意,將計算結果表示為字串,此字串長度小於 10^4^。所以,將變數 `size` 初始化為 1024 並以此宣告記憶體大小,顯然有問題。
:::info
It is guaranteed that the length of the answer string is less than 10^4^ for all the given inputs.
:::
* 若考慮 C style string 的 null terminator。 `size` 應初始化為 10001 較為恰當。
* 第 30~32 行。
* 若 `denominator` (分母)為 0,則回傳空字串。
* 事實上,這個判斷可以刪除,不需要實作這三行。因為題目的 constraints 中已經明白的說明了分子與分母的數值範圍。根據題意,分母不可能為 0。
:::info
Constraints:
* -2^31^ <= numerator, denominator <= 2^31^ - 1
* denominator != 0
:::
* 第 35~39 行。
* 若 `numerator` 為 0,則計算結果必為 0。所以直接在 `result` 所指向的記憶體空間的第一個字元填 `0`,第二個字元填 `\0` (null terminator)。並回傳此 C style string。
* 第 41~53 行。
* 用兩個型態為 `long long` 的變數 `n` 與 `d` 分別儲存 `numerator` 與 `denominator` 的數值。並對 `n` 與 `d` 取絕對值。若 `numerator` 除以 `denominator` 是負數,在第 51~53 行中,把負號填入 `result` 所指向的記憶體空間的第一個字元。然後,指標 `p` 往後移一個 byte。
* 第 51~53 行中判斷最後的答案是否有負號,可改為下列的實作。因為 `numerator` 和 `denominator` 不同時為正數或不同時為負數,最後的答案就會有負號。所以可用 XOR 實作。
```c=51
bool sign = (numerator < 0) ^ (denominator < 0);
if (sign)
*p++ = '-';
```
* 第 55~63 行。
* 分別把 `n` 除以 `d` 的餘數與商數指派給 `remainder` 與 `division` 兩個變數中。
* 以指標 `p` 所指的記憶體位址為起始,用 `sprintf` 與格式化字串把 `division` 列印到 `result` 所指向的記憶體空間。
* 因為第 46~53 已經處理了正負號的問題,所以執行到第 58 行時, `division` 必為正數。第 58 行應改為:
```c=58
sprintf(p, "%ld", (long) division);
```
* 若 `remainder` (餘數)為 0 ,表示整除。可以直接回傳 `result` 所指向的記憶體空間內的字串。
* 若 `remainder` (餘數)不為 0 ,表示有小數位數。所以將指標 `p` 移到字串的尾端(第 62 行)後,在字串尾端加入小數點。
* 第 68~70 行。
* 以指標 `decimal` 指向以 `size` 為大小所動態宣告的記憶體空間。並將此記憶體空間的內容清為 0。
* 以指標 `q` 為操作 `decimal` 所指向的記憶體空間。
* 因為題目已經保證最後的答案部會超過 10^4^,所以第 68~69 行可以改為
```c=68
char *decimal = malloc(size - strlen(result));
memset(decimal, 0, size - strlen(result));
char *q = decimal;
```
* 第 72~75 行。
* 建立並初始化一個有 1333 個 bucket 的 hash table (指標 `heads` 所指的記憶體空間)。每個 bucket 都是一個環狀雙向鏈結串列。
* 不了解為什麼要 1333 個 bucket。
* 第 77~97 行。
* 這個 `for` 迴圈會一直執行到 `remainder` 等於 0 為止。
* `for` 迴圈的 i 等於 0,表示小數點後第一位;i 等於 1,表示小數點後第二位
* 若在 hash table 中找到 `remainder`,代表發生循環小數。
* 透過指標 `p` 的操作,把 `decimal` 裡的資料複製到 `result` 所指向的記憶體空間中。 (第 80~81 行)
* pos 是循環小數開始的位置,所以先將尚未循環的小數位數填到 `result` 所指向的記憶體空間中。
* 透過指標 `p` 的操作,在字串最後加上 open parenthesis。(第 82 行)
* 透過指標 `p` 的操作,再把 `decimal` 中剩下的循環小數填到 `result` 所指向的記憶體空間中。(第 83~84 行)
* 透過指標 `p` 的操作,在字串最後加上 closing parenthesis。(第 85 行)
* 最後,透過指標 `p` 的操作,填上 `'\0'` (null terminator)。然後回傳結果(就是 result` 所指向的記憶體空間中的 C style string)。 (第 86~87 行)
* 若在 hash table 中找不到 `remainder`,代表沒有發生循環小數。
* 用動態記憶體宣告產生一個 `struct rem_node` 型態的 object,並以指標 `node` 指向它。利用此物件把這一次的 `numerator` 與 `i` 記錄在 hash table (指標 `heads` 所指的記憶體空間)中。 (第 89~91 行)
* 透過指標 `q` 的操作,將小數點後的第一位寫入 `decimal` 所指向的記憶體空間中的字串的尾端。 (第 95 行)
* 更新 `remainder` 。 (第 96 行)
* 第 99~100 行
* 若第 77~97 行的 `for` 迴圈結束(`remainder` 歸 0),表示沒有循環小數發生。把 `decimal` 裡的所有字元全部複製到 `result` 所指向的記憶體空間中的字串的尾端。
* 回傳最後結果。
:::spoiler 完整程式碼
```c
struct list_head {
struct list_head *prev;
struct list_head *next;
};
#define container_of(ptr, type, member) \
__extension__({ \
const __typeof__(((type *) 0)->member) *__pmember = (ptr); \
(type *) ((char *) __pmember - offsetof(type, member)); \
})
#define list_entry(node, type, member) container_of(node, type, member)
#define list_for_each_entry_safe(entry, safe, head, member) \
for (entry = list_entry((head)->next, __typeof__(*entry), member), \
safe = list_entry(entry->member.next, __typeof__(*entry), member); \
&entry->member != (head); entry = safe, \
safe = list_entry(safe->member.next, __typeof__(*entry), member))
#define list_for_each_entry(entry, head, member) \
for (entry = list_entry((head)->next, __typeof__(*entry), member); \
&entry->member != (head); \
entry = list_entry(entry->member.next, __typeof__(*entry), member))
#define list_last_entry(head, type, member) \
list_entry((head)->prev, type, member)
typedef struct {
int capacity, count;
struct list_head dhead, hheads[];
} LRUCache;
typedef struct {
int key, value;
struct list_head hlink, dlink;
} LRUNode;
static inline void INIT_LIST_HEAD(struct list_head *head)
{
head->next = head;
head->prev = head;
}
static inline void list_add(struct list_head *node, struct list_head *head)
{
struct list_head *next = head->next;
next->prev = node;
node->next = next;
node->prev = head;
head->next = node;
}
static inline void list_del(struct list_head *node)
{
struct list_head *next = node->next;
struct list_head *prev = node->prev;
next->prev = prev;
prev->next = next;
#ifdef LIST_POISONING
node->prev = (struct list_head *) (0x00100100);
node->next = (struct list_head *) (0x00200200);
#endif
}
static inline void list_move(struct list_head *node, struct list_head *head)
{
list_del(node);
list_add(node, head);
}
struct rem_node {
int key;
int index;
struct list_head link;
};
static int find(struct list_head *heads, int size, int key)
{
struct rem_node *node;
int hash = key % size;
list_for_each_entry (node, &heads[hash], link) {
if (key == node->key)
return node->index;
}
return -1;
}
char *fractionToDecimal(int numerator, int denominator)
{
int size = 10001;
char *result = malloc(size);
char *p = result;
if (numerator == 0) {
result[0] = '0';
result[1] = '\0';
return result;
}
/* using long long type make sure there has no integer overflow */
long long n = numerator;
long long d = denominator;
/* deal with negtive cases */
if (n < 0)
n = -n;
if (d < 0)
d = -d;
bool sign = (numerator < 0) ^ (denominator < 0);
if (sign)
*p++ = '-';
long long remainder = n % d;
long long division = n / d;
sprintf(p, "%ld", (long) division);
if (remainder == 0)
return result;
p = result + strlen(result);
*p++ = '.';
/* Using a map to record all of reminders and their position.
* if the reminder appeared before, which means the repeated loop begin,
*/
char *decimal = malloc(size - strlen(result));
memset(decimal, 0, size - strlen(result));
char *q = decimal;
size = 1333;
struct list_head *heads = malloc(size * sizeof(*heads));
for (int i = 0; i < size; i++)
INIT_LIST_HEAD(&heads[i]);
for (int i = 0; remainder; i++) {
int pos = find(heads, size, remainder);
if (pos >= 0) {
while (pos-- > 0)
*p++ = *decimal++;
*p++ = '(';
while (*decimal != '\0')
*p++ = *decimal++;
*p++ = ')';
*p = '\0';
return result;
}
struct rem_node *node = malloc(sizeof(*node));
node->key = remainder;
node->index = i;
list_add(&node->link, &heads[remainder % size]);
*q++ = (remainder * 10) / d + '0';
remainder = (remainder * 10) % d;
}
strcpy(p, decimal);
return result;
}
```
:::
## 延伸問題 2. 在 Linux 核心原始程式碼的 `mm/` 目錄 (即記憶體管理) 中,找到特定整數比值 (即 fraction) 和 bitwise 操作的程式碼案例,予以探討和解說其應用場景
* working
---
# 測驗 6
## 題目
[__alignof__](https://gcc.gnu.org/onlinedocs/gcc/Alignment.html) 是 GNU extension,以下是其可能的實作方式:
```c
/*
* ALIGNOF - get the alignment of a type
* @t: the type to test
*
* This returns a safe alignment for the given type.
*/
#define ALIGNOF(t) \
((char *)(&((struct { char c; t _h; } *)0)->M) - (char *)X)
```
請補完上述程式碼,使得功能與 __alignof__ 等價。
請補完,使得程式碼符合預期,儘量以最簡短且符合一致排版的形式來撰寫
M = ?
X = ?
## 作答
* `M` = `_h`
* `X` = `0`
* test my answer
```c
#include <stdio.h>
#include <stddef.h>
//#define ALIGNOF(t) \
// ((char *)(&((struct { char c; t _h; } *)0)->_h) - (char *)(&((struct { char c; t _h; } *)0)->c))
#define ALIGNOF(t) \
((char *)(&((struct { char c; t _h; } *)0)->_h) - (char *)0)
int main()
{
printf("char, %ld\n", __alignof__(char));
printf("unsigned char, %ld\n", __alignof__(unsigned char));
printf("int, %ld\n", __alignof__(int));
printf("long, %ld\n", __alignof__(long));
printf("long long, %ld\n", __alignof__(long long));
printf("unsigned ing, %ld\n", __alignof__(unsigned int));
printf("unsigned long, %ld\n", __alignof__(unsigned long));
printf("unsigned long long, %ld\n", __alignof__(unsigned long long));
printf("double, %ld\n", __alignof__(double));
printf("float, %ld\n", __alignof__(float));
printf("---------------\n");
printf("char, %ld\n", ALIGNOF(char));
printf("unsigned char, %ld\n", ALIGNOF(unsigned char));
printf("int, %ld\n", ALIGNOF(int));
printf("long, %ld\n", ALIGNOF(long));
printf("long long, %ld\n", ALIGNOF(long long));
printf("unsigned ing, %ld\n", ALIGNOF(unsigned int));
printf("unsigned long, %ld\n", ALIGNOF(unsigned long));
printf("unsigned long long, %ld\n", ALIGNOF(unsigned long long));
printf("double, %ld\n", ALIGNOF(double));
printf("float, %ld\n", ALIGNOF(float));
return 0;
}
```
* output
```
char, 1
unsigned char, 1
int, 4
long, 8
long long, 8
unsigned ing, 4
unsigned long, 8
unsigned long long, 8
double, 8
float, 4
---------------
char, 1
unsigned char, 1
int, 4
long, 8
long long, 8
unsigned ing, 4
unsigned long, 8
unsigned long long, 8
double, 8
float, 4
```
* 參考資料:
* [Linux 核心原始程式碼巨集: container_of](https://hackmd.io/@sysprog/linux-macro-containerof)
* [kevinshieh0225](https://hackmd.io/@Masamaloka/linux2022-quiz2#%E6%B8%AC%E9%A9%97-6%EF%BC%9A__alignof__)
* [6.44 Determining the Alignment of Functions, Types or Variables](https://gcc.gnu.org/onlinedocs/gcc/Alignment.html)
* [offsetof - wikipedia](https://en.wikipedia.org/wiki/Offsetof)
## 延伸問題 1. 解釋上述程式碼運作原理
* `(char *)(&((struct { char c; t _h; } *)0)->_h)`
* 先將 `struct { char c; t _h; }` 型態結構體的位址變更為 0。
* 然後再提取型態為 `t` 的成員 `_h` 的記憶體位址。
* 然後再轉成 `char *` ,也就是由位址 0 到成員 `_h` 的 offset。
* [The (char *) casting in container_of() macro in linux kernel](https://stackoverflow.com/questions/20421910/the-char-casting-in-container-of-macro-in-linux-kernel)
* 同理,`(char *)0` 就是取得位址 0 。
* 兩者相減就是型態 `t` 的 alignment。
* 編譯器處理 `&((TYPE *)0)->MEMBER` 時,不會真正去存取地址為 0 的記憶體區段,只是將 0 看做指標操作的一個運算元。也就是說,編譯器只是把記憶體位址拿來當數字一樣做運算。並不會真的對記憶體位址所表示的記憶體區間做存取的動作。
## 延伸問題 2. 在 Linux 核心原始程式碼中找出 __alignof__ 的使用案例 2 則,並針對其場景進行解說
* working
## 延伸問題 3. 在 Linux 核心源使程式碼找出 ALIGN, ALIGN_DOWN, ALIGN_UP 等巨集,探討其實作機制和用途,並舉例探討 (可和上述第二點的案例重複)。思索能否對 Linux 核心提交貢獻,儘量共用相同的巨集
* working
---
# 測驗 7
## 題目
```c=
static inline bool is_divisible(uint32_t n, uint64_t M)
{
return n * M <= M - 1;
}
static uint64_t M3 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1;
static uint64_t M5 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 5 + 1;
int main(int argc, char **argv)
{
for (size_t i = 1; i <= 100; i++) {
uint8_t div3 = is_divisible(i, M3);
uint8_t div5 = is_divisible(i, M5);
unsigned int length = (2 << KK1) << KK2;
char fmt[9];
strncpy(fmt, &"FizzBuzz%u"[(9 >> div5) >> (KK3)], length);
fmt[length] = '\0';
printf(fmt, i);
printf("\n");
}
return 0;
}
```
## 作答
* `KK1` = `div3`
* `KK2` = `div5`
* `KK3` = `div3 << 2`
* note: 第 17 行應改為
```c=17
strncpy(fmt, &"FizzBuzz%u"[(8 >> div5) >> (KK3)], length);
```
否則,當 i 不為 3、5 或 15 的倍數時,無法印出 i 的值。
:::spoiler 完整程式碼
```c
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
static inline bool is_divisible(uint32_t n, uint64_t M)
{
return n * M <= M - 1;
}
static uint64_t M3 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1;
static uint64_t M5 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 5 + 1;
int main(int argc, char **argv)
{
for (size_t i = 1; i <= 100; i++) {
uint8_t div3 = is_divisible(i, M3);
uint8_t div5 = is_divisible(i, M5);
unsigned int length = (2 << div3) << div5;
char fmt[9];
strncpy(fmt, &"FizzBuzz%u"[(8 >> div5) >> (div3 << 2)], length);
fmt[length] = '\0';
printf(fmt, i);
printf("\n");
}
return 0;
}
```
:::
## 延伸問題 解釋上述程式運作原理並評估 `naive.c` 和 `bitwise.c` 效能落差 (避免 stream I/O 帶來的影響,可將 printf 更換為 sprintf)
## 延伸問題 2. 分析 [Faster remainders when the divisor is a constant: beating compilers and libdivide](https://lemire.me/blog/2019/02/08/faster-remainders-when-the-divisor-is-a-constant-beating-compilers-and-libdivide/) 一文的想法 (可參照同一篇網誌下方的評論),並設計另一種 bitmask,如「可被 3 整除則末位設為 1」「可被 5 整除則倒數第二位設定為 1」,然後再改寫 bitwise.c 程式碼,試圖運用更少的指令來實作出 branchless; (參照 [fastmod: A header file for fast 32-bit division remainders on 64-bit hardware](https://github.com/lemire/fastmod))
## 延伸問題 3. 研讀 [The Fastest FizzBuzz Implementation](https://tech.marksblogg.com/fastest-fizz-buzz.html) 及其 [Hacker News](https://news.ycombinator.com/item?id=29413656) 討論,提出 throughput (吞吐量) 更高的 Fizzbuzz 實作
## 延伸問題 4. 解析 Linux 核心原始程式碼 [kernel/time/timekeeping.c](https://github.com/torvalds/linux/blob/master/kernel/time/timekeeping.c) 裡頭涉及到除法運算的機制,探討其快速除法的實作 (注意: 你可能要對照研讀 `kernel/time/` 目錄的標頭檔和程式碼)
> 過程中,你可能會發現可貢獻到 Linux 核心的空間,請充分討論
---
# 答案卷
* [2022 年 Linux 核心設計第 2 週隨堂測驗 (A)](https://docs.google.com/forms/d/e/1FAIpQLSf80zijSR-BFkgmQULgOS2-6m0HhFkJ7XYN85kpeUtzxd0rdg/viewscore?viewscore=AE0zAgDzT6wysm6S4xl0rhDlzL2X9oj48eZNmUq7-egYf88J6ELqWB1Zrid3MRr6SQ)
* [2022 年 Linux 核心設計第 2 週隨堂測驗 (B)](https://docs.google.com/forms/d/e/1FAIpQLScn1eavQ3ecWxw3pfujjm_6ENGDC3uo8Ks3smTfT0dMhaHHBw/viewscore?viewscore=AE0zAgDJguT91rKdx6Unrl9LdaESpf-9Q58JRUFNCcXc0W9xGEh-1z_XXWPZj-gL2w)