注:お使いのマシンにメモリマッピングユニット(MMU)があると仮定します。MMUを必要としないLinuxバージョン(µClinux)がありますが、この答えは当てはまりません。
MMUとは何ですか?これはハードウェアであり、プロセッサやメモリコントローラの一部です。共有ライブラリのリンクを理解するには、MMUがどのように機能するかを正確に理解する必要はなく、MMUによって論理メモリアドレス(プログラムで使用されるアドレス)と物理メモリアドレス(メモリバスに実際に存在するアドレス)。メモリはページに分割され、Linuxでは通常4Kのサイズです。4kページでは、論理アドレス0〜4095はページ0、論理アドレス4096〜8191はページ1などです。MMUはそれらをRAMの物理ページにマッピングし、各論理ページは通常0または1物理ページにマッピングできます。特定の物理ページは複数の論理ページに対応できます(これがメモリの共有方法です。複数の論理ページが同じ物理ページに対応します)。これはOSに関係なく適用されることに注意してください。ハードウェアの説明です。
プロセスの切り替え時に、カーネルはMMUページマッピングを変更し、各プロセスが独自のスペースを持つようにします。プロセス1000のアドレス4096は、プロセス1001のアドレス4096と完全に異なる場合があります(通常は異なります)。
ほとんどの場合、アドレスを見ると、それは論理アドレスです。ユーザー空間プログラムは、物理アドレスをほとんど処理しません。
現在、ライブラリをビルドする方法は複数あります。プログラムがfoo()
ライブラリ内の関数を呼び出すとしましょう。CPUは、シンボルや関数呼び出しについては何も知りません。論理アドレスにジャンプし、そこで見つかったコードを実行する方法を知っているだけです。これを行うには、いくつかの方法があります(ライブラリが独自のグローバルデータにアクセスする場合など、同様のことが当てはまります)。
- 呼び出す論理アドレスをハードコーディングできます。これには、ライブラリが常にまったく同じ論理アドレスでロードされる必要があります。2つのライブラリが同じアドレスを必要とする場合、動的リンクは失敗し、プログラムを起動できません。ライブラリには他のライブラリが必要な場合があるため、基本的にはシステム上のすべてのライブラリに一意の論理アドレスが必要です。ただし、動作する場合は非常に高速です。(これはa.outがどのようにしたかであり、事前リンクが行うセットアップの種類です)。
- 偽の論理アドレスをハードコーディングし、ライブラリをロードするときに適切なアドレスで編集するように動的リンカーに指示できます。これには、ライブラリのロードにかなりの時間がかかりますが、その後は非常に高速です。
- インダイレクションのレイヤーを追加できます:ライブラリがロードされる論理アドレスを保持するためにCPUレジスタを使用し、そのレジスタからのオフセットとしてすべてにアクセスします。これにより、各アクセスにパフォーマンスコストがかかります。
少なくとも汎用システムでは、#1を使用する人はほとんどいません。32ビットシステムでは一意の論理アドレスリストを維持することは不可能であり(回避するには十分ではありません)、64ビットシステムでは管理上の悪夢です。ただし、事前リンクの並べ替えは、システムごとにこれを行います。
#2を使用するか#3を使用するかは、ライブラリがGCC -fPIC
(位置独立コード)オプションでビルドされたかどうかによって異なります。#2はなし、#3はあり。一般に、ライブラリはで構築される-fPIC
ため、#3が行われます。
詳細については、Ulrich Drepperの共有ライブラリの記述方法(PDF)を参照してください。
だから、最後に、あなたの質問に答えることができます:
- ライブラリが(ほぼ確実にそうであるように)ビルドされている 場合、
-fPIC
ページの大部分は、ロードするすべてのプロセスでまったく同じです。あなたのプロセスa
とb
同様に異なる論理アドレスでライブラリをロードする可能性がありますが、これらは同じ物理ページを指すようになります:メモリが共有されます。さらに、RAMのデータはディスク上のデータと正確に一致するため、ページフォールトハンドラーが必要とする場合にのみロードできます。
- ライブラリがなし
-fPIC
で構築されている場合、ライブラリのほとんどのページでリンクの編集が必要になり、異なることがわかります。したがって、それらは別々の物理ページでなければなりません(異なるデータが含まれているため)。つまり、共有されていません。ページはディスク上にあるものと一致しないため、ライブラリ全体がロードされても驚かないでしょう。もちろん、その後(スワップファイルで)ディスクにスワップアウトできます。
これは、pmap
ツールを使用して、またはでさまざまなファイルをチェックして直接調べることができます/proc
。たとえば、これはpmap -x
2つの異なる新しく生成されたの(部分的な)出力ですbc
。pmapで表示されるアドレスは、一般的な論理アドレスであることに注意してください。
pmap -x 14739
Address Kbytes RSS Dirty Mode Mapping
00007f81803ac000 244 176 0 r-x-- libreadline.so.6.2
00007f81803e9000 2048 0 0 ----- libreadline.so.6.2
00007f81805e9000 8 8 8 r---- libreadline.so.6.2
00007f81805eb000 24 24 24 rw--- libreadline.so.6.2
pmap -x 17739
Address Kbytes RSS Dirty Mode Mapping
00007f784dc77000 244 176 0 r-x-- libreadline.so.6.2
00007f784dcb4000 2048 0 0 ----- libreadline.so.6.2
00007f784deb4000 8 8 8 r---- libreadline.so.6.2
00007f784deb6000 24 24 24 rw--- libreadline.so.6.2
ライブラリが複数の部分にロードされており、pmap -x
それぞれの詳細が個別に提供されていることがわかります。論理アドレスが2つのプロセス間で異なることに気付くでしょう。それらは同じであると合理的に期待します(同じプログラムが実行され、コンピューターは通常そのように予測できるため)が、意図的にそれらをランダム化するアドレス空間レイアウトランダム化と呼ばれるセキュリティ機能があります。
サイズ(Kバイト)と常駐サイズ(RSS)の違いから、ライブラリセグメント全体がロードされていないことがわかります。最後に、より大きなマッピングの場合、dirtyは0であることがわかります。これは、ディスク上の内容に正確に対応していることを意味します。
で再実行できpmap -XX
ます。実行中のカーネルバージョンに応じて、-XX出力はカーネルバージョンによって異なるため、最初のマッピングにはShared_Clean
176があり、これはに完全に一致していRSS
ます。Shared
メモリとは、物理ページが複数のプロセス間で共有されることを意味し、RSSと一致するため、メモリ内のすべてのライブラリが共有されることを意味します(共有とプライベートの詳細については、以下を参照してください)。
pmap -XX 17739
Address Perm Offset Device Inode Size Rss Pss Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenced Anonymous AnonHugePages Swap KernelPageSize MMUPageSize Locked VmFlagsMapping
7f784dc77000 r-xp 00000000 fd:00 1837043 244 176 19 176 0 0 0 176 0 0 0 4 4 0 rd ex mr mw me sd libreadline.so.6.2
7f784dcb4000 ---p 0003d000 fd:00 1837043 2048 0 0 0 0 0 0 0 0 0 0 4 4 0 mr mw me sd libreadline.so.6.2
7f784deb4000 r--p 0003d000 fd:00 1837043 8 8 8 0 0 0 8 8 8 0 0 4 4 0 rd mr mw me ac sd libreadline.so.6.2
7f784deb6000 rw-p 0003f000 fd:00 1837043 24 24 24 0 0 0 24 24 24 0 0 4 4 0 rd wr mr mw me ac sd libreadline.so.6.2
こちらもご覧ください
-fPIC
使用法は少し前に完全に変更されました)?