実際にファイルを開くとどうなりますか?


266

(少なくとも私が使用している)すべてのプログラミング言語では、ファイルを読み書きする前にファイルを開く必要があります。

しかし、このオープン操作は実際には何をするのでしょうか?

典型的な関数のマニュアルページは実際にはそれが「読み取り/書き込みのためにファイルを開く」こと以外は何も教えていません:

http://www.cplusplus.com/reference/cstdio/fopen/

https://docs.python.org/3/library/functions.html#open

明らかに、関数の使用を通じて、ファイルへのアクセスを容易にする何らかの種類のオブジェクトの作成が含まれていることがわかります。

別の言い方をすると、もし私がopen関数を実装したとしたら、それはLinuxで何をする必要があるでしょうか?


13
CLinux に焦点を当てるためにこの質問を編集します。LinuxとWindowsの動作が異なるためです。そうでなければ、それは少し広すぎます。また、より高いレベルの言語は、システムのC APIを呼び出すか、Cにコンパイルして実行するため、「C」のレベルのままにすると、Least Common Denominatorに配置されます。
ジョージストッカー2015年

1
言うまでもなく、すべてのプログラミング言語がこの機能を備えているわけではなく、環境に大きく依存する機能でもあります。もちろん、最近では確かにまれですが、今日まで、ファイル処理はANSI Forthの完全にオプションの部分であり、過去の一部の実装には存在していませんでした。

回答:


184

ほとんどすべての高水準言語では、ファイルを開く関数は、対応するカーネルシステムコールのラッパーです。他の空想的なこともできますが、現代のオペレーティングシステムでは、ファイルを開くときに常にカーネルを経由する必要があります。

これが、fopenライブラリ関数の引数、またはPythonのopen引数がopen(2)システムコールの引数によく似ている理由です。

ファイルを開くことに加えて、これらの関数は通常、結果として読み取り/書き込み操作で使用されるバッファーをセットアップします。このバッファーの目的は、基盤となるシステムコールへの呼び出しが返す値が少ないかどうかに関係なく、Nバイトを読み取る場合は常に、対応するライブラリ呼び出しがNバイトを返すようにすることです。

実際に自分の機能を実装することに興味はありません。一体何が起こっているのかを理解するだけで...あなたが好きなら「言語を超えて」。

Unixライクなオペレーティングシステムでは、への呼び出しが成功するとopen、ユーザープロセスのコンテキストでは単なる整数である「ファイル記述子」が返されます。その結果、この記述子は、開かれたファイルと対話するすべての呼び出しに渡され、それを呼び出しcloseた後、記述子は無効になります。

の呼び出しは、openさまざまなチェックが行われる検証ポイントのように機能することに注意してください。すべての条件が満たされていない場合-1、記述子の代わりに戻ることで呼び出しは失敗し、エラーの種類がに示されerrnoます。重要なチェックは次のとおりです。

  • ファイルが存在するかどうか。
  • 呼び出しプロセスがこのファイルを指定されたモードで開く特権を持っているかどうか。これは、ファイルのアクセス許可、所有者ID、およびグループIDを呼び出しプロセスのそれぞれのIDと照合することによって決定されます。

カーネルのコンテキストでは、プロセスのファイル記述子と物理的に開かれたファイルの間に何らかのマッピングが必要です。記述子にマップされる内部データ構造には、ブロックベースのデバイスを処理するさらに別のバッファー、または現在の読み取り/書き込み位置を指す内部ポインターが含まれる場合があります。


2
UnixライクなOSでは、カーネル内の構造ファイル記述子がマップされることを「オープンファイル記述」と呼ぶことに注意してください。したがって、プロセスFDはカーネルOFDにマップされます。これは、ドキュメントを理解するために重要です。たとえばman dup2開いているファイル記述子(たまたま開いているFD)と開いているファイルの説明(OFD)の間の微妙な点を確認してください。
rodrigo 2015年

1
はい、許可はオープン時にチェックされます。カーネルの「オープン」実装のソース:lxr.free-electrons.com/source/fs/open.cにアクセスして読むことができますが、ほとんどの作業は特定のファイルシステムドライバーに委任されています。
pjc50 2015年

