檔案描述符、開啟檔案表以及inode

changjixiong發表於2018-01-04

linux系統相關書籍中關於檔案描述符及相關內容,通常會出現一張類似這樣的圖

(filegraph1)

或者這樣的圖

(filegraph2)

第一個圖來自Michael Kerrisk的《Linux/UNIX系統程式設計手冊》,第二個圖來自《UNIX環境高階程式設計》(也就是APUE)。

文中對相關資訊做了論述並且配上了上面這樣的圖,但是我相信很多人看完以後覺得好像懂了,那麼請嘗試想一想一下幾個問題:

1.假如建立了檔案a得到了檔案描述符fa1,並且正在寫入的過程中再次開啟檔案a得到了檔案描述符fa2。這個時候通過fa2對檔案重新命名,會有什麼結果。

2.fa2對檔案重新命名後,通過fa1獲得的檔名是原來的檔名還是修改後的檔名。

3.fa1能否繼續寫入。

如果以上3個問題完全沒有疑惑,說明已經對檔案描述符及相關內容掌握的非常清楚,可以不用繼續看下去了。如果還有疑問,那麼請看下面這段程式碼

func main() {

    fileOldName := "rotate.log"
    fileRename := "rotate1.log"
    file, _ := os.OpenFile(fileOldName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.FileMode(0644))

    fmt.Println("open file", fileOldName)
    for i := 0; i < 5; i++ {
        file.WriteString(fmt.Sprintf("%v line: %d\n", time.Now(), i))
    }

    var statOldFile syscall.Stat_t
    if err := syscall.Stat(fileOldName, &statOldFile); err != nil {
        panic(err)
    }

    fmt.Println(fileOldName, "statOldFile.Ino:", statOldFile.Ino)

    err := os.Rename(fileOldName, fileRename)
    if err != nil {
        fmt.Println("file rename Error:", err)
    }

    fmt.Println("rename ", fileOldName, "->", fileRename)

    var statRenamedFile syscall.Stat_t
    if err := syscall.Stat(fileRename, &statRenamedFile); err != nil {
        panic(err)
    }

    fmt.Println("fileRename", "statRenamedFile.Ino:", statRenamedFile.Ino)

    fmt.Println("file.Name:", file.Name())

    for i := 5; i < 10; i++ {
        file.WriteString(fmt.Sprintf("%v line: %d\n", time.Now(), i))
    }

    fileOld, err := os.OpenFile(fileOldName, os.O_WRONLY|os.O_APPEND, os.FileMode(0644))

    if nil != err {
        fmt.Println(fileOldName, " open error:", err)
    } else {
        fmt.Println("fileOld.Name:", fileOld.Name())
    }

}

執行後的輸出為

open file rotate.log
rotate.log statOldFile.Ino: 28567907
rename  rotate.log -> rotate1.log
fileRename statRenamedFile.Ino: 28567907
file.Name: rotate.log
rotate.log  open error: open rotate.log: no such file or directory

rotate1.log檔案中有10行記錄。

上面的程式碼首先建立了一個名為rotate.log的檔案,然後寫入了5行記錄,列印出檔案的inodeID 28567907,然後將檔案重新命名為rotate1.log,再次列印檔案的inodeID 28567907,繼續寫入5條記錄,列印檔案物件的名字,最後再次開啟檔案rotate.log,提示檔案不存在。

現在來分析一下程式的邏輯:

  1. 建立並開啟檔案rotate.log其inodeID為28567907,file物件中包含了一個檔案描述符姑且稱為FA1,指向一個檔案表項姑且稱為FT1,FT1指向一個V節點項。此V節點項的檔案就是inodeID為28567907的rotate.log檔案,寫入了5條記錄。
  2. 利用rename將檔案rotate.log重新命名為rotate1.log。實際就是開啟了一個檔案描述符FA2,指向檔案表FT2,FT2指向inodeID為28567907的V節點項,也就是前文的rotate.log,將V節點中這個檔案的檔名屬性修改為rotate1.log,檔案還是那個檔案,因此列印出來的inodeID還是那個inodeID。
  3. file物件再次寫入5條記錄,FA1沒變,FT1沒變,V節點中的inode沒變,僅僅只是檔名屬性變了,因此記錄依然寫入了原來那個檔案,雖然檔名已經變了。
  4. 列印file物件的檔名屬性,顯示是修改前的名字,因為file物件的name屬性在建立時已經賦值,即使V節點中的檔名已經修改,file物件中的name不會變。
  5. 再次開啟rotate.log會發現已經打不開了,因為這個檔案已經不存在了,畢竟V節點中這個檔案現在已經叫rotate1.log了。
  6. rotate1.log中有10行記錄,其中5行是改名前寫入的,5條是改名後寫入的。

通過這個例子,大概能明白文章開頭的兩個圖了吧。

為啥檔名叫rotate.log?因為logrotate就是重新命名後再次用原名建立新檔案繼續寫的。

更多golang示例程式碼見 https://github.com/changjixiong/goNotes

相關文章