LoginSignup
3
1

More than 3 years have passed since last update.

OSのメモリ管理について勉強したことをまとめる

Last updated at Posted at 2020-12-30

はじめに

私はUniversity of the People という無償のonline大学(アメリカ)に行ってます。授業はすべて英語ですがレベルはかなり低く、たぶん40万くらいでアメリカのBachelor of Degreesという学士がもらえます。それが何の役に立つかはその人次第ですが、自分はただ「大学を卒業したと言いたい」ためにやってます。お金があればもっといい大学を選びましたが、当時手取り17万でその選択肢はありませんでした。

さて、そのCS(computer science)でOSの授業を取りました。それが難しく面白かったので、忘れないようにここにまとめたいと思います。9週間のクラスで、明日から8週目。テキストを読むのが追いつかなくて半分以上ジャーナル出せていないので、理解度は推して知るべしです。わかっていることわからないことを整理するためにこの記事を書きます。

教科書

メモリはChapter 12-24. 思ったより濃くてびっくりしました。

わかっていないこと

  • CPUの話が混ざってくるため、OS, CPU(MMU/TLB), RAM, Swapの垣根がよくわからなくなっている。
  • どうしても普段使うLinuxに紐づけて考えたいので、pseudoな理想論が混ざってよくわからなくなっている

はじまり

Chapter 12より前のお話

序盤はCPUの話がメイン。その中で重要なことは
- すべてのinstruction(CPUへの命令、計算内容、つまりアプリのコード)はメモリに入れる必要がある
- physical memoryを直接扱わず仮想メモリとして扱う。(ざっくりいうと)OSから見てハードウェアはすべて仮想的なフィルタを通して見る。ハードウェアはすべてVM扱い。linuxのkernel parameterに vm_ という名前が多いのはそのせい。ESXi/KVMのVMじゃなくて、ハードウェアがマシーンで、それをヴァーチャルにしたからVM. この授業でVMというとすべてそっちの話し。個人的にはvirtualなH/WということでVHWと呼びたい。weeek 1-2あたりでその2つのVM、contextをはっきりさせる課題が出された。うまい。なぜvmにする必要があるのかも教科書の前半にあるので、興味があればぜひ。

ここらへんの話とか面白いです。

Chapter 13 Address Spaces code

OSがメモリ管理すると楽だよね、という話。開発者が他のprocessが使ってるメモリ領域を気にするなんてやってられない、だからやるんだよ。
address spaceはstackを前に、heapを後ろに配置する。真ん中は未使用領域で、中心に向かって領域を広げて使っていく。この違いはいまいちよくわかってない。

物理メモリを直接扱わず仮想メモリ(diskに置くRAMのことじゃなくて、RAM自体を仮想的に見るコンテキスト)にするのは、例えばメモリ1Gしかなくても2G以上のアプリ動かしたいじゃん。だったら仮想メモリ使うしかないよね、的な説明。

そのとき、物理メモリと仮想メモリのアドレスは違っちゃうよね。この差異がすべての始まり。

ゴールは3つ
1. 開発者(process)にメモリのことを気にさせない
2. 効率的に管理する(例えば4Gのメモリ管理するのに4Gの変換テーブル持ってたらそれで終わっちゃうよね)
3. セキュアにする(Aが使ってる領域をBに消されたらやばいよね的な)

Chapter 14 Memory API

stackは C-langで int x; としたときに automaticに 確保される領域。

Cのmalloc(), free()の話。

intでもdoubleでも適切な領域を確保する。free()はリリースする。
processが死ねば、領域はすべて自動的に開放される。これもOSの役目。

そこらのOSに「メモリちょうだい」とか頼む関数を API と呼ぶ。
OSが提供するAPI、という意味。

Chapter 15 Address Translation

ここから混乱が始まる。物理アドレスと仮想アドレスの変換話。「論理アドレス」という言葉は曖昧なので自分は使わないし、教科書にもほとんど出てこなかった気がする。