1
(ext2システムでは、ディレクトリエントリを読み取ってメタデータが含まれているiノードを識別し、そのiノードをiノードキャッシュにロードします。「/ proc」や「/ sys」などの任意のことを行う疑似ファイルシステムがあることに注意してください。ファイルを開いたとき)
pjc50

1
ファイルを開くときのチェック(ファイルが存在するかどうか、ユーザーがアクセス許可を持っているかどうか)は、実際には不十分です。足元でファイルが消えたり、アクセス権が変わったりする可能性があります。一部のファイルシステムはこれを防止しようとしますが、OSがネットワークストレージをサポートしている限り、防止することは不可能です(ローカルファイルシステムが誤動作して合理的である場合、OSは「パニック」に陥る可能性があります。実行可能なOS)。これらのチェックはファイルを開くときに行われますが、他のすべてのファイルアクセスでも(効果的に)行う必要があります。
Yakk-Adam Nevraumont 2015年

2
ロックの評価や作成を忘れないでください。これらは共有することも、排他的にすることもでき、ファイル全体またはファイルの一部にのみ影響を与える可能性があります。
Thinkeye 2015年

83

このガイドは、open()システムコールの簡略化されたバージョンでご覧になることをお勧めします。次のコードスニペットを使用します。これは、ファイルを開いたときに舞台裏で行われる処理を表しています。

0  int sys_open(const char *filename, int flags, int mode) {
1      char *tmp = getname(filename);
2      int fd = get_unused_fd();
3      struct file *f = filp_open(tmp, flags, mode);
4      fd_install(fd, f);
5      putname(tmp);
6      return fd;
7  }

簡単に言うと、そのコードは行ごとに次のようになります。

  1. カーネル制御メモリのブロックを割り当て、ユーザー制御メモリからファイル名をその中にコピーします。
  2. 未使用のファイル記述子を選択します。これは、現在開いているファイルの拡張可能なリストへの整数インデックスと考えることができます。カーネルによって管理されていますが、各プロセスには独自のリストがあります。コードから直接アクセスすることはできません。リストのエントリには、inode番号、プロセスのアクセス許可、オープンフラグなど、ディスクからバイトをプルするために基礎となるファイルシステムが使用するすべての情報が含まれています。
  3. filp_open機能は実装してい

    struct file *filp_open(const char *filename, int flags, int mode) {
            struct nameidata nd;
            open_namei(filename, flags, mode, &nd);
            return dentry_open(nd.dentry, nd.mnt, flags);
    }

    これは2つのことを行います。

    1. ファイルシステムを使用して、渡されたファイル名またはパスに対応するiノード(または、より一般的には、ファイルシステムが使用するあらゆる種類の内部識別子)を検索します。
    2. struct fileiノードに関する重要な情報を使用してを作成し、それを返します。この構造体は、前述の開いているファイルのリストのエントリになります。
  4. 返された構造体をプロセスの開いているファイルのリストに格納(「インストール」)します。

  5. カーネル制御メモリの割り当てられたブロックを解放します。
  6. その後のようなファイル操作関数に渡すことができるファイル記述子を返しread()write()close()。これらはそれぞれ、制御をカーネルに渡します。カーネルはファイル記述子を使用して、プロセスのリストで対応するファイルポインターを検索し、そのファイルポインターの情報を使用して、実際に読み取り、書き込み、またはクローズを実行します。

意欲的である場合は、この単純化された例をopen()、Linuxカーネルでのシステムコールの実装(と呼ばれる関数)と比較できますdo_sys_open()。類似点を見つけるのに問題はないはずです。


