在Linux中,是否有系統層/腳本來處理檔案的開啟?

在Linux中,是否有系統層/腳本來處理檔案的開啟?

在Linux中,是否有一個層/腳本來處理開啟檔案的程式請求?

就像您在 bash 中開啟檔案描述符時一樣:exec 3 <>/documents/foo.txt或者您的文字編輯器打開/documents/foo.txt

我不敢相信編輯器可以“僅打開一個文件”來自行進行讀取/寫入訪問。

我寧願想像這是對「層」的請求(init.d 腳本?),首先可以只開啟一定數量的文件,並透過其存取類型、開啟文件的進程等來追蹤開啟的文件。

答案1

這一層位於 Linux 和其他系統的核心內部,與歷史上的 Unix 設計相去不遠(大多數非 Unix 作業系統也是如此)。

內核的這一部分稱為VFS(虛擬檔案系統)層。 VFS的功能是管理開啟檔案的資訊(檔案之間的對應關係)檔案描述符,開啟文件描述和目錄條目),解析檔案路徑(解釋和/),並將目錄條目上的操作分派到正確的檔案系統驅動程式。...

大多數檔案系統驅動程式也在核心中,但是保險絲檔案系統驅動程式允許將此功能委託給核心外部。如果較低層級的儲存裝置這樣做,檔案系統操作也可能涉及使用者空間程式碼,例如,如果磁碟檔案系統位於循環裝置

答案2

Linux中的檔案開啟是由核心直接處理的但您可以採取一些措施來影響和研究該過程。


Linux儲存堆疊圖


系統調用

從頂部開始,您可以看到應用程式用於與文件互動的介面是系統調用

打開,做你期望的事,同時統計數據返回有關文件的資訊而不打開它。

您可以使用 strace 研究程式對檔案相關係統呼叫的使用情況:

$ strace -e trace=%file /bin/ls /etc
[...]
stat("/etc", {st_mode=S_IFDIR|0755,  ...}) = 0
openat(AT_FDCWD, "/etc", O_RDONLY...) = 3

這分析了 引起的系統調用ls /etc,顯示statopenat是在/etc目錄上調用的。

您可能想知道為什麼我們要在目錄上呼叫檔案操作。在 UNIX 中,目錄也是檔案。實際上一切都是文件


檔案描述符

您可能想知道openat() = 3上面輸出中的內容。

在 UNIX 中,開啟的檔案由檔案描述符,它是某個進程打開的檔案的唯一表示。檔案描述符 0、1 和 2 通常保留給標準流(使用者輸入/輸出),因此第一個開啟的檔案將為 3。

您可以使用以下命令來取得給定進程的開啟檔案描述符列表lsofstF島):

$ cat /dev/urandom > /dev/null &
[1] 3242
$ lsof -p 3242
COMMAND  PID      USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
...
cat     3242 user         0u   CHR  136,0      0t0      3 /dev/pts/0
cat     3242 user         1w   CHR    1,3      0t0   1028 /dev/null
cat     3242 user         2u   CHR  136,0      0t0      3 /dev/pts/0
cat     3242 user         3r   CHR    1,9      0t0   1033 /dev/urandom

FD列顯示檔案描述符編號以及存取權限。

您也可以使用fuser搜尋儲存特定檔案的進程:

$ fuser /dev/urandom
/dev/urandom:         ...  3242  ...

進程資訊偽檔案系統 - /proc

現在您可能想知道:lsof但首先如何知道哪些文件被打開

好吧,讓我們來看看吧!

