# 2019q3 Homework1 (review) contributed by < `ArielWu0203` > ###### tags: `linux_2019` * [作業要求](https://hackmd.io/@sysprog/rJM4SPw8S) ## 第一周測驗題 Q1 :::info 在 Linux 核心原始程式碼找出類似的 alignment 巨集並解釋其作用 ::: * Data alignment macro : [tools/testing/scatterlist/linux/mm.h](https://elixir.bootlin.com/linux/latest/source/tools/testing/scatterlist/linux/mm.h#L33) ```cpp #define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1) #define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask)) #define ALIGN(x, a) __ALIGN_KERNEL((x), (a)) ``` ### 作用 * a 為 2 的倍數時,可以做對齊 (alignment) 。例如 : * x = 0x1011 , a = 4 時,會輸出 0x1014。 * x = 0x1014 , a = 4 時,會輸出 0x1014。 * x = 0x1016 , a = 4 時,會輸出 0x1018。 * ***(typeof(x))(a) - 1)*** 是為了得到需要對齊的位數。 * ***((x) + (mask)) & ~(mask)*** *x + mask* 要讓 x 可以加到下個可以對齊的地方。 再用 *&(~mask)* 就可以將多餘的位數歸為 0,即可對齊。 * 因為對齊是以位元 (bit) 為基準,所以 a 必須為 2 的倍數(1,2,4,8...)。 ### 目的 * cpu 不會一次抓取 1 byte 的資料,因為這樣太慢了,所以 cpu 會一次抓取 4 bytes (根據電腦規格而定),而且是按照==順序==去取,例如 : 第一次存取 0~3 ,第二次存取 4~7。 因此,若在存取資料的分布在 1~4,cpu 就需要存取兩次(0~3 , 4~7),會造成存取速度降低。 * [reference : 課程資料--記憶體管理、對齊及硬體特性](https://hackmd.io/@sysprog/BkuMDQ9K7?type=view) ## 第一周測驗題 Q2 :::info 解釋程式運作原理並在 GitHub 找出實際應用的程式碼 ::: ```cpp #include <stdint.h> #define INT_SIZE_OF(n) \ ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)) ``` * 和 Q1 類似,也是 data alignment。 **(sizeof(int) - 1)** 為需要對齊的位數。 **sizeof(n) + sizeof(int) - 1** 讓 n 可以加到下個可以對齊的地方,在用 *&(~(sizeof(int) - 1))* 將多餘的位數歸為 0,即可對齊。 ### 運作原理 * 假設 m 為一整數, 且 n 不為 4 的倍數, n 的範圍為 $4m<n<4(m+1)$ * 為了要讓 n 可以到下個對齊的地方,加上 *sizeof(int) - 1* ,則 n 的範圍為 $4(m+1)\leq n<4(m+2)$ * 若 n 為 4 的倍數, n 加上 *sizeof(int) - 1* 不會到下一個單位(4 bytes),所以對齊後,仍然為 n ### GitHub 實際應用的程式碼 * [link](https://github.com/weolar/miniblink49/blob/master/vc6/include/crt/stdarg.h) ```cpp= #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) #define va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) ) #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) ``` ## 課程簡介和注意須知 : C語言可不是只有語法 :::info 思考下方程式碼的作用,以及如何應用 ::: ```cpp= uint32_t func(uint32_t x) { uint32_t n = x; n = ((n & 0xffff0000) >> 16) | ((n & 0x0000ffff) << 16); n = ((n & 0xff00ff00) >> 8) | ((n & 0x00ff00ff) << 8); n = ((n & 0xf0f0f0f0) >> 4) | ((n & 0x0f0f0f0f) << 4); n = ((n & 0xcccccccc) >> 2) | ((n & 0x33333333) << 2); n = ((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1); return n; } ``` ### 作用 若有 1 byte 要反轉。 > n = ++1001++ ++1011++ > 先反轉以 4 bits 為單位 -> n = ++1011++ ++1001++ > 再反轉以 2 bits 為單位 -> n = ++11++ ++10++ ++01++ ++10++ > 再反轉以 1 bits 為單位 -> n = ++1++ ++1++ ++0++ ++1++ ++1++ ++0++ ++0++ ++1++ ### 目的 由於用迴圈對一個個位元做反轉,效率太低了,所以以 n bits 當作單位,來做反轉。 ### 實驗測試 [github : reverse_test](https://github.com/ArielWu0203/reverse_test) * reverse with loop 一個個 bit 做反轉。 ```cpp= int32_t reverse(int32_t x){ int32_t val = 0; int32_t i = 0; for (i = 0; i < 32; i++) { val = (val << 1) | (x & 0x1); x >>= 1; } return val; } ``` * Reverse integer bitwise without using loop 以 n bits 當作單位,來做反轉。 ```cpp= int32_t reverse(int32_t new){ new = ((new & 0xffff0000) >> 16) | ((new & 0x0000ffff) << 16); new = ((new & 0xff00ff00) >> 8) | ((new & 0x00ff00ff) << 8); new = ((new & 0xf0f0f0f0) >> 4) | ((new & 0x0f0f0f0f) << 4); new = ((new & 0xcccccccc) >> 2) | ((new & 0x33333333) << 2); new = ((new & 0xaaaaaaaa) >> 1) | ((new & 0x55555555) << 1); return new; } ``` * 測試結果 ![](https://i.imgur.com/XETpq0n.png) ==reverse without loop== 程式的完成時間比較短。 ### 應用 * crc 校驗 : [ linux/bitrev.h](https://elixir.bootlin.com/linux/v4.12/source/include/linux/bitrev.h) * [linux/crc32.h](https://elixir.bootlin.com/linux/v4.12/source/include/linux/crc32.h#L76) ```cpp= #define ether_crc(length, data) bitrev32(crc32_le(~0, data, length)) #define ether_crc_le(length, data) crc32_le(~0, data, length) ``` ## 第一週課程進度 ### Windows / Ubuntu 雙系統安裝 1. 建立磁碟分割區 * 按住Win + X,選擇`磁碟管理`。 * 選擇剩餘空間較大的可分配磁碟,右鍵並選擇*壓縮磁碟區*,這裡我選了 300GB 。 2. 關閉快速啟動 * 按住Win + X,選擇`電源選項` `其他電源選項` `選擇按下電源按鈕時的行為` `關閉快速啟動`。 3. 關閉 Secure Boot * 重啟進入 BIOS ,我的電腦是 ASUS ,按 F2 * `Security` `Secure Boot` `Disabled` 4. USB 開機 * [UNetbootin](https://justhodl.blogspot.com/2018/01/unetbootin-ubuntu-linux-usb-windows.html) 5. 安裝 * 插 USB ,重啟進入 BIOS , 選擇 `Save&Exit` `USB_NAME(選沒有 UEFI 的)` * 分割硬碟 * `\` 15GB * `swap` 8GB * `\home` 剩餘的空間 6. [Grub Customizer](https://www.jianshu.com/p/e8fbf2aef5f2) * 為了要讓 Windows 為預設。 #### Reference * [Windows10+Ubuntu雙系統安裝](https://www.itread01.com/content/1546486745.html) * [輕鬆學會 Windows / Ubuntu 雙系統安裝 (簡易教學)](https://www.youtube.com/watch?v=rjpBTT6AmeU) * [完全看懂:灌 Linux 前該怎麼分配硬碟?](https://www.techbang.com/posts/6153-fully-understand-linux-distribution-before-irrigation-how-to-drive) ### [記憶體管理、對齊及硬體特性](https://hackmd.io/@sysprog/BkuMDQ9K7?type=view) #### Memory * 為什麼無法對 `void *` 做數值操作? 因為 `void` *沒有明確大小*,所以需要強制轉型(or 顯示轉型)。 * Overcommiting * 配置還不能使用的記憶體。 * 為什麼要 Overcommiting? VMA(The virtual memory allocator) 會使用 overcommiting ,先配置,等可以用的時候再用,因為若等到有充足的記憶體時再配置,新的要求會卡在外面處理不完。 * 為什麼使用 malloc 時要看記憶體是否可用? 因為可能 OOM killer 會因記憶體不足,而根據使用率將配置過的記憶體刪除。 * VLA (varialble-length arrays) * 在執行的過程中,才能確定 array 所配置的大小。 * 為什麼 linux kernel 不允許 VLA? 因為有安全的疑慮。 * slab allocator * 善用 cache memory ,增加效能。 * mmap * 將檔案映射到記憶體上,可多對一。 * Copy-on-write * 改過的檔案不要再放回原本的記憶體,放入新的記憶體中。 :::danger 避免只談記憶體,應該精確地提及 virtual/physical pages :notes: jserv ::: #### data alignmaent * cpu 不會一次抓取 1 byte 的資料,因為這樣太慢了,所以 cpu 會一次抓取 4 bytes (根據系統而定)。 因此,若在存取資料的分布在 1~4,cpu 就需要存取兩次( 0~3 , 4~7 ),會造成存取速度降低。 ### [數值系統](https://hackmd.io/@sysprog/BkRKhQGae?type=view) * `a` 為正數時,`~a+1` 為 a 的負數。 * `x & (x - 1) == 0` 的數學意義 若 x = 12 , x-1 = 11,用 4 bits 表示。 ``` x 1100 x-1 1011 (& ------------ 1000 ``` 消除了最右邊的 1 。 而 `x & (x - 1) == 0` 表示 x 的位元中,只有一個 1,表示 x 為 $2^n,n=1,2,3...$ #### xorSwap ```cpp void xorSwap(int *x, int *y) { *x ^= *y; *y ^= *x; *x ^= *y; } ``` $proof$ $x = x \oplus y,\ y=y$ $y = y \oplus (x \oplus y) = (y \oplus y) \oplus x=0 \oplus x = x$ $x = (x \oplus y) \oplus x = y$ #### Detect NULL ```cpp #define DETECT(X) \ (((X) - 0x01010101) & ~(X) & 0x80808080) ``` 先看一個 byte 的大小 `((X) - 0x01) & ~(X) & 0x80` ``` ~( ~(X-0x01) | X ) & 0x80 ``` `~(X-0x01)` 為 `X*(-1)` , 為 X 的負數。 `負數 | 正數` 的數學意義: X 是有正負的,及 X 為**非零數**。 所以 `~( ~(X-0x01) | X ) & 0x80 == 1` ,則表示 X 為 0。 `~( ~(X-0x01) | X ) & 0x80 == 0` ,則表示 X 不為 0。 #### Reverse integer bitwise without using loop ```cpp new = num; new = ((new & 0xffff0000) >> 16) | ((new & 0x0000ffff) << 16); new = ((new & 0xff00ff00) >> 8) | ((new & 0x00ff00ff) << 8); new = ((new & 0xf0f0f0f0) >> 4) | ((new & 0x0f0f0f0f) << 4); new = ((new & 0xcccccccc) >> 2) | ((new & 0x33333333) << 2); new = ((new & 0xaaaaaaaa) >> 1) | ((new & 0x55555555) << 1); ``` 若有 1 byte 要反轉。 > n = ++1001++ ++1011++ > 先反轉以 4 bits 為單位 -> n = ++1011++ ++1001++ > 再反轉以 2 bits 為單位 -> n = ++11++ ++10++ ++01++ ++10++ > 再反轉以 1 bits 為單位 -> n = ++1++ ++1++ ++0++ ++1++ ++1++ ++0++ ++0++ ++1++ ### [C語言 : 指標](https://hackmd.io/@sysprog/HyBPr9WGl?type=view) * `&` : the address of * `*` : the value of #### Funciton Pointer * 儲存某一個涵是起始的 memory address * Example : ```clike= #include <stdio.h> int Mul(int a, int b){ return a*b; } int FuncA(int x, int y, int (*Opt)(int, int)){ return Opt(x, y); } int main(){ int result = FuncA(5, 10, Mul); printf("%d\n",result); } ``` **typedef** ```clike= typedef int (*MathMethod)(int, int); int Mul(int a, int b) { return a*b; } int Divide(int a, int b) { return a/b; } int Calc(int x, int y, MathMethod Opt){ return Opt(x, y); } int main(){ int a = 8, b = 7; printf("a x b = %d\n", Calc(a, b, Mul)); printf("a / b = %d\n", Calc(a, b, Divide)); } ``` #### C99 type * 「陣列」、「函式」,以及「指標」都稱為 ==derived declarator types== * Practice > Q : 設定絕對地址為 0x67a9 的 32-bit 整數變數的值為 0xaa6,該如何寫? > Ans : ```clike= *(int32_t * const) (0x67a9) = 0xaa6; // 位址需要為 const ``` #### A pointer to a pointer ##### linked list * pointers to objects ```cpp void remove_list_node(List *list, Node *target) { Node *prev = NULL; Node *current = list->head; // Walk the list while (current != target) { prev = current; current = current->next; } // Remove the target by updating the head or the previous node. if (!prev) list->head = target->next; else prev->next = target->next; } ``` * indirect pointer ```cpp void remove_list_node(List *list, Node *target) { // The "indirect" pointer points to the *address* // of the thing we'll update. Node **indirect = &list->head; // Walk the list, looking for the thing that // points to the node we want to remove. while (*indirect != target) indirect = &(*indirect)->next; *indirect = target->next; } ``` 原本的 head 的 lifetime 限制在 function 內,但用 a pointer a pointer 可以增加 lifetime