もちろん、これは、呼び出し時に何が起きるかの「最上位層」にすぎません。open()より正確には、ファイルを開くプロセスで呼び出されるカーネルコードの最高レベルの部分です。高水準プログラミング言語は、これに追加のレイヤーを追加する場合があります。低いレベルで行われていることがたくさんあります。(説明してくれたRuslanpjc50に感謝します。)大まかに、上から下へ:

  • open_namei()dentry_open()カーネルの一部でもあるファイルシステムコードを呼び出して、ファイルとディレクトリのメタデータとコンテンツにアクセスします。ファイルシステムは、ディスクからの生のバイトを読み込み、ファイルやディレクトリのツリーとして、これらのバイトパターンを解釈します。
  • ファイルシステムは、カーネルの一部であるブロックデバイスレイヤーを使用して、ドライブからこれらの生のバイトを取得します。(事実:Linuxでは/dev/sda、などを使用して、ブロックデバイスレイヤーから生データにアクセスできます。)
  • ブロックデバイスレイヤーは、カーネルコードでもあるストレージデバイスドライバーを呼び出して、「セクターXの読み取り」のような中レベルの命令からマシンコードの個々の入出力命令変換します。ドライブが使用できるさまざまな通信規格に対応して、IDE(S)ATASCSIFirewireなど、いくつかのタイプのストレージデバイスドライバーがあります。(名前が混乱していることに注意してください。)
  • I / O命令は、プロセッサチップとマザーボードコントローラの組み込み機能を使用して、物理ドライブへの配線で電気信号を送受信します。これはハードウェアであり、ソフトウェアではありません。
  • ワイヤーの反対側では、ディスクのファームウェア(埋め込まれた制御コード)が電気信号を解釈して、プラッターを回転させてヘッドを移動(HDD)するか、フラッシュROMセル(SSD)を読み取るか、そのタイプのストレージデバイス。

これは、キャッシュのために多少不正確になる場合もあります。:-P真剣に、しかし、私が省略した多くの詳細があります-人(私ではない)は、このプロセス全体がどのように機能するかを説明する複数の本を書くことができます。しかし、それはあなたにアイデアを与えるはずです。


67

話したいファイルシステムやオペレーティングシステムはどれでも結構です。いいね!


ZXスペクトラムでは、LOADコマンドを初期化すると、システムがタイトなループになり、オーディオ入力ラインが読み取られます。

データの開始は一定のトーンで示され、その後に長い/短いパルスのシーケンスが続きます。短いパルスはバイナリ用0で、長いパルスはバイナリ用です1https://en.wikipedia.org/ wiki / ZX_Spectrum_software)。緊密なロードループは、バイト(8ビット)がいっぱいになるまでビットを収集し、これをメモリに格納し、メモリポインタを増やし、ループバックしてさらに多くのビットをスキャンします。

通常、ローダーが最初に読み取るのは短い固定形式のヘッダーで、少なくとも予期されるバイト数を示し、ファイル名、ファイルタイプ、ロードアドレスなどの追加情報を示します。この短いヘッダーを読み取った後、プログラムはデータのメインバルクのロードを続行するか、ロードルーチンを終了してユーザーに適切なメッセージを表示するかを決定できます。

End-of-file状態は、予想どおりに多くのバイトを受信することで認識できます(固定数のバイト、ソフトウェアに組み込まれている、またはヘッダーに示されているような可変数)。ロードループが予期された周波数範囲のパルスを一定時間受信しなかった場合、エラーがスローされました。


この回答の背景

説明されている手順では、通常のオーディオテープからデータを読み込みます。したがって、オーディオ入力をスキャンする必要があります(標準のプラグでテープレコーダーに接続されています)。LOADコマンドは、技術的には同じであるopenファイル-それは、物理的に結び付けられています実際にファイルをロードします。これは、テープレコーダーがコンピューターによって制御されておらず、ファイルを(正常に)開くことはできないが、ロードすることができないためです。

「タイトループ」が言及されているのは、(1)CPU、Z80-A(メモリが提供されている場合)が非常に遅いためであり、3.5 MHzであり、(2)スペクトラムに内部クロックがないためです。つまり、すべてのTステート(命令時間)を正確にカウントする必要がありました。シングル。命令。そのループ内では、正確なビープタイミングを維持するためだけに使用します。
幸いなことに、その低いCPU速度には、1枚の紙のサイクル数を計算できるという明確な利点があり、したがって、実際にかかる時間も計算できます。


10
@BillWoodger:はい。しかし、それは公正な質問です(つまり、あなたの質問です)。私は「広すぎる」と締めくくりましたが、私の回答は、質問が実際に非常に広範であることを示しています。
usr2564301 2015年

8
答えを広げすぎていると思います。ZX SpectrumにはOPENコマンドがあり、それはLOADとはまったく異なりました。そして理解するのは難しい。
ロドリゴ2015年

3
質問を閉じることにも同意しませんが、私はあなたの答えが本当に好きです。
Enzo Ferber

