# 作業系統筆記 ## chapter 1 電腦的基本架構: ![](https://i.imgur.com/Qlbrirg.png) 作業系統核心概念: * 資源分配器 * Manages all resources * Decides between conflicting requests for efficient and fair resource use * 控制程式 * Controls execution of programs to prevent errors and improper use of the computer * interupt base : 呼叫才會做處理 * 作業系統中永遠運行的程式就是kernel,除了kernel以外的,都叫system program或application program * CPU會把資料從memory裡丟到local buffer,然後memory會直接跟buffer做溝通 ![](https://i.imgur.com/en8y6g7.png) * interrupt比喻:櫃台叫你去填表單,填完後再告訴他,你填東西的這段時間,櫃台還可以服務其他人 * device drive(hardware)或application(software)做完後,會發出interrupt告訴CPU任務做完了 * interrupt handling * polling:一個一個檢查誰需要interrupt * vector:回傳一個值告訴作業系統誰需要interrupt,較有效率,OS大多採用這個 * Storage-Device Hierarchy 越往下越慢,容量越大 ![](https://i.imgur.com/U7hD8o0.png) * bootstrap program:把kernel load進去 * timesharing:透過很短暫的時間快速切換工作,讓user有種OS只服務他一人的感覺(時間管理大師) * Memory Layout for Multiprogrammed System ![](https://i.imgur.com/NuVnksA.png) * dual mode : 有些東西不能讓使用者直接碰觸到,所以要有區分 * user mode : user可以直接存取呼叫 * kernel mode : user要透過system call來存取呼叫 * system call : 讓user存取system program的介面,user呼叫interrup,讓kernel去存取受到限制的資源(kernel mode) ![](https://i.imgur.com/v3EYR8K.png) ## chapter 2 * Operating System Services * User interface * Program execution * I/O operations * File-system manipulation * Communications * Error detection * Resource allocation * Accounting * Protection and security * 作業系統為甚麼不採取完全沒有錯誤的方法:會運行的很慢,找出問題並解決,才是重點 * 作業系統架構: ![](https://i.imgur.com/Tl3AhAE.png) * API:再對system call做一層包裝,因為sys call會隨版本更新變動,透過一個共用介面來簡化程式 * 一個sys call後會做非常多事 ![](https://i.imgur.com/8Q6C3Mg.png) * 只服務一個user single tasking,memory只有一個process: ![](https://i.imgur.com/4L11oGM.png) * 單晶片,沒有作業系統,用bootloader載入程式,更簡單基本 ![](https://i.imgur.com/AbLj1XR.png) * 多人多工OS,memory有多個process ![](https://i.imgur.com/N8IN79F.png) * link and load * link:把函式庫link進二進位檔 * load:把執行檔從disk載入到memory ![](https://i.imgur.com/9ZDDRYj.png) * monolithic structure:UNIX使用,不把系統分太細,主要分為兩大塊: * system programs * the kernel * system call interface * file sys、CPU scheduling、memory management... * UNIX的架構,沒有layer的概念: ![](https://i.imgur.com/m8W1Fze.png) * linux structure:在unix加入modular design * 原先的設計是有新的裝置controller要加入,整個系統就要重新compile * modular:可以把一些功能load到kernel裡,不用重新compile * layer approach: * 每一層depend on上一層 * 沒效率,因為底下operation都會做到 * 中間的層難以排序,沒有一定的相依性 ![](https://i.imgur.com/pgJuMnK.png) * micro kernel:減少kernel大小,需要用到時再從user space載入到kernel ![](https://i.imgur.com/CJ10Vo2.png) ## chapter 3 * process:執行檔load到memory後就稱為process ![](https://i.imgur.com/ppo0VtR.png) * memory layout ![](https://i.imgur.com/2Bp0fsu.png) * 程式 to 程序 * 程式檔案的頭部會存放資訊告訴OS怎麼把它放到記憶體裡,程式進入點在哪 * windows: PE(Portable Executable) Header * Linux:ELF(Executable and Linkable Format) * process state ![](https://i.imgur.com/lpjamdH.png) * 前面的layout沒有儲存process state的資訊,所以要有PCB(process control block) ![](https://i.imgur.com/0sl2cAk.png) * 一個process對應一個PCB * PCB內會有pointer指向真實在memory的位址 * CPU Switch From Process to Process ![](https://i.imgur.com/kxywMLS.png) * context snapshot:將被switch out的process的資訊(register、IO、進度)存回PCB,讓下一次再選到這支process執行時,可以重新載入 * process內可能會有很多thread,可以假想成有很多program counter在跑 * process含有兩大部分,PCB與process真實存放的記憶體位址 ![](https://i.imgur.com/pgTPPBW.png) * PCB與PCB之間會用雙向linked list連接 * process scheduling * 為了避免去檢查整個list,所以用queue存放,增加效率 * ready queue : 存已經可以執行的process的PCB * wait queues : 存還不能馬上執行(等待IO、...)的process的PCB * ![](https://i.imgur.com/2KZlnRD.png) * schedulers * long term/job scheduler : 每幾秒甚至每幾分鐘才執行一次,挑一批process進入ready queue,限制多少process可以進入ready queue * 會盡量讓I/O 與 CPU bound process各佔一半比例,避免太多人排隊給CPU執行 * 隨著電腦越來越強,這種機制已漸漸被淘汰 * short term/CPU scheduler : 幾ms執行一次,挑一個process執行,限制每個process可以執行的時間 * medium term scheduler : 因為某些原因導致ready queue內放的不是想要的,這時候就用swapping * swapping : 把process直接從memory丟到disk去(並非丟執行檔,而是記憶體原封不動的丟) * 比喻:long term->會議室外的等待空間,short term->每個人可以報告的時間 * context switch * context就是PCB * context switch的時間,CPU無法做任何事 * context switch時間減少,CPU效率提升 * process creation * 用parent產生children的概念 * 會用parent process來產生新process是因為這樣會更方便複製process的資料結構也就是直接把parent複製一份fork,然後改pid即可 * 父子process可以share記憶體,所以設計可以非常靈活 * ![](https://i.imgur.com/thofRuv.png) * process在fork完後才會將執行檔load進memory,然後存指標到PCB * 父process會等待子process結束後才能結束 * 執行一個程式 : 父process執行fork -> 子process執行exec * fork,假設child的pid都為0 * fork回傳的是自己的pid,並非child process的pid * fork完後產生child,parent與child除了pid之外都一模一樣 * 當CPU選中child去執行時,就會跑到case pid==0裡,然後執行exec把這段程式碼蓋掉成想要的 ![](https://i.imgur.com/Q6axPvX.png) * process termination : process結束後釋放記憶體 * parent process有權限把child process終止,以避免沒有把記憶體釋放乾淨 * thread概念:一個process裡有不同的program counter,所以一個process裡可能有多個TCB(thread control block) * chrome記憶體用量大原因:每開一個分頁就是一個process,會這樣做是為了避免某個分頁掛掉,其他分頁也跟著掛掉 * interprocess communication(IPC) * shared memory : 兩個process共用一塊記憶體,存取快速便利,但不安全 * 問題:要如何解決寫入到一半時被interrupt後,造成其他人讀取到不正確的值?(同步問題) * message passing : process之間透過類似system call的方式從kernel讀取共用的記憶體,較安全 * ![](https://i.imgur.com/51rDv25.png) * communication * direct : 直接發送接收資訊,1對1 * indirect : 放到mailbox,別人再從mailbox拿出來 * synchronization * blocking : 等到receive完後才能結束send,等到send完所有message後才能receive * non-blocking : 丟完後就可以結束send,接受訊息也不管有沒有正確 * pipe : 用父子process的關係讓溝通變簡單(約定某塊記憶體收發訊息) * remote procedure call(RPC):執行遠端電腦的system call或程式 ## chapter 4 * Single and Multithreaded Processes * 一個PCB可能指向多個TCB(依實作而定) ![](https://i.imgur.com/YuHCZ65.png) * 為何要用thread : 不用每次都複製一大堆相同程式碼,節省記憶體 * Responsiveness * Resource Sharing * Economy * Scalability * concurrency : 快速切換來同時運行多個task,但實際上同時只執行一個 * parallelism : 用multicore來同時執行多個task,真正意義上的同時執行多個 * data parallelism : 資料分成不同core來執行,某段只由一個core執行 * task parallelism : 同一段資料可能有多個core來執行 ![](https://i.imgur.com/Dafwbbc.png) * user and kernel threads * ![](https://i.imgur.com/M1MYXeW.png) * many to one : 有一個thread call system call後就interrupt process,造成其他thread也跟著被暫停 * one to one : user thread自動產生或對應到一個kernel thread,library與kernel息息相關 * many to many : 可以提供1對1,也可以提供多對多,自動判斷如何對應(檢查thread pool) * two level : kernel thread被綁定在user thread * implicit threading : compiler自動幫你multithread,但並沒有在程式碼裡實作multithread,顯性則反之 * fork-join parallelism : compiler自動把task切割fork出去執行,然後把結果join起來 * grand central dispatch : runtime時才變成multithread * serial queue: 序列化執行,無法平行化的程式會丟到這個queue * concurrent queue: 平行化執行 * multithread process執行exec後會將所有thread覆蓋掉 * multithread下的fork有兩種 * 複製所有thread * 只複製執行到fork的thread : 通常會用後fork後馬上exec * signal handling : 其他process對發生例外狀況的process發出通知,做後續處理(關閉某個或某些thread) * thread cancellation * asynchronous : 立即cancel thread * deferred : 檢查現在是否可以被cancel,不行的話就等待thread做完後才cancel * off : 無法被cancel * thread-local storage(TLS) : 存一些function與function之間的information,thread特有的資訊 * scheduler activations : user thread與kernel thread之間溝通的流程 * light weight process(LWP) : 存user thread與kernel thread之間的關聯對應資訊的資料結構,位於kernel space * 讓user thread以為LWP是一個virtual process * ![](https://i.imgur.com/khkedsF.png) * 每個user thread都會對應到一個LWP,LWP也只會對應到一個user thread,但可能對應到多個kernel thread * upcall : kernel thread通知user thread訊息 * 可以是kernel thread產生多個LWP,也可以是多個LWP assign到不同的kernel thread,有很多種做法 * LWP就是user thread在kernel的代理人 ## chapter 5 * CPU scheduler : 在ready queue中尋找下一個適合的process執行,雖然真正的最小執行單位是thread,但是存放的資訊還是在process裡,所以scheluder是選擇process而不是thread,process選完後才近一步選thread,thread再去選用哪個CPU來執行。 * preemptive vs non-preemptive * preemptive : 執行到一半時可能因scheduler而被強制交出CPU使用權,有效率 * non-preemptive : 會等到該process離開running state後才執行scheduler,running自己主動呼叫scheduler的情形就屬於non-preemptive,沒效率 * 呼叫scheduler時機 #必考 * running state to waiting state (做IO) => non-preemptive * running to ready state (interrupt occurs、時間用光了) => preemptive * waiting to ready (IO完成),比喻:填完表單的人可以讓櫃台決定要不要先服務你,而不用從頭開始排隊 => preemptive * running terminate (執行結束) => non-preemptive ![](https://i.imgur.com/TIIhwp3.png) * dispatcher : scheduler選好下個process後,就交給dispatcher,做以下處理: * context switch * 把現在的process存回PCB * 把選定的下個process的PCB資訊載入到CPU register * switching to user mode * user mode之間的轉換 * jumping to the proper location in the user program to restart that programjum * program counter指到接下來要執行的process的程式碼 * dispatch latency : 執行dispatch的時間 ![](https://i.imgur.com/R2IXFvT.png) * 沒有人可以打斷dispatch,比喻:皇帝旁的太監 * scheduling criteria * CPU utilization : CPU越忙越好,榨乾時間 * throughput : 單位時間處理越多process越好 * turnaround time : 每個process從開始處理到結束的時間越短越好 * waiting time : process在ready queue裡等待時間越短越好 * response time : 從request到response的時間越短越好 * 不可能全部都同時優化 * First Come First Served(FCFS) : 依據進來的順序去執行,也就是直接拿ready queue的第一個element * ![](https://i.imgur.com/XcibBQv.png) * 但average waiting time是17,如果先做P2、P3,可以讓average waiting time減少 * ![](https://i.imgur.com/4mgQ1B2.png) * convoy effect : 讓short process先做,就可以降低waiting time * 為了降低waiting time,所以有了SJF * Shortest Job First (SJF) * 但問題是還沒執行前都不知道burst time是多少,所以通常用預估的方式來決定burst time ![](https://i.imgur.com/KzlxMtu.png) * 預估方法: ![](https://i.imgur.com/2khENGQ.png) * EX: 下一次預估 = 本次預估\*0.5 + 實際時間\*0.5 ![](https://i.imgur.com/fQ0uPhK.png) * shortest remaining time first : FCFS與SJF結合 * 看剩多少時間要執行,先選最少的,此方法為preemtive,如果出現一個更少剩餘時間的process,就會強制切換 * ![](https://i.imgur.com/LJDG1wI.png) * priority scheduling * 選低priority值的process執行 * Round Robin * 每個process執行一段時間(time quantum q)就換人,一直輪流下去 * time quantum q usually 10-100 ms,context switch < 10us * quantum 通常設計為 80% of CPU bursts should be shorter than q ![](https://i.imgur.com/g3JFxxf.png) * 在現實中很少見,因為人腦無法快速切換工作 * Multilevel queue : 把不同種類的process丟到不同的queue,現在OS大多用這種方法 * 用不同排程演算法處理不同queue ![](https://i.imgur.com/Vj27auu.png) * queue之間也有priority ![](https://i.imgur.com/CEn897n.png) * Multilevel Feedback queue : process可以在不同queue之間移動,會有一些機制來把process丟到其他queue,避免等太久 * 各種排程的比較 ![](https://i.imgur.com/RGB6o52.png) * 期中必考1 ![](https://i.imgur.com/WuCIJ3V.png) * 期中必考2 ![](https://i.imgur.com/V71t3Kv.png) ![](https://i.imgur.com/Ov82Jl8.png) ![](https://i.imgur.com/0vd0QLu.png) * Multiple Processor scheduling * Homogeneous processors : processor之間的運算能力是一樣的 * Asymmetric multiprocessing : 有一個專門負責scheduling的processor * Symmetric multiprocessing(SMP) : 現代OS常用,每個processor自己決定CPU scheduling * 所有thread存在common ready queue * 所以多個processor有可能選到同一個thread,產生大問題 * 因此現在改變為每個processor都有一個ready queue * 每個thread會被分配到不同的ready queue裡 * 把所有multiple processor放到同一個chip就叫multicore processor * Multithreaded Multicore System * 趁其他thread在存取記憶體時切換到另一個thread執行 ![](https://i.imgur.com/qy7ft5S.png) * Chip-multithreading : 每個core裡都有2條hardware thread,完全由硬體實現,會多一套register來存另一個thread的register值 * hyperthreading in Intel ![](https://i.imgur.com/DhXAC3y.png) * two level of schduling * kernel thread對應到hardware thread就會用前面提到的排程演算法 ![](https://i.imgur.com/oFpjY8y.png) * load balancing : 不讓每個CPU上面放的task太多 * push migration : 把overloaded CPU的task push到其他CPU * pull migration : 閒置的CPU把task從overloaded CPU拉過來 * Processor Affinity : 盡量讓task留在同一個CPU,讓快取起到作用 * soft affinity : OS想辦法讓task只在一個CPU上執行,但不保證 * hard affinity : 保證task只在某幾個CPU上執行 * Non-uniform memory access (NUMA) : * 移動task後,CPU就要存取到其他CPU的memory,這樣的速度很慢 * 因此盡量避免移動task到其他CPU * ![](https://i.imgur.com/XxBB177.png) * 一個core同時只能處理一個hardware thread,這是concurrency而非parallelism * Real time CPU scheduling : 要求在很準確時間執行的task(EX火箭),不能被其他process影響 * soft real-time : priority高的先處理,但無法保證高priority一定可以被schedule * hard real-time : 一定滿足條件,重點放在執行的時間 * event latency : event發生到被serve的這段時間 * interrupt latency : interrupt進來後直到執行完interrupt的時間 * dispatch latency : 選好下個process到執行完dispatch的時間 * ![](https://i.imgur.com/ylgzCI5.png) * dispatch有可能conflict,也就是要等別人處理完資料,花額外時間去處理conflict * preemptive,priority based scheduling : 一個更緊急的事件出現後,要把現在的process換掉 * 每個thread會periodic的出現,每p秒出現一次,執行t秒(event latency),deadline是d * 需求 : 0 ≤ t ≤ d ≤ p * ![](https://i.imgur.com/FPuHiyp.png) * Rate Montonic Scheduling * 給每個thread或process一個period或priority,週期越長,priority越低,週期越短,表示常常執行,priority越高 ![](https://i.imgur.com/aCNLOiU.png) * missed deadline ![](https://i.imgur.com/HKaZpvP.png) * 公式算出來是83%小於2個utilization相加的0.94,因此會miss * Earliest Deadline First Scheduling(EDF) : 依據離deadline的時間來排程 ![](https://i.imgur.com/GAAgCTj.png) * 最佳theoretically optimal solution 比rate montonic好 * 但經常切換,所以要考慮到context switch的時間,所以不一定適用所有狀況 * Proportional Share Scheduling * 所有process有T個share * 現在已經有N個share * ![](https://i.imgur.com/SkGsqw6.png) * 判斷D進來後是否有辦法分配下去,無法的話就拒絕 * linux scheduling * version 2.4 * global runqueue/expired queue * 把所有thread丟到runqueue * runqueue的process執行完後丟到expired,直到runqueue都被執行完後,就開始執行expired queue * 高priority得到更大的quantum * version 2.5 * priority從0~140,用140個queue的array存放所有thread * CPU可以直接從priority最高的queue內拿出thread * 有thread存於queue的priority就會把該priority的bit變為1,使搜尋時間達到O(1) * 高priority得到更大的quantum * version 2.6.23 * completely fair scheduler(CFS) : 值的部分重新設計 * ![](https://i.imgur.com/jZ2bXMa.png) * virual run time : 記錄每個thread用了多少時間 * 根據virual run time來決定下個選誰,不只考慮priority ## chapter 6 & 7 * race condition : 多個process在同時競爭(使用)相同的資源,結果與存取資源的順序性有關連時,race condition就會發生 * Synchronization : 避免race condition的發生 * critical section problem * critical section : 會動到與其他人share data的程式碼段落,簡稱CS * critical section protocol : 在執行到critical section之前與之後去做一些措施保證critical section執行期間不會被schedule out * entry section * exit section ![](https://i.imgur.com/g0R0nNo.png) * solution * mutual exclusion : 在執行critical section期間,只有該process可以做,沒有其他process可以中途插入,一定會等critical section做完 * 晚到的entry會被block起來 * 執行結束後知會其他process可以進入critical section了 * progress : 要持續進展,否則其他人會被卡死,無法執行下去 * 在多個entry中決定下個進入的entry * bounded waiting : 要保證不會無限地等下去,遲早有天會輪到你去存取 * 不能根據速度去決定下個entry否則會不符合bounded waiting,可能有人無限等下去,永遠選不到他 * EX ![](https://i.imgur.com/SgwSWQK.png) * 只判斷是不是輪到自己 * 無法滿足progress與bounded waiting條件,因為如果只有一個process,而且turn又不符合條件,就會無限等下去 * EX ![](https://i.imgur.com/hpfjdSO.png) * 只判斷對方要不要做,對方要做,就等對方 * 也無法滿足progress與bounded waiting條件,如果同時執行到while,則兩邊會互相等待,無限等下去 * peterson's combined solution * LOAD與STORE都不可被中斷 * 同時判斷 是不是輪到自己 與 對方要不要做 * ![](https://i.imgur.com/nsEfKNI.png) * 滿足所有條件 * Prove the validity of a critical section? * Mutual exclusion – 假設兩個(以上) 的processes可以同時進出CS,找出其矛盾 * Progress – 沒有process在CS中,一個process要進去CS,一定可以進去 – 多個process要進CS,在有限的時間內做出決定 * Bounded waiting – 找出次數的上限 * peterson's solution ![](https://i.imgur.com/NZuFkRn.png) * comiler或硬體可能把`x = 100;`與`flag = true`調換順序,使Mutual exclusion條件無法符合 ![](https://i.imgur.com/oxxrT3x.png) * 所以需要有hardware支援才能真正解決Synchronization問題 * Synchronization Hardware * Memory barriers : 實現某些單位的memory之更改可以馬上被別人知道 * memory model * strongly ordered : 一個processor對memory做任何動作會馬上讓其他processor知道 * weakly ordered : 以上反之 * ![](https://i.imgur.com/Zq7SG0q.png) * Hardware instructions : 透過硬體確保不會在執行過程中被interrupt,不滿足bounded waiting,有機會永遠等不到 * Test and Set Instruction * ![](https://i.imgur.com/MaQJGoQ.png) * Compare and Swap Instruction * 至少確保不會有多個人去使用 * 實作出lock的概念 ![](https://i.imgur.com/qaT8ltw.png) ![](https://i.imgur.com/SJhzKKt.png) * 在entry與exit section內都有可能發生race condition,所以其實這些解法都只是將複雜的大問題(critical section)轉化成小問題(entry與exit section),但問題本身還是存在,在讀寫lock變數時還是有機會被interrupt。 * Bounded-waiting Mutual Exclusion with compare-and-swap ![](https://i.imgur.com/vTvgQMw.png) * Atomic variables : 確保寫入變數時不會被interrupt * Bakery Algorithm * [麵包店算法](https://zh.wikipedia.org/wiki/Lamport%E9%9D%A2%E5%8C%85%E5%BA%97%E7%AE%97%E6%B3%95) ![](https://i.imgur.com/KgxJrQC.png) * 為何不使用hardware解決Synchronization就好? * 因為hardware較適用於實作entry section的single且簡單的資料結構(int、bool...),而無法處理CS內複雜的資料結構(buffer、array...) * 無法達成Bounded waiting條件,無法確保所有人都會被選中,可能一直等下去 * 解法 : 軟硬兼施,改用mutex lock或semaphore * busy waiting : 不context switch,而是一直不斷檢查,用於context switch cost 很高的情形 * mutex lock : 在OS層面實作lock的library * (mutual exclusion) lock * lock變數都是使用Atomic variables * 可以滿足三大條件 * busy waiting * ![](https://i.imgur.com/SAjk6H6.png) * Semaphore * busy waiting * user可以確保哪個process先執行 * ![](https://i.imgur.com/1LJ5JoY.png) * ![](https://i.imgur.com/aSGfArP.png) * ![](https://i.imgur.com/qPZxmxm.png) * 等到P1跑完`signal(synch);`後,P2才可以進入`S2;` * 不用busy waiting的方法? * 改用waiting queue(list指標) ![](https://i.imgur.com/Z9zpICt.png) ![](https://i.imgur.com/7A5zSwO.png) * Four Typical Uses of Semaphores * Mutual exclusion * Force suspend : 強制某個process先執行, Keep the execution in a particular order * Count-down lock : 限制可以進入的process數量 ![](https://i.imgur.com/i2qnwRw.png) * Notification : Indicate that an event has occurred ![](https://i.imgur.com/t57rTGA.png) * deadlock and starvation * starvation * indefinite blocking * A process may never be removed from the semaphore queue in which it is suspended. * ![](https://i.imgur.com/bRxDTrj.png) * priority inversion * 高priority因為lock的關係導致必須等待低priority的人 ![](https://i.imgur.com/e4dPtHI.png) * Priority Inheritance : 低priority的task卡住要高priority的task,就要把priority繼承過去,避免之後被其他較低priority的task(Task2(M)) preempt ![](https://i.imgur.com/Y62fdOe.png) * Monitor : 自己定義資料結構(ADT),OS會自動保證Mutual exclusion,不會有deadlock的狀況 ![](https://i.imgur.com/yDx0si9.png) * signal and wait : 離開去等待,通知下一個人 * signal and continue : 繼續做完剩下的事才離開 ![](https://i.imgur.com/ISkhqIh.png) * 針對每筆資料都可以有一個condition * 用Semaphore實作出Monitor ![](https://i.imgur.com/pduuRkH.png) * What’s the different between semaphore and monitor? * 比喻 : monitor像公廁,一個人進去其他人就不行進去 * 比喻 : semaphore像bicycle hire,大家共用某幾台bicycle * Classical Problems of Synchronization * Bounded-Buffer Problem * ![](https://i.imgur.com/vHJl1T6.png) * ![](https://i.imgur.com/aaMEoal.png) * 如果`wait (mutex);`與`wait (empty);`寫反,則可能造成deadlock的狀況 * Readers and Writers Problem * 有一shared data set * reader : 只會讀取,不會寫入data set * writer : 可讀可寫 * problem * 多個reader同時讀取 * 同時只能有一個writer存取 * writer寫入時,reader不能讀取 * 如果有reader在讀,writer必須等待 * Dining-Philosophers Problem ![](https://i.imgur.com/VIoigV1.png) ## 期中考古題 ![](https://i.imgur.com/IwMEX4k.png) ![](https://i.imgur.com/UxhXcYS.png)
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up