--- robots: index, follow tags: NCTU, CS, 共筆, Unix description: 交大資工課程學習筆記 lang: zh-tw dir: ltr breaks: true disqus: calee GA: UA-100433652-1 --- 高等 UNIX 程式設計 -- 黃俊穎 === http://people.cs.nctu.edu.tw/~chuang/courses/unixprog unix107 up19peppa ## Syllbus - Unix 越來越熱門 - 老師: 黃俊穎 (chuang@cs.nctu.edu.tw) - Prerequiste - C / Cpp - (little) assembly (IA32, IA64) - OS - Computer network - W. Richard Stevens and Stephen A. Rago, "Advanced Programming in the UNIX Environment" - Topics - Fundamental tools - File & directories - file / standard IO - System data & info. - Process environment - Process control - Signals - Assembly - Threads - Thread control - Daemon - Advanced IO - Inter-process communication - Network IO - ... - 用 VM - 需要開 VT-x (BIOS) - Grading - Mid: 25% - Final: 35% - HW: 40% (3~6) ## Overview & env - prepar UNIX ```sh sudo apt-get install gcc g++ gdb make manpages-dev manpages-posix manpages-posix-dev ``` - Kernel ![](https://i.imgur.com/kxxeHvg.png) - Boot - OS loader (ex. grub) - 從特定位址 load program - Kernel init system hardware - first process - init - systemd - /bin/sh (系統救援時,可以叫 sh 先起來) - after process - mount - network - daemon - login interface - ... - File System (FS) - windows: partition base directory - linux: root base directory ![](https://i.imgur.com/exgCKEF.png) ![](https://i.imgur.com/yF3yRfW.png) - Filesystem Header Standard - Linux command - [LSB (Linux Stanard Base)](https://wiki.linuxfoundation.org/lsb/start) - [FHS (Filesystem Hierarchy Standard)](https://wiki.linuxfoundation.org/lsb/fhs) - 如何 touch 有 '-' 開頭的 file? (- 感覺會被 parse 成參數):用 `touch ./-aaa` - Redirection - `>` `<` - Pipe - `|` - man (manu page) - `man [section] command` - 不寫 section 了話都是 section 1 - `man -k command`: 找到有哪些 section - 標準 section - ![](https://i.imgur.com/LR7TBwt.png =300x) - Sample Code - http://people.cs.nctu.edu.tw/~chuang/courses/unixprog/resources/inclass-20160530.tar.gz (老師修改的 code) - https://people.cs.nctu.edu.tw/~chuang/courses/unixprog/resources/textbook-20180326.tar.gz (課本的 code) - Return value - 0: true, !0: false - `echo $?`: 上一個 process 的 return value - `||`: 如果前面 return true,就不用執行後面的 - `&&`: 如果前面 return true,才執行後面的 - Handle Program - argc, argv[] - getopt - getopt_long - Time - time command - real: average amount of time required per iteration, in microseconds. Time is measured in elapsed time, not CPU time. - user: user space 用的時間 - sys: kernel space - Error - return value - errno: error number(3) (global) - ![](https://i.imgur.com/L8lTK4G.png) - 轉換成看得懂的 - strerror - perror - Error Recovery ### Tools - Compiler - ex. gcc, clang, ... - gcc - `-S`: 生成 .s (assembly) - `-c`: 只編譯,不 link - `-l`: link library - `-I`: add include path - `-L`: add lib path - 大 project 建議分開 compile 再 link (Makefile) - 這樣單獨檔案被跟改時,不會需要所以 source code 都被重新 compiler 一次 - 不同語言 - 通常語言會各自加自己的 header 在變數名稱(text session)前面 - => 不同語言不能直覺直接 link - `nm`: 可以解析 object file 的指令(list symbols) ```cpp #ifdef__cplusplus (only needs for a C++ compiler) extern "C" { (declare that everything within the scope) int b(); (should be treated as C symbols, not C++ ) } #endif ``` - make, Makefile - Ref: https://www.gnu.org/software/make/manual/make.html - 簡化 build process - 管理相依性與多平台選擇參數問題 - 參數 - `-C`: 修改工作目錄 - `-f`: 指定檔案 (就不一定要叫做 Makefile 了) - `-j`: 同時編譯 - 內容 ```Makefile rulename: dependencies rule # comment split to\ multi-lines ``` - 預設變數 - `$@`: target 名稱 - `$<`: **第一個** prerequisite - `$?`: 比 target 新的 prerequisite - `$^`: 所有 prerequisite (會移除重複) - `$+`: `$^` 但不移除重複 ```Makefile haha: a b c a d echo $@ echo $< echo $? echo $^ echo $+ # output: # haha # a # a b c d # a b c d # a b c a d ``` - 注意,只要 rulename 內的任一檔案 timestemp 不同,make 都會把 rulename 內的全部 rule 再跑一次 - .PHONY: 不事先檢查檔案是否存在 - 在不指定開始的 rulename 時,會以第一個 rulename 開始執行 (不是 all) - `.c.o:`: .c 檔轉成 .o 檔 - 舊式,新式改成 `%.o: %.c` - `.c.o` [缺點](https://www.gnu.org/software/make/manual/make.html#Suffix-Rules) - GDB - `-g`: compile 時產生 debug synble,不用不影響 gdb 指令,但是會看不到 source code - debug symble 不會存 souce code,他是 link 到 source file,所以如果 source file compile 後又被改過,會看到錯誤的資訊 - command: - `list`: 列出 sourse code - `run`: always run from begin - `s/step`: 跳一行,但會跳進 function call (if esixt) - `n/next`: 跳一行 - `ni/nexti`: next instruction (asm level) - `p/print`: print source code - `c/continue`: 跳到下一個 break point - `b/break` - `layout {ams|regs|src}` - `bt/backtrace`: 查看 call stack - `x/FMT <address>`: 用 MFT 方式查看記憶體位址 - `fin/finish`: 從 call stack 中跳回上一層 - Core dump file - `ulimit` - ... - debug w/o asm - stript - ... ## File IO & Standard IO - File IO - file discriptor - 所有開啟的檔案在 kernel 下都有一個 FD 來描述(紀錄) - 非負整數 [0, OPEN_MAX-1] - STDIN_FILENO(0), STDOUT_FILENO(1), STDERR_FILENO(2) - Unbuffered IO - user space 下是沒有 buffer 的 - 直接跟直接跟 kernel 溝通 - 通常只有這五種: open, read, write, lseek, and close - 可以用 pipe 做重導向 - `a>b`: 將 FD a 重導向 FD b - `> file`: 通常不寫就是將 FD 1(stdout) 重導向 file - `|`: pipe() - Standard I/O - buffered IO - user space 下有 buffer - ex. fwrite() 事先寫入 buffer 才寫入 file - buffer 填滿後才一起塞入 write => 減少 kernel 呼叫 - 透過 wrapper of FD 存取 - stdin, stdout, and stderr - 透過 fileno() 可以看到 FD number - ex. - open(path, flag, mode) - mode: permission - rwxr-xr-x: 655 - flag: - O_RDONLY, O_WRONLY, O_RDWR - O_APPEND, O_CREAT, O_EXCL(exclusive?), O_TRUNC(把從在的檔案清空,讓看起來像 create 的), O_SYNC(去掉 device buffer 的影響) - create(path, mode) - close(fd) - process 結束時會自動 close 所有的 open file - lseek(fd, offset, whence) - 用來移動 file pointer (device 要可以 random access) - whence: SEEK_SET(最前), SEEK_CUR(目前), SEEK_END(最後) - 多用 lseek64(fd, offset64, whence) - Random access vs Sequential access - file: Random access - pipe: Sequential access - `/var/spool/cron/FIFO`: Sequential access - Large File Support (LFS) - 2GB 以上 - 需要 define _LARGEFILE64_SOURCE, _LARGEFILE_SOURCE 與 _FILE_OFFSET_BITS 64 (在 include 任何 c library header 前) - 開檔需要參數 O_LARGEFILE - `gcc –D_LARGEFILE64_SOURCE –D_LARGEFILE_SOURCE –D_FILE_OFFSET_BITS=64` - File hole: 一份檔案裡有部分空間沒有寫到,沒寫入的部分 FS 會自動填入 0 - 檔案大小是從第一個 pointer 到最後一個 pointer (最後接 EOF) - 檔案實際大小(硬碟消耗空間)是 4KB * n > 寫入大小 - ex. 寫 10byte + 跳 16374 + 寫 10byte - file size: 16394 B - write disk size: 8 KB - `ls -s`: 看 write byte - `strip`: re-format block write,有機會整理壓縮寫入 block size - ![](https://i.imgur.com/3OncVAa.png =400x) - ![](https://i.imgur.com/ZkVcn8R.png) ### File IO other issue - IO Efficiency - 如果 buffer size 太小,因為每次 read 都要 kernel call,效率會減低 - 如果 buffer size < block size - 每次 read 進 DMA 後,來到 disk 會是同一個 block => DMA 需要重複讀取一樣的資料 - File sharing - kernel 如何實作 file open - process table - table of file descriptors - v-node - ![](https://i.imgur.com/4SdKVv9.png =500x) - sharing 時 - 到 file pointer 時,都是 per process 的 - 然後將 file pointer 的 v-node 指向同一個 v-node - fork(),因為 file table 存在 heap (per process),所以會 duplicate 一份 - process-table 會複製一份,file table 也會複製一份,vnode 是同一份 - Atomic Operation - 如果開檔後,fork 一個新的 process,然後再把 file pointer 移到最後面,加入 10B - 會產生非預期操作 (unexpected order) - Atomic: 只用一個 x86 instruction 可以執行出一個 function call => 不用擔心 interrupt - pread, pwrite - pread: seek + read - pwrite: seek + write - open = check + create => interrupt - open(pathname, O_CREAT | O_EXCL, mode) - check + create 會在同時執行 (中間不會被中斷) - dup, dup2 (duplicate) - ![](https://i.imgur.com/IWINm1M.png =250x) - dup2 的 target fd 要確保關閉了 - dup(n): 將 fd{n} 的 file table entry 複製一份到最新關閉的 fd (target 由 fd 自己維護) - dup2(1, 3): 將 fd1 的 file table entry 複製一份到 fd3 (target 需要是 close 的) - ![](https://i.imgur.com/FcVeTVj.png =500x) - sync, fsync, fdatasync - 因為 IO 其實會先 queue 在 buffer 再一起寫入 - 跟 buffered IO 的 buffer(user space) 不同,是 kernel 裡的 buffer - 對某個 fd - fsync: filedata + metadata - fdatasync: filedata only - 所有檔案 - sync: filedata + metadata - 如果想要強迫寫入,可以用 sync - ioctl - 所有 device 在 UNIX 下都是 file => 可以用 ioctl access - dependency by driver - linux cross reference - /dev/fd - virtual file system - `/proc/[pid]/fd/` : 看 process 有維護的 fd ### Standard IO 最大的差別,stdio 有 **buffer**,他不會每次 call 都做 system call,而是只有在 buffer 被填滿時才 system call 寫入 - Buffering - 3 mode - fully buffered - 整份檔案 call 進 buffer - ex. fopen(..., "rb") 中的 `b` - ex. stdio - line buffered - 一行一行吃,在 buffer 中讀到 `\n` 時,再去跑一次 kernel call - ex. fopen(..., "rt") 中的 `t`,會讓這個 io 一行一行吃 - unbuffered - ex. stderr - fdopen(fd, mode) - 將指定 fd 的檔案的 fp 抓出來回傳,好處是之後可以直接用 file pointer 來操作這個 檔案 / io / device / ... - size_t fwrite(void *ptr, size_t obj_size, size_t nr_obj, FILE *fp); - 回傳寫入了幾個 object - 如果 size of obj 沒有寫完全,就是沒有寫入這個 obj - ex. `fwrite(pt, sizeof(double), 2, fp)` - 如果今天 pt 只有 2Byte,無法完全寫入一個 double 的長度,就會回傳 0 - fseek() - 暫存檔 - 一種直接被覆寫也不會怎麼樣的檔案 - `char *tmpnam(char *ptr)` - 會在 `/tmp/fileXXXXXX` 開出暫存檔 - 檔名相同時,會被 overwrite (危險) - `tmpfile(void)` - 推薦使用 ## File & Dir - linux 的 filename 除了 `/` 跟 `null` 外,都可以用 - 最多 255 character (PATH_MAX) - 每個 process 都有自己的 working directory - cwd (current working directory) - chdir (cd) - home dir 是使用者登入時的第一個 working dir - 在 `/etc/passwd` 設定 - file information: - api - `int stat(const char *path, struct stat *buf);` - `int fstat(int fd, struct stat *buf);` - `int lstat(const char *path, struct stat *buf);` - stat 與 lstat 幾乎一樣,lstat 不 follow symbolic link ### File information - file type - Regular file、Directory file、Block special file、Character special file、FIFO、Socket、Symbolic link - block special file 與 character special file 差在 random access - file permission (discentralized permission <-> SELinux) - Real UID, Real GID - 實際登入的人 - Effective UID, Effective GID - 確認檔案權限 - SUID, SGID - set UID - exec 會儲存狀態 - 當 SUID enabled 時,RUID 還是會是執行本人,但 EUID 就會變成程式檔案的擁有者 (SUID) - function: setuid(2), setgid(2) - 9bit permission - ![](https://i.imgur.com/PfdhZ2Y.png =300x) - access permission - dir 要有 x 權限 - 刪除檔案要有 dir wx 權限 - 可以用 id 指令來查看 id / `/etc/passwd` / `/etc/groups` - `int access(const char *path, int mode);`: 確認權限 - mode bit: - R_OK, W_OK, X_OK: read, write, exec permission - F_OK: file exist - `mode_t umask(mode_t cmask);`: mask 掉權限 - 直接用 umask 指令可以看到 touch 檔案時的預設權限 (被 mask 掉的數字) - 可以用 umask 指令改寫預設檔案權限 `umask 0` -> 預設所有權限都開 - 12-bit file permission - suid,sgid,sticky,9-bit permission - sticky bit - executable - cache 在 swap - 減少 loading 時間 - dir - delete or rename 的權限 - owner of file - owner of dir - superuser - ex: /tmp - file system - ![](https://i.imgur.com/GV08pTn.png) - i-node - file metadata - type - permission - data blocks - timestamps - reference counts - 通常為正整數 - 特別用途 - 0: reserved, or does not exist - 1: list of bad/defective blocks - 2: root directory of a partition - 兩個 dir 可以指向同一個 i-node - ![](https://i.imgur.com/gAhDLc3.png) - create dir - ![](https://i.imgur.com/rFbGrA4.png) - ![](https://i.imgur.com/rZ9tpIE.png) - reference counts - 指向 i-node 的 pointer 個數 - 增加 - link - hard link (必須在同一個 partition) - 減少 - unlink - symbolic link (soft link) - 大小為 target name 的長度 - 小心迴圈 - soft link 是可以指向不存在的檔案,但 hard link 不可 - ![](https://i.imgur.com/Tmin7Lj.png) - symlink - 建 symbolic link - target 和 link 可以在不同 file system - readlink - read target pathname - 結合 open, read and close - file time - ![](https://i.imgur.com/1tNc3kn.png) - ![](https://i.imgur.com/laANFlv.png) - utime - change access and modification time - directory operation - `void rewinddir(DIR *dir)` - reset pointer 到 dir stream 的開始 - `off_t telldir(DIR *dir)` - dir stream 現在的位置 - `void seekdir(DIR *dir, off_t offset)` - device special file - `dev_t` device number - major - device driver - minor - specific sub device - `st_dev` - `st_rdev` - created by `mknod` or auto generated when dev register - `hdaN?`、`sdaN?`、`scdN`、`ttyN`、`ttySN`、`pts/N`、`null`、`zero`、`random` ## sysinfo