$ strace -e trace=%file lsof -p 3242
...
stat("/proc/3242/", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
openat(AT_FDCWD, "/proc/3242/stat", O_RDONLY) = 4
...
openat(AT_FDCWD, "/proc/3242/fd", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 4
readlink("/proc/3242/fd/0", "/dev/pts/0", 4096) = 10
lstat("/proc/3242/fd/0", {st_mode=S_IFLNK|0700, st_size=64, ...}) = 0
stat("/proc/3242/fd/0", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
openat(AT_FDCWD, "/proc/3242/fdinfo/0", O_RDONLY) = 7
...

所以lsof知道透過...閱讀更多文件來開啟哪些文件!具體來說,目錄/proc/3242/fd.下面的所有內容/proc都是內核保存的“假”檔案系統。你可以ls -l透過它來查看它的結構。


影響文件打開

您可以使用多種方法來影響文件打開,儘管它們並不像替換某些腳本那麼簡單。

如果您希望更改檔案的儲存或存取方式,例如提供加密、快取、將其分佈在多個磁碟上或類似的方式,那麼很可能已經存在一個現有的裝置映射器適合您的需求。

如果您想要對特定目錄/掛載中的檔案開啟進行細粒度控制,您可以編寫一個簡單的保險絲文件系統並掛載它。

在程序/進程級別,您可以使用LD_預先載入更改 C 庫呼叫並阻止它們執行正常的系統呼叫。

最困難但最靈活的方法是編寫自己的檔案系統驅動程式。

答案3

管理對檔案的存取是作業系統的第一個也是最重要的功能。 DOS 是個人電腦上最古老的作業系統之一,意思是磁碟作業系統。它允許程式在大多數情況下直接存取硬件,但不能存取檔案。程式必須使用 DOS 調用,而 DOS 將管理程式將資料放入和取出的檔案。只有磁碟實用程式才能在 DOS 下直接存取硬碟和檔案。

像Linux 這樣的現代保護模式作業系統可以像DOS 一樣處理文件訪問,但它們也要求對程式本身(或已配置為與之共享記憶體的任何其他程式)之外的任何內容的每次訪問都必須經過核心(Linux 是一個核心)。

Linux 上的程式可能會呼叫 C 函式庫中的函數來讀取檔案資料或將資料寫入檔案。然後,C 庫會完成其組織對文件中資料的存取的部分,同時仍在與程式相同的上下文中運行。然後,C 函式庫將使用正確的函數呼叫核心 (Linux) 來存取文件,從而將 CPU 切換到環 0 或特權模式。 CPU現在以特權模式運行Linux檔案系統驅動程式和硬碟驅動程式軟體,直接存取硬體來存取檔案。資料被複製到 C 庫指示 Linux 放置資料的記憶體區域,CPU 將切換回使用者模式,並具有程式的安全上下文,C 庫將恢復並執行其需要執行的任何處理。

答案4

簡而言之,這就是程式寫入檔案時發生的情況

  1. 該程式向核心請求open一個由路徑給出的檔案進行寫入。
  2. 核心設定一些內部結構並將開啟檔案的一些任務委託給特定於檔案系統類型的驅動程式。然後核心向程式傳回一個檔案描述符,它只是一個整數(例如3)。
  3. 程式要求核心將write位元組序列(例如字串)加入到檔案描述符所引用的檔案中。
  4. 核心再次將工作委託給驅動程式。
  5. 步驟 3 和 4 可能會重複幾次。
  6. 程式向核心請求close檔案描述符引用的檔案。
  7. 核心再次將工作委託給驅動程序,然後銷毀內部結構。

這是一個相當簡約的彙編程序,其中寫入“Hello World!”到文件greeting.txt:

.text
.globl _start

_start:
    # Open and possible create file
    mov $2,             %rax        # syscall 'open'
    mov $path_start,    %rdi        # path
    mov $0101,          %rsi        # create + write
    mov $400,           %edx        # only user gets read permissions
    syscall

    mov %rax,           %r10        # file descriptor

    # Write string to file
    mov $1,             %rax        # syscall 'write'
    mov %r10,           %rdi        # file descriptor
    mov $msg_start,     %rsi        # start of data
    mov $msg_length,    %edx        # length of data
    syscall                         # perform syscall

    # Close file
    mov $3,             %rax        # syscall 'close'
    mov %r10,           %rdi        # file descriptor
    syscall

    # Exit program
    mov $60,            %rax        # syscall 'exit'
    syscall                         # perform syscall


.section .rodata

path_start:
    .string "greeting.txt\0"
path_end:
path_length = path_end - path_start


msg_start:
    .string "Hello World!\n"
msg_end:
msg_length = msg_end - msg_start

將程式碼儲存到 write.s 並使用進行構建

as -o write.o write.s
ld -o write   write.o

然後運行

./write

希望一切順利。

(注意:我不做任何錯誤處理。這只是玩具代碼。)

相關內容