アセンブリが出てくるので辛いが、その前のclass(名前忘れた、CS1105的なのだったかな)で触ってるのでなんとなく読んでいく。
アセンブリが何をしてるかって言うと、CPU - レジスタ - メモリの間のバケツリレーを表している。aをbにcopyしたい場合、aをレジスタに入れる。レジスタをbに入れる、ということをしていく。そのレジスタが eaxだったりebxという名前だったりする。たぶん。

OSは起動時にpage tableを作る。ここで物理メモリの容量に合ったテーブルができるので、OS起動中のメモリ容量変更ができない理由なんだと思う。そこでinitializeがされる。kernelの大事な領域(trap, system call handler etc)が確保される(これは他の章だったかな)

興味深かったのは役割の橋渡しフロー図で、ハードウェアがOSとprogramの間にいること。hw, os, programかと思ったらos, hw, programなのね。
OSが命令し、CPUが演算し、結果をprogramに返す。programが必要に応じてsystem callでOS経由の命令を出す。

context switchをするためにOSがtimerを使ってprocessのstop/restartをする。そのたびにCPUのレジスタはprocess固有の内容に入れ替えられる。

internal flagmentation

Linuxではpage sizeが4Kに固定されている。1byteのデータがほしくても4KB使ってしまう。このムダをinternal flagmentationと呼ぶ。これ、後々度々出てくる。

Chapter 16 Segmentation

segmentation fault よく見るよね、あれは正しくないアドレスが指定された時に出る。例えばstart:0 size:8 の領域がある時にstart:5のアクセスが来ると、それはデータの中ではあるけど、スタート地点として正しくないのでfaultされる、的な。たぶん。このstart地点のことをoffsetと呼ぶ。たぶん。

各メモリ領域にはメタデータ的なエリアがあり、protection bitなどがある。r/w権限のチェックができたりする。

process Aは1G使う。 B は2G使う。このとき、メモリの分割最小単位は何にするか?という話。ここらへん混乱して何度も教科書読んだ。

結局、この後出てくるpagingと比較するとわかりやすい

Segmentaion: 動的にpage sizeを変える
Paging: 固定サイズ(Linuxなら4K)にする

最初はsegmentationをpagingの中にある1機能的な捉え方をしてたので超混乱した。

どちらにもPros and Consはあるが、segmentationはオーバーヘッドが大きくPagingが主流みたい。
アドレスのスタート地点がバラバラだと、頭からnodeを追っていかないといけなかったり、後述するpaging / buddy systemの2本木構造(高速fetch)ができなかったりする。
ただ必要な分しか確保しないから、internal flagmentionが起きないのは利点だよね。
でもその代償として external flagmentationが起こる。例えば1K, 3K, 2Kの領域がポツポツと飛び飛びに空いたとしよう。並べて6Kの領域にしたいけど、もうどこにも連続した空きがない場合、じゃあ、1K使ってるところを入れ替えさせてもらおう的な・・・1Kのやつどこにいる? 結果、全部が入る場所を探すのは大変だ。

ん?使ってない場所を探すのってどうやるんだ?→次の章へ

Chapter 17 Free Space Management

この章は面白かった。未使用領域をOSはどうmanagementするのか? なるほど言われてみると、そうか、使っていないところを認識するには使っていないという知識を 使う 必要があるのだ。考えたことがなかった。

Slab allocatorやBuddy allocator(Linuxで使われている。 cat /proc/buddy* してみて)の話が出てくる。

malloc(サイズ) で容量を指定するが、free(変数名)だけでサイズは不要。これはアロケータが容量をテーブルに持っているからできる。

1Mの領域くれと言われ、複数の領域が空いている時allocatorはどうやってそれを選ぶ? best hit = 全部なめて、最小限で済む領域を返す。 worst fit = いっちゃんでかい領域を使う。それなら入りやすそうだね!って、非効率的だからworstなのね。First hit = 最初に見つけた十分な領域を使う。とかまあいろいろ。

Buddy allocator
2のn乗の二本探索木で空き領域を記録。64byteメモリだったらその下に32byte * 2 がぶら下がる。片方の32Bがusedで、requestが32byteだったら、もう片方の32byteに賭けるしかない、的な判断がしやすい。

