---
# System prepended metadata

title: 2023q1 Homework2 (quiz2)
tags: [linux2022, Linux Kernal]

---

# 2023q1 Homework2 (quiz2)
contributed by <`Urbaner3`>
###### tags: `linux2022` `Linux Kernal`

測驗 `1`
------
```cpp!
uint64_t next_pow2(uint64_t x)
{
    x |= x >> 1;
    x |= x >> 1;
    x |= x >> 1;
    x |= x >> 1;
    x |= x >> 1;
    x |= x >> 1;
    x |= x >> 1;
	x |= x >> 1; /*add a line to make it work*/
    x |= x >> AAAA;
    x |= x >> 16;
    x |= x >> BBBB;
    return CCCC;
}
```
將 x 右移並與 x 做或運算，會從有1的位數，右移後加回本為零的位數，增加顯示1的位元。題目有提示說到，依次右移1,2,4,8,16,32,64。因為一旦到達指定數字樣式，即1...1，右移多少位元，因為沒有零的位數，數字都不會變，且是我們要的樣式。若不是按照2的等比數列，如1,3,5,...中間會有零的位數，就不是冪減一了，不是全位數1的2進位數。所以 AAAA = 1。 奇怪這樣有落差，少了一行右移一，加入一行。AAAA = 8, BBBB = 32, CCCC = x+1

#### 延伸問題
1. 解釋上述程式碼原理，並用 [`__builtin_clzl`](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) 改寫
(程式POW)
```cpp!
#include <stdint.h>
static unsigned long int next_pow2(unsigned long int x)
{
    int zs = __builtin_clzl(x); /*zeros;*/
    return 1 << (sizeof(unsigned long int)-zs);
} 
```
解釋如前段，為了得到相同輸出，直接對1做左移。
2.  在 Linux 核心原始程式碼找出類似的使用案例並解釋
[weak clz/ctz](https://github.com/torvalds/linux/commit/4df87bb7b6a22dfc6fdd5abb3dd362b3af2c164d) 
```cpp!
int __weak __clzdi2(long val)
{
	return 64 - fls64((u64)val);
}
```
[fls64](https://github.com/analogdevicesinc/linux/blob/master/tools/include/asm-generic/bitops/fls64.h) not in <linux/export.h, kernel.h> = linux/include/linux/kernel.h [better eq.](https://www.daemon-systems.org/man/fls64.3.html) 函數回傳由右向左數的非零位數，用64減去該數，得到左邊開始連續0的位數數量。
3.  當上述 clz 內建函式已運用時，編譯器能否產生對應的 x86 指令？
參考 [aaron](https://hackmd.io/@aaron881011/rkZbh-zyn) ，執行 `cc`
```shell!
	.file	"next_pow2.c"
	.text
	.ident	"GCC: (Ubuntu 12.2.0-3ubuntu1) 12.2.0"
	.section	.note.GNU-stack,"",@progbits
	.section	.note.gnu.property,"a"
	.align 8
	.long	1f - 0f
	.long	4f - 1f
	.long	5
0:
	.string	"GNU"
1:
	.align 8
	.long	0xc0000002
	.long	3f - 2f
2:
	.long	0x3
3:
	.align 8
4:
```
沒有出現，圖例為對程式POW執行的結果。

測驗 `2`
------
參考[作業](https://hackmd.io/@p4uHLG53RQ2mdlxeWiipWg/linux-2023-quiz2)發現 len 與 i 有關聯，但沒有進一步想到，一起合併，紀錄在 ans 並每次更新，以致於一時之間沒有發現。有些對2進位表示不習慣，我以為還要經過一個函數，有個固定語法的印象，一沒找到就認不出來，沒有想法了。也可說是對串接數字的方式沒有清楚的概念。 
[實驗共筆](https://hackmd.io/PO1-iGQfT_OUQSFYUGkLLA?view#%E6%B8%AC%E9%A9%97-3)

測驗 `3`
------
解釋原理：
因為2進位補數 0b10111111 是負數，所以更大的數，數值較小，第六第七位會是後續位元組開頭的樣式。所以用-65當作 magic number 這個演算法可以算出是否為後續位元組。
參考[範本](https://hackmd.io/@willwillhi/quiz2#%E6%B8%AC%E9%A9%97%E4%B8%89) 
AAAA=3, BBBB=7, CCCC=7
新演算法把8個數字串在一起，當作一個數字運算，如同題目開頭2數字串接的例子一樣，範本中，使用題目的說法，利用位元運算的性質，反轉再經過 mask 讓其他六位數字歸零，再繼續整理，讓目標樣式輸出一，計算一的數量。 [好解釋](https://hackmd.io/@yanjiew/linux2023q1-quiz2#%E6%B8%AC%E9%A9%97-3)




測驗 `4`
------
參考[列表](https://hackmd.io/@willwillhi/quiz2#%E6%B8%AC%E9%A9%97%E5%9B%9B)

```shell!
pattern: 0x8000 = 0b1000 0000 0000 0000
pattern: 0xc000 = 0b1100 0000 0000 0000
pattern: 0xe000 = 0b1110 0000 0000 0000
pattern: 0xf000 = 0b1111 0000 0000 0000
pattern: 0xf800 = 0b1111 1000 0000 0000
pattern: 0xfc00 = 0b1111 1100 0000 0000
pattern: 0xfe00 = 0b1111 1110 0000 0000
pattern: 0xff00 = 0b1111 1111 0000 0000
pattern: 0xff80 = 0b1111 1111 1000 0000
pattern: 0xffc0 = 0b1111 1111 1100 0000
pattern: 0xffe0 = 0b1111 1111 1110 0000
pattern: 0xfff0 = 0b1111 1111 1111 0000
pattern: 0xfff8 = 0b1111 1111 1111 1000
pattern: 0xfffc = 0b1111 1111 1111 1100
pattern: 0xfffe = 0b1111 1111 1111 1110
pattern: 0xffff = 0b1111 1111 1111 1111
```
[同義解](https://hackmd.io/@ItisCaleb/HyTEU5j0i#%E6%B8%AC%E9%A9%97-4)，

```cpp!
bool is_pattern(uint16_t x)
{
    const uint16_t n = ~x+1;
    return (n ^ x) < x;
}
```
改寫程式碼為 pattern 的生成器：
```cpp!
bool is_pattern(uint16_t x)
{
    int n = __builtin_popcountll(x ^ 0x8000llu);
	uint16_t gen = 0x8000llu;
	for (int i=0; i<n; i++)
		gen |= gen>>1;
	
	return gen;
}
```
考慮到
0x8000 = $-2^{15}$ (mod $2^{16}$)，每一行都是前一行右移1位元，也就是除以二，這裡要邏輯位移，補上 MSB digit =1，所以新程式碼告訴我們邏輯位移的實做，`x |= x>>1`，我想也是除以2的實做。
x^-x = x<<1 = x * 2 = x - (-x)
