---
# System prepended metadata

title: 2022q1 Homework1 (quiz1)
tags: [linux2022]

---

---
tags: linux2022
---

# 2022q1 Homework1 (quiz1)

contributed by < `SmallHanley` >

> [linux2022-quiz1](https://hackmd.io/@sysprog/linux2022-quiz1)

## 測驗 `1`

:::success
解釋上述程式碼運作原理
:::

首先是資料結構的部分：

```c
struct hlist_node { struct hlist_node *next, **pprev; };
struct hlist_head { struct hlist_node *first; };
typedef struct { int bits; struct hlist_head *ht; } map_t;

struct hash_key {
    int key;
    void *data;
    struct hlist_node node;
};
```

`struct hlist_node` 這個結構體包含指標 `next` ，用來指向下一個節點，以及指標的指標 `prev`，指向前一個節點的 `next` 指標，可以在接到 `hlist_head` 時不用額外判斷，避免分支。

:::warning
這裡不懂開發者選擇使用雙向鏈結串列的考量，查看 history 最早出現 `hash_node` 是在 [v2.5.64](https://elixir.bootlin.com/linux/v2.5.64/A/ident/hlist_node)，github commit history 沒有這麼舊的資料，[ChangeLog-2.5.64](https://cdn.kernel.org/pub/linux/kernel/v2.5/ChangeLog-2.5.64) 也沒有查到有用的資訊。
:::

`struct hlist_head` 結構體則是 hash table 各個 bucket 的 head，其中 `first` 指標指向第一個 `struct hlist_node` 節點。

而 `map_t` 是 hash table，`bits` 代表 bucket 的多寡 (以多少位元表示)，因此 bucket 的數量為 2 的冪。`ht` 則是一個陣列，用來存放每個 bucket 的 head。

`struct hash_key` 結構體是用來包裝每個 key-value pair，key 會根據 hash function 找到對應的 bucket，該 bucket 的 `struct hlist_head` 會串接所有映射到這個 bucket 的 `struct hlist_node`，示意圖如下：

```graphviz
digraph G {

    rankdir=LR;

    node[shape=record];
    map [label="hlist_head.first |<ht0> |<ht1> |<ht2> |<ht3> |<ht4> |<ht5> |<ht7> |<ht8> "];
    
    node[shape=none]
    null1 [label=NULL]
    null2 [label=NULL]

    subgraph cluster_1 {
        style=filled;
		color=lightgrey;
        node [shape=record];
        hn1 [label="hlist_node | {<prev>pprev | <next>next}"];
        label="hash_key 1"
    }
    
    subgraph cluster_2 {
        style=filled;
		color=lightgrey;
        node [shape=record];
        hn2 [label="hlist_node | {<prev>pprev | <next>next}"];
        label="hash_key 2"
    }
    
    subgraph cluster_3 {
        style=filled;
		color=lightgrey;
        node [shape=record];
        hn3 [label="hlist_node | {<prev>pprev | <next>next}"];
        label="hash_key 3"
    }
    
    map:ht1 -> hn1
    hn1:next -> hn2
    hn2:next -> null1
    hn2:prev:s -> hn1:next:s
    map:ht5 -> hn3
    hn3:next -> null2
    
    hn1:prev:s -> map:ht1
    hn3:prev:s -> map:ht5
}
```

* **map_init()**
根據傳進來的 `bits` 來決定 hash table 的大小，`bits` 代表 bucket 的多寡 (以多少位元表示)，因此 hash table 的 size 為 2 的冪。配置 `map_t` 以及 `map->ht` 的空間，並將所有 entry 的 `first` 指標指向 `NULL`。
* **hash()**
即 hash function 的所在，這裡使用 magic number `0x61C88647`，看後續分析 linux kernel 相關實作會不會探討，這裡先跳過。
* **find_key()**
首先根據 key 的值，透過 hash function 映射到 hash table 的其中一個 bucket，並用 for 迴圈走訪每一個 `struct hlist_node` 結構體，如果該結構體的 `key` 是我們要找的，則回傳該結構體，若找不到則回傳 `NULL`。
* **map_get()**
先呼叫 `find_key()` 取得目標結構體，並回傳其 `data`。
* **map_add()**
插入新的 key-data pair 到 hash table，如果該 key 已經存在，則不做任何事情。
* **map_deinit()**
釋放 hash table 相關的資源。

:::success
研讀 Linux 核心原始程式碼 [include/linux/hashtable.h](https://github.com/torvalds/linux/blob/master/include/linux/hashtable.h) 及對應的文件 [How does the kernel implements Hashtables?](https://kernelnewbies.org/FAQ/Hashtables)，解釋 hash table 的設計和實作手法，並留意到 [tools/include/linux/hash.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/hash.h) 的 `GOLDEN_RATIO_PRIME`，探討其實作考量
:::

光是 hash table 的 define 就有分三種：
```c
#define DEFINE_HASHTABLE(name, bits)						\
	struct hlist_head name[1 << (bits)] =					\
			{ [0 ... ((1 << (bits)) - 1)] = HLIST_HEAD_INIT }

#define DEFINE_READ_MOSTLY_HASHTABLE(name, bits)				\
	struct hlist_head name[1 << (bits)] __read_mostly =			\
			{ [0 ... ((1 << (bits)) - 1)] = HLIST_HEAD_INIT }

#define DECLARE_HASHTABLE(name, bits)                                   	\
	struct hlist_head name[1 << (bits)]
```
中間多了 `__read_mostly`，追蹤相關程式碼，發現 `__read_mostly` 的定義是被分散在不同處理器架構的 cache.h (arch/xxx/include/asm/cache.h)，其中，有部分架構定義如下，包含 `arm`、`arm64`、`ia64`、`mips`、`parisc`、`powerpc`、`s390`、`sh`、`sparc`、`x86`：

```c
#define __read_mostly __section(".data..read_mostly")
```

其中 `__section` 又被定義在 [include/linux/compiler_attributes.h](https://elixir.bootlin.com/linux/latest/source/include/linux/compiler_attributes.h#L276) 中，在 [GNU C and C++](https://gcc.gnu.org/) 中，可以用來描述函式的特定性質 (參見 [Declaring Attributes of Functions](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html))。而這裡使用到的是 `section` 這個 attribute (參見 [Function-Attributes-section](https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html))，概念上是將一段程式放置在指定的 special section，推測跟 cache 的相關優化有關。

### hashtable.h

Linux hashtable.h 有 include `linux/rculist.h`，可以參考 linux Documentation [rcu.rst](https://github.com/torvalds/linux/blob/master/Documentation/RCU/rcu.rst)、[listRCU.rst](https://github.com/torvalds/linux/blob/master/Documentation/RCU/listRCU.rst)。

**重點整理：**
* RCU (read-copy update) 的概念是將 destructive operation 分成兩個部分，第一是將要 destroy 的資料隱藏不讓其他 reader 看到，第二是實際執行 destruction 的動作。而兩個部分中間有一段時間稱作 "grace period"，grace period 需要足夠長使得所有 reader drop the reference。
* RCU 在鏈結串列的操作之一，流程上是將節點從鏈結串列中移除，等待 grace period，然後釋放對應空間。
* 使用情境：==待補充==
    * Read-mostly list: Deferred Destruction
    * Read-Side Action Taken Outside of Lock: No In-Place Updates
    * Handling In-Place Updates
    * Eliminating Stale Data
    * Skipping Stale Objects

#### hash_init()

* 將 hash table 初始化。
* 從上面示意圖可以知道 hash table 是一個 `struct hlist_head` 組成的陣列，每個 `struct hlist_head` 是映射到同一個 bucket 的鏈結串列的 head。將每個結構體的成員 `first` 初始化成 `NULL`。

```c
// linux/list.h
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)

// linux/hashtable.h
static inline void __hash_init(struct hlist_head *ht, unsigned int sz)
{
	unsigned int i;

	for (i = 0; i < sz; i++)
		INIT_HLIST_HEAD(&ht[i]);
}

#define hash_init(hashtable) __hash_init(hashtable, HASH_SIZE(hashtable))
```

#### hash_add()

```c
// linux/hashtable.h
#define hash_add(hashtable, node, key)						\
	hlist_add_head(node, &hashtable[hash_min(key, HASH_BITS(hashtable))])

// linux/list.h
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
	struct hlist_node *first = h->first;
	WRITE_ONCE(n->next, first);
	if (first)
		WRITE_ONCE(first->pprev, &n->next);
	WRITE_ONCE(h->first, n);
	WRITE_ONCE(n->pprev, &h->first);
}
```

[commit c54a274](https://github.com/torvalds/linux/commit/c54a2744497db4b6887b9c905ef7aa0b3620c956)

> Also add various WRITE_ONCE() in __hlist_del(), hlist_add_head()
and hlist_add_before()/hlist_add_behind() to pair with
the READ_ONCE().

## hash.h 

在 [tools/include/linux/hash.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/hash.h) 可以發現一個巨集 `GOLDEN_RATIO_PRIME`，

```c
/*
 * Knuth recommends primes in approximately golden ratio to the maximum
 * integer representable by a machine word for multiplicative hashing.
 * Chuck Lever verified the effectiveness of this technique:
 * http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf
 *
 * These primes are chosen to be bit-sparse, that is operations on
 * them can use shifts and additions instead of multiplications for
 * machines where multiplications are slow.
 */
#if BITS_PER_LONG == 32
/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */
#define GOLDEN_RATIO_PRIME 0x9e370001UL
#elif BITS_PER_LONG == 64
/*  2^63 + 2^61 - 2^57 + 2^54 - 2^51 - 2^18 + 1 */
#define GOLDEN_RATIO_PRIME 0x9e37fffffffc0001UL
#else
#error Define GOLDEN_RATIO_PRIME for your wordsize.
#endif
```

在 [commit 689de1d](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/include/linux/hash.h?id=689de1d6ca95b3b5bd8ee446863bf81a4883ea25) 後改成以下實作版本：

```c
/*
 * This hash multiplies the input by a large odd number and takes the
 * high bits.  Since multiplication propagates changes to the most
 * significant end only, it is essential that the high bits of the
 * product be used for the hash value.
 *
 * Chuck Lever verified the effectiveness of this technique:
 * http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf
 *
 * Although a random odd number will do, it turns out that the golden
 * ratio phi = (sqrt(5)-1)/2, or its negative, has particularly nice
 * properties.  (See Knuth vol 3, section 6.4, exercise 9.)
 *
 * These are the negative, (1 - phi) = phi**2 = (3 - sqrt(5))/2,
 * which is very slightly easier to multiply by and makes no
 * difference to the hash distribution.
 */
#define GOLDEN_RATIO_32 0x61C88647
#define GOLDEN_RATIO_64 0x61C8864680B583EBull
```

其中有討論到有關 `GOLDEN_RATIO_PRIME` 的實作考量，出自於 The Art of Computer Programming (Knuth vol 3, section 6.4, exercise 9) 這本書，以下取自這本書的片段。

**Theorem S.** Let $\theta$ be any irrational number. When the points {$\theta$}, {$2\theta$}, ..., {$n\theta$} are placed in the line segment [0 ... 1], then $n + 1$ line segments formed have at most three different lengths. Moreover, the next point {$(n + 1)\theta$} will fall in one of the largest existing segments.
(用 Three-gap theorem 推導。)

:::info
{$n \theta$} 代表 $n \theta$ 的小數部分。
:::
之所以要取小數部分，原理是這個分割問題可以 model 成 [Three-gap theorem](https://en.wikipedia.org/wiki/Three-gap_theorem)。line segment [0 … 1] 對應到 Three-gap theorem 中周長為 1 的圓，$n \theta$ 的整數部分相當於繞了圓多少圈 (即在 line segment 循環多少次)，而小數部分相當於新的點坐落於圓上的哪一個位置 (即 line segment 中新分割點的位置)。

有關 **Theorem S** 的證明在 exercise 8

![](https://i.imgur.com/s2klaYD.jpg)

![](https://i.imgur.com/uwOsYPZ.jpg)

任意數乘上這兩個數再取其高位元，可呈現好的數值分布。實際的 hash function (舉 32-bit 為例) 是定義在 [tools/include/linux/hash.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/hash.h#L57-L69)：

```c=
#ifndef HAVE_ARCH__HASH_32
#define __hash_32 __hash_32_generic
#endif
static inline u32 __hash_32_generic(u32 val)
{
	return val * GOLDEN_RATIO_32;
}

static inline u32 hash_32(u32 val, unsigned int bits)
{
	/* High bits are more random, so use them. */
	return __hash_32(val) >> (32 - bits);
}
```

上述的實作是將 0 ~ 1 的浮點數以 32-bit 無號整數表示。第 6 行將欲進行 hash 的數值 `val` 乘上 `GOLDEN_RATIO_32` ，第 12 行是取最高的 `bits` 個位元。對應上述 Fibonacci Hashing 理論，`val` 對應到 $n$，`GOLDEN_RATIO_32` 為 $\phi^{-1}$ 以 32-bit 無號整數表示，兩者相乘，因為會產生溢位 (一次溢位相當於 Three-gap theorem 中繞了圓一圈)，相乘的結果為 {$n \phi^{-1}$} 以 32-bit 無號整數表示，取其高位元，則相當於取小數前幾位數。

## 實驗

實際對比新、舊版本的 `GOLDEN_RATIO_PRIME` 實作，新版的實作可以讓 hash function 結果的分佈更為均勻。以下分別測試 0 到 1000、10000 映射到 hash table 不同 locations 的次數，hash table 的大小為 1024 (10 bits)：

![](https://i.imgur.com/rKQvfVb.png)

![](https://i.imgur.com/VqL1geo.png)

## Reference
[並行程式設計: Atomics 操作](https://hackmd.io/@sysprog/concurrency-atomics)
[6.46 When is a Volatile Object Accessed?](https://gcc.gnu.org/onlinedocs/gcc/Volatiles.html)
[Documentation/memory-barriers](https://www.kernel.org/doc/Documentation/memory-barriers.txt)
[Golden_ratio](https://en.wikipedia.org/wiki/Golden_ratio)
[Fibonacci_number#Relation_to_the_golden_ratio](https://en.wikipedia.org/wiki/Fibonacci_number#Relation_to_the_golden_ratio)