本人再看深刻理解Linux內核的時候發現比較難懂,看了Linux系統編程一說後,以爲Linux系統編程仍是簡單易懂些,而且兩本書都是講Linux比較底層的東西,只不過側重點不一樣,本文就以Linux系統編程爲例而且會穿插一些深刻理解Linux內核的內容來寫。html
Linux內核3.9,gcc編譯器4.8,C庫2.17node
文件必須打開才能訪問linux
同一個文件能夠由多個進程或者同一個進程屢次打開。系統會爲每一個打開的文件實例提供惟一描述符。進程能夠共享文件描述符,用戶空間的程序通常須要本身協調來確保對文件的同步訪問是合理的。ios
文件一般是經過文件名訪問的,經過索引節點很麻煩,目錄上存放是索引節點和文件名的對應關係。文件名和inode是經過hlist_head,hlist_node這兩個數據結構來實現哈希列表的訪問方式。算法
Linux內核採用緩存(dentry cache)存儲目錄的解析結果,使用相對路徑的時候,內核會獲取當前的目錄的絕對目錄,用相對路徑和當前工做目錄的組合獲得絕對路徑。shell
當不一樣的名字的多個連接映射到一個索引接點上時,這個連接爲硬連接。
索引節點刪除須要link count爲0.編程
有inode和block,block中放的是連接的文件的絕對路徑數組
Linux只支持四種特殊文件:塊設備文件,字符設備文件,命名管道,UNIX域套接字。特殊文件遵循一切皆文件的理念。
字符設備是做爲線性隊列來訪問的。漏讀數據或者不按順序讀是不可能的。
塊設備是做爲字節數組來訪問。能夠隨機訪問數組中的任何字節。緩存
塊設備的最小尋址單元是扇區sector。通常是512b
文件系統的最小單位是塊block,通常sector<block<page(內存的最小尋址單元)
UNIX系統只有一個共享的命名空間,對全部的用戶和進程均可見。Linux支持進程間的獨立的命名空間,容許每一個進程均可以持有系統文件和目錄層次的惟一視圖。默認狀況下,每一個進程都繼承父進程的命名空間,可是進程也能夠選擇建立本身的命名空間,包含本身的掛載點和獨立的根目錄。安全
進程是執行時的目標代碼:活動的,正在運行的程序。可是進程不只包含目標代碼,它還包含數據,資源,狀態和虛擬計算機。
最重要的是文本段,數據段,bss段。文本段包含可執行代碼和只讀數據如常量,一般標記爲只讀和可執行。數據段包含初始化的數據,包含給定C變量,一般標記爲可讀寫。bss(block started by symbol)段包含未初始化的全局數據。bss段的設計徹底是出於性能優化。
線程包含棧,處理器狀態,目標代碼的當前位置。進程的其餘部分由全部線程共享,最主要的是進程地址空間。
線程和輕量級進程都是操做系統相關的概念,Linux內核實現了獨特的線程模型,它們實際上是共享某些資源的不一樣進程。
Linux還支持訪問控制列表。ACL支持更詳細的權限和安全控制方式,代價是複雜度變大和磁盤開銷。
信號是一種單向異步的通知機制,因爲信號的異步性,處理函數須要注意不要破壞以前的代碼,只執行異步安全(async-safe,信號安全)的函數。
在對文件進行讀寫操做以前,首先要打開文件,內核會爲每一個進程維護一個打開文件的列表,這個列表是由一些非負整數進行索引,這些非負整數稱爲文件描述符。
文件描述符從0開始到上限值-1,每一個進程至少包含3個文件描述符:0,1,2,除非顯示的關閉這些描述符。
遵循一切接文件的理念,幾乎全部可以讀寫的東西均可以經過文件描述符來訪問。
umask是進程級的屬性,由login shell設置經過umask來修改。umask爲022,取反爲755,而後與mode參數0666取與爲0644,新建文件的權限爲644。
用戶空間發起write()系統調用時,Linux內核會作幾個檢查,而後把數據從提供的緩衝區拷貝到內核緩衝區。而後後臺內核收集全部的髒頁,進行排序優化,而後再寫入磁盤(這個過程稱爲回寫)。這就是延遲寫。爲了保證數據按時寫入,內核設置了最大緩存時效(maximum buffer age),經過/proc/sys/vm/dirtytime_expire_seconds ,單位是釐秒(0.01秒)。linux也支持強制文件緩存寫回,甚至是將全部的寫操做同步。
下面是我Ubuntu虛擬機的設置
root@wis-virtual-machine:~# cat /proc/sys/vm/dirtytime_expire_seconds 43200
Linux內核提供了一些選擇能夠犧牲性能換來同步操做。
fsync是在硬件驅動器確認數據和元數據都寫到磁盤以後返回,對於包含寫緩存的磁盤,fsync沒法知道數據是否已經真正在屋裏磁盤上了,硬盤會報告說數據已經寫完了,實際上數據還在磁盤的寫緩存上。
fdatasync會寫文件的大小,fdatasync不保證非基礎的元數據也寫到磁盤上,它比fsync更快,它不考慮元數據如文件修改時間戳。
這兩個函數不保證文件相關的目錄也寫到磁盤上,因此也須要對更新的目錄使用同步函數。
O_SYNC能夠理解爲調用write以後再調用fsync。
O_DSYNC能夠理解爲調用write以後再調用fdatasync。
O_DIRECT位使I/O操做都會忽略頁緩存機制,直接對用戶空間緩衝區和設備進行初始化。全部的I/O都是同步的。使用直接I/O時,請求長度,緩衝區以及文件偏移都必須是底層設備扇區大小(通常是512字節)的整數倍。
時間局部性原理是認爲剛被訪問的資源在不久以後再次被訪問的機率很高。
空間局部性原理是認爲數據訪問每每是連續的。預讀功能就是利用的這個原理。
一下兩種狀況會發生頁回寫:
要讀取1024個字節,若是每次只讀取一個字節須要執行1024次系統調用,若是一次讀取1024個字節那麼就只須要一次系統調用。對於前一種提高性能的方法是用戶緩衝I/O(user-buffered I/O),讀寫數據並無任何變化,而實際上,只有數據量大小達到文件系統塊大小張數倍的時候,纔會執行真正的I/O操做。
塊大小通常是512,1024,2048,4096個字節,I/O操做最簡單的方式是設置一個較大的緩衝區,是標準塊的整數倍,好比設置成4096,或者8192。
實際應用程序的讀寫都是在本身的緩衝區。寫的時候數據會被存儲到程序地址空間的緩衝區,當緩衝區數據大小達到給定值的時候,整個緩衝區會經過一次寫操做所有寫出。讀也是一次讀入用戶緩衝區大小且塊對齊的數據,應用讀取數據是從緩衝區一塊一塊讀的,當緩衝區爲空時,會讀取另外一個塊對齊的數據,。經過這種方式,雖然設置的讀寫大小很不合理,數據會從緩衝區中讀取,所以對文件系統仍是發送大的塊對齊的讀寫請求。其結果是對於大量數據,系統調用次數更少,且每次讀請求的數據大小都是塊對齊的。經過這種方式,能夠確保有很大的性能提高。
C標準庫中提供了標準I/O庫,它實現了跨平臺的用戶緩衝解決方案。
應用是使用標準I/O(能夠定製用戶緩衝行爲),仍是直接使用系統調用,這些都是開發人員應該慎重權衡應用的需求和行爲後肯定。
標準I/O程序集不是直接操做文件描述符,他們經過惟一表示符,即文件指針來操做。在C標準庫裏,文件指針和文件描述符一一映射。文件指針是由指向FILE的指針表示,FILE定義在<stdio.h>中。
在標準I/O中,打開的文件成爲流(stream)。流能夠用來讀(輸入流),寫(輸出流)或者並且都有(輸入/輸出流)。
當不存在和流相關的標準I/O函數時,能夠經過文件描述符對該流執行系統調用。爲了得到和流相關的文件描述符,可使用fileno函數:
# include <stdio.h> int fileno(FILE *stream);
成功時返回關聯的文件描述符。最好永遠都不要混合使用文件描述符和基於流的I/O操做。
標準I/O提供了三種類型的用戶緩衝:
在訪問共享數據時,有兩種方式能夠避免修改它:
標準I/O函數本質上是線程安全的,每一個函數內部都關聯了一把鎖,一個鎖計數器,以及持有該鎖並打開一個流的線程。每一個線程在執行任何I/O請求以前,都必須持有該鎖。所以,在單個函數調用中,標準I/O操做是原子操做。
標準I/O最大的詬病是兩次拷貝帶來的性能開銷。當讀取數據時,標準I/O會向內核發起read系統調用,把數據從內核複製到標準I/O緩衝區,當應用經過標準I/O如fgetc發起讀請求的時候,又會拷貝數據,此次是從標準I/O緩衝區拷貝到指定緩衝區。寫入請求剛還相反,數據先從指定緩衝區拷貝到標準I/O緩衝區,而後又經過write函數,從標準I/O緩衝區寫入內核。
分散彙集I/O是一種能夠在單次系統調用中對多個緩衝區輸入輸出的方法,能夠把多個緩衝區的數據寫到單個數據流,也能夠把單個數據流讀到多個緩衝區。這種輸入輸出方法也稱爲向量I/O(vector I/O)。以前提到的標準讀寫系統調用能夠稱爲線性I/O(linear I/O)。
向量I/O是經過readv和writev這個兩個系統調用來實現的。
#include <sys/uio.h> ssize_t readv(int fd, const struct iovec *iov, int count); #include <sys/uio.h> ssize_t writev(int fd, const struct iovec *iov, int count);
除了操做多個緩衝區以外,readv和writev功能和read,write功能一致。
每一個iovec結構體描述一個獨立的,物理不連續的緩衝區,咱們稱之爲段(segment):
#include <sys/uio.h> struct iovec{ void *iov_base; /* pointer to start of buffer */ size_t iov_len; /* size of buffer in bytes */ }
一組段的集合稱爲向量(vector)。每一個段描述了內存中須要讀寫的緩衝區地址和長度。readv函數在處理下個緩衝區以前,會填滿當前緩衝區的iov-len個字節。writev函數在處理下個緩衝區以前也會把當前緩衝區全部iov_len個字節輸出。這兩個函數都會順序處理向量中的段,從iov[0]開始,接着iov[1],一直到iov[count -1]。
Linux內核中全部的I/O都是向量I/O,read和write是做爲向量I/O實現的,且向量中只有一個段。
條件觸發是默認行爲,poll和select就是這種模式。
內核支持應用程序將文件映射到內存中,即內存地址和文件數據一一對應。這樣開發人員就能夠直接經過內存來訪問文件,Linux實現了POSIX.1標準中定義的mmap()系統調用。
#include <sys/mman.h> void * mmap (void *addr, size_t len, int prot, int flags, int fd, off_t offset);
Linux提供了系統調用madvise,進程對本身指望如何訪問映射區域給內核一些提示信息。
I/O調度器實現兩個基本操做,合併(merging)和排序(sorting)。
合併是講兩個或者多個相鄰的I/O請求合併爲一個。
排序是選取兩個操做中相對重要的一個,幷按塊號遞增的順序從新安排等待的I/O請求。如5,20,7這樣,排序後就變成5,7,20。若是這個時候有6的訪問,這個就會被插入到5和7之間。
若是隻是對請求進行排序的話,如一直訪問50-60之間的那麼100的請求就會一直得不到調度。
處理這個問題的方法就是Linus電梯調度算法,在該方法中若是隊列有必定數量的舊的請求,則中止新的請求。這樣雖然總體上能夠作到平等對待每一個請求,但在讀的時候卻增長了讀延遲。這個算法是Linux2.4內核使用的,在2.6廢棄了,使用了幾種新的調度器算法。
這個算法就是在電梯算法上添加了兩個隊列,分別爲讀隊列和寫隊列。每一個請求到了之後除了加到標準隊列,還要加到相應隊列的隊尾,每一個請求都設置了過時時間,讀的爲200毫秒,寫的是5秒。當讀寫的兩個隊列的隊首超出了過時時間,調度器就會中止從標準隊列中處理請求,轉而處理相應隊列隊首的請求。
這個調度器是deadline的改進版(就是多了一個預測機制),也是三個隊列,可是這個調度器會在每一個請求到達過時時間以前調度它,不會像deadline那樣立刻調度它而是等待6毫秒。若是這段時間還有對硬盤同一部分發起請求,這個請求就會馬上響應,Anticipatory 調度器會繼續等待。若是6毫秒內沒有收到請求,調度器就會認爲預測失敗,而後返回正常操做(如處理標準隊列中斷的請求)。
CFQ意爲Complete Fair Queuing,這個調度器中,每一個進程都有本身的隊列,每一個隊列分配一個時間片。調度程序輪詢方式處理隊列中的請求。知道隊列的時間片耗盡或者全部的請求都處理完(若是是全部請求處理完以後會等待一段時間默認10毫秒緣由和Anticipatory調度器的預測機制同樣)。
CFQ I/O調度器適合大多數場景。
這個是最簡單的調度算法,它只進行合併不作排序。SSD大部分使用這種調度器。
有篇文章推薦下:http://orababy.blogspot.com/2014/06/best-io-schedulerelevator-for-oracle.html
要選擇調度器能夠修改文件/sys/block/device/queue/scheduler來修改。目錄/sys/block/device/queue/iosched包含了調度器相關的選項。
個人虛擬機中的設置:
[root@node2 block]# pwd /sys/block [root@node2 block]# for i in `ls */queue/scheduler`; do echo $i;cat $i; done dm-0/queue/scheduler none dm-1/queue/scheduler none dm-2/queue/scheduler none fd0/queue/scheduler noop [deadline] cfq sda/queue/scheduler noop [deadline] cfq sr0/queue/scheduler noop deadline [cfq] [root@node2 iosched]# pwd /sys/block/sda/queue/iosched [root@node2 iosched]# for i in `ls`; do echo $i;cat $i; done fifo_batch 16 front_merges 1 read_expire 500 write_expire 5000 writes_starved 2
/sys 和/proc會繼續共存
sysfs文件系統的目的是要展示設備驅動程序模型組件間的關係,該文件系統的相應高層目錄
在最初的PC體系結構中,cpu是系統惟一的總線控制器,也就是說爲了提取和存儲ram存儲單元的值,cpu是惟一能夠驅動地址/數據總線的硬件設備。
設備驅動程序能夠採用兩種方式使用DMA,同步DMA是由進程觸發數據的傳送(聲卡播放音樂),異步DMA是由硬件設備觸發數據的傳送(網卡收到lan的幀,保存到本身的IO共享存儲器中,而後引起一箇中斷,其驅動程序確認該中斷後,命令網卡將收到的幀從IO共享存儲器拷貝到內核緩衝區,當數據傳送完成後,網卡會引起新的中斷,而後驅動程序將這個新幀通知給上層內核層)。