buddyもslab allocatorもそうだけど、問題はscalingに弱いこと。途中で物理メモリが増減したら、buddyなら二本木が破綻しちゃうね。

glibc allocatorがreal worldだとの紹介だけあるが、そこまでまだresearchできてない。

とにかくアロケータは複雑で色んなものがあって、すげーんだぜ。

Chapter 18 Introduction to Paging

pagingの話。固定サイズの領域だと高速にアドレス変換しやすいね。

メモリアドレスの変換計算がまあ苦手だった。0x011234 はVPN(Virtual page number) と offset に分けられる。 VPN=01 offset=1234
変換テーブルには VPNの変換がある。例えば 01が仮想アドレスのとある丁目で、それは物理の32丁目に対応するてきな。offsetは番地で、ここは変換されない。仮想アドレスだと1丁目1234番地が、物理では32丁目1234番地になる、的な。


Linuxのpage sizeは4K固定。
メモリが128Kなら、page数は 128/4 = 32pages必要ということになる。
32をbinaryで表すと(2進数にすると) 2**5 = 5bit 必要。要求されたアドレスの上位5bitをアドレス変換し、残りはoffsetとしてそのまま物理アドレスへGO.

ここでいくつか追加の判定用bitが紹介される。

valid bit: 未使用領域はinvalidになってるので、想定と違うとOSにtrapを投げる。OSはprocessを殺す、的な。

dirty bit : 書き換えがあると立つ。後の Chapter: page replacementで出てくるが無駄なpagingを防ぐなどの用途に使う

どこの章か忘れたのでここに書くけど、protection bitは大事。
他のprocessなら読めない。kernelのなら読めない。2018年(だっけ)のspecture/heart breed 問題で、kernelとuser processのメモリ空間は完全に分離された。これ、日本語に多いkernel 2.6あたりの文献だと同じとこに入ってると書いてあるので要注意です。

Figure 18.7: A Virtual (And Physical) Memory Trace はまったく理解不能だった。

混乱は加速していく。この変換テーブルの話はOS+RAMの話だ。次の章でTLBが出て爆発する。

Chapter 19 Translation Lookaside Buffers

TLBは address-translation cache. メモリ内容をキャッシュするんじゃなくて、変換テーブルをキャッシュする。

大事だからもう一度いうよ!!

*TLBは address-translation cache. *

これは僕の勝手なsummarizeなんだけどこういうことだと思う、たぶん

通常時

program: cpuに命令 変数xを見せろ
cpu: x の場所わからん おいOS、教えろや
os: 変換テーブル(RAMに置いてあるので遅い)だと2丁目5番地でさ
cpu: とってきたで

TLBがある場合

program: cpuに命令 変数xを見せろ
cpu: なあTLB, x の場所知っとる?
tlb: さっきも来たピョン。2丁目5番地だピョン(cpu内にあるので超早い)
cpu: とってきたで

最初に「xがほしい」という情報を受け取るのはCPUなので、CPU内で「変換作業が」完結すると早い(TLB-hit)。OSに聞きに行くと遅い。RAMに取りに行くところは遅いが、それは仕方ない。もしかしたらCPUのレジスタの中にあったりしたら爆速で早い。
TLBになければTLB-miss. TLB-missを減らし、TLB-hitを高速に行う方法がpage replacementの永遠の課題につながる。

いやー、この記事書いてよかった。いろいろ理解できた。このsummarizeができるようになった。復習してよかった。余裕大事。今週はもう課題出し終わってるから焦らなくてよくて。余裕があると教科書の意味が理解できるな!!!!余裕大事だな!!!

Chapter 20 Advanced Page Tables

pagingをsegmentationとhybridにしたり、multi-level page tableを持つ話が出てくる。要は

  • page tableがでかくなるとfetchに時間がかかる
  • page tableが小さくなるとhitしない

この相反する問題をなんとかするために、小さいtable、ちょっと大きいtable, 大きいtable、みたいな多段構えにしてfetch早い→hitしやすいまで段階を作ろう的な話なんだと思ってる。余裕なくてじっくり読めてない。

linuxは4階層だったがkernel 3?4?あたりから5階層もできるようになってた気がする。

