讀書筆記之Linux系統編程與深刻理解Linux內核

前言

本人再看深刻理解Linux內核的時候發現比較難懂,看了Linux系統編程一說後,以爲Linux系統編程仍是簡單易懂些,而且兩本書都是講Linux比較底層的東西,只不過側重點不一樣,本文就以Linux系統編程爲例而且會穿插一些深刻理解Linux內核的內容來寫。html

1 入門與基本概念

本書的背景

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,信號安全)的函數。

2 文件I/O

在對文件進行讀寫操做以前,首先要打開文件,內核會爲每一個進程維護一個打開文件的列表,這個列表是由一些非負整數進行索引,這些非負整數稱爲文件描述符。
文件描述符從0開始到上限值-1,每一個進程至少包含3個文件描述符:0,1,2,除非顯示的關閉這些描述符。
遵循一切接文件的理念,幾乎全部可以讀寫的東西均可以經過文件描述符來訪問。

新建文件的權限

umask是進程級的屬性,由login shell設置經過umask來修改。umask爲022,取反爲755,而後與mode參數0666取與爲0644,新建文件的權限爲644。

write()行爲

用戶空間發起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

同步I/O

Linux內核提供了一些選擇能夠犧牲性能換來同步操做。
fsync是在硬件驅動器確認數據和元數據都寫到磁盤以後返回,對於包含寫緩存的磁盤,fsync沒法知道數據是否已經真正在屋裏磁盤上了,硬盤會報告說數據已經寫完了,實際上數據還在磁盤的寫緩存上。
fdatasync會寫文件的大小,fdatasync不保證非基礎的元數據也寫到磁盤上,它比fsync更快,它不考慮元數據如文件修改時間戳。
這兩個函數不保證文件相關的目錄也寫到磁盤上,因此也須要對更新的目錄使用同步函數。

O_SYNC與O_DSYNC

O_SYNC能夠理解爲調用write以後再調用fsync。
O_DSYNC能夠理解爲調用write以後再調用fdatasync。

直接I/O

O_DIRECT位使I/O操做都會忽略頁緩存機制,直接對用戶空間緩衝區和設備進行初始化。全部的I/O都是同步的。使用直接I/O時,請求長度,緩衝區以及文件偏移都必須是底層設備扇區大小(通常是512字節)的整數倍。

頁緩存

時間局部性原理是認爲剛被訪問的資源在不久以後再次被訪問的機率很高。
空間局部性原理是認爲數據訪問每每是連續的。預讀功能就是利用的這個原理。

頁回寫

一下兩種狀況會發生頁回寫:

  • 當內存空餘小於設定的閾值的時候,髒頁就會寫到磁盤上,這樣來釋放內存。
  • 髒頁的時長超過設定的閾值。
    回寫是由一組flusher的內核線程來執行的。當上述的條件知足時,這個線程就被喚醒,把髒頁寫到緩衝區,直到不知足回寫的觸發條件。

3 緩衝I/O

要讀取1024個字節,若是每次只讀取一個字節須要執行1024次系統調用,若是一次讀取1024個字節那麼就只須要一次系統調用。對於前一種提高性能的方法是用戶緩衝I/O(user-buffered I/O),讀寫數據並無任何變化,而實際上,只有數據量大小達到文件系統塊大小張數倍的時候,纔會執行真正的I/O操做。

塊大小

塊大小通常是512,1024,2048,4096個字節,I/O操做最簡單的方式是設置一個較大的緩衝區,是標準塊的整數倍,好比設置成4096,或者8192。
實際應用程序的讀寫都是在本身的緩衝區。寫的時候數據會被存儲到程序地址空間的緩衝區,當緩衝區數據大小達到給定值的時候,整個緩衝區會經過一次寫操做所有寫出。讀也是一次讀入用戶緩衝區大小且塊對齊的數據,應用讀取數據是從緩衝區一塊一塊讀的,當緩衝區爲空時,會讀取另外一個塊對齊的數據,。經過這種方式,雖然設置的讀寫大小很不合理,數據會從緩衝區中讀取,所以對文件系統仍是發送大的塊對齊的讀寫請求。其結果是對於大量數據,系統調用次數更少,且每次讀請求的數據大小都是塊對齊的。經過這種方式,能夠確保有很大的性能提高。

標準I/O

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提供了三種類型的用戶緩衝:

  • 無緩衝(Unbuffered)數據直接提交給內核,不多使用,標準錯誤默認是採用無緩衝模式。
  • 行緩衝(Line-buffered)緩衝以行爲單位,沒遇到換行符緩衝區就會被提交到內核。行緩衝是默認終端的緩衝模式,好比標準輸出。
  • 塊緩衝(block-buffered)緩衝以塊爲單位執行,每一個塊都是固定的字節數。默認狀況和文件相關的全部流都是塊緩衝模式,標準I/O稱塊緩衝爲徹底緩衝(full buffering)。

線程安全

在訪問共享數據時,有兩種方式能夠避免修改它:

  • 數據同步訪問(synchronized access)機制(加鎖)
  • 把數據存儲在線程的局部變量中

標準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緩衝區寫入內核。

4 高級I/O

分散/彙集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實現的,且向量中只有一個段。