23
質問を編集してlinux / windows OSに限定して開いたままにしましたが、この回答は完全に有効で便利です。私の質問で述べたように、私は何かを実装したり、他の人に私の仕事をさせたりするのではなく、学びたいと思っています。学ぶためには「大きな」質問をしなければなりません。「広すぎる」という理由でSOに関する質問を絶えずクローズすると、何を、どこで、なぜ説明することなく、人々にあなたのコードを書いてもらうだけの場所になってしまうリスクがあります。学びに来られる場所として残したい。
jramm

14
この回答は、質問の解釈が広すぎるのではなく、質問の解釈が広すぎることを証明しいるようです。
jwg 2015年

17

ファイルを開いたときに正確に何が起こるかは、オペレーティングシステムによって異なります。以下では、ファイルを開いたときに何が起こるかがわかり、詳細に興味がある場合はソースコードを確認できるため、Linuxで何が起こるかを説明します。この回答が長くなりすぎるため、許可については取り上げません。

Linuxでは、すべてのファイルは、 iノード。各構造には固有の番号があり、すべてのファイルが取得するiノード番号は1つだけです。この構造には、ファイルのメタデータ(ファイルサイズ、ファイルのアクセス許可、タイムスタンプ、ディスクブロックへのポインターなど)が格納されますが、実際のファイル名自体は格納されません。各ファイル(およびディレクトリ)には、検索用のファイル名エントリとiノード番号が含まれています。ファイルを開くと、適切な権限があると想定して、ファイル名に関連付けられた一意のiノード番号を使用してファイル記述子が作成されます。多くのプロセス/アプリケーションが同じファイルを指すことができるので、inodeにはファイルへのリンクの総数を維持するリンクフィールドがあります。ファイルがディレクトリに存在する場合、そのリンクカウントは1です。ハードリンクがある場合、リンクカウントは2になり、ファイルがプロセスによって開かれる場合、リンクカウントは1ずつ増加します。


6
これは実際の質問とどのような関係がありますか?
Bill Woodger、2015年

1
Linuxでファイルを開いたときに低レベルで何が起こるかを説明します。質問がかなり広いので、jrammが求めていた答えではなかったのではないかと思います。
Alex

1
繰り返しますが、権限のチェックはありませんか?
Bill Woodger、2015年

11

主に簿記。これには、「ファイルが存在するか」などのさまざまなチェックが含まれます。「書き込みのためにこのファイルを開く権限はありますか?」

しかし、それはすべてカーネルに関するものです。自分のおもちゃのOSを実装しているのでなければ、掘り下げることはあまりありません(もしそうなら、楽しんでください-それは素晴らしい学習体験です)。もちろん、ファイルを開いているときに受け取る可能性のあるすべてのエラーコードを学習して、適切に処理できるようにする必要があります。

コードレベルで最も重要な部分は、開いているファイルへのハンドルを提供することです。これは、ファイルに対して行う他のすべての操作に使用します。この任意のハンドルの代わりにファイル名を使用できませんか?まあ、確かに-しかし、ハンドルを使用するといくつかの利点があります:

  • システムは、現在開いているすべてのファイルを追跡し、それらが削除されるのを防ぐことができます(たとえば)。
  • 最新のOSはハンドルを中心に構築されています。ハンドルを使用して実行できる便利な機能が数多くあり、さまざまな種類のハンドルはすべてほぼ同じように動作します。たとえば、Windowsファイルハンドルで非同期I / O操作が完了すると、ハンドルが通知されます。これにより、ハンドルが通知されるまでハンドルをブロックするか、操作を完全に非同期で完了することができます。ファイルハンドルの待機は、スレッドハンドル(スレッドの終了時に通知される)、プロセスハンドル(ここでも、プロセスの終了時に通知される)、またはソケット(非同期操作の完了時)の待機とまったく同じです。同様に重要なのは、ハンドルはそれぞれのプロセスによって所有されているため、プロセスが予期せず終了した場合(またはアプリケーションの記述が不十分な場合)、OSは解放できるハンドルを認識しています。
  • ほとんどの操作は定位置です- readファイルの最後の位置からです。ハンドルを使用してファイルの特定の「オープン」を識別することにより、同じファイルへの複数の同時ハンドルを持ち、それぞれがそれぞれの場所から読み取ることができます。ある意味では、ハンドルはファイルへの移動可能なウィンドウとして機能します(そして非同期I / O要求を発行する方法であり、非常に便利です)。
  • ハンドルはファイル名よりはるかに小さいです。ハンドルは通常、ポインターのサイズであり、通常は4バイトまたは8バイトです。一方、ファイル名は数百バイトになることがあります。
  • ハンドルを使用すると、アプリケーションがファイルを開いていても、OS はファイルを移動できます。ファイル名が変更されても、ハンドルは引き続き有効であり、同じファイルを指します。

