2019 System Programming Notes ================== ###### tags: `course` Lecture by PJ Cheng Video: https://www.youtube.com/playlist?list=PLsIDmAHPDaPF8QV1D-ah1GeYvilyOiY7x ### Ch 00 (9/11) 1. (p5) 作業系統的角色 * 垂直方面:扮演黑盒子角色,隱藏kernel和硬體所做的事,user-friendly. * 水平方面:resource manager(分配CPU、記憶體等資源) 2. (p7) UNIX architecture * application:由使用者編譯的程式 * shell:使用者可以下指令和kernel互動(如:ls) * system calls:應用程式/shell可向kernel要求權限執行某樣系統服務(如:開檔) * library routines:已經編譯好,建構在標頭檔的物件(如:stdio.h) * kernel:和硬體溝通、開機、處理程序、中斷程序等等 3. (p10) Function call vs System call: * function call 自己call自己的程式 * Sysyem call 呼叫OS執行(要先檢查權限讓CPU經過了mode switch而執行某項process) 4. (p11) Kernel mode vs User mode * User mode 中只能動到自己的記憶體 5. (p12) * Kernel space 在kernel mode執行程序時用到的空間 * User space 正常時候能夠access到的記憶體,不能碰到OS在allocate的記憶體,除非是在system call當中 6. (p14)三種時間: * real time: 從開始執行到return的時間(包涵disk access的時間),real time = user time + system time * user time: 在user mode中「一定要」吃CPU的時間(代表各process不「總是」都佔用CPU) * system time: 在kernel mode吃CPU的時間 * 原則上real = user + system,但是如果有multithreading,user+sys可能會超過real * sleep(p15)不會吃到CPU time, 但是while迴圈就會。 7. (p16) C-library針對UNIX的system call有時會有相對應的function call. ### Ch 01 (9/18) 1. (p22) User Identification ``/etc/passwd``內,7-tuple with a user ``[login-name]:[passwd(x)]:[userID]:[groupID]:[comment]:[homeDir]:[shellProgram]`` ``passwd``檔是worldwide readable,所以密碼的hash值會在``shadow``當中(僅superuser可以read 2. (p27) Programs and Processes * 程式Program 是指存在硬碟當中的可執行檔 * 程序Process 是指正在執行的程式檔,具有獨特的process ID * 相關指令: ```ps```提供當下的快照,```top```是動態的。 3. CPU Scheduling * 當有多個程序需要執行時,CPU會採用time-sharing的方式,形同一次執行多道程序(將time切成很小塊,理頭髮一個人剪兩刀),**想要享用CPU的先決條件是程序執行所要的資料「已經」跑到memory中**。 * CPU將程序踢出有兩種狀況:時間到了(重排回ready queue)或是執行完畢了(進到event queue跑輸入輸出等) * **Remark:** Event queue代表process正在等輸入進入,當輸入已經完成了就會進到ready queue. ![](https://i.imgur.com/fulSH52.png) ### Ch 03 File I/O (9/25, 10/02) 1. (p4-6) Unbuffered I/O 每呼叫一次就拿到CPU權限一次進行讀寫。 **Remark** (1)unbuffered I/O當然也會有buffer,不過buffer是在kernel的buffer;buffer IO則是在user space上有buffer。 (2)因為kernel上有buffer,所以不見得每一次unbuffered I/O都會連動到disk I/O 2. (補) sector > cluster(many sectors) > track > cylinder 3. (p5) Unbuffered I/O example ![](https://i.imgur.com/HdGND6c.png) (1)呼叫read, trap into system. (2)做Disk i/o 從硬碟讀取紅色&粉紅色跑到kernel的buffer cache. (3)同上 (4)將兩個block送回user process (5)再call一次read讀紅色 (6)發現已經放在kernel buffer了! Remark: 如果(5)改成write紅色呢?為求效率只會更新在RAM裡面喔! 4. ```read(STDIN_FILENO, buf, 100)```,從stdin裡面讀資料為什麼不用先fopen?其實shell已經幫你做了! 5. ```fprintf```為什麼不用先fopen stdout? C的function call已經幫你做了!(ANSI C standard) 6. (p6) Unbuffered / buffered的差別? **沒有C-library所設的buffer的I/O** 7. (p6) open file descriptor table(從0,1,2,3開始編的...),對於每個使用者的0號都可能指到不同的檔案,但是這個table卻是kernel space,使用者access不到,即便給定address去讀取雖然是在user的memory結果卻不能改動!好像是你的房間都放跟你有關的資料可是政府貼封條,那些東西你都不能去動!此種是分散式儲存資料的方法,把資料放在user那可是user不能碰。 8. . (p9老師還沒講) 要怎麼找到最大路徑的path name limitation 9. . (p10) open / openat: * open代表開啟某檔案 * openat確保開啟的檔案相對於某個檔案的位置(是個atomic operation) * 萬一要開一個檔,結果working_directory在中間被改掉了,那就讀不到檔了 解決方式:先用openat建一個dir fd,準確命中檔案位置 * flag: * 權限:r/w/rw/append * nonblock(預設blocking) * 同步 10. (p12) blocking / nonblocking * Blocking等到讀取**完成**再回傳,如果讀不到就永遠在那邊等(Default) * Nonblocking有多少拿多少,一次讀kernel buffer cache讀不到就沒有(error) 11. (p12) * Synchronized強制在kernel buffer的資料同步在Disk上面 * Asynschronized沒有同步,更新buffer就回去了(Default),可以在open時要求同步 12. (p15) 檔案在系統中有兩種資料:屬性資料(metadata cache@v-node table)、內容資料,一個檔案不論open幾次,在v-node table entry只會存一次。Open file table則不然,open一次就會出現一次entry,open後的狀態稱之file status flag。 13. (p16) 什麼時候open file table reference count會超過1(指向同一個file table entry的descriptor數目)? when child process inherits from parents! 14. (p16) v-node for virtual node,Linux沒v-node有i-node(for index node better) 15. (p17) 每個 open file table entry 有: * file status(read, write, block? syn?...) * current file offset讀寫頭 * a pointer point to v-node **Remark:** 可能很多個file table entry 對到同一個v-node,因為檔案可能會被開啟多次。 16. (p23) pipe/FIFO都不能seek,檔案可以seek 17. (p27) Delayed write: 有個daemon每30秒把dirty page從kernal buffer寫進disk 18. (p30)I/O Efficiency探討: * stdin, stdout, stderr是誰開的?shell! * text/binary? 有沒有換行的概念 ![](https://i.imgur.com/9uV9ten.png) * (1)Buffer變大,loop次數減少 * (2)Buffer變大,User CPU可降,SystemCPU未降,因為一次至少搬一個user memory的量,所以假設user buffer > kernel buffer,不可能只搬0.xxx次。 * (3)Read-ahead--先讀進kernel buffer導致時間減少的效果,就不用再去disk找,隨著Buffer變大,read-ahead的效果會變小。為什麼呢?因為把一塊pre-fetch的資料從kernel buffer搬到user memory隨著buffer size變大會變短,來不及等後備的pre-fetch從disk搬到kernel buffer了,所以就有感,怎麼卡卡的。 19. (p34-40) Atomic Operation: * 動機:兩個程序同時讀一個檔案,沒問題(file descriptor可有兩個每個對到不同的讀寫頭),但是寫就有問題。 * 如果有個lseek,write process在一半的時候被打斷進行另外一個lseek write,(p37藍綠對決) 會發生Aotomic Operation: 不給你自己呼叫lseek+write(append),系統會要碼就全做,要碼全不做。 * 例子:append, pread, pwrite Remark:如同Database的transaction(? * 結論:如果有一個process在context switch時會發生問題,就要考慮atomic operation,把動作包在一起~ * Remark: 不一定是不同支程式才會遇到,有可能遇到一個signal就去run另一個function....(learn after midterm) 20. (p41) * ```dup(src)```把src指到的file table entry複製一份到另一個空的file descriptor dup2(scr, dest) * ```dup2(src, dest)``` 把src指到的複製一份到dest * 兩個差別:dup不介意要複製到哪,dup2介意複製到哪(如果那個dest有指到東西要先關掉) 21. (p47) * ```fsync()```把buffer cache和inode attribute data的資料全部掃到disk去; * ```fdatasync()```只把buffer cache寫到disk上去。 * ```sync()```只是要求把dirty page去queue上排隊。 Remark:有個daemon會每30秒執行sync...所謂delayed write有人寫進去的**幕後主使者** * Compare fsync with O_SYNC(open要求立刻同步):雖然high cost,但如金融系統上會希望馬上同步,雖然database有recover系統,但是一天都忍受不了(x。 * compare fdatasync with O_**D**SYNC:在於fdatasync不管attribute,D_SYNC會管跟後續讀寫有關的attribute(如append後要讀新增資料) * O_**R**SYNC 在read前都要確保disk跟buffer的檔案同步(metadata不一定同步) 22. fcntl() for file control:可以get file status flag、 ### Ch 14 Advanced I/O (10/09) 1. slow system call: 程式潛在可能forever block的system call,如果用blocking mode就永遠停在那裡,如pipe, terminal, network * read是slow system call? 很滑頭的答案:Depending on the file descriptor...因為不知道stdin是什麼,但default預設disk一定讀的到 2. Non-blocking mode的busy waiting:先return,有資料就拿有資料就拿,就會造成busy waiting 3. Assume that PTT server wnants to handle many users I/O: * sol1: non-blocking I/O 一直要等到有人傳資料進來才能進行別人的IO所以如果去上廁所就等在那 * sol2: multi-server就是多個process分別處理每個user的I/O,但是process間的talk會出問題 * sol3:multi I/O讚讚 4. (p17) Comparison: * blocking: 等到data ready才能讀,等待時間直接浪費掉 * non-blocking: 一直問資料好了沒,一直check check check * I/O miltiplexing: 如果設non-blocking mode開read資料,如果資料還沒處理好就先去處理其他事情,系統會告訴你準備好了,所以只要不定期回來問就好 * AIO_ networking 會教 5. ```select``` * nfds:要觀察的fd範圍,(從0開始) * readfds/writefds: 給定的bitmap(1代表要觀察,0代表不要理) * 回傳「幾個」fd準備好了,如果還沒準備好,會把原本要觀察的1設回0 * 所以,小心bug,要紀錄一份要觀察的名單,while迴圈內要先memcpy * timeout 則是說每n micro秒回傳一次,截至目前為止有幾個要觀察的準備好了 * (p14範例)每5sec就從terminal讀進資料一次,不會像我們scanf就在那裡等 6. ```poll``` * fdarray可以存要觀察的fd struct,每個struct的結構裝fd的號碼、要觀察的fd event(read or write),以及顯示出來的outcome * 和```select```的比較 * select的結果會改掉要觀察bitmap,poll會有我要觀察的bit跟回傳是否準備好的bit * select要觀察的範圍一定是從0開始,poll可以選擇單一要觀察的目標 * poll 有多種觀察項目,select只有read write error 7. File lock: 郵局同時在櫃台和ATM領錢,要同時處理這個問題(類似atomic operation) 8. Write starving: read lock在有lock的時候都可以要到lock,可是write要完全沒lock才能access,導致有些read要求比較晚到,卻比write還要早要到,檔案永遠處於有lock的狀態,write看得到吃不到好餓~~ 9. file lock存在inode裡面,所以當有人關掉某個檔案(可能inode reference > 0)時,會把同一個process的lock全部弄掰掰掉(**是同一個process還是所有process?**) 10. lock不會繼承(廢話,不然有兩個write lock在不同process上怎麼辦?) 11. lock的控管是在不同process之間,自己process先要求read lock再要求write lock根本就搞笑,但是系統會把他release掉! ### Ch 04 Files and Directories (10/16, 10/23) 1. (p5) * stat回傳某path對到檔案的status * fstat回傳指定fd的檔案status * lstat回傳symbolic link的info 2. 當remove一個檔案的時候,對檔案不需要有權限,只要對該檔案在的目錄上有w+x的權限!**因為remove其實是unlink的動作**(要被洗腦成功),沒必要殺掉檔案,但是檔案何時會消失?沒有任何檔名指到該檔案的時候就會自動消失。 3. `id`們: * `real user id`:誰跑起來這個程式的。 * `effective user id`:預設是real user id,可是要看程式的權限有沒有set-uid或set-gid,如果set-uid或set-gid就是在程式執行的時候給與owner的權限 4. octal digit permission * 01234:0表示八進位,1是set u/gid 234表usr/grp/other權限 5. 想一想:如果你只要讓有一個朋友去copy一個檔案? * sol1: 目錄開x,檔案開r * sol2: 開一個程式檢測執行者是否該user,如果是的話就set-user-id 6. 想一想:如果一個老師在開w時改成績,後來還是把權限設成000了,為什麼成績還是被改了? 因為學生在老師w時就open for write了,access test只會在開啟file descriptor時檢測,如果一直開著即使inode裡面的權限關了也無力回天。 7. (p20) create檔/permission相關都是看process執行中的effective uid,而gid有可能是(1)Process的GID或是(2)directory的GID 8. 6. (p28) 承上,如果run一個process在目錄下建立檔案,如果你想幫那個檔案setgid,會被關掉,不然萬一那個檔案是setgid而且是執行檔就代表另一個人跑這個process就會把這個gid借出去(不是自己的gid)。 9. (p24) `umask`僅在process內有效,建立的檔案都會把特定的permission bit關起來,process結束之後就沒了 10. (p28) chmod update i-node的資料 * 非superuser設sticky bit在一般檔案上無用 * 新創檔案的GID(會是所在目錄的gid)不等於process的effective GID會把setgid bit關掉 * 非執行檔也會把setuid/gid關掉(如果不是superuser) 11. process裡面至少有兩個variable:working directory、umask,小孩process沒有權力動到parent process的這些variable 12. (p30) If sticky bit在executive file on起來,第一次執行就會放到swap area(放在硬碟上)是一個連續放的partition,這樣找比較快,不用移動讀寫頭連續在一個cylinder上找就好。 然而,現在已經不必要了,因為有virtual memory和speeding file system的技術。 13. (補充)什麼是virtual memory呢?我們現在碰到的基本上都是virtual memory,**physical的東西通常都會被擋掉**,每個process都有自己的virtual memory,電腦會把virtual memory和physical memory切成一塊一塊(page/frame)會有一個table一一對應 14. Sticky bit放到directory上,代表要remove/rename檔案時代表要有以下其一的權限:owns the file/ owns the directory/ is the superuser,會顯示t 15. chown/fchown(fd)/lchown(link):改變檔案的uid(僅su能改), gid(只能改到自己所屬的群組)。 16. (p34)邏輯大小/硬碟大小 誰大誰小?沒大沒小? 邏輯大小 > 硬碟大小:有洞 硬碟大小 > 邏輯大小:至少要補滿一個block 哈哈哈,你答錯了。 17. (p37)Partition就是一個filesystem,每個partition裡面會紀錄那是不是boot的blocks,還有很多cylinder group 18. **inode** * 內存type/permission/size/data_blocks,指向真正的資料(可能分成好幾個分散的data block) * inode數目是pre-defined,所以data block的檔案是有上限的,即使block還沒弄完也一樣! * 檔名不在i-node裡面,紀錄在directory裡面,檔名會經由hard link指到inode(所以在同一個file system上面移動東西只是移動link而已,不會太耗時) * inode裡面的direct block指48KB的data,single indirect第一層接著指很多direct block,然後double indirect存很多single indirect的東西繼續指... sequencial的 19. Hard link/ Soft link: * Hard link:就是目錄下檔名指到inode的link,inode的reference count會增加,不是我們能改或建立的 * Soft link:捷徑檔(本身就是一個檔),記的是link到的路徑,會先讀出路徑出來,然後再open真正的檔案 * link檔 -> link檔案inode -> link檔資料:路徑 -> 真正檔entry -> 真正檔inode -> 真正檔data * 有可能會是dead link * Unlink hard link是減link count,soft link不會影響到要開的真正檔案 * inode link count到0還不會殺掉,要看有沒有人正在open這個檔案(open reference count != 0),系統不會把硬碟搬掉,ls不到這個檔案... * Corollary: close檔案的時候會檢查reference count和link count是否為0,是的話就要把檔案殺掉。 * (p48-49)高竿的系統程式設計:要存大量暫存時,先open一個檔案然後unlink,還是可以讀/寫,以防程式當掉暫存檔還在很麻煩,如同草稿紙一樣,不管程式成功失敗都丟掉。 * 用open 開啟symbolic link可能會掉到無窮迴圈,要用readlink 20. (p67例子解釋) working directory演變:/ -> usr -> 發現spool是symbolic link, -> ..(/) -> var -> spool -> uucppublic,working directory只會經過hard link ### Ch 08 Process Control (10/30, 11/6) 1. (p3) Why process control? 程序創造、中止、執行 * 每個process都有專屬自己的id,是個non-negative unique integer 2. (p4) Bootingstrapping: * 從冷開機(cold boot)開始 * 一開始先開啟ROM(唯獨記憶體,code寫死燒在裡面)裡面的BIOS * 兩階段開機:boot program把小kernel叫起來,換小kernel控制(找device、叫一些系統的process),call大kernel開啟所有程序。 * 進入single user mode: 代表以superuser的身份進去,不會mount所有的partition。 * startup scripts: 不同系統會有不同的檔案(init檔) 3. (p5) Special Pids: * `PID0` swapper scheduler(負責context switch的排程大總管) * `PID1` init * 對bootstrapping來說,這是最後一步 * 對end user來說,init是所有process的共同祖先,意義非凡 * `PID2` pagedaemon * `inetd`網路程序大總管 4. (p7-10) Process control block: process的metadata * 狀態: * new * running(被CPU選到了) * waiting(做I/O等待資料進入) * ready(data在memory中或是context switch時被趕出來再回ready queue) * terminated (exit掉) * CPU register:被趕出來時要紀錄好 * sceduling information:CPU排程的資料,如有沒有比較高的priority * Memory-management:對應到virtual memory的page table * I/O status information: 紀錄是否可以送到不同的狀態 5. **從init往下看,process基本上就是一棵樹** 6. (p8) **一個process可以有多個小孩,可是小孩只能有一個parent**,紀錄的時候是紀錄parent的pid,所以如果process要看children有誰必須掃過整個process table(不會太大不會很吃時間)。 * status: 如上 * uid, suid, pid, p(arent)pid, user-time, sys-time, xstat(死亡的時候要return給parent的訊息) 7. (p11) CPU switch / process 注意multitasking不見得最好,在做context switch的時候系統是被耗費掉的,這個機制的目的是要讓response time比較短) ![](https://i.imgur.com/0UAoUJf.png) 8. (p16) 經由bootstrapping後生出init,要生出其他process就是要call fork。 圖片上面是parent,下面是小孩,從左到右是time軸 child process可以呼叫exec,也可以不呼叫(把process的一個程式不執行,換跑另一支程式) 小孩死了要exit(),留下臨終遺言,然後用wait去拿(可能死前或死後),然後再去拿。 問題:小孩什麼時候死parent怎麼知道?萬一小孩死parent不呼叫wait(殭屍)?萬一parent死誰要呼叫wait(孤兒)? ![](https://i.imgur.com/LpJpkD2.png) 9. process也有content(virtual memory address)和metadata(process control block) 10. (p17-27)**fork** * parent fork 完會return生出小孩的pid * child fork 完return 0,要知道父母的pid可以直接看PCB的ppid * 以上兩點歸結:fork once return twice * parent和child執行同一份執行檔。 * 不能知道誰先執行誰後執行 * child會copy parent的所有東西,變數、變數值、fd也會指到同一個file table(跟dup一樣效果(同程序內)) * parent/child process跟memory page的關係: * parent的程序和child的程序各有一塊virtual memory,把要用的memory對到同一個physical memory(copy on write),而非做全部都複製一份在physical memory上(exact copy) * 但是當一個被改,就會啟動exact copy * p21實驗: before fork當導向時會噴兩個before fork,因為連virtural memory的buffer都會複製一份(printf在terminal中是line buffer,遇到換行就flush掉,但是在導向中是fully buffered)。 * 什麼東西不會繼承? * pid, ppid, return value 廢話 * process time會從0開始 * file_lock(不然要有兩個write lock在?) * alarm(要傳signal給process)不會繼承,signal * 什麼時候process會失敗? 太多process或是每個real process id達到fork child process的上限 11. (p28-30) vfork(沒有copy on write的機制) * 設計目的:有些fork要跑別的程式,當初copy那麼辛苦幹麻? * 小孩一定會先跑,等到小孩死掉或是呼叫exec parent才會跑,不會copy那些memory而採用share。 * 所以當小孩死掉之前改動的資料是parent的資料 * 既然小孩必先跑,萬一小孩要parent幹麻才繼續執行,就會形成deadlock * **\_exit**: 如果呼叫vfork通常小孩會呼叫\_exit(見後面) 12. (p31-32) **deadlock** * 如vfork小孩先跑卻要父母做什麼事才跑 * A有f1的write_lock,B有f2的write_lock,A想要用f1,B想用f2,結果一事無成 * 解方:預防(不要形成cycle)/治療(政府硬性把某個process幹掉) 13. (p34-35) Process Termination(ch7.3) * normal termination * return from main * exit() * \_exit() * Abnormal termination * about(自殺也算非正常死亡) * 被signal殺 14. (p36) **exit** * exit會call exit handlers, close掉所有buffer,而且一呼叫永不回傳。 * 所以如果用vfork要殺掉小孩要執行\_exit,如果用exit會把parent的file object close掉,導致buffer i/o 出錯 15. (p37-47) **wait/waitpid** * 小孩死了會傳SIGCHILD的訊號給parent, * wait * (放status當參數進去) slow system call: 有個小孩死掉才會return * 呼叫之後三個case: block, return error, return child status * 只回傳一個隨便的死小孩 * waitpid等特定小孩 * 可以允許non-blocking mode,萬一小孩還沒死可以馬上return。 * waitpid(pid, \*status, op) * pid == -1代表任何小孩, >0是指定小孩, ==0是有group id相同的小孩,-1是groupID=pid的小孩 16. (p48) **Zombie process** * 還沒有被parent process wait到然後死掉的殭屍 * PCB還留著 17. (p48) **Orphan** * init會接收所有孤兒,孤兒的ppid會變成1 18. (p48) 總結:當一個process死掉上有老下有小,對上,其parent process要執行wait,對下, 會掃過一次process table把所有該process小孩(orphan)的ppid改成1(init),只要小孩有死的init會wait掉。(爸媽死了殭屍小孩有政府收屍) 19. why we need zombie? 小孩馬上把pid還回去然後又拿到一個 20. 讓程式在background執行的方法 * double fork (p53) * 先fork出一個,然後wait child die * child再fork出一個,然後自己馬上死掉(最原本的人會收屍),grandchild直接被政府接管 21. (p56) **Race Condition** * 定義:When multiple process are trying to do something with shared data, the final outcome depends on the order in which the processes run. * (p57) parent, child都要印東西,輪流印東西會亂掉 * 如double fork,沒辦法保證地一個小孩馬上死掉 22. **exec** 跑到一半換一隻程式跑 * IPA: l for list, v for vector, end with NULL * p: 預設環境變數:PATH+filename, no p: filepath * p69-70 如果沒有給環境變數,就直接丟該程式的default環境變數 * exec會繼承的東西:FD_CLOEXEC * effective uid ### Ch 15 Interprocess communication 1. Pipe(單向、有共祖) * 從fd1寫,到fd0讀 * 如果搭配fork就是兩邊的fd1寫到FIFO buffer,到fd0讀,各關掉一邊的fd1、fd0就變成p76了(一定要關)! * 寫完關掉,讀的人讀完了就會return 0 * 讀完關掉,寫的人就會收到signal告訴你沒人在讀了 * 如果同時有兩個人在寫,會不會被插斷?在放資料的大小沒有超過pipe的大小不會被插隊,達成atomic的pipe * p79 connecting pipes to standard I/O * 先把fd[0] dup到stdin(這時stdin也指到了pipe,open for writing) * 然後把fd0關掉 2. named pipe(FIFO) * 是有名字的pipe * 可以丟資料進去,也可以讀 * 檔案的內容不用跟inode綁在一起,建在system裡面 * 如果最後一個reference到的process結束時FIFO就關掉了 * 不用共同祖先,因為pipe是因為要用fork來達成 * FIFO for unrelated process,因為有一個檔名就可以呼叫,甚至可以設定權限 ### Ch 05 Buffered I/O 1. Buffered I/O 在user space有buffer的I/O,不會每次呼叫trap into system 2. 呼叫fopen會得到指向FILE_object的pointer 3. buffer I/O是unbuffered I/O的再包裝 * 裡面有一個fd * buffer_size, 還有幾個char未讀、error flag 4. strace看看a.out檔裡面查看buffer io裡面的unbuffer io的system call 5. freopen(path, type, fp) 把fp關掉換成path 6. fdopen(fildes, type) type一定要是fildes的type一個subset 7. fileno(fp) 得到FILE的fd 8. 要注意FILE pointer指向的buffer是否是有效buffer(比如說呼叫fclose時要確認你指向的buffer還是在)! 9. Buffer io的目的是減少read write次數,FILE的buffer第一次要用(prinf fprintf等等)的時候才會allocate buffer pointer指向的buffer 10. Buffer type * fully buffered: 整個buffer滿了才呼叫system call(unbuffered i/o) * line buffered: 看到換行的時候才trigger i/o * 幾個狀況會就算沒有換行也trigger:buffer滿了/如果有input的request的時候就會先掃出去 * unbuffered: buffer pointer in FILE 指向NULL,如stderr 11. fflush把C buffer說要跟kernel buffer cache同步 * 有可能沒有呼叫fflush的情況下fsync沒用 12. setbuf(FILE, buf(有min_size))要在第一次進行I/O時指定,不然系統就會直接指定,沒有講是fully/line buffer,如果是NULL一定是unbuffer 13. setvbuf可以指定size(一次read,write的buffer size),也可以指定buffer/type,最佳buffer size就是disk block size 14. rewind():移到最前面, ftell(告知現在offset),fseek跟其他一樣 --- ### Ch 08 Process Control (part2) (11/20) 1. (p72) setuid把uid改成saved uid或是real user id * login時是以superuser的身份執行,只要login成功,全部的uid都會變使用者 * setreuid(real effective互換,過時的產品,當時並無saved set uid) * setuid當你是superuser時會把全部都設成同一個id,seteuid則是只會變euid * 但如果是一般人,setuid/seteuid都只能設成real user id或saved uid 2. (p77) 呼叫exec的程式如果沒有setuid則euid和saved uid不會變(期中考題恨恨恨恨) 3. (p79) tip的步驟:該什麼時候有uucp權限時再賦予 4. (p84) interpreter是interpreter file真正要跑的程式,如/bin/bash。 * argument會先排interpreter的arg,才是exec的arg * exec會recgonize script檔的第一行,如果以 #!為開頭就會執行這個程式,然後跑下面的程式 * awk案例分析 ### Ch 10 Signal (11/20, 11/27, 12/4, 12/11, 12/18) 1. **signal的對象是process**,過程是透過政府去送 * interrupt的對象是CPU,有internal跟external的 * 兩階段:一開始可能會有interrupt to CPU,CPU會有一個note趕快記錄下來,然後才讓OS trigger signal給process * Comment: signal = software interrupt 2. generate signal * delete/ctrl+c SIGINT for signal interrupt * divided by 0 SIGFPE for floating pointer error * illegal memory access SIGSEGV sefmentation fault * 被殺了 SIGKILL 神主牌不能ignore * 小孩死了 SIGCHILD * 被鬧鐘叫醒 SIGALRM * 沒人read了 SIGPIPE 3. 收到signal的action可以有哪些? * ignore (SIGKILL / SIGSTOP不能被忽略) * catch(收到signal時呼叫某function = signal handler) * default 4. signal舉例 * SIGABRT 自殺藥 * SIGILL instruction出錯 * SIGHUP 重新讀設定檔(hang up),比如說要改port number * SIGINT delete/ctrl C * SIGIO 資料讀完了請接收的訊號 * SIGKILL 必死藥 * SIGPWR 要斷電了 UPS不斷電系統 送給init負責最後的shut down 5. signal是在做設定,等到有信號來執行某個signal handler 6. (p19)呼叫exec時,原本catch的signal會變default,但是原本ignore的還是會ignore 7. fork則會繼承原本處理signal的方式 8. (p25)在signal handler裡面有些function不能亂call,因為可能被signal中途插斷。 9. (p25)因為不能control什麼時候會發生某signal,所以不能接受non-reentrant function在signal handler中(不然就弄到一半就爛掉) 10. Non-reentrant function的例子: * static variable,下次再宣告在某個scope時會保留上次宣告的值 * 就programmer角度而言,是access不到這個variable(compiler擋掉) * 就system角度而言,memory其實還在,還留著值 * 如果這個function被reentrant兩次,會access到同一個variable很可怕! * return pointer * malloc() free() * stdI/O 11. SIGNAL的問題:unreliable signal,有可能signal generate但是不見了!註冊一次signal handler僅一次有效,不會註冊多次多次有效 * open source會在signal handler被叫起來時再註冊一次。 * (p33)race condition: 在sig_int()和signal中間第二個interrupt的signal來了,結果process就死了,按沒有按照我們一接收signal就註冊的規則去run * (p34)如果signal來了我等下才要處理,先在handler把flag開起來。在while和pause中間萬一signal來了就沒接到,然後就去等訊號了(等沒有人)... 12. (p37) Reliable signal * 有signal generate的時候,會先在PCB上的signal flag上面先set * 當signal 來要執行action時會叫做signal delivered * generate跟deliver中間叫做pending * **我們可以在signal action前決定要不要執行signal(把signal block住),並非決定是否generate(這必然要發生)** 這在還沒delivered之前都可以改變你針對signal disposition(可以反悔之前所註冊的signal handler) * signal flag: 當前process是否有signal傳入 * signal mask: 是否要block住該signal 13. (p37)Reliable signal二問: * 如果有很多一樣的signal進來被block住,到時候deliver的時候只會deliver1次 * 如果signal被block時產生很多個同種,只會有一個進來,因為在PCB當中用bitstring用的,然後會讓比較嚴重的signal deliver,讓process一死了之 14. `kill` 送signal給process,要有的權力: * 兩個real/effective uid同or real user id = effective uid * kill(pid, 0)代表要確定那隻process是活的死的 * 如果是-1代表process不存在 * 注意:time to check / time to use * 如果今天收到自己(kill自己)的unblock signal,會deliver一個signal(either自己的signal number或是其中一個pending signal,為什麼的話要在abort才會講) 15. `alarm` 幾秒之後送SIGALM,但不要保證剛好的秒數,可能在忙別的事或是context switch,但是在loading不太重的狀況下不會差太多。 * 如果重複設鬧鐘會回傳第一次鬧鐘還剩下的秒數 * 預設action是termination 16. `pause` * 不能被ignore/block的signal叫醒(因為才會啟動signal handler,並return pause) 17. `sleep` n秒之後醒來或是中間有signal打進來睡不飽(回傳沒睡飽的時間) 18. (p43) 實作 sleep version1: 調鬧鐘pause等醒來。 * 看起來沒問題:當中間pause被其他非alarm signal叫醒,也會回傳睡不飽的時間 * 實際上有三大問題: * 萬一先前已經調鬧鐘然後overlap了呢? * 寫在外面sig_alrm的signal handler被蓋掉了怎麼辦 * 有可能調完鬧鐘還沒執行pause,被context switch掉,然後鬧鐘響了連睡都還沒睡 * 第一個問題:先前有鬧鐘怎麼辦? * 如果上個鬧鐘會先響,就把鬧鐘調成離舊鬧鐘響的秒數 * 如果當前設的鬧鐘先響,那麼就先pause,然調再把就鬧鐘時間設定 * 第二個問題:previous signal handler * 把old handler存起來之後再設回去 * 第三個問題:race condition(後解) 19. (p46) Nonlocal jump: * 目標:從深層迴圈跳出來或是處理signal * 系統call function是是存在stack frame裡面的,每call一次function就會長一塊 20. (p49) setjmp and longjum: * setjmp 像是設label * longjmp 像是GOTO * setjmp有兩種return方式,如果是return 0,就代表是作設定,如果是!=0(longjmp的第二個參數,不能是0),就代表是從longjmp跳來 * longjmp只能回去先前尚未執行完的function(p52反例) 21. (p53) 解sleep race condition * 把alarm pause包起來setjmp,把longjmp包在sig handler裡面 * 所以假設鬧鐘在alarm和pause之間響,會在sig handler裡面longjmp設成0,再檢查一次說不用執行了 * 還是有問題:alram在中間插入的signal deliver signal handler時響,就直接跳回sleep2,少掉了中間那個signal handler 22. (p55) alarm的作用:timeout read,超過nsecs時就送alarm 23. (p58) signal mask:決定signal發生時要不要block住的bit string,是per process property 24. (p60) sigprocmask: * 如果oldset不是null,代表回傳當前的mask給oldset當中 * 如果oldset是null,用how(block, unblock, setmask)把原先mask和set做union,結果回傳到oldset。 * **如果有unblock signal pending,在設定sigprocmask return之前一定會有一個以上unblocked signal delivered**(call下去注意一下)。 25. (p63) sigpending: * 回傳當前處於pending的signal set。 * (p64,65解釋:把sigquit block住,設定unblock時就會把sigquit delivered出來) * (p66)解答:A,B,E執行sig_handler,C,D block住。 26. (p67) fork/exec繼承: * signal mask都繼承 * disposition fork會繼承 * pending signal、鬧鐘時間 exec會繼承(同一個process) * fork()會歸零pending signal, alarm(因為signal對象是process) * exec因為換code執行,disposition會繼承 27. (p69) sigaction: * reliable的signal(),可以檢查、更動signal handler * **註冊一次直到下次註冊都有效!** * oact如果Not NULL,會傳當前struct給oldact。 * act如果Not NULL,改動handler * sa_mask設定附帶也要block住的signal(預設執行handler時,會把自己signal就先blocked住了) * 當handler處理完之後mask會回到原本的狀況而非sa_mask * sigaction call時,不會改變到signal mask,因為只是在註冊而已。 * sa_flag可以設定一些怪怪的東西,如註冊一次一次有效,在執行handler時不要預設block自己 * 預設考試之後都是sigaction並非signal 28. (p77) ``sigsetjmp`` & ``siglongjmp`` * setjmp, longjmp不會儲存memory變數的值,只有CPU register內的變數會,應該要assume不能restore,考慮portability。 * 那sigsetjmp直接明擺講會不會save signal mask(非0) * sig_atomic_t 類型變數只能通通改或通通不改 * (p78, p79)open source技法:要注意還沒setjmp之前signal就來,handler裡面有longjmp,就爆了,解法:設canjump 29. (p82) `sigsuspend(mask)` = sigpromask換成mask,然後pause()的atomic版,保證一定等的到要接的電話 31. (p84) critical section 案例: * critical section 是 sigpromask跟sigsuspend中間保護起來的部份,不被signal打擾掉。 * 如果sigint在critical section傳出來被block住,當呼叫sigsuspend時,裡面有sigpromask,其side effect是要deliver一個unblock unignored的signal出來。 * 當sigsuspend接到function,處理完handler之後,會把mask設定到sigsuspend之前的樣子(也就是critical section的樣子),所以如果要回覆到critical section之前的樣子,一定要在呼叫一次sigpromask 32. 作業p94,95(b)(c),預設unblock全部按照generate的順序deliver ### Ch 07 Process Environment (12/4, 12/11) 1. virtual memory跟address bus有關,跟physical memory無關。bus的寬度跟可以定址的bit數有關。 2. system做multitasking,所以感覺CPU都是你在用。有virtural memory,所以感覺memory都你在用。 3. (p5) 程式開始是從start-up routine開始跑的,不是main function 4. (p14) 做exertal reference,object file裡的位置都可能是未知的,要跟別人link在一起,會把資料存在object file rel.text裡面 5. (p21) text不會從0開始存,通常virtual memory是4G,只會有3G左右給用,有一塊是kernal space但是放在你家(如:open file descriptor 6. stack frame rbp、rsp 紀錄一個stack frame的頂層/底層 7. disk單位是block,virtual memory是page,process memory是segment 8. (p21) main func的參數somehow是在stack之上的位置,因為是第一個stackframe 9. stack/heap之間的「肚子」除了當緩衝外,還有其他用途。 * 動態連結:執行中第一次call某個func會去動態函式庫去找該func,load到memeory(放在heap跟stack中間,因為底下的位置已經固定死了) * env如果太長,只能往heap長,因為往上kernal space,往下壓到stack 10. virtual memory好像memory全部都是單一user再用,其實是virtual memory到physical memory的mapping(page table: per process info,由OS控管) * 如果page table一個page對應的是4K,則virtual memory就會4K、4K的切 * 再探copy-on-write:每當fork一個child process,會把新的process的virtual memory都對應到parent process指到的physical memory,省physical memory的空間 11. virtual address要有log(4K) bits做virtual memory定址,剩下為virtual page number,page table就有$2^{\text{virtal page number}}$個entry,對應到page table會紀錄對應到的physical page number,permission等等 13. (p31) caller要傳參數、存CPU register,紀錄return時的位置 14. (p31) 在function裡宣告static variable會放在global variable(看是否有initialize)區,但是在別的function的scope卻access不到,是compiler不給你過 15. (p36) heap: 動態記憶體,會有alignment 16. (p39) 呼叫malloc,其實背後的system call是sbrk,很耗資源,會一次給很多然後再一次一次分,會在要到記憶體指到的pointer前一點點描述這個pointer的size,之後free的時候才知道要free多少 17. (p37) 呼叫realloc會看可不可以往後長,如果不行就重找一塊然後把全部東西搬過去,所以很可能很耗時間。 18. (p38) alloca,動態allocate一塊memory在stack上,好處是func結束後自動pop掉 19. (p42) type qualifers * const 常數 * volatile 易揮發的、會變的(不要眼睛看到就以為是這樣),是給compiler看的,不要亂做optimization(p43) * restrict:如果一個pointer宣告成restrict,就代表該pointer指到的pointer只能藉由該pointer指到 * `memcpy` 不會有overlap * `memmove` 可能可以有overlap 20. (p41)結果解釋 * `./testjmp` * 沒有restore setjmp 的 data * `./testjmp.opt` * auto和register有回復 * auto如果打開,opt會把他放到register * 因為jmp會存register的資料 * variable如果存在memery裡面就不會restore 21. (p46) stack/heap中間肚子空空的一大塊的作用:mmap()把檔案的某一塊map到virtual memory * motivation:不管是(un)buffered I/O都有東西要搬動,但是如果有mmap,對應file的位置也會被讀/寫到(還是要透過virtual memory) * 參數:addr通常是NULL,因為不知道從哪裡開始,len長度,prot表示存取權限,動態連結函式庫常常用mmap load到memory * flag可設定別人看不看得到對應的page,所以也可以做某程度的shared memory,事實上是透過file(sht_) * fd是開啟的檔案 22. (p49) 實例:看一下課本的performance分析 23. Tips: 把memory layout摸熟,要很熟stack frame。 ### Ch 11 Thread(執行緒) (12/18, 12/25) 1. (p7) Thread Model:有很多process同時在執行,達到某種程度的independent,去control。 * 和process的差異: * process uid不一樣,thread會一樣 * process 有parent/child概念,thread沒有,all threads are equal * thread 共用資源,process一般來說不太能共用資源 * thread creating/switch 的 cost 比較低(在user space) 2. (p6) per thread item: program counter(執行到哪裡)、CPU register, thread stack、error variable, signal mask(可以決定signal來時要跑哪個thread),但是signal handlers會共用喔! 3. (p10) thread好處:可以處理multiplex I/O(才不會被block住什麼都做不了),有多顆CPU時可以讓全部CPU全力跑某支process, 整體throughput(單位時間可以做的task數量)會增加。 4. (p11) thread壞處:難處理,單一cpu其實根本沒用(硬體要配合) 5. (p14, p19) thread有thread table,有自己對應的id, stack位置、register... 6. (p20) `pthread_create(tid_ptr, attr, func want to run, function argv)`,create之後並沒有parent/child關係。 * attr: default is NULL 7. (p24) `pthread_attr_getdetachstate` * 如果設成detached就不用收屍,死的乾乾淨淨 * joinable則是要人來收屍(TCB要留, default為此) * 如何讓thread datached: * pthread_dateach() * creation set DETACHED on init * `pthread_join()` (類似收屍) 8. (p26) 案例: * why not use ntid,有race condition造成ntid可能還是錯的還沒更新到 * 下方Race condition是因為可能thread根本還沒被schedule到main就死了,main thread死就全死 9. (p32) thread function return 跟function內呼叫pthread_exit效果相同,別的thread呼叫pthread_exit(只把自己own's resource關掉,如thread stack)跟main thread呼叫效果不同(會等到全部thread done才結束,所以main最後要呼叫這個) 10. (p33) `pthread_exit` * 如果是thread call,就return然後thread死 * main thread call this function,會等到全部thread死才死 11. (p34) `pthread_join(tid, retunr_value)`:收屍,跟wait很像 * 如果要收屍的thread是DETACHED,會顯示error * 如果joinable的thread沒被收屍則會便zombie * 如果兩個以上要收同一個屍not defined。 * return value為null就代表不管return code * 如果join別人的thread死了就無效了。 12. (p36) 範例:thread死後其stack's local variable位置仍有效,只是值都被蓋掉了。 13. 1. (p38-41) `pthread_cancel`有cancel state 和 cancel type: * state: 決定是否被cancel,cancel別人不會被block住,被cancel的人也不會馬上被cancel掉;如果被cancel的人不想被cancel掉,function就會一直pending * cancel是在cancellation point的時候(某些function),thread才會被cancel掉 * cancel type: deferred(到point才死)、asynchronous(馬上死) * 如果沒有呼叫到cancellation point的function,可呼叫`pthread_testcancel()`達到cancellation point 14. (p42) `pthread_cleanup_[push/pop]()`可以決定cancellation point達到之後執行哪些function。 * 如果function return而非被cancel掉不會執行這些stack裡的東西 * (p43) push/pop要成雙成對(即使不要執行還是要寫pop(0)) 15. (p46) thread sync: 要保護shared resources,有三種保護方法。 * mutex(第一種方法): * set_lock/unlock:如果要lock要不到就會block住,一旦unlock以後全部要lock的人會一起競爭,搶不到的會繼續被block住。 * `pthread_mutex_init/destory(回傳mutex, attr)` * 初始attr: `pthread_mutexattr_init/destory(attr)` * 設定attr: `pthread_mutexattr_(get/set)(pshared/type)()` * mutex type: 會不會檢查relock without unlock? unlock when not owned? * 上lock: `pthread_mutex_lock/trylock/unlock(mutex)`:trylock就是non-blocking lock * Read/writer locks (第二種方法):跟I/O一樣,一樣會有write starving問題 * 優點是比較平行化,把read/write分開 * 缺點(p57):thread pool為例,thread一直問job queue有沒有他的工作要做 * #### 口頭禪: 我這個mouse... 媽呀 好好笑喔 so 我們看這個UNIX(後面可接任何字) 你聽懂我意思 家ㄏㄨㄛ #### 語錄: 年紀大了以前年輕時多熱血啊,當機還在那邊殺process,現在勒?電腦壞掉買新的、電腦當機重開機、ctrl+Z回不去就don't save 我也不知道我這影片有沒有在錄,反正按下去、錄心安的,好像出門帶一本書就好像有念 我字其實沒那麼醜 我們不是補習班還複習,給你題庫考會考 這什麼東西啊?它已經侵犯到我的主權了對不對?這誰受得了? 那我不是全白教了!媽呀! 考試的時候才知道你們班這麼多人耶,你知道 一大堆人都來要答案,沒有答案,答案就是心中的一把尺 全世界也沒有一本書給你把全部的case 期中考試範圍: Ch1, Ch3, Ch4, Ch5, Ch8.1-8.10, Ch14, Ch15 (第八章佔很重比重) 期末考試範圍: Ch8後半, Ch10, Ch11, Ch7 @1/8