SP 提過的或我覺得不重要的會跳過。
下半部分主要參考林忠緯的課程,NTU COOL 連結(雙班是林忠緯)。Youtube 播放清單
Mode | Description | Metrics | Key Focus |
---|---|---|---|
Batch mode | 執行後與外界無互動,如:編譯器、ML | CPU utilization、Turnaround time、Throughput,所以不用公平,把資源都集中給正在做的任務。 | Efficiency |
Online mode | 有互動,最常見,如:遊戲、瀏覽器 | Waiting Time、Response Time,避免資源都給最花時間的任務,要讓同一個長度的時間內完成的任務數量差不多,所以才要公平分配資源。 | Responsiveness |
Real-time mode | 有互動以外,還有時間限制,如:機器人控制、自駕車 | Tardiness(任務超時時間),要盡量避免任務超時。未必要把所有資源給任務,而是需要恰好使需要的資源都準備好。 | Predictability |
沒有一個系統能同時做好所有模式。
使用者 -> 應用程式 -> 作業系統 -> 硬體
包含輸入、處理、輸出,處理部分的 Central Processing Unit (含 Central Unit、Arithmetic Logic Unit(ALU)) 會與 Memory Unit 互動。Data 和 Instruction 都放在 Memory Unit 中。
完全符合 Turing Machine。
Central Unit 可以獨立存取 Instruction Memory、Data Memory、ALU。與 von Neumann 的差異只有 Instruction 和 Data 放在不同的 Memory 中。
現在的機器主要是 von Neumann Architecture,但有些小元件可能會使用 Havard Architecture,通常會是混合的。
過去的瓶頸主要是 CPU,也是要設計演算法的原因,因此 von Neumann 比較好用,因為不用頻繁存取 Data,bus 主要是設計來存取 Instruction。但比如現在的機器學習往往都是大量的簡單運算,需要頻繁存取 Data,所以或許未來的系統未必會是 von Neumann。
概念上,銜接硬體與應用程式,應用程式分成 system program (ships with OS, not part of kernel)、application program。OS 也稱為 middleware,現在一台電腦可以跑很多系統,比如用 docker、虛擬機,這些也是 middleware。
資源管理,讓應用程式更容易實作,而不用管太多硬體問題。
kernel 依架構分類:
第一堂課的 hack 就是改 -2 層的 memory,偷到 superuser 權限
現在通常都是混合,像 Linux 主要是 monolithic,但也有 modular 的成分。
只要不是 single processor、single core,就是 distributed OS。像 Google 搜尋就可能用幾千台電腦以網路連接。
Application - System call/services - OS - Interrupts - Hardware
System call 大致種類:
fork
- Creates a new process.open
- Opens a file for reading or writing.ioctl
- Performs device-specific input/output operations. read
, write
。time
- Returns the current time.pipe
- Creates a pipe for inter-process communication (IPC); socket
- Creates a socket for network communication.chmod
- Changes file permissions (e.g., read, write, execute).OS 實作 system call 的功能,也是 OS 課程的主要內容,SP 是介紹 application 到 system call 的 interface。
要 Pass 參數給 system call,可以:
功能:
先考慮 Uni-processor,一次執行一個 process,因此 Isolation 就簡單達成了,但就要做 scheduling,由 process state 協助。
admit 代表 OS 決定要不要執行一個 process,即 resource allocation,不夠的話就不給過。或是 real-time mode 時,還會考慮能不能在時間內做完。
由於 OS 是共享的,有可能一個 processor 進入 kernel mode,也許就會看到其他 process 的資料,很危險,需要重新設計。
SMP 以外,還有:
找到下個 ready process,並執行。目標是 maximize CPU use、快速的在 process 之間切換、公平分配等。
以 workload 分成 I/O-bound、CPU-bound。通常 CPU-bound 的 process 應該要分配到更久的時間。但 busy waiting(如 spin lock) 會浪費時間。
花時間,要把 state(Register、Program Counter、Memory、I/O 相關資訊等) 存到 PCB,PCB 越大就越花時間。
如果 CPU 有多組 register,也許就不需要保存 register,會快一點。
滿直覺、悲觀的一個上界。
: 必須 sequential 執行的程式比例
: core 數量
最好的情況下,把能平行化的部分(比例 )給 個 core 做。
Amdahl's Law 是考慮把 sequential 的程式平行執行,Gustafson's Law 是考慮把平行化的程式 sequential 執行,較樂觀。
假設在平行化時,sequential 部分執行時間 、平行化部分執行時間 ,則最差的情況(平行化效果最佳時) sequential 需要執行 的時間。也可以順便考慮大小核的問題,比如小核效能是大核一半的話,在 個小核平行跑 的時間等於在大核 sequential 跑 的時間。
SP 分類成 user level thread (model)、kernel level thread (model) 和 hybrid。而這邊是用不太一樣的等價分類方式,應該是比較貼近現實,把 thread 分成 user thread 和 kernel thread,user thread 代表 program 所使用的 thread,kernel thread 則是指 kernel 實際上排程的 thread。這邊有可能有理解錯,有發現問題的話歡迎更正。
先介紹 threading model 的分類:
以下都是 synchonous threading:
pthread_join
CPU 變多核後,多執行緒程式往往有成千上百個 thread,需要解決很多問題:同步、負載平衡、溝通成本、除錯、資源管理與記憶體一致性問題等。
#pragma omp <directive> [clause[[,] clause] ...]
。
OpenMP 的例子,向量加法、矩陣等:
矩陣乘法也類似,可參考原始碼
pthread_key_t
作為 key,pthread_key_create(&key, destory_func)
時每個 thread 使用 pthread_setspecific(key, ptr)
設定指標,pthread_getspecific
取得指標。program 執行時,需要把 text、data 搬到 memory,並分配 physical memory 給 process。把 program 放到 memory 稱為 Address Binding,有三種時間點:
透過 MMU(Memory Management Unit) 來對應。
過於簡單的模型,但可以幫助理解。直接分配一段連續的 memory 給每個 process,並把所有 text、data 載入到 memory。會產生 hole,也就是 external fragmentation。
尋找可用的 hole 的方法:
小塊、難以利用的空間
First fit 分配 N 個 block 則大概會有 N/2 個 block 的 external fragmentation。
可以動 process 來把 hole 集中在一起,因此 address 要是相對的。
每個 process 是一個連續的區間,因此檢查是否在 區間內即可。
避免 External Fragmentation,把 physical memory 分成固定大小的 frame,把 logical memory 分成固定大小的 page,兩者通常一樣且為 2 的冪次。用 page table 對應。
每個 process 中的 logical address 分成 page number ()、page offset ()。若 logical address space 為 ,每個 page 有 bytes 大,則 。
page table 可以簡單的以一個 array 實作,長度為 ,第 格存放第 個 page 對應的 frame number。
Internal Fragmentation 的期望值是 page size 的一半,因此 page size 越小則 Internal Fragmentation 越小,但 page table 會變大,因此還是得折衷。
Free frame 可以用一個 list 紀錄,若沒有 free frame list,相當於每次 page fault 後都要先找 victim 再寫入,會浪費時間。
如果 page table 直接放在 main memory,則實際存取記憶體就需要先存取 page table 找到位址,再存取實際內容,會導致 overhead,時間幾乎浪費在存取 memory。
這以 paging 的設計無法避免,但可以用硬體如 translation lookaside buffer(TLB) 來解決,cache page table 的部分。
實際上 x86-64 只會用 48-bit 的 logical address
page table 的 cache。
有些 TLB 會儲存 address-space identifiers(ASID),區分 process 用,TLB 的結構就是 (ASID, page number) 對應到 frame number。
TLB 是用 associative memory 實作,特性是可以平行化的以值找到 key,但成本較高,一般 TLB 只有幾千個以下的 entry。
cache 最近使用的 page table entry,而 replacement policy 就很重要。Wire down 一個 entry 代表使其不能被從 TLB 移除。
Hit ratio: ,要找的 entry 已經在 TLB 中的機率,通常會很高,實際上可能有 99%。
page table 每個 entry 會有 valid-invalid bit,表示此 page 是否在 memory 中。因為很有可能用不完整個 logical memory,所以未必要建好整個 page table,可以用 length register 來紀錄長度,並把超過長度的存取也視為 invalid。
常用的 library(read only,因此不需要上鎖保護)可以讓所有用到的 process 共用 physical memory,讓 page table 中對應到同一塊就行了。像是 C、compiler、window system、database system 等。
這些 code 需要是 reentrant,因為不同 process 可能會同時交替使用同個 function。
原因: 每次都記下整個 page table 會浪費空間,如 32-bit 可能每個 process 要 4MB,64-bit 更誇張。
像是 SP 教到的 file system 中的 data block 有 indirect block,用多層的 page table 來解決。因為一層的很多個 page table 可以放在不連續的空間,避免了需要一塊很大的連續記憶體。
但 overhead 變得更大,因為要存取很多次記憶體,64-bit 可能要 7 次,很不實際。
每個 entry 用一個 linked list 串起許多 (page, frame)。64-bit 系統可能會使用 clustered page tables,把連續的 page 集合起來,可能是 16 個。比如 page 共用 page table entry,要存取 的位置就計算 a>>4
的 hash,再從 entry 中找到 block 中第 a&1111
個 frame number。可參考原論文
原本是找到 page,讀取對應到 frame。改成 page table 的大小為 frame 數量,每個 entry 有 (pid, page),找到後 index 就是 frame number,因此整個系統可以共用一個有一點大的 process table。
有可能需要找整個 page table,因此為了加速,可以用 hash table,輸入 (pid, page),bucket 中儲存許多 frame,再去 page table 中一一確認。
我的疑問: 是否用 hash table 加速後就變得和 Hashed Page Tables 非常接近?
但有個問題是無法做 shared memory,可以透過把 page table 中的每個 entry 改成一個 linked list 解決,但搜尋的 overhead 就更大了。
logical memory 總和大於 physical memory 時,需要把一些 memory(以 process 或 page 為單位)暫存到 secondary storage(如 disk)。
移動裝置基本上不 swapping,因為 flash memory 寫入次數相當有限,可能會用 compressed memory 代替。
存取 swap space 會比一般的檔案更快一點,因為會是很大的區塊,而不需要檔案系統多餘的處理。
因為常常不需要 load 整個 program,所以可以用 demand paging,只有當真的需要時才 load。這樣一次可以跑更多 process,而且比較不用 swapping,載入、運行時間也更快。
OS 在舊的 CPU 上,virtual address 與 logical address 是一樣的,但現在的 CPU 有 segment。
分成 CS(Code)、DS(Data)、SS(Stack)、ES(Extra),每個 segment 有各自的 base register,支援更大的空間。
切分 segment 可以達到保護的效果,像是 CS 就是 read only。
存取超過 segment 的部分會有 segmentation fault。
需要的時候才從 disk、swap space (lazy swapper) 載入。使用者不需管這些事情(稱為 transparent)。
此時 valid-invalid bit 代表在 memory 且 legal (屬於這個 process,已 allocate),這樣若發現 invalid 並不一定是範圍外,有可能只是有 allocate 但沒 swap in,此時是 page fault,OS 會在 physical memory 找一個 free frame 並 swap in,更新 page table。
page fault 後要恢復 process 的狀態,相當於 context switch,比如 register、program counter。如果 page fault 發生在:
Locality of reference: 如果一個 instruction 需要存取散布在許多 page 的 data,則就會導致一直 page fault,延遲嚴重。但實際上不太可能發生,因為資料往往都不會這樣分布,通常都比較集中,可能是在編譯時整理過。
舉例: 類似 memcpy
或 memmove
的函數,如果簡單實作,遇到 source 或 destination 跨越多個 page 且發生 page fault 的情況,如果 source 與 destination 又有重疊,則這個 instruction 從頭再次執行的話會出錯。簡單的解方是先確定 source 與 destination 都已經 load 進 memory,再進行複製,確保不會被 page fault 打斷。也可以用 register 記下重疊部分。
在 swap in 或 allocate 新記憶體時,需要找到沒有在使用的 frame(free frame),紀錄 free frame list。Zero-fill-on-demand: 通常會填 0,確保安全性。
當 free frame 少於一定數量或甚至沒有時,需要 replacement algorithm。
swap in 所需的時間比起存取記憶體多很多,所以 EAT 幾乎是正比於 page fault rate。EAT 在計算時,page fault 的 case 要計算 overhead + swap in + swap out,swap out 是指當初 page 被 swap out 的時間,也要計算。
有些老的系統會在開始時就把整個 program 寫入到 swap space,這樣 swap in 比較快,但畢竟現在記憶體很大,不太需要這樣做。
現代的 Linux/Windows 會在開始時 demand paging,而後續 swap out 才寫入 swap space。
logical memory 大於 physical memory (類似航空公司 overbooking),則 free frame 可能會滿。以 reference string 來模擬並測試演算法,連續存取同個 page 可以合在一起。正常情況,總 page 數量愈多,page fault rate 會遞減。
需要找到一個 victim page,移到 swap space,並把新 page swap in。另外會用 dirty bit 紀錄是否有修改過,如果沒有則不需要放入 swap,可以直接丟棄,read-only 也是可以直接丟棄。
最早 load 進 memory 的 page 會被 swap out,紀錄一個 queue 即可。 比如 7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1,frame 數量為 3 時,page fault 的 page 有: 7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1。
FIFO 有這個現象,frame 數量增加時,page fault rate 反而增加(可能只是局部)
比如 1,2,3,4,1,2,5,1,2,3,4,5,frame 數量為 3 時,page fault 的 page 有: 1,2,3,4,1,2,5,1,2,3,4,5。
frame 數量為 4 時,page fault 的 page 有: 1,2,3,4,1,2,5,1,2,3,4,5。
直觀的看是因為 1,2 雖然近期有使用,但不會因此被保留。看完之後的方法,我們會知道其他方法用的是每個 page 的 priority,而 FIFO 是與 frame 數量有關,因此會有這個問題。
如果知道未來(完整)的 reference string,則可以找到最佳的 page replacement,也就是讓 swap out 的 page 是未來最晚使用的,但實際不可能,僅當作學術研究的比較。
類似 Optimal,但變成過去最久沒使用的 page 會被 swap out。
與 optimal 一樣,是用一個 stack(或稱 replacement queue) 來紀錄,越上面是最近使用的 page,因此 victim 是最下面的 page,且 reference 時會把 page 移到最上面。因為是用 doubly-linked list,需要修改 6 個 pointer。
LRU 和 OPT 等都是 stack algorithm,都是用 page 的 priority 判斷。可以發現若有 N 個 frame,則 page frame 中會有最近使用到的 N 個 page,所以 N+1 個 frame 則 page frame 必定包含 N 個時的 page frame,因此不會有 Belady's Anomaly。
LRU 要在每次存取 page 都更新存取時間,或搬動 stack 內容,沒有硬體支援這樣的操作。類似 LRU 精神的替代方法是記下一個 reference bit,預設 0,有用到就設為 1,但沒辦法知道每個區段內的順序。也許會每一段時間把所有都設為 0。
記錄每個 page 被使用的次數
Application 可能比 OS 更懂要用哪些記憶體,比如 database 自己更知道 replace 哪些 page 會更好,以及讀取或寫入的 buffer 應該要多大最好。但一般情況還是別管太多讓系統決定比較好。
mlock
(禁止 swap)madvise
(指定區塊是順序、隨機或是不再需要)mmap
(直接分配大區塊,2MB or 1GB 等)O_DIRECT
跳過 buffer cache每個 process 有最低 frame 數量要求,因為
最高的限制當然就是 physical memory 的大小。
Global 可能發生 logical 總和大於 physical,產生大量 page fault。因此會做 Reclaiming: 盡量保持 free frame 數量在一定範圍內,低於 min 開始 replace、高於 max 停止。
replacement 有沒有分 Global 與 Local?實際上通常會用 Global。好壞處 left as an exercise。
更極端的狀況,Free memory 真的太低時,會 ternimate 部分 process,在 kernel 必要的以外,試著 kill 用量大的 process 以減少 kill 的 process 數量。
一些細緻(meticulous)的演算法能讓使用者設定哪些 process 可以犧牲,具體是在 /proc/<pid>/oom_score
,可以修改 /proc/<pid>/oom_score_adj
,越高越容易被 kill。
如果有很多 CPU,每個 CPU 有各自的 local memory,則存取其他 CPU 的 memory 會比較慢。會盡量把 process 在上次執行的 CPU 上執行。
這裡指的是在同一張主機板上的 CPU,因為 CPU、記憶體很快,通常不會用網路連接記憶體,除了 DSM。
Linux CFS scheduler: 階層式的 scheduling domain 結構,每個可能包含多個 CPU,讓執行緒盡量在同個或相近的 domain 執行。Solaris也是類似,改叫 lgroups
(locality groups)。具體細節要再確認一下。
CPU 使用率太低時,系統會喚醒待命狀態的 process 以增加多工度。但達到一定程度時,記憶體不夠,會一直 page fault,進而一直 page in/out。CPU 沒有實際做事,因此 CPU 使用率又更低,形成惡性循環。
實際上 page fault 時,會有顯性延遲: page in/out 的 I/O 時間,以及隱性延遲: TLB 等 cache miss 增加。
這種現象包括時間區域性(temporal locality)和空間區域性(spatial locality)。畫出橫軸為時間,縱軸為 memory access 的 address,會發現可以分成幾個時段,每個時段活躍的使用同一些 page。如果所有 process 總和大於 physical memory,則會發生 thrashing。
根據 Locality Model 的觀察,稱一段區間內存取的 page 的集合為 working set。
可以簡單的加總所有 process 的 working set 大小,若太低就多跑一些 process;若超過 physical memory,則把一些 process swap out 並 suspend。
難處就是區間要如何設定,有以下方法:
然而,最簡單的方法還是暴力的增加更多 physical memory,各種規模的裝置都適用。
Kernel memory 比較不能接受被 swap out,因此通常不會用 paging system,會一直保留在 physical memory 中,因此需要更注重 Internal Fragmentation。且有些 memory 需要有連續的 physical memory,所以要用特殊的方式管理。
因為可能需要很大的連續記憶體,合併連續的 frame 作為 segment,管理會更有效率。但又可能需要很小的記憶體,因此把 segment 分成 2 的冪次大小,用 binary tree 管理,節點稱為 buddy。比如需要 31 KB 就拿 256 KB 的 segment 切三次變成 32 KB 的 buddy。
但這樣的 Internal Fragmentation 還是太大,比如 33 KB 就要 64 KB 的 buddy。
因為 kernel 是已編譯、固定的,因此可以知道每個 object 的大小,比如 Linux 關於 process 的 task_struct
就是大約 1.7 KB。而系統中會有很多 process,可以把所有 task_struct
放在一起增進效率。
一個 slab 可能橫跨多個 page,而一個 cache 可能包含多個 slab。同大小的 object 通常放在同一個 cache 裡,cache 的 slab 數量是可以擴展的。
優先找未滿的 slab(partial),否則從空的 slab 分配,再否則創建新的 slab。
Linux 中 SLAB 的改良版
在記憶體很少的情況適用,比如嵌入式系統,分成 small(< 256 bytes)、medium(< 1 KB)、large(< 4 KB) 三種。
系統複雜時需要分工,OS 希望看到的簡單一點,像是管理 Memory 就用 Virtual Memory。I/O 則是會有 Driver 等機制幫忙。
PCIe Bus,許多裝置可能接到各自的 Controller,再接到 PCIe Bus,比如螢幕、儲存、USB 等。
在硬體端控制硬體。
處理器通常是用 register (of device) 把指令或資料給 Controller,因此可以:
command register 可以想成 control register
Handshaking:先看 status register 的 busy bit 是否 on、寫入指令、設 command-ready 為 on、Controller 執行並設為 busy、裝置執行並清空 command-ready、error、busy。
操作時需要一直問是否 ready,造成 busy waiting。因此用 interrupt 來解決。
理解為一條電線伸到 CPU 裡。
若觸發就會執行 handler routine。一台安靜的桌機可能十秒內有 23,000 次 interrupt。
有 Interrupt Vector,紀錄每個 interrupt 的 handler routine 的位址。
Driver 開始 I/O 後,CPU 可能就去做其他事情,等 Controller 做完後會發送 interrupt,CPU 執行 handler routine。
比如 Page Fault 就是一個 Interrupt
希望大量資料傳輸別牽扯到 CPU,CPU 發起指令後,讓 Controller 自己傳輸資料到 memory。
DMA vs memory-mapped I/O: memory-mapped 主要是為了低傳輸量如 control bits,DMA 則是為了高傳輸量。
USB、802.11、Mouse、Keyboard 等
OS 通常有 back door,比如 UNIX ioctl
,繞過 driver
read、write、seek
Block:
O_DIRECT
。Character:
get、put
通常與 Disk 的方式不同,透過 socket 連接
要對時,或是五秒後給一個 interrupt 等。
再複習一次,Nonblocking 是讀到什麼就馬上回傳,而 Asynchronous 像是觸發一個指令,之後某個時間總之會完成。
同時做好多個 I/O,比如 readv
。
scatter-gather method 比多個個別的 system call 好,省時而且比較不會被干擾(race condition)
同時可能會有很多 threads 要用某 device,所以需要排程,未必要 First come first serve。
I/O 可能有很多問題,可能會以回傳值的方式表達,比如 USB 傳輸時突然被拔掉,程式要處理這些事情。
避免 User 亂用,基本上都要透過 system call 執行(trap to kernel)。
需要紀錄開了哪些檔案,有哪些連線。像 Windows 會把所有指令當作 message,雖然有 overhead 但更方便。
邊緣裝置如手機難以散熱,伺服器可能散熱比本身運算還耗電,沒有冷氣很可怕。
Power Collapse: 讓裝置冬眠,只消耗很少的電但仍能反應。
例如 Android 用 Device Tree 了解 Component 間的關係,如果一個 bus 或整個 system 上的裝置都沒在使用,則可以把 bus 關掉或把 system power collapse。
ACPI(Advanced Configuration and Power Interface)
UNIX 的方法,模組化的設計,每個 stream module 有 read/write queue,可能分很多層。
I/O 常常使用,且會產生大量 Interrupt、Context Switch,所以要注意。
有些 process 是 CPU Bound(較多長的 CPU Burst)或 I/O Bound(較多短的 CPU Burst),整體而言長的 CPU Burst 會很少,大部分都是短的。CPU/IO Burst(突發) 代表做事的時間段。
CPU scheduling 就是找 ready 的 process,決定要執行哪個。以下情況需要決定:
1、4 都是要跑新的 process,2、3 則也許可以選擇繼續執行原本的 process。
Pre-empt: 搶在…之前說話(或行動);預先制止
幾乎現代系統都是 Preemptive。
可能有 race condition,比如 shared data 但有一個 process 被 preempt 了,這需要透過同步來解決。
執行下一個 Process,可能要 Context Switch、Mode Switch 等,Dispatch latency 指停下目前的 Process 到下一個 Process 開始執行的時間。
不會只看一個指標,會綜合評估
最簡單,就是一個 queue,FIFO。
Convoy effect: 需要執行很久的 Process 卡住其他的 Process。解決方法比如可能會想要讓 I/O-Bound process 先做,等待時間讓 CPU-Bound process 執行。
Nonpreemptive。如果所有 process 同時 ready 且沒有新的 process arrive(變成 ready),則 SJF 是 Minimize average waiting time 的最佳解。
與 Optimal Page Replacement 一樣,要估計或是問使用者花多少時間,這很難做到。可以用 Exponential averaging: 第 個估計為 ,實際上第 個 CPU Burst 為 ,則 , 通常是 0.5。這樣的話, 會慢慢接近實際的 CPU Burst 時間。需要選 。
若 Preemptive 則是 Shortest-Remaining-Time-First(SRTF)。
Shortest-Remaining-Time-First 基本上是 Minimize average waiting time 的最佳解,不管 arrive 時間。
輪流做一段小時間(Time Quantum q,大概 10-100ms),可以減少 response time。
若 q 非常大就變成 FCFS。不考慮 context switch 與 dispatch 的 overhead,肯定是 q 越小越好。實際上一個 context switch 可能會小於 10us,也是差不多能忽略,因此 q=10-100ms是合理的。
通常會讓 80% 的 CPU Burst 小於 q(一個 Quantum 就做完)。
可以是 Nonpreemptive 或 Preemptive,Priority 設得好可以變成 SJF。
可能有 Starvation 的問題: Priority 低的可能永遠執行不到,可以搭配 Round-Robin,或是用 Aging: 隨著時間增加優先權。
通常數值低的優先權高。
每個 Priority 一個 queue,每個 queue 可以有不同作法。
通常 process 的 priority 是:
Process 可以移動到別的 queue,Aging 可以藉由此方法達成。
現代實際上是在 Schedule (kernel-level) threads。
Pthread 可以用 pthread_attr_getscope
來查詢或以 pthread_attr_setscope
來設定,可以是 PTHREAD_SCOPE_PROCESS
或 PTHREAD_SCOPE_SYSTEM
。
Intel 稱 hyper-threading。每個 core 會有多個 thread,可以在一個 thread 存取記憶體(memory stall) 時,讓其他 thread 交替執行。
OS 會直接把 thread 當作 core 來用。
這樣需要兩層 scheduling,第一層是決定 kernel-level threads(Software threads) 要在哪些 Hardware threads(CPU threads) 上執行,第二層是決定 Hardware threads 要在執行哪個 Processor Core 執行。兩層未必完全獨立,如果互有認知的話會找到更好的解。
Process(Thread) 會想在之前跑的 processor 上跑,可能是因為 Processor 上有相關的 cache。Load Balancing 時會考慮這個問題,因為若移動到別的 Processor 會需要多餘時間讀資料。
分 Soft、Hard,是否強制跑在同個 Processor 上
NUMA 的話可能會盡量分配與 Processor 鄰近的記憶體給 Process。
引進 Deadline,分 Soft、Hard。
Event latency: 事情發生到做出反應。可能被 Interrupt latency(Interrupt 到 ~ Routine 開始做,比如分辨類型、Context Switch)、Dispatch latency 影響。
Event latency 中,可能是由 Interrupt latency、Interrupt routine 執行時間、Dispatch latency(Routine 切換成處理事件的 Process)、處理事件的 Process 組成。
通常會分配某種 Priority,達成 Soft real-time。
以下 Real-time scheduling 會考慮一個簡單的 Model:Processing time t、Deadline d、Period p,每 p 時間,會要求在 d 時間內做完要花 t 時間做的事。,通常 。
POSIX Real-Time Scheduling
可以設同 Priority 的要以 FIFO 或 RR,比如 SCHED_FIFO
、SCHED_RR
早期是 ,但後來想考慮多一點,用 Completely Fair Scheduler(CFS)。以 Scheduling class(Priority) 來 schedule。
POSIX.1b: 把 -20~19 對應到 100~139,0~99 留給 Real-time 的各種 class。
一樣是 Preemptive、priority-based。有分 class(Real-time 等),之後再分 priority,是一個 multi-level 的設計,但高的 class 低 priority 不一定比低 class 高 priority 優先。
與使用者(前端)相關的程式,會提升 scheduling quantum 到約 3 倍。
Windows 7 支援 User mode scheduling,讓使用很多 thread 的程式自己 schedule。
首先還是得決定要注重什麼指標。
Real-time 可能需要用 worst case 而不是 average,可能會用悲觀的估計方式。
Secondary storage,介紹其物理結構、性能特性、I/O scheduling、系統提供的有關服務(比如 RAID)
SSD、Flash Memory。以 page 分區讀寫,但寫入前需要擦除涵蓋多個 page 的整個 block,所以 Controller 會用 Garbage Collection,把需要擦除的 block 裡的資料搬離。
硬碟的每個部分有一定的寫入次數限制,因此 wear leveling 會均勻的寫入到所有記憶體單元。另外,會以 TRIM 來讓硬碟提早填入 0,這樣寫入時就不用先填 0。
通常會視一個硬碟為 logical block 的一維陣列,以 logical block address(LBA) 來存取。
最小化 access time,最大化 Bandwidth。
讀寫都要經過 system call,以及檔案管理等,而且有 request queue,可以排程。
現代的系統通常不知道 physical 位置,但總之 LBA 越大 physical address 通常也越大,相近的 LBA 通常也相近。
浪費時間
磁頭從一端到另一端,然後再回來,像電梯一樣。
問題:假設 request 是均勻分布在 cylinder 上,掃到一邊時,另一邊的 request 密度最高,且等了很久。
磁頭從一端到另一端,然後回到起點,這樣的話就不會有一邊的 request 等很久。
以上只是一些簡單的方法,但 SCAN、C-SCAN 就已經不錯了。Linux 會用 deadline scheduling,儲存 read、write 在不同 queue,read 優先,因為 process 通常是因為 read 而 block。
其實不太需要 scheduling,因為沒有物理機械,random access 很快。用 FCFS 就可以了,Linux 還會把鄰近的 request 合併。
Write amplification: write 導致 garbage collection,再導致許多 read/write。
硬碟中通常會有 bootstrap,放驅動程式等,這會放在 boot blocks 中。
比如 Master boot record(MBR),存 partition table,也紀錄哪個 partition 是 bootable。
Bad block: 壞掉的 sector。
可以是一個 partition 或一個檔案。會紀錄 swap map,代表一個 page slot 被幾個 process 使用,或沒被使用。
與 SP 高度重複,跳過。
要考慮 Sequential Access 與 Random Access 的情況。
需要知道哪些 block 還沒被使用
ZFS 為了支援大量檔案、FS,考量了 metadata I/O 的時間。把空間分成多個 metaslab,每個有對應的 space map,是依照時間順序紀錄 log,把操作依時間順序紀錄,需要存取 metadata 時根據 log 模擬一遍。
SSD 不能直接寫到 disk,而是要先填 0。
把 file data 當作 virtual memory 來管理,效率更高。Mmap I/O 就會用 page cache。
Unified virtual memory: process page 與 file data 都用同樣的 page cache。
若有 Buffer cache 又有 Mmap I/O,就會有 page cache + buffer cache 的 double caching 問題。
壞處:
若 Mmap I/O 與一般 read/write 都用 Unified Buffer Cache,就能解決。
Page cache 用 LRU 不太好,因為順序讀取某個檔案時,剛讀完的部分可能接下來反而比較不會再讀一次。
順序讀取的改良方法:
很多大概算常識,跳過。
統一的遠端資訊存取方式。
要如何實作:多個使用者同時存取一個檔案、一個使用者修改後其他使用者看到的結果。
寫入檔案後,要關閉檔案才會被其他人看到變動。就像每次儲存變動都是一個 git commit + push。
最簡單的方法,Shared file 不能改變。
一樣與 SP 高度重複。
Requirements:
以 flag 代表要不要做事,以 turn 代表在做事(atomic 的變數)。
前兩個明顯符合,重點是第三個,若 Pi 在 while
等的時候,就不會設 turn=j
,且 flag[i]=1
,因此 Pj 就會被 for
擋住。
不過現代處理器、編譯器可能會把不相依的 load/store 重新排序。Single-thread 沒差,Multi-thread 可能有問題,因為對某 thread 可以 reorder 對其他 thread 可能就不行。
flag
與 turn
順序相反會導致 Peterson's solution 出問題。
不同 Memory Model:
Memory Barrier 讓其他 processor 強迫看到改變,會等前面的 load/store 完成才繼續執行。像是在 include 間加入註解避免 formatter 亂排序。
現代系統提供以下 atomic operations:
test_and_set(bool *target)
: 先紀錄 target
目前的值,設為 1 再回傳原本的值。compare_and_swap(bool *target, bool expected, bool new)
: 如果 target
的值是 expected
,就設為 new
,並回傳原本的值。以上兩段程式都沒辦法達到 Bounded Waiting,但加其他操作就可以(比如 critical section 完指定執行下個 thread)。
通常以 compare_and_swap
來做其他 Synchronization Tools,比如 atomic variable。
一個只能以兩種操作存取的 int,像是停車場空位數字:
wait(S)
: 等到 S 小於 0,把 S 減一。signal(S)
: 把 S 加一。可以用來限制同時執行的 thread 數量,如果最高只有 1 就可以作為 mutex。若不用先 wait
也能 signal
,也可以當成通知機制。
若不要 busy waiting,就可以 suspend,並讓其他 thread 通知,像是 condition variable。
signal
、wait
可能有人亂用,因此可以用 Monitor 來包裝。
一種 ADT(Abstract Data Type,以一些 function 包裝 data,如各種資料結構。像是網站只提供 API,使用者不用管具體怎麼執行。),把資料包裝起來,使其在存取時自動進行 lock/unlock 等 synchronize,只有一個 thread 能執行,就不怕使用者亂寫 code。
會提供一些 condition variable 讓 thread 可以等。
一些系統必須滿足的性質,使 thread 運作。
Safety: 希望壞事別發生。
L M H 三種 priority 的 thread,L 在用某資源,H 在等,但 M 卻可以中斷 L。
解決方法是 Priority Inheritance: L 的 priority 提升到 H 的 priority,等 L 做完後再恢復。