# D05: list contributed by < `rex662624` > ## 生日悖論 ### 如何透過近似的運算式,估算上述機率計算呢?附上有效的 LaTeX 算式 若今天進入一個有22個人的房間內,加上自己有23個人。若用一年365天來計算,則在這個房間至少有一個人與我生日相同的機率已經超過50% 。而在這裡我利用 Poisson distribution 作解。 首先 Poisson distribution 敘述一事件發生的機率為 $P(k\ \ events\ \ in\ \ interval )= e^{-\lambda} \dfrac{\lambda^k}{k!}\\其中\\ 1.\ \ \lambda\ \ is\ \ the\ \ average\ \ number\ \ of\ \ events\ \ per\ \ interval\\ 2.\ \ e\ \ is\ \ the\ \ number\ \ 2.71828... (Euler's\ \ number)\ \ the\ \ base\ \ of\ \ the\ \ natural\ \ logarithms\\ 3.\ \ k\ \ takes\ \ values\ \ 0, 1, 2, …\\ 4.\ \ k!= k × (k − 1) × (k − 2) × … × 2 × 1\ \ is\ \ the\ \ factorial\ \ of\ \ k.$ 先寫出我們所求的結果應該為: $P(k>0)=1-P(k=0)$ 意思為,算出至少有一個 event k 發生(k>0)的機率,這個 event k 指的是幾個人與我的生日相同。而我們要算的$1−P (k=0)$已經有了$k=0$,現在還缺了$\lambda$。這裡透過 [Poisson approximation for the binomial](https://en.wikipedia.org/wiki/Poisson_distribution "Poisson distribution")知道$\ \ \lambda = np。其中$ $\ n=C^{23}_2 ,是23人中取某2人生日相同,p=1/365,是兩個人生日在某一天的機率。$$因此\lambda =np=\dfrac{C^{23}_2}{365}=0.6932$。 $結果:P(k>0)=1-P(k=0)=1-e^{-0.6932} \dfrac{-0.6932^0}{0!}≈1-0.499998≈0.500002$ 可以得證在23人共處一房間時,至少兩人生日相同的機率>50%。 ### [Birthday problem](https://en.wikipedia.org/wiki/Birthday_problem) 對資訊安全的影響在哪些層面? 參考[birthday attack](http://www.zwbk.org/zh-tw/Lemma_Show/127621.aspx),這裡寫道通常birthday attack用於尋找hash function 的 collision 。因為若是做訊息通信,可能會先加密再傳出去,傳到空氣中的電磁波(或是在網路上傳遞的數據),在中間若是被攔截,就可能會被透過這種方式被猜出未加密的訊息是甚麼。因此在有限長度之下,要產生一組 hash key 有可能重覆的機率就大幅的提升。 [維基百科](https://en.wikipedia.org/wiki/Birthday_attack)也寫道,`To avoid this attack, the output length of the hash function used for a signature scheme can be chosen large enough so that the birthday attack becomes computationally infeasible`,若要防範 birthady attack,則信息或是密碼的長度上限可以設大一點,大到 birthday attack 無法運算,或是運算出來要花十分長的時間。 因此 hash 的安全標準也可以由此驗證,一個 40bits 長的訊息用大約一百萬次隨機Hash可至少以1/2的概率找到一個 collision 。安全的 Hash 的標準輸出長度選爲 160 bits 是出於這種考慮。 ### 像是 [Random Number Generator](http://stattrek.com/statistics/random-number-generator.aspx) 這樣的產生器,是否能產生「夠亂」的亂數呢? 首先,這個網址自己有講到`Stat Trek's Random Number Generator produces numbers that are nearly random.`。首先觀察這個 generator,發現它在 min=0 max=2147483647 時會在下面的 table 顯示 error (m>=0就不會),2147483648時會在 calculate 上面顯示紅色error。大致可以推測它只能產生 32-bit 數字。而沒有放 seed 的時候每次產生的亂數都不同,放 seed 則是每次相同,因此他也說給 seed 只是 `short-term convenience`,不適用於長期。 沒有任何的亂數產生器可以產生完全 random 的數字,但是這個 generator 保證可以產生近乎 random 的數字。因此來想想這句話是否真的保證正確。 首先,[維基百科](https://zh.wikipedia.org/wiki/%E9%9A%8F%E6%9C%BA%E6%95%B0%E7%94%9F%E6%88%90%E5%99%A8)寫道,`大部分計算機上的偽隨機數(pseudo-random numbers),並不是真正的隨機數,只是重複的周期比較大的數列,是按一定的算法和種子值生成的。`。我認為這句話是關鍵所在。最上面我寫的那段只是說明了能夠處理的數字大小 max 是 32-bit 以內。但是究竟能處理「多少數字」。回去觀察generator 果然發現這個 tool 最多只能產生1000個數字。因此我推測它與維基百科解釋的相同,是先由 seed 算出並用一個很長的數列來儲存輸出的 random number 。因此他說的`Stat Trek's Random Number Generator produces numbers that are nearly random.`我覺得是正確的,因為這個 tool 本身就限定最多只能產生1000個數字。 但若是遇到要產生非常巨大數量的 random number ,我覺得類似這樣的 generator 就不可行,因為若用數列產生,一定會有走到數列盡頭的一天。(用birthday problem也可以知道數列長度365,只要23個數字就有50%機率有重複)。 但一定也有解套的方法,例如若要產生1000個 6位數, 內建 random number list 長度是100,則seed產生100個數字後,前三位先從100個數字隨機選一個,後三位再從100個數字隨機選一個,用如此的方法切割數字,只要存100個數字就有100\*100種可能。比起原本若是只印出產生的 list ,會重複10次相同的數列。或許用類似的方法,就能突破 generator 會受限於內建數列長度的瓶頸。 結論:要能夠定義數字「夠亂」到底是甚麼程度,還有針對甚麼數據。這類的 random number generator,若是針對小量的數字,可以產生近乎亂數的數字,但是若是需求是超大量的數量,可能就要有另外的演算法突破。 ## Linux 風格的 linked list - **為何 Linux 採用 macro 來實作 linked list?一般的 function call 有何成本?** function call 在執行時,必須要將傳進來的 argument copy 一份 push 進 stack ,而且必須先儲存本來的 state ,等等 return 時才能 pop出來。因此如此的在 stack 上 push pop 的動作,時間成本較多。而 marco 在 compile 時就會展開程式碼到呼叫的地方,因此雖然有了空間上的成本,但是時間上卻可以免去 push pop 的動作。 - **Linux 應用 linked list 在哪些場合?舉三個案例並附上對應程式碼,需要解說,以及揣摩對應的考量** 1. [linux/include/linux/fs.h](https://github.com/torvalds/linux/blob/master/include/linux/fs.h#L5) -- **作用:管理 file system** 根據檔案名稱,大致可以猜測到他是用來管理 file system 相關的 code 。 其中的 1002~1006行( linux kernel 隨時會更新,不一定準確) code 為 ```clike= struct file_lock { struct file_lock *fl_next; /* singly linked list for this inode */ struct list_head fl_list; /* link into file_lock_context */ struct hlist_node fl_link; /* node in global lists */ struct list_head fl_block; /* circular list of blocked processes */ ``` 觀察註解寫道` struct file_lock represents a generic "file lock". It's used to represent POSIX byte range locks, BSD (flock) locks, and leases.` 這個結構應該是在管理 file 被某一個 process 寫入時,其他的 process 就不能寫入這個 file 的 file lock 角色。可以發現 這裡的第5行就用到 linked list ,把其他想用此 file 資源但被 block 的 process ,串成一個 linked list 。 2. [linux/fs/inode.c](https://github.com/torvalds/linux/blob/master/fs/inode.c) -- **作用:處理 inode 的各種操作** 首先要先釐清 inode 到底是甚麼東西。根據 OS 課程張大緯老師的講義 ch11 其中的P8 寫道: ==File Control Blocks (FCB) is a data structure contains information about a file. In UNIX, an FCB is called an inode.== 可以知道它是用來儲存每個檔案的一些資訊。 再看看其中一段關於產生新inode 的 code: ```clike= struct inode *new_inode(struct super_block *sb) { struct inode *inode; spin_lock_prefetch(&sb->s_inode_list_lock); inode = new_inode_pseudo(sb); if (inode) inode_sb_list_add(inode); return inode; } ``` 可以看到第7行首先呼叫函式產生新的 inode 結構,而後第9行把他加進list裡面。繼續往下看呼叫的`inode_sb_list_add(inode);`,可以發現的確呼叫了 list add 把他串進 linked list 中 ,而推測這個 list 是用於做索引,讓系統可以一次走訪所有 inode。 ```clike= void inode_sb_list_add(struct inode *inode) { spin_lock(&inode->i_sb->s_inode_list_lock); list_add(&inode->i_sb_list, &inode->i_sb->s_inodes); spin_unlock(&inode->i_sb->s_inode_list_lock); } ``` 3. [linux/kernel/sched/fair.c](https://github.com/torvalds/linux/blob/master/kernel/sched/fair.c)-- **作用:管理CPU排班** 我推測 CPU 排班本來就會用到 list (至少我的OS作業是這樣做),因此去找cpu 排班的code,但是找到後一直不知道為何是用 fair.c 作為檔名,後來才發現 linux 系統預設排班法是用`Completely Fair Scheduler(CFS)`,因此也蠻合理的。 首先我們知道 CFS 排班法是選擇花費 CPU 時間最少的 process 起來執行,因此讓我們看看選擇 process 的 function ==pick_next_entity()==。 ```clike= pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr) { struct sched_entity *left = __pick_first_entity(cfs_rq); struct sched_entity *se; /* * If curr is set we have to see if its left of the leftmost entity * still in the tree, provided there was anything in the tree at all. */ if (!left || (curr && entity_before(curr, left))) left = curr; se = left; /* ideally we run the leftmost entity */ .......(程式碼省略) /* * Someone really wants this to run. If it's not unfair, run it. */ if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, left) < 1) se = cfs_rq->next; clear_buddies(cfs_rq, se); return se; } ``` 因為linux是利用 rbtree 來維護 process ,所以一開始找 leftmost ,最左邊的process,就是有最小 runtime 的process ,這裡第20行的 next 我本來以為就是linked list 的應用,結果發現他這個`cfs_rq->next`指的只是下一個要跑的人,並不是一個一個指下去的 list 關係。而這裡是用 rbtree 結構來維護 process,因此應該不算 linked list 。 參考[這裡](https://blog.csdn.net/conansonic/article/details/78203405)總算找到這份 fair.c 用 list_add(第11行) 應用 linked list 。這裡寫到, cfs_task 這個 list 是用來做 CPU Load balancing ,意思就是用來處理多核心狀況下,把負擔較大的CPU 分配工作給其他閒置的CPU 。 ```clike= account_entity_enqueue(struct cfs_rq *cfs_rq, struct sched_entity *se) { update_load_add(&cfs_rq->load, se->load.weight); if (!parent_entity(se)) update_load_add(&rq_of(cfs_rq)->load, se->load.weight); #ifdef CONFIG_SMP if (entity_is_task(se)) { struct rq *rq = rq_of(cfs_rq); account_numa_enqueue(rq, task_of(se)); list_add(&se->group_node, &rq->cfs_tasks); } #endif cfs_rq->nr_running++; } ``` - **GNU extension 的 [typeof](https://gcc.gnu.org/onlinedocs/gcc/Typeof.html) 有何作用?在程式碼中扮演什麼角色?** typeof(a) 會 return a 的 type 。在程式碼中可以用來判斷是否兩者的 type 相同。 - **解釋以下巨集的原理** ```clike= #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) ``` 參考[這裡](https://myao0730.blogspot.tw/2016/09/linuxcontainerof-offsetof.html),它的作用是用來取得 struct 結構的起始點,只要知道該結構的任一個成員的位址,就可以使用 container_of 巨集來算出該結構的起始位址。因此我們只要有指向某個 member 的 pointer,就能透過此巨集獲取指向某 struct 的 pointer 。而觀察程式碼,總共做了兩個動作。 1. 先定義一個指向 **member** 這個 type 的指標 `__pmember`,然後將參數 ptr (就是我們的 input , 指向某個 member 的指標)餵給該指標。 2. 然後將`__pmember`值(裡面存的是我們 input member 的address),減去他在整個 struct 的 offset 值(另外有定義巨集 offsetof ,用來獲取某 member 與 struct 開頭的 offset),就可以得到整個 struct 的起始 address。 - **除了你熟悉的 add 和 delete 操作,`list.h` 還定義一系列操作,為什麼呢?這些有什麼益處?** [ list.h ](https://github.com/torvalds/linux/blob/master/include/linux/list.h)光是 list_add,就有 list_add_valid、hlist_add_behind、hlist_add_before、list_tail等等。而觀察程式碼一開始的註解寫到 ```clike= /* * Simple doubly linked list implementation. * * Some of the internal functions ("__xxx") are useful when * manipulating whole lists rather than single entries, as * sometimes we already know the next/prev entries and we can * generate better code by using them directly rather than * using the generic single-entry routines. */ ``` 可以知道呼叫後面有 \_\_XXX 的函式,比起 general 的 add ,這種特定操作的add 能帶來效能上的助益,因為當我們已經知道 next 或 prev 的 entry,我們或許可以善加利用這些資訊,而非只是用 general 的 add。 - **`LIST_POISONING` 這樣的設計有何意義?** [List poisoning](https://en.wikipedia.org/wiki/List_poisoning) 在 wikipedia 上面的定義是用於電子郵件攻擊,被攻擊者用一些 invalid mail address 攻擊。而參考[ linux-like-list ](https://github.com/ecsv/linux-like-list/blob/master/list.h) ,裡面寫到了 ```clike= /** * hlist_del() - Remove a hlist node from the hlist * @node: pointer to the node * * The node is only removed from the hlist. Neither the memory of the removed * node nor the memory of the entry containing the node is free'd. The node * has to be handled like an uninitialized node. Accessing the next or pprev * pointer of the node is not safe. * * Unlinked, initialized nodes are also uninitialized after hlist_del. * * LIST_POISONING can be enabled during build-time to provoke an invalid memory * access when the memory behind the next/prev pointer is used after an * hlist_del. This only works on systems which prohibit access to the predefined * memory addresses. */ ``` 可以看到 12\~15行說使用了`hlist_del`後,可能會發生LIST_POISONING,回頭看5~8行也有寫原因是因為用了這個函數後, node 只是從 list 裡面移除,list 所佔的 memory 或是裡面的資料都沒有被 free,因此若是此時又去訪問他的 next 或是 prev ,則這是不合法的,因為他被從 list 移除,操作他的行為應該要與操作一塊空白記憶體相同。 此外可以發現 [ linux/list.h ](https://github.com/torvalds/linux/blob/master/include/linux/list.h)之中,list_del 將要刪除的 entry 的 next 和 prev 都指向 LIST_POISON 。 - **linked list 採用環狀是基於哪些考量?** 1. 不需要 maintain 兩個 pointer front 和 rear ,只需要一個 last,我們就知道他的下一個 node 是 front。 2. 設計演算法較一般的 linked list 容易,彈性也較大,因為沒有第一個或是最後一個的問題,例如如果要改 last 到另一個 node ,一般的 list 需要把新 last 搬到後面,last->next=NULL 等動作,而 circular list 只要 改 last指標就好,結構完全不需變動。 - **`list_for_each_safe` 和 `list_for_each` 的差異在哪?“safe” 在執行時期的影響為何?** 首先比較程式碼 ```clike= /** * list_for_each - iterate over a list * @pos: the &struct list_head to use as a loop cursor. * @head: the head for your list. */ #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) ``` ```clike= /** * list_for_each_safe - iterate over a list safe against removal of list entry * @pos: the &struct list_head to use as a loop cursor. * @n: another &struct list_head to use as temporary storage * @head: the head for your list. */ #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) ``` 原本的版本處理 list_del 時會出問題。首先,我在`LIST_POISONING`那道題目最後講到,list\_del 將要刪除的 entry 的 next 和 prev 都指向 LIST\_POISON。因此如果是用 list_each 來 delete ,delete 完目標之後,下次回來for會做的是 pos =pos->next,但此時的 pos 已經是 LIST\_POISON ,因此就會錯誤。 但是在 list_each_safe 的版本中,用一個 temp 指標n,在回到 for 的一開始就做 pos = n ,因此若是刪除 pos 這個 node ,下次回來會先執行 pos = n,而此時的n 是上一個循環存的pos->next,也就是刪除的人原本的 next ,因此就能成功的指向正常 list。 - **for_each 風格的開發方式對程式開發者的影響為何?** [for each](https://msdn.microsoft.com/zh-tw/library/e5sk9w9k.aspx),`for_each(InputIterator _First, InputIterator _Last, Function _Func);`,可以讓使用者針對範圍( first 到 last )去做特定的 function ,比起原來用 for 迴圈,再 call function,程式碼簡潔得多。 - **程式註解裡頭大量存在 `@` 符號,這有何意義?你能否應用在後續的程式開發呢?** 參考[ doxygen ](http://nano-chicken.blogspot.tw/2009/07/doxygen.html)。 Doxygen 是一個 document generator ,可以將程式中的註解轉換成為說明文件。如果我們寫註解的時候,可以依據某種格式,就可以不用另外寫說明文件,可以透過這個 generator 直接產生說明文件,若今天程式碼做修改,我們也不用再去維護文件,而是只要用 Doxygen 產生新版的文件就好。 而根據[ doxygen 說明文件](https://www.stack.nl/~dimitri/doxygen/manual/commands.html#cmda)裡面第一句話就寫到了`All commands in the documentation start with a backslash (\) or an at-sign (@).`用`@`作為註解開頭,是 Doxygen command 規定的特定語法。我也思考 linux kernel 為什麼不用`\`作為開頭而是用`@`作為開頭,因為文件裡面寫兩者其實都可以。推測是因為 C 語言裡面的`\`是作為 escape character 。例如我們打`printf(%s,"\"")`,印出來的是`"`,因此本身C程式碼可能就會出現很多`\`,如果我們要搜尋整份程式碼寫的 doxygen 註解,用`@`才不會搜尋到我們不想要的資訊。 - **`tests/` 目錄底下的 unit test 的作用為何?就軟體工程來說的精神為何?** `tests/`裡面分為許多檔案,而每個檔案的用處就是針對某個 list.h 的某個功能做測試。這樣的測試就軟體工程來說可以方便 debug 。先確定每個小模組是正確無誤的,然後再兜起來如果發生錯誤,我們就可以排除是某個小模組出錯的可能,而是可能在兜起來的邏輯上有錯誤。 - **`tests/` 目錄的 unit test 可如何持續精進和改善呢?** 可以增加變異性,並測試一些 boundary condition 。或是故意測試一些會出錯的值,看程式碼是否能夠抓到並印出 warning。 ## 針對 linked list 的排序演算法 * **對 linked list 進行排序的應用場合為何?** 1. 若使用者想知道 process消耗的 CPU 資源,可能就須經過 sort 把消耗資源比率由大排到小。 2. 管理 Memory 時,可能需要知道分散的 memory space 由小排到大的關係,才能用作例如 Best-Fit 或是 Worse-fit 等分配。 3. 第一個作業的 phonebook ,如果本來數據是打亂的,要把它變回原本按照英文字母排序的形式儲存在 list ,也要使用 sort。 * **考慮到 linked list 在某些場合幾乎都是幾乎排序完成,這樣貿然套用 quick sort 會有什麼問題?如何確保在平均和最差的時間複雜度呢?** 首先看到[ linux kernel 的 list sort ](https://github.com/torvalds/linux/blob/master/lib/list_sort.c)是用 Merge sort,而不是用 quicksort 。比較一下兩者的 Complexity 差異:[來源](https://softwareengineering.stackexchange.com/questions/146173/merge-sort-versus-quick-sort-performance) ||mergesort|quicksort| | ---| --- | --- | |best case|$O(n log n)$|$O(n log n)$| |average case|$O(n log n)$|$O(n log n)$| |worst case|$O(n log n)$|$O(n^2)$| 可以發現主要是考慮到 Worse case , quicksort 的效能將會大幅下降,因此在不清楚資料分布的狀況下選用 Mergesort 是較好的選擇。而 Quick sort 遇到「幾乎已排好」或「幾乎正好排顛倒」的 data 時會遇到 worse case。 * **能否找出 Linux 核心原始程式碼裡頭,對 linked list 排序的案例?** 利用[這個工具](https://livegrep.com/search/linux)可以從 linux 程式碼找關鍵字,另外想起上作業系統時,老師第一堂課有介紹[ linux lsr ](https://elixir.bootlin.com/linux/v4.8.17/ident) 應該也是用來尋找關鍵字的網站。首先 linux 的 sort function 是命名為 **list_sort**。因此我鍵入關鍵字看能夠找到甚麼案例。 發現檔名比較親切的只有 [linux/fs/ext4/fsmap.c](https://github.com/torvalds/linux/blob/master/fs/ext4/fsmap.c),裡面的 line 451 有用到 list_sort。乍看之下是用來顯示 file system 的 information (參考[這裡](https://patchwork.kernel.org/patch/9654745/)和[這裡](https://sort.veritas.com/public/documents/vis/7.2/aix/manualpages/html/man/file_system/html/man1/fsmap.1.html))。而裡面的 function `ext4_getfsmap_find_fixed_metadata` 裡面用到 sort,這個 function 的目的是獲取 filesystem 的 metadata。先收集完全部之後存在 meta_list ,然後會用 list_sort 做一次排序。 * **透過 [skiplist](https://en.wikipedia.org/wiki/Skip_list) 這樣的變形,能否加速排序?** ![](https://i.imgur.com/TTnHSgr.png) skiplist是上圖這樣的資料結構。最底下一層是普通的 list ,儲存著所有的 element,而上面幾層則越來越少,每層都是下面一層的 subset。 若是對一個還沒排好的 skiplist 進行排序,我不認為比起一般的 list 可以帶來顯著的加速。因為若是先對上面的 list 做 sort ,到下面一層後還是要看上面已經排序好的那些 element ,才能知道確切要放在什麼位置。 * **研讀 [How to find the kth largest element in an unsorted array of length n in O(n)?](https://stackoverflow.com/questions/251781/how-to-find-the-kth-largest-element-in-an-unsorted-array-of-length-n-in-on),比照上方連結,是否能設計出針對 linked list 有效找出第 k 大 (或小) 元素的演算法?** 參考演算法講義`09_Medians and Order Statistics` P19 ,(或是上方連結有寫到)但這種演算法是尋找第 i 小的元素,因此稍加修改可以做出尋找第 i 大的元素 找出第 i 大的元素 pseudocode 為 \: ``` Select(i) { /*找 median-of-medians*/ 將n個元素分成 ⌈n/5⌉ 個由5個元素構成的小群 找出每群的中位數 利用 Select 函數找出這 ⌈n/5⌉ 個中位數的中位數 x /*找出第 i大的元素*/ 利用 x 作為 Partition 使用的 Pivot , Partition 後 x=A[k] 如 i=k 則 return x 如 i>k 則對大於 x 的那些元素做 Select(i) 如 i<k 則對小於 x 的那些元素做 Select(i-(n-k)) //因為全部index > k 的人都比他大,共 n-k 個。 } ``` * **[linux-list](https://github.com/sysprog21/linux-list) 裡頭 `examples` 裡頭有兩種排序實作 (insertion 和 quick sort),請簡述其原理** 程式碼: ```clike= static void list_insert_sorted(struct listitem *entry, struct list_head *head) { struct listitem *item = NULL; if (list_empty(head)) { list_add(&entry->list, head); return; } list_for_each_entry (item, head, list) { if (cmpint(&entry->i, &item->i) < 0) { list_add_tail(&entry->list, &item->list); return; } } list_add_tail(&entry->list, head); } static void list_insertsort(struct list_head *head) { struct list_head list_unsorted; struct listitem *item = NULL, *is = NULL; INIT_LIST_HEAD(&list_unsorted); list_splice_init(head, &list_unsorted); list_for_each_entry_safe (item, is, &list_unsorted, list) { list_del(&item->list); list_insert_sorted(item, head); } } ``` 首先看上方的 insertion sort , list_insertsort()在 30 行,會把 item 傳進 list_insert_sorted() 內,而意義就是 insertion sort 的把目前要 insert 的 key 與目前已經排好的 list 做比較。因此在第10~14行,就是走訪目前 list ,直到找到比 key 還大的人就停下來插入 list。 ```clike= static void list_qsort(struct list_head *head) { struct list_head list_less, list_greater; struct listitem *pivot; struct listitem *item = NULL, *is = NULL; if (list_empty(head) || list_is_singular(head)) return; INIT_LIST_HEAD(&list_less); INIT_LIST_HEAD(&list_greater); pivot = list_first_entry(head, struct listitem, list); list_del(&pivot->list); list_for_each_entry_safe (item, is, head, list) { if (cmpint(&item->i, &pivot->i) < 0) list_move_tail(&item->list, &list_less); else list_move(&item->list, &list_greater); } list_qsort(&list_less); list_qsort(&list_greater); list_add(&pivot->list, head); list_splice(&list_less, head); list_splice_tail(&list_greater, head); } ``` 以上是 quicksort 程式碼,可以注意到13~14行是在做選出 pivot key。然後16~21行,是對 list 中的每個數字,若是比 pivot 大就加入大的 list ,反之則加入小的 list。而23~24行對大小 list 做 recursive call 。最後終止條件在第7~8行。 ## 參考資料 - [LaTeX 語法與示範](https://hackmd.io/s/B1RwlM85Z) - [Poisson_distribution](https://en.wikipedia.org/wiki/Poisson_distribution) - [Linked List in Linux Kernel](http://neokentblog.blogspot.tw/2008/01/linked-list-in-linux-kernel.html) - [重要的資料結構 file_operations,file,inode](http://welkinchen.pixnet.net/blog/post/41068895-%e9%87%8d%e8%a6%81%e7%9a%84%e8%b3%87%e6%96%99%e7%b5%90%e6%a7%8b-file_operations%2cfile%2cinode-) - [linux cfs](https://blog.csdn.net/gatieme/article/details/52068016) - [linux cfs2](https://blog.csdn.net/dog250/article/details/5302819) - [list\_for\_each_safe](https://blog.csdn.net/choice_jj/article/details/7496732) - [list\_for\_each_safe2](https://blog.csdn.net/shendl/article/details/6397898) - [container_of](https://myao0730.blogspot.tw/2016/09/linuxcontainerof-offsetof.html)