Kubernetesの上での開発で役に立つコマンド
目的
- GKE, EKSなどのKubernetesの上で開発する際に役に立つコマンドを紹介します
コマンド達
- 内部で他のアプリケーションへ疎通確認等で利用する
- curlを実行できるpodを作成して、シェルにアタッチする
```
kubectl run -it curl --image=radial/busyboxplus:curl --rm --restart=Never -- /bin/sh
```
- portforward
- kubernetes上のpod, svcをローカルにport fowardする
- kubernetes上のサーバー等にローカルからアクセスして動作確認したい時に使います。
```
kubectl port-forward svc/nginx 8080:80
```
- kubernetes上の既存のリソースや、コマンドで生成するリソースからyamlを生成する
- リソースをyamlから変更したい時等に使います。
```
kubectl run busybox --image=busybox --dry-run=client -o yaml --restart=Never > yamlfile.yaml
```
protocol buffersの紹介
protocol buffersとは
Googleが開発した高性能で効率的なRPC(Remote Procedure Call)フレームワークであるgRPCのIDLです。
protocol buffersの利点
shellcodeの実装と書式文字列攻撃
shellcodeの実装
shellcodeを実行できるようにするために、何らかの文字列を関数として実行する必要が在る。
文字列の配列を準備
文字列の配列を関数ポインタにキャストする
その関数を実行する
関数ポインタ型 (void(*) ())
printf関数は第一引数に指定されたフォーマット指定子の数だけスタックを走査し、該当のアドレスのデータを出力する。フォーマット指定子を増やすことで、スタックの中身を自由に見ることができ、書式文字列攻撃と呼ぶ。
プログラミング言語のメモリ管理について
最近勉強したプログラミング言語のメモリ管理について紹介する。
参考にした本*1
そもそもなぜメモリ管理が必要なのか
- 不要なメモリ領域を開放する。必要な時にメモリ領域を割り当てることをメモリ管理と呼ぶ
- 不要なメモリが開放されないと、例えばwebサーバーのような長期間動くプロセスでは、利用するメモリがどんどん増えていき、コンピュータ自身の動作を不安定にする。メモリリークと呼ばれる
- 必要なメモリ領域が確保されていない(例えば、開放された後に参照してしまう)と、何が起こるかわからない状態になる。エラーで落ちてくれるかもしれないし、アクセス許可をしていない値にアクセスしてしまうかも知れない。
代表的なメモリ管理方法とその詳細
- 静的変数
- プログラムを開始する前に必要なメモリ領域をすべて決めてしまう。
- すべての変数がプログラムの開始から終了まで生存する
- メリット
- すべての変数のメモリが確保されており、開放されることもないのでメモリ管理の最低限の目的が達成される
- デメリット
- プログラム起動後にメモリ容量を変えることができない
- 自動変数
- 変数にスコープを設定して、それに従いメモリ領域を開放する
- メリット
- 自動で開放されるので、メモリリークの心配がない
- デメリット
- 開放された後のメモリ領域にアクセスしてしまうことが在る
- メリット
- 変数にスコープを設定して、それに従いメモリ領域を開放する
- メモリの手動確保、開放
- C,C++
- プログラム作成者が任意のタイミングでメモリの確保、開放を行う
- メリット
- メモリの確保、開放のタイミング、その大きさを自由に決めることができる
- デメリット
- 管理が複雑。間違って2回開放してしまった場合などにプログラムをクラッシュさせてしまう。
- ヒープを使うので、実行コスト大きい
- スマートポインタ
- ガベージコレクション
- Rustのメモリ管理
まとめ
色々なメモリ管理方法があるが、システムプログラムをかくならRustの方が他の言語よりもメンテナンス性、パフォーマンスの面で1段階上にいるとおもう。
*1:Software Design 2021年9月号
ページングについてわからなかった所とその現時点での理解
ページングテーブルについて理解が曖昧だった所の理解が進んだのでその記録*1
曖昧だった点
- なぜ、ページングテーブルを多段に分けるのか?
- 多段に分けても、同じアドレス幅で扱えるページ量は変わらないのでは?
現時点での理解
- 多段に分けるのは、ページングテーブル自体に仮想記憶を適用するため
- ページングテーブルを多段に分けることで
- 扱えるページ量を増やすためではない
そもそもページングとは
メモリを区分けして何らかの属性を付与することで、プロセスごとでメモリ空間を扱いやすくしたり、仮想記憶を実現している。
しかし、扱えるメモリの最小単位である1バイト単位で属性を設定し、管理することは難しいので、一定の大きさに分けて管理している。
その区分け方法として、任意の大きさに分けるセグメント方式、一定の大きさに分けて必要な分だけ切り貼りするのがページング方式。
ページング方式ではプロセスのアクセスの局所性を考慮することで、必要な分だけメモリにロードする仮想記憶の仕組みを導入することができ、メモリを効率よく利用できる。この方法はデマンドページングと呼ばれる。
ページと実際の物理メモリの対応関係を記したものをページテーブルと呼ばれる。
ページテーブルにアクセする際は、CPU内のCR3(コントロールレジスタ3)の内容を取りだす。そこにはページテーブルが格納されている場所を表す、ページディレクトリテーブルが格納されている。
ここでアクセスされるページテーブルが通常のメモリと同様にページとして扱われ、仮想記憶を用いて効率よく利用される。
PTE : Page Table Entryページングに用いられる、データ構造。ページの場所を表す値や、書き込み、読み込みの履歴を管理するための値も格納されている
TLB : Translation Lookaside Buffer メモリアクセスの度にPTEを読み込んでいると遅くなるので、CPUに搭載されているPTE専用のキャッシュがありそれをTLBという。PTEが読み込まれた際に同時にTLBにコピーを置いておく。
OSレベルでのI/Oの制限方法
486でどのようにI/Oコントロールが行わているか。*1
486 ではIOPLビットでIOコントロールをするが、全部禁止、全部許可しかできない。
TSSの中にI/O許可マップというものを準備して、各ポートについての権限を管理する。
また、リアルモード用のアプリケーションをプロテクトモードで動かしても、直接IOポートにアクセスするので、他のプロセスとの連携がとれず、排他制御などに困る。
よってI/Oの仮想化をOS側が準備して、全てのI/Oへの信号をコントロールする
IOPL : I/O Privilege Level, CPUのEFLAGSレジスタの中のIOPLフィールドに設定するもの。I/Oにアクセスする際は動作しているプロセスの権限と、I/Oポートの権限レベル(IOPL)が比較され、プロセスの特権レベルがIOPLより小さい場合に限りあくせすができるようにする。
関数呼び出しがどうやって実装されているか
アセンブラでの関数呼び出しは以下のように実装されていることが多い。
呼び出す側の関数をA、呼び出される側の関数をBとする
例えば、
int sum(int a, int b){
return a+b
}
void main(){
sum(1,2)
}
ならmainがA, sumがBを指す
- 引数をスタックに積む
- Aに戻った際に次に実行すべき命令が格納されているアドレスをスタックに積む
- Aを実行していたときのベースレジスタを積む
- ebpを退避しておいて、ebpにespを代入する
- ebpを基準に引数を取得する(例えばこの時、途中で関数呼び出しがあっても、ebp+4で引数が呼べる)
- 関数から戻る時はこれと逆の処理をする