- 起動早期の段階で、Couldn’t allocate runtime areaエラーが出た場合の対処法が、BootmacOSさんのページにあります。
カーネル読み込みメモリ空間を確保する (OpenCore編)
OpenCore編とありますが、いまのCloverも同じOcQuirksになったので、設定は同じです。
ひとまず設定方法はこれでOKなんですが、設定方法の一部である、
「DevirtualiseMmioをtrueにする」
について、私の拙い知識で説明を試みました。
ここからです。
ますデバイスとは、USBなどでつながった周辺機器ですが、「パソコンの「中」にある機器にもデバイスと呼ばれるものもあります。たとえばCPU、メインメモリ、グラフィックメモリ(VRAM)、CD・DVD・Blu-ray DISCへの読み書きに用いるディスクドライブなど」も入ります。
参考「今さら聞けない。「デバイス」ってなに!?」
見える形で感じるところは、Windowsのデバイスマネージャーです。
誤解を恐れずに言うと、この画像にある、全部がデバイスです。
そして、先に言ってしまうと、そのデバイスの根元がプログラムに、メモリとして見える部分(MMIO)ということです。たとえば、USBであればつながっているのはほとんどの場合チップセットのホストコントローラーです。
上の画像で言うところの、Intel(R)USB3.1eXtensive Host Controller-1.10(Microsoft)がそれです。
メモリはDDRなどの記憶装置です。それと同じ命令で読み書きできる「デバイスのプログラムに見える部分(入出力装置)」=IOが、つまりMMIO、メモリマップドIOです。
同じIntel(R)USB3.1eXtensive Host Controller-1.10(Microsoft)のプロパティを見てみましょう。
リソースの種類にメモリの範囲と書いてあり、横に長い16進数があります。この長い16進数が仮想メモリアドレス(読み書きできる住所)で、ここにプログラムからアクセスできるということです。ちなみに何度か起動すると判りますが、この値はいつも変化しています。(ウイルス等への対策だと思います)
ただ、この仮想アドレスは、OSがIO(参考ではPCI)のベースアドレス(最初のアドレス)を決め、IOの要求によって、そこから大きさを割り当てているものです。OSが立ち上がる前段階(BIOSが割り当てた時)とは違っています。
参考:7. PCI
つぎにバーチャルメモリ、仮想メモリとその動作について、誤解を恐れないで簡単に書きます。
仮想化は歴史的に言うと、みんなが物理(本物のDDRとかの)メモリを使いたいけど、物理メモリは高くて少量しか使えないので、仮想的(絵空事で)に広大なメモリ地図をみんなには見せておいて、実際には物理メモリに置く置き場所への変換や、物理メモリ上でいらない部分はディスクにいったん退避してもらうことで、物理メモリを効率よく使って、仮想メモリ=みんなで大きなメモリを自由にうまく使えるようにしようということから始まります。
A.仮想記憶
1.スワッピング・・・置換。何らかのアルゴリズム(計算の手順・やり方)で、あまり使わない(実行しない)プログラムの一部分を、より遅くて安い記憶装置(例えばHDD等)に記憶させてしまい。もっと頻繁に動く部分のプログラムを残しておくこと。あまり実行しない部分はよけて置けるので、今今実行しないといけないプログラムの部分を物理メモリに残します。
こういう方法で、お高い物理メモリに頻繁な部分のみを入れておくことで、効率的に物理メモリをみんなで効率用使おうという方法です。
頻繁でないところを後になって使うときには、(やり方は色々あるようですが)単純に言えば実行するときにHDD等から取り出して、実行すればいいのです。
2.アドレス(マップ上の置き場所の)変換・・・上のようにHDDにもプログラムを置けるので、みんなで使う大きな(物理メモリを超えた)仮想的なメモリマップ(仮想メモリマップ)を作ります。そして、仮想メモリのどこ(アドレスつまり住所)が、物理メモリ(実際のメモリ)のマップのどこ(同じくアドレスつまり住所)に置いたかを記しておけば、実行したりデータを読み書きすることができます。(そして、仮想アドレスだけ皆に見せておけば、皆は面倒な共有を意識することなくうまく使えます。)
この仮想メモリアドレスを、物理メモリアドレスに変換するということが、アドレス変換です。
さらに、物理メモリからスワップしまった部分(1でスワップした部分)はHDD等のどこにあるかを記録しておきます。ここでのアドレス変換で物理メモリへの変換結果がないとわかった段階で、この記録を見てHDD等から取り出してくればいいのです。(実際の方法は色々あるようです)
大事なことは、物理アドレスのどこにおいてあっても、仮想アドレス(みんなから見た見た目)から見ると同じアドレスに見えるということです。
参考:「【図解】仮想記憶(仮想メモリ)の本質や仕組み、メリット 〜スワップ、MMU、ページングテーブルについて〜」
参考:オペレーティングシステム 加藤真平 東京大学大学院情報理工学系研究科
B.メモリ管理
1.リロケータブル・・・再配置可能。プログラムは物理メモリ主のどの領域に配置しても実行できるようにします。置き場所を選ばないでおければ後で片付け(コンパクション)ができるので、これも効率的にメモリを使う方法に必要なこととなります。
(そもそも、プログラムをそうできるように作っておくか、(起動でもスワップでも)読み込み時に、プログラム内のアドレスを書き換えて読み込めば、どこにおいても良くなります)
2.リエントラント・・・再入可能、同じプログラムを動かすときに、同じプログラムを物理メモリにおいて効率的でしょうか?プログラムの作り方として、1つの物理メモリに置いておいて、実行をもう一度してもデーターがこんがらがらないようになっていれば、同じプログラムは1つだけ物理メモリに置けばいいのです。効率よく物理メモリを使う1つの方法です。
3.コンパクション
プログラムをたくさんいろいろ起動した後に、そのプログラムが役割を終えると、そこがいらなくなります。そしてそこが物理メモリの再利用可能部分(空きメモリ)になります。このようにやってくと、飛び飛びになった空きメモリ領域は小さくなってて、まとまったプログラムの大きさでは使えないことが多くなりがちです。
そこで、1.リロケータブルでの「プログラムが、物理メモリのどこにおいてもいい(実行できる)」という性質を使って、物理メモリ上に、きっちり(とはいえ後でうまく動くように余裕は持って)詰め替えるのです。
HDDで言うデフラグメントと同じです。空き(利用できる)領域がフラグメントする(断片化する)のでコンパクションをして領域を確保するのです。(いわばお掃除で、メモリクリーナーはこれをするプログラムです。)
そして、A.仮想記憶の2.アドレス変換がありしたから、仮想記憶のアドレスを、物理メモリのアドレスに変換できます。
コンパクションを行って物理メモリのプログラムの位置に変更があっても、仮想メモリでは、相互のアドレスを変換するところ(上で言った表(テーブル)のです)をいじくっておけいいのです。そうすると、前とみんなから見た目=仮想アドレスは同じなので、位置が変わったとは感じずに済むのです。こうして、仮想メモリアドレスを変換して物理メモリアドレスを実行できるので、実行プログラムとデータの管理(プロセス管理)がうまくいきます。
※)つまり、逆に言うと、仮想メモリでは、「スワップ」ばかり目が行きがちですが、仮想メモリで仮想と物理アドレスの変換ができるので、「仮想アドレスが変わらないようにうまく物理メモリ上でコンパクション(お掃除で移動)できる」というわけです。(これでおおむね正しければいいのですが、もっといい説明方法を誰かご存知方がいれば教えて下さい)
参考:「明星大学 2011年度講義資料 オペレーティングシステム 主記憶管理(メモリ管理)」
こうやって、高いメモリをみんなで有効に使いましょうというのが、仮想メモリとメモリ管理です。
これは、ミニコンをみんなで使う時代の頃始まったことです。今は受け継がれ、1人の人がたくさんのプログラムを動かすときにこの考え方が利用できるので、これを使っているのです。
以上が論理的概論です。
次に実際の話へもっていきます。
実際のメモリのアドレスバスやデータバス、IO用の各バス(IOポートもメモリマップドIOも)や、PCIexブリッジは、チップセット内で全く別に扱われています。
参考:BTOパソコン ミニ館 チップセット比較
x86モード、AMD64モードなどで、プログラムから見た目の物理メモリ、簡単な仮想メモリ管理(特権的なメモリアクセスへの割込み管理で特別なプログラムを動かすなど)があるだけで、ほかの一切の仮想メモリ処理はカーネル等の基本プログラムが行います。(ここでの実体としての仮想メモリの扱いは構造体やクラス~インスタンスです。)
参考:MMUはメモリマップドレジスタをどのように処理しますか?
4. メモリ管理
ただ、メモリ割り当ては、最適化アルゴリズム(Linuxの場合)等にそってOSが行いますが、それでも直線的に割り当てるため、コンパクションは必要です。(下に書いたQ&Aを参考にしてください)
MMIOは、物理メモリマップ内では存在しますが、ほとんど全てのOSで仮想メモリマップには基本的に乗せません(デバイスはカーネルを通して使うものであってアプリケーションには必要ないからです)。
実際にカーネル側からアクセスするドライバですら、(Linuxの場合)仮想アドレスに登録した後でアクセスします。(MACやWindowsの場合はもっと抽象的です。)
参考:メモリマップドI/O MMIO(Memory Mapped I/O)
このように、仮想メモリマップで、IOを扱うこと自体ありえません。
但し、実際には例外があります。それは、EFI内のプログラム(*.efi)が動く環境で、OpenCoreやCloverが動くときです。
この、UEFI基本ソフト(ライブラリ)配下での、仮想メモリは、Identity Mapped Pagingというのが基本なのです。下の参考では「これはページング機構は有効になっているけど、そのまま仮想メモリアドレス=物理メモアドレスとして扱えるようにページテーブルが設定された状態になっています。」とあります。
参考:最小ステップで作る UEFI OS v0.1
この意味は、仮想アドレス自体が、何もいじらないと、AMD64(x64)モードの物理アドレスと同じマップになっているということです。つまり、仮想メモリアドレスで、直接MMIOにアクセスできます
Hackintoshでお馴染みなのは、以下のようなUEFIシェルでのmemmapでしょう。
Type Start End # Pages Attributes
RT_Data 0000000000000000-0000000000000FFF 0000000000000001 800000000000000F
Available 0000000000001000-000000000008FFFF 000000000000008F 000000000000000F
RT_Code 0000000000090000-0000000000090FFF 0000000000000001 800000000000000F
Available 0000000000091000-000000000009EFFF 000000000000000E 000000000000000F
Reserved 000000000009F000-000000000009FFFF 0000000000000001 000000000000000F
Available 0000000000100000-000000005B72DFFF 000000000005B62E 000000000000000F
BS_Data 000000005B72E000-000000005B76DFFF 0000000000000040 000000000000000F
Available 000000005B76E000-000000006244FFFF 0000000000006CE2 000000000000000F
LoaderCode 0000000062450000-000000006254EFFF 00000000000000FF 000000000000000F
LoaderData 000000006254F000-0000000069587FFF 0000000000007039 000000000000000F
BS_Data 0000000069588000-0000000069889FFF 0000000000000302 000000000000000F
BS_Code 000000006988A000-00000000698A4FFF 000000000000001B 000000000000000F
BS_Data 00000000698A5000-000000006B656FFF 0000000000001DB2 000000000000000F
BS_Code 000000006B657000-000000006B669FFF 0000000000000013 000000000000000F
BS_Data 000000006B66A000-000000006B68EFFF 0000000000000025 000000000000000F
BS_Code 000000006B68F000-000000006B6A5FFF 0000000000000017 000000000000000F
BS_Data 000000006B6A6000-000000006B6CDFFF 0000000000000028 000000000000000F
BS_Code 000000006B6CE000-000000006B6FCFFF 000000000000002F 000000000000000F
BS_Data 000000006B6FD000-000000006B715FFF 0000000000000019 000000000000000F
BS_Code 000000006B716000-000000006B72DFFF 0000000000000018 000000000000000F
LoaderData 000000006B72E000-000000006B73CFFF 000000000000000F 000000000000000F
BS_Data 000000006B73D000-000000007773CFFF 000000000000C000 000000000000000F
Available 000000007773D000-0000000077893FFF 0000000000000157 000000000000000F
BS_Code 0000000077894000-00000000786DAFFF 0000000000000E47 000000000000000F
Reserved 00000000786DB000-000000007A2EDFFF 0000000000001C13 000000000000000F
ACPI_Recl 000000007A2EE000-000000007A36BFFF 000000000000007E 000000000000000F
ACPI_NVS 000000007A36C000-000000007A431FFF 00000000000000C6 000000000000000F
RT_Data 000000007A432000-000000007AB52FFF 0000000000000721 800000000000000F
RT_Code 000000007AB53000-000000007AC0EFFF 00000000000000BC 800000000000000F
BS_Data 000000007AC0F000-000000007AC0FFFF 0000000000000001 000000000000000F
Available 0000000100000000-000000047DFFFFFF 000000000037E000 000000000000000F
Reserved 00000000000A0000-00000000000FFFFF 0000000000000060 0000000000000000
Reserved 000000007AC10000-000000007AEFFFFF 00000000000002F0 000000000000000F
Reserved 000000007AF00000-000000007AFFFFFF 0000000000000100 070000000000000F
Reserved 000000007B000000-000000007FFFFFFF 0000000000005000 0000000000000000
MMIO 00000000E0000000-00000000EFFFFFFF 0000000000010000 800000000000100D
MMIO 00000000FE000000-00000000FE010FFF 0000000000000011 8000000000000001
MMIO 00000000FEC00000-00000000FEC00FFF 0000000000000001 800000000000100D
MMIO 00000000FED00000-00000000FED03FFF 0000000000000004 800000000000100D
MMIO 00000000FEE00000-00000000FEE00FFF 0000000000000001 8000000000000001
MMIO 00000000FF000000-00000000FFFFFFFF 0000000000001000 800000000000100D
Reserved : 28,772 Pages (117,850,112 Bytes)
LoaderCode: 255 Pages (1,044,480 Bytes)
LoaderData: 28,744 Pages (117,735,424 Bytes)
BS_Code : 3,795 Pages (15,544,320 Bytes)
BS_Data : 57,691 Pages (236,302,336 Bytes)
RT_Code : 189 Pages (774,144 Bytes)
RT_Data : 1,826 Pages (7,479,296 Bytes)
ACPI_Recl : 126 Pages (516,096 Bytes)
ACPI_NVS : 198 Pages (811,008 Bytes)
MMIO : 69,655 Pages (285,306,880 Bytes)
MMIO_Port : 0 Pages (0 Bytes)
PalCode : 0 Pages (0 Bytes)
Available : 4,064,516 Pages (16,648,257,536 Bytes)
Persistent: 0 Pages (0 Bytes)
--------------
Total Memory: 16,239 MB (17,028,464,640 Bytes)
ここで、MMIOは0xE0000000から、0xFFFFFFFFまでを占めています。実はこれはすでに、PCIexのコンフィグレーション(アドレス配置)が決まった後の姿です。詳しくは(補筆1に書きます。)
この部分はまだ、UEFI の環境上での物理アドレス=仮想アドレスの状態です。
MMIODevirtualizeをTrueにしても、ここのマップが変わらないのは、UEFI下では仮想MMIO必要で、それを利用してUEFIシェルが動いているからでしょう(後のWhite Listの通り、UEFIのもとで動くプログラムでは、仮想MMIOが必要だからです。)。
思うに、カーネルを読み込む前に仮想化を解いて対応しているのだと思います。(この辺がわかる方がいれば、教えてください。)
では、仮想化MMIO(Virtualization MMIO)に戻ります。
○仮想化MMIOはプログラムと違って、普通にはスワップはできなし、コンパクション(メモリマップ上でのリアロケーション(再配置)=これがFrizとの英語のメッセージの意味です)も難しくほとんど全くといいほど使えないし使われてない(移動できないのはIOにこちらからアドレスをわりあてるものだからです。難しいのは補筆1をご覧ください)。その仮想化をするのに仮想化MMIOはメモリを食っている(隠している)ので、仮想化をやめてそれを使えるようにしてる。そして、カーネル読み込みに使えれば目的達成ということです。
ちなみに、「仮想化MMIO」とここで言ういいかたは、仮想化だとスワップとまず思うから不思議に聞こえるだけです。スワップだけでなく、「メモリ変換」によって、メモリの再配置(この場合隠れたメモリを再割当する)ができるから「仮想化MMIO」と言っているのです。
非仮想化(Devirtualization)を選んで、そのためにMMIOが使ってる物理メモリが隠れているメモリに置き換わって、カーネルを読み込める連続領域ができるかも知れないというわけです。
このあと、どうしてOSのドライバが困らないかというと、まずOS自体は、ここでの仮想アドレスでなく、物理アドレスから定義をもらって、アクセスして(または、PCIから再コンフィグレーションして(補筆1参照))いるからです。ドライバは、先ほど言ったようにLinuxの場合は再仮想アドレスの付与をOSから受けて(MACやWindowsの場合はもっと抽象的です。)あつかいます。
○ただ、一部のAMDのCPUなどでは、ここでの仮想化をしておかないと動かないものがあるのでWhite Listで残せるようにしたということです。
実際のWhiteListの例では、
Item 1
Address:4275159040 ;=0xFED1C000
Comment:Haswell: SB_RCBA is a 0x4 page memory region, containing SPI_BASE at 0x3800 (SPI_BASE_ADDRESS)
Enabled:true
Item 2
Address:4278190080 ;=0xFF000000
Comment;Generic: PCI root is a 0x1000 page memory region used by some firmwares
Enabled:true
以上のように、MMIOを残すように、指定しています。
[Q&A]
Q1.
仮想メモリと物理メモリの変換を使えば、ハードウェア的にテーブルを使って、物理メモリの利用されていない断片化した部分を、仮想メモリ上で断片化なく置き換えることができます。
だとすれば、コンパクションをする意味はなく、割り当てのまま仮想ベースで断片化なく使えばいいのではないですか?
A1.
コンパクションは、物理メモリに連続化した空き領域を作るためにあります。物理メモリに断片化があることが、問題だからできたのです。
参考:DDRコントローラの選択術
この参考の、2ページ目表1を見てください。ここから解るのは、ランダムアクセス(完全に断片化した場合)が20%あったら、データ転送効率は30%まで下がるのです。つまり、想像しかできませんが、プログラムが3倍以上遅くなるんです。これは困りものです。
ここでは、連想記憶法を使ったDDRの転送速度を調べていて、ランダムアクセスでも80%くらいの転送率もでると言っています。このくらいなら我慢できるかもしれません。
でも、ここで言いたいのは、メモリ(D-RAM)の物理的構造上、連続したデータを送るように高速化していっているということです。
初めに、D-RAMのメモリのセル(1ビットを電気的に記憶するところ)について考えましょう。効率よくたくさんのセルを操るにはどうしたらいいでしょう?
そう考えると配置は、どうしても格子状になります。世の中3次元でシリコンチップに回路を作るには面でないといけないからです。この格子状の並びの指定の仕方は、ロウ(行)とカラム(列)を使えばいい事になります。
初めに、まず原始的なD-RAMは、あるアドレス(物理メモリの住所にあたるもの)が来た時にアドレスバスを2分割します。原始的ですが本当に簡単に線の集まり(バス)を2つにマルチプレクサという電子的スイッチを使って、切り替えてばらすんです。
分割した1つ目のバスでロウの位置(ロウアドレス)を決め、2つ目のバスでカラム(カラムアドレス)を決めて、データ1ビットが出ます。(エクセルの表の考えと同じです。)
やり方は同じなので、D-RAMを並べれば何ビットでも行けます。(だからと言って、数を増やして高速化するのは、命令長(32ビット、64ビット)ごとにCPUが動いていますから、それ以上増やしたって、CPUの方で処理不能なので意味がありません。DDR4ならスロットに2本差して64ビットでちょうどいいアクセスになるのです。)
さて、ここで、より高速化するには、どうすればいいのかですが、格子状に並んでいるのですから、ロウ位置(ロウアドレス)が決まったときに一気に相当のビット数(カラムの個数)のデータが出てるはずです。これを選択してカラム位置(カラムアドレス)の1ビットを出しています。
言い換えれば、ロウを決めたときに一気に相当のビット数が(カラムデータとして)出ているのに、それをカラムアドレスで絞っているわけです。ですから、ロウアドレスが決まったときに、用意できているデータ数が(カラムの数分)いっぱいあるのにうまく使ってないのです。
そこで、DDRでは、一度アドレスを入れたら、特定のカラムとロウが決まりますが、ロウをそのままにして、用意されたカラム分のたくさんのデータをもとに、(その後のアドレス(次に読む+1したアドレス)をチップに入れなくても)、クロックという(その名の通り時計みたいに一定の周波数で上下する信号)の電圧の上り下りの2回を(この2回がDDR=Double Data RateのD=Doubleのことです)めがけて、カラムアドレスをチップ内部で1つ進めてデータを出します。これをDDR2では4ワード(32x4ビット)、DDR3からは8ワード(32×8ビット)だします。このモードをバーストモードと言います。
こうやって、アドレスを一回だけ入れることで、そこから続く連続したアドレスのデータを高速に読み取ることができるのです。
上では今主流のDDRのバーストモードについて説明しました。これができるのはなぜかと言えば、大本の理由はメモリのセル(1ビットを記憶するところ)の配置が格子状に並んでいるからなのです。
格子状に並べるしかないのです。シリコンウエハーが面だからです。だから、早くしようとして、自動的に次々に送ったしても、連続したデータになっちゃうのです。
もっというと、チップを作るのには2次元しかないので格子状に並べるから、連続したデータしか出せないのでその方法で高速化したのです。
話をを戻しましょう。
上の理由で、メモリ(D-RAM)の物理的構造上、連続したデータを送るように高速化していっているということです。
もっと戻ると・・・
DDRは物理的構造上、断片化してデータを得るのは、とても苦手な機構であらざるを得ないのです。(世の中が3次元でできているから、チップは2次元で構成しないと回路が組めないので)
というわけで、より低速に動くようにならないために、コンパクションして、連続化した領域を作る必要があるのです。
(※ここでは、一番問題となるであろうDDRから説明しましたが、キャッシュやパイプライン等の高速化技術も、プログラクがまっすぐ進む(断片化しない)ことを前提としています。)
(補筆1)
〇PCI 構成スペースアクセスメカニズム#1
2つの32ビットI / Oロケーション(IOポートアドレス)が使用されます。最初のロケーション(0xCF8)はCONFIG_ADDRESSと呼ばれ、2番目のロケーション(0xCFC)はCONFIG_DATAと呼ばれます。CONFIG_ADDRESSは、アクセスに必要な構成アドレスを指定し、CONFIG_DATAへのアクセスは実際にそのアドレスで指定されたレジスタとの間でデータを読み書きします。
※この2つのIOアドレスを使って、PCI構成スペース(Configration space 256-byte)にアクセスできる。
※ここから、各ヘッダータイプの、構成スペース(256-byte Configuration space.)にアクセスして、PCIデバイスの構成(メモリマップ)を決めることができる。
※この方法で、PCI-exの構成スペース(4K-Byte Configration space)にアクセスすると、(レガシー機構なので)互換性領域(最初の 256 バイト)にしかアクセスできない。そのため、PCI-exのメモリマップを配置できない。
※PCIexでは、ECAMと呼ばれる領域で、PCI-exの構成スペース(Configration space)にアクセスする。
CPUのIMC(バス0、dev 0、fun 0=IOポートアドレスのベース)のPCI設定空間からレジスタPCIEXBAR(IOポートのオフセット0x60)を読むことでECAMのベースアドレスを見つけることができます(反対に書くことで指定もできます)。
※この操作でECAMのメモリアドレスが決まり、PCIexの4KiBの構成スペース(Configration Space=MMIOのアドレスを決めるところ)を得ることができ、MMIOのアドレスを決めることができる。
上をまとめると、CPU I/Oポートの0xCF8(固定)と0xCFC(固定)を使ってPCIデバイスのコンフィギュレーション空間にアクセスし、PCI互換レジスタを設定する(この方法はチップセット実装に依存する)。チップセット(ホストブリッジ)のPCIEXBARレジスタにコンフィグレーションレジスタをマッピングするメモリアドレス等を設定する(参考ページでは、今動いているosとの整合があるため、読む方にとどまっている)。これにより、次のメモリ・マップト・コンフィグレーション・アクセスが可能になる。PCIeではコンフィギュレーションレジスタにアクセスするためにメモリ・マップト・コンフィグレーション・アクセス、英語表記ではMemory Mapped Configuration (MMCFG,MMCONFIG)を使う。
参考:Intel CPU の Host Bridge/DRAM Registers 内にある PCIEXBAR 調査メモ
こうやって、PCIの時はIOポートだけでMMIOのアドレスを(256-byte Configuration spaceの集まりで)決めることができていたが、PCIexになると、メモリ上にある、ECAM領域(4K-Byte Configration spaceの集まり)を設定してMMIOアドレスを決めることになる。
memmapで見ている、MMIOはそれをすでに、設定した後の姿である。
コメント