他にも実行できるいくつかのトリックがあります(たとえば、物理ファイル使用せずにプロセス間でハンドルを共有して通信チャネル作成します。UNIXシステムでは、ファイルはデバイスやその他のさまざまな仮想チャネルにも使用されるため、これは必ずしも必要ではありません)、しかし、それらはopen操作自体に実際には関連付けられていないため、ここでは詳しく説明しません。


7

その中核をなすものを読むために開くとき、空想は実際に起こる必要はない。それが行う必要があるのは、ファイルが存在することを確認し、アプリケーションがそれを読み取り、ファイルに対して読み取りコマンドを発行できるハンドルを作成するための十分な権限を持っていることです。

実際の読み取りがディスパッチされるのは、これらのコマンドです。

多くの場合、OSは、読み取り操作を開始して、ハンドルに関連付けられたバッファーをいっぱいにすることで、読み取りを開始します。その後、実際に読み取りを行うと、ディスクIOで待機する必要がなく、バッファの内容をすぐに返すことができます。

書き込み用に新しいファイルを開くには、OSが新しい(現在は空の)ファイルのディレクトリにエントリを追加する必要があります。そして再び、書き込みコマンドを発行できるハンドルが作成されます。


5

基本的に、openの呼び出しでは、ファイルを検索し、必要なものをすべて記録して、後のI / O操作でファイルを再度検索できるようにする必要があります。それはかなりあいまいですが、すぐに思いつくすべてのオペレーティングシステムに当てはまります。詳細はプラットフォームごとに異なります。すでにここにある多くの答えは、現代のデスクトップオペレーティングシステムについて語っています。CP / Mで少しプログラミングを行ったので、CP / Mでどのように機能するかについての知識を提供します(MS-DOSはおそらく同じように機能しますが、セキュリティ上の理由から、今日は通常このように行われていません)。

CP / Mでは、FCBと呼ばれるものがあります(Cについて述べたように、それをstructと呼ぶことができます。実際には、さまざまなフィールドを含むRAM内の35バイトの連続した領域です)。FCBには、ファイル名を書き込むフィールドと、ディスクドライブを識別する(4ビット)整数があります。次に、カーネルのOpen Fileを呼び出すときに、CPUのレジスタの1つに配置することにより、この構造体へのポインタを渡します。しばらくして、オペレーティングシステムは構造体をわずかに変更して戻ります。このファイルに対して行うI / Oが何であれ、この構造体へのポインタをシステムコールに渡します。

CP / MはこのFCBで何をしますか?特定のフィールドを独自に使用するために予約し、これらを使用してファイルを追跡するため、プログラム内からこれらのフィールドに触れないようにすることをお勧めします。ファイルを開く操作では、ディスクの先頭にあるテーブルを検索して、FCBにあるものと同じ名前のファイルを探します(ワイルドカード文字「?」は任意の文字に一致します)。ファイルが見つかると、ディスク上のファイルの物理的な場所を含むいくつかの情報がFCBにコピーされ、その後のI / O呼び出しで最終的にBIOSが呼び出され、BIOSがこれらの場所をディスクドライバーに渡します。このレベルでは、詳細は異なります。


-7

簡単に言うと、ファイルを開くときは、実際にオペレーティングシステムに、セカンダリストレージから処理するRAMに目的のファイルをロードする(ファイルの内容をコピーする)ように要求しています。そして、その背後にある理由(ファイルのロード)は、Ramに比べて速度が非常に遅いため、ハードディスクから直接ファイルを処理できないためです。

openコマンドはシステムコールを生成し、システムコールはファイルの内容をセカンダリストレージ(Hard disk)からプライマリストレージ(Ram)にコピーします。

また、ファイルの変更された内容をハードディスク内の元のファイルに反映する必要があるため、ファイルを「閉じる」。:)

お役に立てば幸いです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.