Android 10のART仮想マシンについて学ぶ(3)

原点

私は最近家で仕事をしています。自宅での作業は、オフィスでの作業よりも明らかに疲れます。ナンセンス、放浪、ラングリング、トイレへ行くこと、水を汲むことはほとんどありません。私は、ほとんどの人がこのリズムに確実に適応していないと思います。個人的には、本を書いていると1日16時間できるので元気です。唯一の違いは、本を書く場合、この状態は週末に最大2日間であることです。内務省が毎日これをしなければならないという事実に誰も耐えられない。

今月は、JVMの知識を体系的に学びました。Zhou Zhimingの本に加えて、「Java仮想マシンのプログラミング」と「仮想マシンの高度な設計と実装」の2冊の本を読みました。最初の本は主にJavaバイトコードの実行に関するものです。それを読んだ後、バイトコードとその実行は見慣れないものになるはずです(コンジュ)。JVM仕様によれば、JVMはストレージ構造としてスタックを使用するエグゼキュータです。操作中のデータはスタックに格納されます。この本は難しくありません、中低。ポピュラーな科学素材として利用できます。

2冊目の本ははるかに難しく、後ほど難しくなります。私はそれらすべてを読み終えていません、半分以上。この本の難しさの理由は、JVM設計のさまざまな詳細と関連する理論的知識の体系的なレビューであることです。明らかに、JVMの実装を読んでいないと、この本は理解できません。

ARTを書いているときにこの本を読みましたが、最初から最後まで読んでいませんでした。今、最初から最後まで見ると、全然違います。要約すると、Androidソースコードを学習するには2つの方法があります。

  • 理論がメインで、補助がコード分析です。たとえば、私の14歳の「Wi-Fi、NFC、GPS」では、理論のサポートがなければ、コードを理解することは絶対に不可能です。

  • ソースコードを直接分析します。たとえば、第1巻と第2巻の詳細な理解。正直なところ、理論はありません。直接ソースコードの分析に進んでください。理由はソースコードにあります。

私のART研究で使用するのは、ソースコードを直接分析する2つ目のより暴力的な方法です。しかし以前のパブリックアカウントで学んだように、Android 10のART仮想マシン(2)は言った実際、ARTに代表されるJVMは、理論的に強力なバックグラウンドを持っています。したがって、ARTをVolume 1およびVolume 2のように扱うことはできません。

さらに、2冊目のもう1つの啓蒙は、ARTの設計を理解するために特定のコード実装からジャンプする必要があるということです。それで、最近アートをART 10コードで再訪するためにいくつかの絵を描いた。これらの図は、主にいくつかのデータ構造に関連しています。どうしてそれをするの。実際、プログラムは単にデータ構造+処理ロジックです。開発の便宜上、ARTはC ++であまりにも多くのパッケージを実行しましたが、これは特に痛いようです。私が描いた絵は、これらのパッケージをはがして、データに直接向きます。まず、いくつかのコンテンツについてお話します。

HandleScope関連

コンテンツのこの部分を読むときは、ARTコードをある程度理解している必要があることに注意してください次のコードは、ソースコードでよく見られます。

その中で、StackHandleScopeおよびHandleとは何ですか?コードを見ると、煩わしいものになります。ARTパッケージが多すぎます。したがって、そこに含まれるデータを確認するだけです(動作は考慮しません)。

上記はHandleScopeファミリのデータ構造です。

  • BaseHandleScopeとHandleScopeには2つのメンバーが含まれます。link_およびumber_of_reference_

  • FixedSizeHandleScope:storage_配列が含まれます。この配列の要素はStackReferenceを指します。正確には何ですか?また話します

  • StackHandleScope:現在のスレッドオブジェクトを指すもう1つのself_。

データ構造のメンバーを取得するための多数の操作がARTコードに表示されます。特にアセンブリコードでは、一部のデータがデータ構造から取得されることがよくあります。メンバーに対応するオフセットによって取得されます。たとえば、上の図の右側にある2つのオフセットは、number_of_references_とstroke_を取得するために使用されます。

HandleScopeを解決したら、HandleとStackReferenceを見てみましょう。

