Linux には、ファイルを開くためのプログラム要求を処理するレイヤー/スクリプトがありますか?
bashでファイル記述子を開いたときexec 3 <>/documents/foo.txt
や、テキストエディタで開いたときのように/documents/foo.txt
エディターが単独で読み取り/書き込みアクセスのために「ファイルを開く」ことができるとは信じられません。
私はこれを「レイヤー」へのリクエストだと想像しています(init.d スクリプト?) は、最初から一定量のファイルしか開けず、開いているファイルのアクセス種別や、どのプロセスによって開かれたかなどを記録します。
答え1
このレイヤーは、Linux や、歴史的な Unix 設計からそれほど離れていない他のシステム (およびほとんどの非 Unix オペレーティング システム) のカーネル内にあります。
カーネルのこの部分はVFS (仮想ファイルシステム) 層VFSの役割は、開いているファイルに関する情報(ファイル記述子、オープンファイルの説明およびディレクトリ エントリ) をサポートし、ファイル パスを解析し ( /
、.
およびを解釈..
)、ディレクトリ エントリに対する操作を適切なファイル システム ドライバにディスパッチします。
ほとんどのファイルシステムドライバもカーネル内に存在しますが、ヒューズファイルシステムドライバは、この機能をカーネルの外部に委任することを可能にします。ファイルシステム操作は、低レベルのストレージがユーザーランドコードを必要とする場合、例えばディスクファイルシステムがループデバイス。
答え2
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
、stat
および がディレクトリopenat
で呼び出されることを示します/etc
。
なぜディレクトリに対してファイル操作を呼び出すのか疑問に思うかもしれません。UNIXではディレクトリもファイルです。実際、すべてがファイルです!
ファイル記述子
openat() = 3
上記の出力のについて疑問に思うかもしれません。
UNIXでは開かれたファイルはファイル記述子は、特定のプロセスによって開かれたファイルの一意の表現です。ファイル記述子0、1、2は通常、標準ストリーム(ユーザー入力/出力) なので、最初に開かれるファイルは 3 になります。
特定のプロセスで開いているファイル記述子のリストを取得するには、lsof
(l私stoペンふファイル):
$ 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
つまり、プログラムがファイルに書き込むときに起こることです
- プログラムは、
open
パスで指定されたファイルへの書き込みをカーネルに要求します。 - カーネルはいくつかの内部構造を設定し、ファイルを開くタスクの一部をファイル システム タイプに固有のドライバーに委任します。次に、カーネルは単なる整数 (例: 3) であるファイル記述子をプログラムに返します。
- プログラムは、
write
ファイル記述子によって参照されるファイルにバイトシーケンス (文字列など) を送信するようにカーネルに要求します。 - カーネルは再び作業をドライバーに委任します。
- 手順 3 と 4 はおそらく複数回繰り返されます。
close
プログラムは、ファイル記述子によって参照されるファイルをカーネルに要求します。- カーネルは再び作業をドライバーに委任し、その後内部構造を破棄します。
以下は、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
すべてがうまくいくことを祈ります。
(注: エラー処理は行いません。これは単なるおもちゃのコードです。)