# 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)