owned this note
owned this note
Published
Linked with GitHub
# 2021q1 Homework2 (quiz2)
contributed by < `cyrong` >
## Q1: Linux 核心 [list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h) 與 mergesort
### 解釋上述程式碼運作原理,指出改進空間並著手實作
#### `container_of`
```cpp
/**
* container_of() - Calculate address of object that contains address ptr
* @ptr: pointer to member variable
* @type: type of the structure containing ptr
* @member: name of the member variable in struct @type
*
* Return: @type pointer of object containing ptr
*/
#ifndef container_of
#define container_of(ptr, type, member) \
__extension__({ \
const __typeof__(((type *) 0)->member) *__pmember = (ptr); \
(type *) ((char *) __pmember - offsetof(type, member)); \
})
#endif
```
`__extension__` 代表程式要使用 GNU extension ,加上 `__extension__` 後可以去除 compile warning
[參考資料](https://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html#index-_005f_005fextension_005f_005f)
`__typeof__()` 會回傳傳入值的型態,並使用此型態宣告 `__pmember`
最後一步
```c
(type *) ((char *) __pmember - offsetof(type, member));
```
先是使用 `(char *)` 取得 `__pmember` 的絕對位址
之後再減去 `offsetof(type, member)` 就可以得到這個 type 的開頭位址
最後用 `(type *)` 把開頭位址轉型成 pointer to `type`
在這裡有幾個點可以探討
1. `((type *) 0)->member` 被傳進了 `__typeof__`
先看一下 `__typeof__` 的 argument 要輸入什麼
參考 gcc onlinedoc 中的 [typeof](https://gcc.gnu.org/onlinedocs/gcc/Typeof.html#Typeof)
`typeof` 要輸入的 argument 是 expression or type
看起來 `((type *) 0)->member` 是一個 expression
再進一步分析
`(type *) 0` = 0 is cast to a pointer to struct type,也就是說這段是一個指標
`((type *) 0)->member` 把 0 做 member offset 的位移後取值
此時的型態就會是 member 的型態,因為已經將 0 轉型為 type
再放入 `typeof` 中就可以得到 struct 中 member 的型態
2. `({})` 的使用,其實就是在括號中作運算,
詳情可見 [gcc statement and declaration in expression](https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html)
#### `INIT_LIST_HEAD`
```cpp
/**
* INIT_LIST_HEAD() - Initialize empty list head
* @head: pointer to list head
*/
static inline void INIT_LIST_HEAD(struct list_head *head)
{
head->next = head; head->prev = head;
}
```
對 list head 作初始化,一開始的 head 中 next 和 prev 都是自己
#### `list_add_tail()`
```cpp
/**
* list_add_tail() - Add a list node to the end of the list
* @node: pointer to the new node
* @head: pointer to the head of the list
*/
static inline void list_add_tail(struct list_head *node, struct list_head *head)
{
struct list_head *prev = head->prev;
prev->next = node;
node->next = head;
node->prev = prev;
head->prev = node;
}
```
先做一個 `*prev` 為 `head->prev` 也就是 tail
接下來對這個 tail 跟 head 作配合操作就可以 add tail 了
#### `list_del()`
```cpp
/**
* list_del() - Remove a list node from the list
* @node: pointer to the node
*/
static inline void list_del(struct list_head *node)
{
struct list_head *next = node->next, *prev = node->prev;
next->prev = prev; prev->next = next;
}
```
用傳入的 `*node` 得到這個 node 的前後兩個 node 並接在一起,便成功作到 del node 的效果
#### `list_empty()`
```cpp
/**
* list_empty() - Check if list head has no nodes attached
* @head: pointer to the head of the list
*/
static inline int list_empty(const struct list_head *head)
{
return (head->next == head);
}
```
回傳 `head->next` 是否為 `head`
代表如果只有 `head` 此 list 就是 empty
#### `list_is_singular()`
```cpp
/**
* list_is_singular() - Check if list head has exactly one node attached
* @head: pointer to the head of the list
*/
static inline int list_is_singular(const struct list_head *head)
{
return (!list_empty(head) && head->prev == head->next);
}
```
回傳 list 是否只有一個 node
若 `head->next` 和 `head->prev` 相同代表只有一個 node
#### `list_splice_tail`
```cpp
/**
* list_splice_tail() - Add list nodes from a list to end of another list
* @list: pointer to the head of the list with the node entries
* @head: pointer to the head of the list
*/
static inline void list_splice_tail(struct list_head *list,
struct list_head *head)
{
struct list_head *head_last = head->prev;
struct list_head *list_first = list->next, *list_last = list->prev;
if (list_empty(list))
return;
head->prev = list_last;
list_last->next = head;
list_first->prev = head_last;
head_last->next = list_first;
}
```
把 `list` 中所有的 node 接到 `head` 的尾端
不需要重新鏈接,只需要有 node 們的 first 和 last
把 first 接到 `head` 尾,再將 last 接到 `head->prev` 就完成了
#### `list_cut_position()`
```cpp
/**
* list_cut_position() - Move beginning of a list to another list
* @head_to: pointer to the head of the list which receives nodes
* @head_from: pointer to the head of the list
* @node: pointer to the node in which defines the cutting point
*/
static inline void list_cut_position(struct list_head *head_to,
struct list_head *head_from,
struct list_head *node)
{
struct list_head *head_from_first = head_from->next;
if (list_empty(head_from))
return;
if (head_from == node) {
INIT_LIST_HEAD(head_to);
return;
}
head_from->next = node->next;
head_from->next->prev = head_from;
head_to->prev = node;
node->next = head_to;
head_to->next = head_from_first;
head_to->next->prev = head_to;
}
```
將 `head_from` 中的 `node` 以前的所有 nodes 都搬到 `head_to` 後
#### `list_entry()`
```cpp
/**
* list_entry() - Calculate address of entry that contains list node
* @node: pointer to list node
* @type: type of the entry containing the list node
* @member: name of the list_head member variable in struct @type
*/
#define list_entry(node, type, member) container_of(node, type, member)
```
利用 `container_of` 找到輸入 node 之初始位址
#### `list_first_entry()`
```cpp
/**
* list_first_entry() - get first entry of the list
* @head: pointer to the head of the list
* @type: type of the entry containing the list node
* @member: name of the list_head member variable in struct @type
*/
#define list_first_entry(head, type, member) \
list_entry((head)->next, type, member)
```
使用 `list_entry()` 但是輸入的 node 為 `(head)->next` 因此得到的會是 list 中的第一個 node
#### `list_for_each()`
```cpp
/**
* list_for_each - iterate over list nodes
* @node: list_head pointer used as iterator
* @head: pointer to the head of the list
*/
#define list_for_each(node, head) \
for (node = (head)->next; node != (head); node = node->next)
```
利用 for 迴圈走訪 list 所有節點
#### 搭配使用的 merge sort
```c=
#include <string.h>
typedef struct __element {
char *value;
struct __element *next;
struct list_head list;
} list_ele_t;
typedef struct {
list_ele_t *head; /* Linked list of elements */
list_ele_t *tail;
size_t size;
struct list_head list;
} queue_t;
static list_ele_t *get_middle(struct list_head *list)
{
struct list_head *fast = list->next, *slow;
list_for_each (slow, list) {
if (COND1 || COND2)
break;
fast = fast->next->next;
}
return list_entry(TTT, list_ele_t, list);
}
static void list_merge(struct list_head *lhs,
struct list_head *rhs,
struct list_head *head)
{
INIT_LIST_HEAD(head);
if (list_empty(lhs)) {
list_splice_tail(lhs, head);
return;
}
if (list_empty(rhs)) {
list_splice_tail(rhs, head);
return;
}
while (!list_empty(lhs) && !list_empty(rhs)) {
char *lv = list_entry(lhs->next, list_ele_t, list)->value;
char *rv = list_entry(rhs->next, list_ele_t, list)->value;
struct list_head *tmp = strcmp(lv, rv) <= 0 ? lhs->next : rhs->next;
list_del(tmp);
list_add_tail(tmp, head);
}
list_splice_tail(list_empty(lhs) ? rhs : lhs, head);
}
void list_merge_sort(queue_t *q)
{
if (list_is_singular(&q->list))
return;
queue_t left;
struct list_head sorted;
INIT_LIST_HEAD(&left.list);
list_cut_position(&left.list, &q->list, MMM);
list_merge_sort(&left);
list_merge_sort(q);
list_merge(&left.list, &q->list, &sorted);
INIT_LIST_HEAD(&q->list);
list_splice_tail(&sorted, &q->list);
}
```
#### `get_middle()`
這段程式碼用的手法是 `slow` 每動一格 `fast` 就會動兩格
而等到 `fast` 到達 list 尾端時 走過的 node 數是 list 的大小 `n`
這時 `slow` 正好走過 `n/2` 也就是 list 的中間了
#### `list_merge()`
這裡是在作 mergesort 的合併 `lhs` 代表 left hand side,
`rhs` 代表 right hand side
從兩邊 list 的頭開始走完兩個 list 的所有 node
合併進 `head`
#### quiz 題目
20 行的 `COND1` 和 `COND2` 是為了判斷 `slow` 是否已經到達了 list 的中間
而分成 `COND1` 和 `COND2` 的原因是 `n` 可能是奇數或偶數
`COND1` 對應的是偶數的情況 `fast->next == list`
`COND2` 對應的是奇數的情況 `fast->next->next == list`
24 行是已經找到 middle 了 並且要得到其位址
middle 也就是 `slow` 因此 `TTT` 傳入 `slow`
59 行的目的是把 `list` 切成兩半,分別再作 mergesort
`MMM` 是要找的 middle node ,所以應該要用 `get_middle()`
要注意的是 `get_middle()` 回傳的 type 是 `list_ele_t`
但是 `list_cut_position()` 要輸入的參數是 `list_head *`
因此要對找到的 `list_ele_t` 的 `list` 作取值,並且傳入他的位址,參數型態才會對
參數型態的問題在 `get_middle()` 也要注意,要傳入的是 `list_head *`
所以 `MMM` 答案應是 `&(get_middle(&q->list)->list)`
---
## Q2: power-of-2
### 解釋程式碼
```c=
uint16_t func(uint16_t N) {
/* change all right side bits to 1 */
N |= N >> 1;
N |= N >> X;
N |= N >> Y;
N |= N >> Z;
return (N + 1) >> 1;
}
```
假定 N = 10101~2~ = 21~10~,那麼 func(21) 要得到 16,請補完程式碼,使其運作符合預期。
16~10~ = 10000~2~
而 `return (N + 1) >> 1` = 10000~2~
可以回推 `N` 是 11111~2~
再從程式碼第3行看
```c=3
N |= N >> 1;
```
`N` = `10101 | (10101 >> 1)` = `10101 | 1010` = `11111`
雖然這樣看起來就是答案了
不過程式需要的功能是 change all right side bits to 1
在這個 `uint16_t` 的 type 下,最大的值就是 16 個 bits 都是 1
會是 1 的最高位也就是 16
要做到可以把最高位 1 的 right side 都變成 1
假設現在要操作的值是 `1000 0000 0000 0000`
作第三行的操作後是 `1100 0000 0000 0000`
用 4 步去把 1 個 1 用 `>>` 複製成 16 個
最好的方法就是每次都可以把 1 的數量加倍
`1100 0000 0000 0000`
=> `1111 0000 0000 0000`
=> `1111 1111 0000 0000`
=> `1111 1111 1111 1111`
之後要位移的量就分別是 2, 4, 8
因此 `X = 2` `Y = 4` `Z = 8`
## Q3: bitcpy
考慮到一個 `bitcpy` 實作,允許開發者對指定的記憶體區域,逐 bit 進行資料複製。
### 解釋程式碼
```c=
#include <stdint.h>
void bitcpy(void *_dest, /* Address of the buffer to write to */
size_t _write, /* Bit offset to start writing to */
const void *_src, /* Address of the buffer to read from */
size_t _read, /* Bit offset to start reading from */
size_t count)
{
size_t read_lhs = _read & 7;
size_t read_rhs = 8 - read_lhs;
const uint8_t *source = (const uint8_t *) _src + (_read / 8);
size_t write_lhs = _write & 7;
size_t write_rhs = 8 - write_lhs;
uint8_t *dest = (uint8_t *) _dest + (_write / 8);
static const uint8_t read_mask[] = {
0x00, /* == 0 00000000b */
0x80, /* == 1 10000000b */
0xC0, /* == 2 11000000b */
0xE0, /* == 3 11100000b */
0xF0, /* == 4 11110000b */
0xF8, /* == 5 11111000b */
0xFC, /* == 6 11111100b */
0xFE, /* == 7 11111110b */
0xFF /* == 8 11111111b */
};
static const uint8_t write_mask[] = {
0xFF, /* == 0 11111111b */
0x7F, /* == 1 01111111b */
0x3F, /* == 2 00111111b */
0x1F, /* == 3 00011111b */
0x0F, /* == 4 00001111b */
0x07, /* == 5 00000111b */
0x03, /* == 6 00000011b */
0x01, /* == 7 00000001b */
0x00 /* == 8 00000000b */
};
while (count > 0) {
uint8_t data = *source++;
size_t bitsize = (count > 8) ? 8 : count;
if (read_lhs > 0) {
RRR;
if (bitsize > read_rhs)
data |= (*source >> read_rhs);
}
if (bitsize < 8)
data &= read_mask[bitsize];
uint8_t original = *dest;
uint8_t mask = read_mask[write_lhs];
if (bitsize > write_rhs) {
/* Cross multiple bytes */
*dest++ = (original & mask) | (data >> write_lhs);
original = *dest & write_mask[bitsize - write_rhs];
*dest = original | (data << write_rhs);
} else {
// Since write_lhs + bitsize is never >= 8, no out-of-bound access.
DDD;
*dest++ = (original & mask) | (data >> write_lhs);
}
count -= bitsize;
}
}
```
輸入的參數有 dest, src, write offset, read offset, count
一開始會有一些變數設定
有 `read_lhs / rhs`, `write_lhs / rhs` 以及作過處理的 `source` 跟 `dest`
```c=9
size_t read_lhs = _read & 7;
size_t read_rhs = 8 - read_lhs;
const uint8_t *source = (const uint8_t *) _src + (_read / 8);
size_t write_lhs = _write & 7;
size_t write_rhs = 8 - write_lhs;
uint8_t *dest = (uint8_t *) _dest + (_write / 8);
```
`read_lhs` = `_read` & `7` = `_read` % `8`
`read_rhs` = `8` - `read_lhs`
`read_lhs` 目的是留下 read offset 的 0 ~ 2 bit 的值
因為程式是以一個 byte 作處理
所以配合 `read_rhs`
left 處理開頭,right 處理收尾
而 `source` 的處理
```c
const uint8_t *source = (const uint8_t *) _src + (_read / 8);
```
讓 `source` 是 `_src` 移動 `(_read / 8)` 個單位,也就是 `(_read / 8)` bytes
`source` 就是開始讀取的地方
`write_lhs` 和 `write_rhs` 的作法和 read 一樣
`dest` 則是和 `source` 一樣
接著是兩個 mask 是為了方便等一下的 `bitcpy` 方便使用
```c=16
static const uint8_t read_mask[] = {
0x00, /* == 0 00000000b */
0x80, /* == 1 10000000b */
0xC0, /* == 2 11000000b */
0xE0, /* == 3 11100000b */
0xF0, /* == 4 11110000b */
0xF8, /* == 5 11111000b */
0xFC, /* == 6 11111100b */
0xFE, /* == 7 11111110b */
0xFF /* == 8 11111111b */
};
static const uint8_t write_mask[] = {
0xFF, /* == 0 11111111b */
0x7F, /* == 1 01111111b */
0x3F, /* == 2 00111111b */
0x1F, /* == 3 00011111b */
0x0F, /* == 4 00001111b */
0x07, /* == 5 00000111b */
0x03, /* == 6 00000011b */
0x01, /* == 7 00000001b */
0x00 /* == 8 00000000b */
};
```
最下面的 while 迴圈就是在作 bit copy
```c=40
while (count > 0) {
uint8_t data = *source++;
size_t bitsize = (count > 8) ? 8 : count;
if (read_lhs > 0) {
RRR;
if (bitsize > read_rhs)
data |= (*source >> read_rhs);
}
if (bitsize < 8)
data &= read_mask[bitsize];
uint8_t original = *dest;
uint8_t mask = read_mask[write_lhs];
if (bitsize > write_rhs) {
/* Cross multiple bytes */
*dest++ = (original & mask) | (data >> write_lhs);
original = *dest & write_mask[bitsize - write_rhs];
*dest = original | (data << write_rhs);
} else {
// Since write_lhs + bitsize is never >= 8, no out-of-bound access.
DDD;
*dest++ = (original & mask) | (data >> write_lhs);
}
count -= bitsize;
}
```
41~42
創一個 `uint8_t` data 是當前的 `source`,之後 `source`++, `source` 進到下個 byte
`bitsize` 最大到 8 為了方便使用 mask
43~47
`if (read_lhs > 0)` 這裡判斷有沒有需要對 `data` 作調整
要讓 `data` 是要讀的第一個 byte 的第 `read_lhs` 到 第 7 個 bit ( bit 是 0~7 )
然後再接上下一個 byte 從 0 開始的 `read_rhs` 個 bits
`RRR` 要作的是把 `data` 往左 shift `read_lhs` 次
`RRR` = `data <<= read_lhs`
下一個判斷是 要複製的 `bitsize` 有沒有超過 `read_rhs` 也就是有沒有要跨 byte
如果有就把下個 byte 從 0 開始的 `read_rhs` 個 bits
複製到 data 前 `read_rhs` 個 bits
49~50
如果 `bitsize` 不到 8 就把用不到的 bits 清掉
手法是利用 `read_mask`
54~58
要複製的 `bitsize` 會超出當前 byte 因此要作兩次複製
目前的 byte 利用 `mask` 操作,最後留下的資料是:
前 `write_lhs` bits 保持原狀,後面的 bits 換成 `data >> write_lhs`
下一個 byte 操作前,先置換 `original`
把 `original` 換成下一個 byte 的 original
而`write_mask` 把不會操作到的 bits 保持原狀
進到下一個 byte 操作
把剛剛做好的 `original` & `data << write_rhs`
59~63
`else` 指 `bitsize` 個 bit 操作不會超出 byte bound
因此只需要改 `bitsize` 個 bit ,其他 bits 都要保持原狀
因此 `mask` 要改成除了前 `write_lhs` 個 bit 保持外
還要保持 `write_lhs` + `bitsize` 後的 bits
因此 `DDD` |= `write_mask[write_lhs + bitsize]`
`write_mask[write_lhs + bitsize]` 就是剛才提到 `write_lhs` + `bitsize` 後的 bits
`mask` 做好後用 `original` 與 `mask` 調整好再複製 `data >> write_lhs`