Event Poll

邊緣出發(Level-triggered)與條件觸發(Edge-triggered)

條件觸發是默認行爲,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);

mmap的優勢

  • 使用read和write系統調用時,須要從用戶緩衝區進行讀寫,而使用映射文件進行操做,避免多餘的數據拷貝操做。
  • 除了可能潛在的頁錯誤,讀寫映射文件不會帶來系統調用和上下文切換的開銷。
  • 當多個進程把同一個對象映射到內存中時,數據會在全部進程間共享。只讀和寫共享的映射在全體中都是共享的,私有可寫的映射對還沒有進行寫時拷貝的頁是共享的。
  • 在映射對象中搜索只須要簡單的指針操做,不須要系統調用lseek。

給出映射提示

Linux提供了系統調用madvise,進程對本身指望如何訪問映射區域給內核一些提示信息。

預讀

普通文件I/O提示posix_fadvise,readahead

經濟適用的操做提示

同步(Synchronized),同步(Synchronous)及異步(Asynchronous)操做

I/O調度器和I/O性能

磁盤尋址

I/O調度器的功能

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廢棄了,使用了幾種新的調度器算法。

Deadline I/O調度器

這個算法就是在電梯算法上添加了兩個隊列,分別爲讀隊列和寫隊列。每一個請求到了之後除了加到標準隊列,還要加到相應隊列的隊尾,每一個請求都設置了過時時間,讀的爲200毫秒,寫的是5秒。當讀寫的兩個隊列的隊首超出了過時時間,調度器就會中止從標準隊列中處理請求,轉而處理相應隊列隊首的請求。

Anticipatory I/O調度器

這個調度器是deadline的改進版(就是多了一個預測機制),也是三個隊列,可是這個調度器會在每一個請求到達過時時間以前調度它,不會像deadline那樣立刻調度它而是等待6毫秒。若是這段時間還有對硬盤同一部分發起請求,這個請求就會馬上響應,Anticipatory 調度器會繼續等待。若是6毫秒內沒有收到請求,調度器就會認爲預測失敗,而後返回正常操做(如處理標準隊列中斷的請求)。

CFQ I/O調度器

CFQ意爲Complete Fair Queuing,這個調度器中,每一個進程都有本身的隊列,每一個隊列分配一個時間片。調度程序輪詢方式處理隊列中的請求。知道隊列的時間片耗盡或者全部的請求都處理完(若是是全部請求處理完以後會等待一段時間默認10毫秒緣由和Anticipatory調度器的預測機制同樣)。
CFQ I/O調度器適合大多數場景。

Noop I/O調度器

這個是最簡單的調度算法,它只進行合併不作排序。SSD大部分使用這種調度器。

選擇和配置你的I/O調度器

有篇文章推薦下: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

優化I/O性能

5 進程管理

程序,進程,線程

運行新進程

等待子進程終止

殭屍進程

守護進程

6 高級進程管理

進程調度

I/O約束型進程和處理器約束型進程

徹底公平調度器

I/O優先級

處理器親和力(Affinity)

實時系統

資源限制

7 線程

二進制程序,進程,線程

線程模型

線程模式

同步與鎖

Pthread

線程ID

join加入進程和detach分離進程

8 文件和目錄管理

stat函數

目錄

新建目錄

連接

特殊設備節點

隨機數生成器

帶外通訊(Out-of-Band Communication)

inotify監視文件事件

9 內存管理

進程地址空間

對齊問題

匿名內存映射

內存分配方式(各類方式的優缺點)

鎖住內存

投機性內存分配策略

10 信號

SIGHUP

信號管理

可重入

信號的用途和缺點

10 時間

如下是深刻理解linux內核中的

IO體系結構與設備驅動程序

sysfs文件系統

/sys 和/proc會繼續共存

sysfs文件系統的目的是要展示設備驅動程序模型組件間的關係,該文件系統的相應高層目錄

  • block 塊設備,他們獨立於所鏈接的總線
  • devices 全部被內核識別的硬件設備,依照他們的總線對其進行組織
  • bus 系統中用於鏈接設備的總線
  • drivers 在內核中註冊的設備驅動程序
  • class 系統中的設備的類型,聲卡,網卡,顯卡等等,同一類可能包含不一樣總線鏈接的設備,因而由不一樣的驅動- 程序驅動
  • power 處理一些設備電源狀態的文件
  • firmware 處理一些設備文件固件的文件

直接內存訪問DMA

在最初的PC體系結構中,cpu是系統惟一的總線控制器,也就是說爲了提取和存儲ram存儲單元的值,cpu是惟一能夠驅動地址/數據總線的硬件設備。

同步DMA和異步DMA

設備驅動程序能夠採用兩種方式使用DMA,同步DMA是由進程觸發數據的傳送(聲卡播放音樂),異步DMA是由硬件設備觸發數據的傳送(網卡收到lan的幀,保存到本身的IO共享存儲器中,而後引起一箇中斷,其驅動程序確認該中斷後,命令網卡將收到的幀從IO共享存儲器拷貝到內核緩衝區,當數據傳送完成後,網卡會引起新的中斷,而後驅動程序將這個新幀通知給上層內核層)。

相關文章
相關標籤/搜索