この写真には:

  • 右上隅のmirror :: Objectオブジェクトは、新しいObjectオブジェクトを表します。新しいのは、このオブジェクトへのポインタです。

  • このポインターを直接ARTに保存することは許可されていないので、ObjectPtrデータ構造を作成しました。このデータ構造では、reference_(uintptr_tタイプ、次のポインターを64ビットでも32ビットでも保存できます)。

  • ARTは直接ObjectPtrを使用せず、代わりにObjectReference(CompressedReference、StackReferenceを含む)を使用します。これら3つのReference構造体にはreference_が1つだけあります(uint32_tタイプ、32ビット長、特別な注意が必要)。

  • ObjectReferenceのReference_は、ObjectPtrのReference_の強制データ型から変換されます。64ビットマシンでは、ポインターも64ビットで、ObjectReferenceの長さは32ビットのみです。情報が失われることはありませんか?確かにこの問題がありますが、それは問題ではありません。失われた32ビットデータはすべて0だからです。これには、64ビットデバイスでのJVM設計の考慮が含まれます。64ビットデバイスでは、割り当てられたオブジェクトのアドレスは64ビット長ですが、これらのオブジェクトアドレスを保存する場合は、64ビット長の変数が必要です。10,000個のオブジェクトを割り当てる場合、これらの10,000個のオブジェクトのアドレスを格納するだけで640,000 / 8バイトが必要になります。これは、32ビットシステムの2倍の記憶域です。したがって、JVMは64ビットデバイスでいくつかの最適化を行いました。実際、32ビットのデータは、この64ビットのポインタを格納するために使用されます。また、この64ビットポインターの上位(または下位)32ビットは0(または他の固定値)です。さらに、32ビットと64ビットとの互換性を保つために、割り当てられるオブジェクトのサイズは8バイトの整数倍でなければなりません。したがって、この設計に基づくJVMは、32GB(8 *(2 << 32))を超えるメモリ空間をサポートできません。

  • 下部はハンドルファミリです。そのreference_はStackReferenceポインターを指します。

これで、コード実行の結果を説明できます。

上図の左上隅のコードを実行すると、次の結果が得られます。

  1. heap-> AllocNonMovableXXはAllocedオブジェクトを取得します。これをプリミティブオブジェクトと呼びます。

  2. ARTはこのプリミティブオブジェクトを直接操作できません(以下で説明します)。したがって、Handleオブジェクトは動作するように設計されています。

  3. このHandleオブジェクトにも起源があり、HandleScopeオブジェクトのstorage_配列の要素を表します(図では、オブジェクト変数の名前はhsです)。さらに、Handleを通じて、このプリミティブオブジェクトを操作できます。このプリミティブオブジェクトを直接操作することはできません。ハンドルとそのファミリを介してのみ操作できます。

毛のためにとても複雑ですか?明らかに、最初のステップですでに元のオブジェクトを取得しているので、それを直接処理する必要があるだけです。なぜHandleを使用してHandleScopeをビルドするのですか?これは、JVMがいわゆるRootオブジェクトをトラバースする必要があるためです。下の写真を見てください:

ARTでは、スレッドオブジェクトにtop_handle_scopeリンクリストがあります。このリンクリストの要素はHandleScopeです。先ほど言ったように、元のオブジェクトのアドレスは実際にはHandleScopeのstorage_配列に格納されています。このようにして、VisitRootsにいるときにJVMによって作成されたオブジェクトを見つけることができます。

まとめると、この分野におけるARTのデザインは次のとおりです。

  • 元のオブジェクトが割り当てられた後、それはHandleScopeに格納されます。そして、このHandleScopeは、スレッド内のリンクリストにリンクされています。

  • ARTはプリミティブオブジェクトの直接操作を許可しないため、ハンドルをカプセル化し、ハンドルを介してプリミティブオブジェクトを操作します。

上記の関連コードには少なくとも数千行あります。データのみに焦点を当てた上記の4つのグラフを理解していれば、関連するデータ構造は非常に明確になります。

x86 / x86_64呼び出し規約は関連していますが
、ART呼び出しスタックの設計を抽象化したときに、上記のアイデアに従ってx86 / x86_64呼び出しスタックを簡単に並べ替えました。x86の呼び出し規約についてお話ししましょう。

この画像を説明するには、まず左上隅を見てください。呼び出し規約は2つの部分で構成されています。1つは呼び出し元のルールで、もう1つは呼び出し先のルールです。呼び出し先のルールは、入口ルール(プロローグ)と出口ルール(エピローグ)に分かれています。いわゆる規則とルールは実際にはルーチンです。良いことを話し合う理由が時々ありません。

呼び出し元規則:x86では、関数呼び出しはこのように記述する必要がある(またはコンパイラーがそのようなコードを生成する必要がある)ことを示す呼び出し元規則。

  1. 1つ目は、呼び出し元が保持するレジスタを保存することです(EAX / ECX / EDX、呼び出し元が保存したレジスタ)。レジスタのこのセクションは、呼び出し側が使用する場合にのみ保存する必要があることに注意してください。

  2. 次に、パラメーターをスタックにプッシュします。最後のパラメーターが最初にプッシュされ、最初のパラメーターが最後にプッシュされます。

  3. 次に、callを介してターゲット関数を呼び出します。目的関数の戻り値はEAXに格納する必要があります。通話後、

  4. 発信者のポップに対応するレジスタ。右側の緑色の矢印はスタックを指します。