ここらへんでpage size変えてパフォーマンス測ろうとしてみて、このツイートをする。

Chapter 21 Swapping: Mechanisms

あーswapね的な内容。メモリが足りないとHDD使うよ。遅いよ。でも仮想アドレスでよかったね!的な。

swapにも上限があるよ。だって、最初にaddress translation table作るでしょ? そこが上限さ。

あーたしかに、となりました。

Chapter 22 Swapping: Policies

ここが面白かった。課題は「paging in/outを減らすためにOSは何がんばってる?」的なやーつ。ただ疲れたのでツイート抜粋でゴメ。

一言でいうと、未来さえわかれば最良の選択ができるが、当然不可能なので、「なんとなくこうなるんじゃないかな」を手持ちの情報から取捨選択してく、感じ。





Chapter 23 Complete VM Systems

むかしの革新的なOS, VAX/VMSの話。あらゆる今も現役の技術が
1970年代にあった。

メモリの0番地は必ずinvalidになっている。なぜか? programが間違ってundefined的な、0番地を読みに来る可能性があるからだ。これが null-pointer access だ。配列が0から始まるように、computerは0から始まる。なのに、実用性を考えて0を必ずinvalidにした。これはすごい判断だと思う。

demand zeroing

page-outがいるとき、zero書き込みまですると時間がかかる。mapping tableだけ書き換えて、実際のメモリ領域はそのままにしておく。
→ 「やっぱりまだ必要でした」のとき、data領域はupdate不要で再利用できる!
→「違うデータに使う(page-in)」とき、初めてzero書き込みする(というかたぶん新しいデータで直接上書きしたりしてんじゃないのかな?)

copy-on-write (COW for short).

page-outさせる候補からdirtyな領域を除く。cleanなところはさっと取り除けるが、dirtyはwriteしてからdiscardするので時間がかかる。
これには副次的な効果もあり、writeをある程度溜めておいて一気に書き込むほうが、disk側にとって都合がいい。細切れのリクエスト(e.g. 3, 11, 5, 9)より、一気にくれたほうが 3, 5, 9, 11の順で行くと早いな、的な判断ができるんじゃないだろうか(適当想像)

kernelのアドレス空間とuserのはまったく違う。kernelのは物理にdirect mapping (DMA) されてて早かったりする?的な? ごめんあまり読めてない

huge pages
DBMSなどで使われている、page sizeを4K以上にするやーつ。ごめん見れてないし試してなんかまったくいない。理解してない。でも実際linuxで使えるぽいからいつか。

序盤の章で、programから見えるメモリ領域はかぶるということを知った。中身が同じprocess aとbを走らせて両方で使う変数 x のアドレスを見るとまったく同じ(Cent7で再現した)。実際はアドレス変換されて違う物理アドレスに入ってるんだけど、プログラムからは同じ場所にあるようにみえーる。ハッカーからすると、ようわからんくても2丁目3番地にクレカの番号が来る、とわかってれば便利そうだよね。ここの章でそれが出てきて、ハッカーは、呼び出されている関数のreturnが来る場所のメモリアドレスを自分のmaliciousなコードに流す。これをreturn-oriented programming (ROP)と呼ぶそうだ。quite brilliantだと褒められている。天才すぎる。

で、そのprogram固定な仮想アドレスがmacだとランダムになったりしている。security対策だ。

このアタックを return-to-libc attack と呼ぶ。防御策は address space layout randomization (ASLR) だ。

Meltdown And Spectreの話もここで出てくる。なぜ投機的実行をするのか? 未来がわからないからだよ。投機的実行(speculative execution、これ覚えられない)は、"計算を早くしたい" というよりも 早くメモリ読みたい という欲求が生み出したんだとこの授業のおかげで飲み込めた。memory allocatorの延長線にいるんだ。

Chapter 24 Summary

ごめん読んでない

これは前半に遊んでたツイートのメモ。linuxのcacheを実際に触って見れる。おすすめ。

おわりに

rebootは世界を救う

はあ疲れた。この数週間まじで疲れた。私にメモリを増設してください。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1