天天看點

Linux檔案共享(五)——線程共享檔案

在談論線程之間共享檔案之前,我想首先簡單的介紹下linux線程的實作。最初的程序定義都包含程式、資源及其執行三部分,其中程式通常指代碼,資源在作業系統層面上通常包括記憶體資源、io資源、信号處理等部分,而程式的執行通常了解為執行上下文,包括對cpu的占用,後來發展為線程。線上程概念出現以前,為了減小程序切換的開銷,作業系統設計者逐漸修正程序的概念,逐漸允許将程序所占有的資源從其主體剝離出來,允許某些程序共享一部分資源,例如檔案、信号,資料記憶體,甚至代碼,這就發展出輕量程序的概念。linux核心在2.0.x版本就已經實作了輕量程序,應用程式可以通過一個統一的clone()系統調用接口,用不同的參數指定建立輕量程序還是普通程序。在核心中,clone()調用經過參數傳遞和解釋後會調用do_fork(),這個核内函數同時也是fork()、vfork()系統調用的最終實作:在do_fork()中,不同的clone_flags将導緻不同的行為,對于linuxthreads,它使用(clone_vm | clone_fs | clone_files | clone_sighand)參數來調用clone()建立"線程",表示共享記憶體、共享檔案系統通路計數、共享檔案描述符表,以及共享信号處理方式。(其中前三個标志對應共享的資料結構分别為task_struct中的mm,fs,files)。

(1) clone_vm

    do_fork()需要調用copy_mm()來設定task_struct中的mm和active_mm項,這兩個mm_struct資料與程序所關聯的記憶體空間相對應。如果do_fork()時指定了clone_vm開關,copy_mm()将把新的task_struct中的mm和active_mm設定成與current的相同,同時提高該mm_struct的使用者數目(mm_struct::mm_users)。也就是說,輕量級程序與父程序共享記憶體位址空間。

(2) clone_fs(這個和我們接下來的讨論有關)

    task_struct中利用fs(struct fs_struct *)記錄了程序所在檔案系統的根目錄和目前目錄資訊,do_fork()時調用copy_fs()複制了這個結構;而對于輕量級程序則僅增加fs->count計數,與父程序共享相同的fs_struct。也就是說,輕量級程序沒有獨立的檔案系統相關的資訊,程序中任何一個線程改變目前目錄、根目錄等資訊都将直接影響到其他線程。

(3) clone_files(這個和我們接下來的讨論也有關)

    一個程序可能打開了一些檔案,在程序結構task_struct中利用files(struct files_struct *)來儲存程序打開的檔案結構(struct file)資訊,do_fork()中調用了copy_files()來處理這個程序屬性;輕量級程序與父程序是共享該結構的,copy_files()時僅增加files->count計數。這一共享使得任何線程都能通路程序所維護的打開檔案,對它們的操作會直接反映到程序中的其他線程。

(4) clone_sighand

每一個linux程序都可以自行定義對信号的處理方式,在task_struct中的sig(struct signal_struct)中使用一個struct k_sigaction結構的數組來儲存這個配置資訊,do_fork()中的copy_sighand()負責複制該資訊;輕量級程序不進行複制,而僅僅增加signal_struct::count計數,與父程序共享該結構。也就是說,子程序與父程序的信号處理方式完全相同,而且可以互相更改。

    有了以上分析,我們就可以畫出線程間對應的打開檔案資料結構的關系,如下圖所示。

Linux檔案共享(五)——線程共享檔案

    我們看到,線程間所有檔案結構都為共享資源,不但“檔案表項”(file對象)是共享的,就連“檔案描述符表”(files_struct結構)也是共享的。

    總結:線程的建立僅僅增加的是files和fs的引用計數,“檔案打開計數”(file對象的引用計數)并沒有增加,是以任何一個線程對打開的檔案執行close操作,檔案都将關閉(檔案打開計數為1的情況)。但是如果線程不進行打開檔案的關閉,則檔案直到程序結束時才會關閉,這就是使用多線程實作tcp伺服器時,服務線程必須要顯示調用close的原因,否則永遠不會發送fin終止連結(因為主線程一直處于監聽不會結束)。

繼續閱讀