呼び出し先ルール:呼び出し先ルール。1つはエントリルールで、次のものが含まれます。

  1. スタックを介してEBPを保存し、ESPの値をEBPに保存します。

  2. ローカル変数に必要なスタックスペースを割り当てます。これは、関数が必要とするスタック領域がコンパイル時に決定できることを示しています。

  3. EBX / EDI / ESIなどの呼び出し先が保存したレジスタを保存します。

ターゲット関数が戻る前に、次のような終了規則が実行されます。

  1. ポップEBX / ED / ESI

  2. ローカル変数に必要なスペースを解放する

  3. EBPを復元する

  4. 正しい

呼び出し先ルールに対応するスタック構造は、右端のスタック図にあります。x86呼び出し規約の設計では、次のシナリオが表示されます。

  • 最初のパラメーターは[EBP + 8]でなければなりません。他の類推

  • 最初のローカル変数は[EBP-4]になければなりません、他の類推

だから、これはルーチンです。これで、code_generator_x86.ccのGenerateFrameEntry / GenerateFrameExitコードに移動して、上記の呼び出し先ルールが機能しているかどうかを確認できますか?

次に、x86_64の呼び出し規約を見てください。

x86_64ビットの呼び出し規約は大きく異なります。呼び出し元のルールの場合:

  • Callerが保持するレジスタは、R10、R11および次の6つのパラメータ転送レジスタです。

  • 最初の6つのパラメーターはレジスターを介して渡され、7番目のパラメーターはスタックを介して渡されます。32ビットでは、すべてスタック転送です。さらに、最初のパラメーターはレジスターRDIを使用する必要があり、2番目のパラメーターはRSI、次にRCX、R8、R9でなければなりません。

  • 戻り値はRAXです。

呼び出し先の規則は次のとおりです。

  • Calleeが保持するレジスタは、RBX / RBP / R12 / R13 / R14 / R15です。

  • パラメータ(7番目以上のパラメータ、それらはスタックに格納されます)またはローカル変数は、[RSP + offset]を通じて取得されます。もちろん、32ビットEBPを使用することもできます。

ここでは、Javaプログラマーがレジスターを理解する方法について簡単に説明します。最初の本の冒頭にも影響を受けました。

  1. まず、レジスターをプログラムのグローバル変数と考えることができます。レジスターには、特殊な目的のものと、汎用的なものがあります。

  2. レジスタの古い値を保存したい場合は、この古い値をスタックに保存する必要があります。使用後、復元したい場合は、スタックからデータをポップしてください。

簡単に言えば、2つのタイプのストレージしかないようです。1つはレジスタで、もう1つはスタックです。データはこの2つにあります。JVMの概念設計にはレジスタはなく、すべてのスタックがあることに注意してください。Android dexバイトコードに対応する概念設計は、すべてのレジスターであり、スタックはありません。ただし、最終的には、これら2つはマシンコードに対応し、スタックとレジスタの混合使用になります。

記事の最後に、JD.comのいくつかの本へのリンクを追加しました。電子版があるようです...

フォローアップの手配

JVMに密接に関連するナレッジシステムの確立に焦点を当てたいと思います。ARTソースコードの基礎により、このパスは機能すると思います。JVMを習得することは非常に必要です。国レベルでは、基盤となるコアテクノロジーへの投資が増えると思います。JVMは、非常に適切なブレークスルーです。

最後の最後

  • 私が期待する結果は、私の友人が私の本、記事、ブログから学んだことや彼らが何をしたかではなく、むしろ、私はあなたの肩を踏みました。

  • 私は学習の問題についての議論を終えました。次のパブリックアカウントは、いくつかの基本的な技術と新しい技術を学び、共有します。あなたの貢献も大歓迎です。しかし、私がパブリックアカウントの「連絡先情報」で述べたように、鄭元傑はおとぎ話の王「ウィズダムティース」に私に感銘を与えた文があります。「私は黙秘する権利がありますが、あなたが言うすべての言葉は私のインスピレーションの源になるかもしれません。」したがって、影響は一方向ではなく、あなたから多くを学んだ可能性があります。

シェノンと彼の友人によるエッセイ集

長押ししてQRコードを識別し、フォローしてください

おすすめ

転載: blog.csdn.net/Innost/article/details/104744436