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 /etcstatおよび がディレクトリopenatで呼び出されることを示します/etc

なぜディレクトリに対してファイル操作を呼び出すのか疑問に思うかもしれません。UNIXではディレクトリもファイルです。実際、すべてがファイルです


ファイル記述子

openat() = 3上記の出力のについて疑問に思うかもしれません。

UNIXでは開かれたファイルはファイル記述子は、特定のプロセスによって開かれたファイルの一意の表現です。ファイル記述子0、1、2は通常、標準ストリーム(ユーザー入力/出力) なので、最初に開かれるファイルは 3 になります。

特定のプロセスで開いているファイル記述子のリストを取得するには、lsoflstoペンファイル):

$ 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

ファイルへのアクセス管理は、オペレーティング システムの最初で最も重要な機能です。パーソナル コンピュータで最も古いオペレーティング システムの 1 つである DOS は、ディスク オペレーティング システムを意味します。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. カーネルは再び作業をドライバーに委任し、その後内部構造を破棄します。

以下は、greeting.txt ファイルに「Hello World!」を書き込む、非常にシンプルなアセンブリ プログラムです。

.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

コードを保存して.sでビルドします。

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

そして、

./write

すべてがうまくいくことを祈ります。

(注: エラー処理は行いません。これは単なるおもちゃのコードです。)

関連情報