20201223のLinuxに関する記事は10件です。

Windows上のファイルをLinuxコマンドで整理する - WSLを用いて -

当記事の目的

 ・「Linuxコマンドを試したいが、仮想環境の構築までは面倒だと考えている」方
 ・「勉強のために練習ディレクトリを作成するのではなく、実際に生きたファイルを使いたい」方
 ・「とりあえずWindows上のファイルを効率的に整理したい方」

 上記の方々にWSLを用いて、
 Windows上のファイルをLinuxコマンドを用いて操作(整理)が出来るようになる事が目的です。

 ※環境構築は簡単で、30分前後でWindows上でLinuxコマンドが実行出来るようになります!

WSLとは

 ・「Windows Subsystem for Linux」の略です。
 ・Windows OS上でLinuxの実行環境を実現するサブシステムです。

WSLを用いたLinux環境の構築方法

 今回は「Windows10」ユーザ向けに箇条書きで記載していきます。

 1. WSLの有効化
  WSLを利用するには、WSL機能を有効化する必要があります。
  「スタート」→「設定」の順で移動し、「Windows の機能の有効化または無効化」タブを開く。
  下記のように、「Linux 用 Windows サブシステム」をチェックし、PCを再起動をします。
  image.png

 2. Ubuntuのインストール
  WSLを利用するには、Linuxディストリビューションが必要です。
  下記のように、「Microsoft Store」でUbuntuと検索し、一番上に出てくる「Ubuntu」を
  インストールします。
  image.png
  インストール完了したら、スタートメニューから「Ubuntu」を検索して起動をします。
  image.png

 3. WSLとWindows間のファイル連携方法
  下記の方法で、WSLとWindowsのそれぞれのファイルを参照出来ます。

WSLからWindowsファイルシステムへのアクセス(Ubuntuで入力)
'cd /mnt/{ドライブ名}'
WindowsからWSLファイルシステムへのアクセス(エクスプローラで入力)
'\\wsl$'

最後に

Linux環境の構築には、主に以下の2つの方法があり、それぞれにメリット・デメリットがあります。
それらを認識したうえで、Linuxコマンドの学習とファイルの整理を効率的に行っていきましょう!

WSL
 メリット :ホストのファイルを直接参照するので、セキュリティ面に不安が残る。
 デメリット:別のOSはインストールしないので、手軽にLinuxのCUI環境を利用できる。
仮想化
 メリット :別のOSをインストールするので、ホストに影響を与えない分離した環境の構築。
 デメリット:マシンのリソースを利用するので、パフォーマンスがスペック依存になる。

  

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Proxy経由でゲームがしたいとき

この記事はProxyとの戦いに勝利したProxyを華麗にスルーしてWi-Fiを飛ばすには - Qiitaの後日談です。
環境などは上の記事に準じておりますので,是非ご一読の上こちらの記事をお読みください。

Proxy経由だとなぜゲームができない?

ゲームではウェルノウンポート以外のポートを多用するためです。
つまり,ゲーム側がウェルノウンポート以外でTLS通信をしている場合は,Squid側で設定してあげることで正常に通信ができるようになります。

具体的にどう設定したの(追加するポートを4432番とします)。

/etc/squid/squid.conf
- acl SSL_ports port 443
+ acl SSL_ports port 443 4432

+ acl Safe_ports port 4432

SSL_portsとSafe_portsに追加したいポートを加えるだけです。

参考文献

443以外のSSLポートを使用するプロキシサーバ(Squid)の設定 : https://minory.org/proxy-squid-403-error.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LowMemoryKiller 〜AndroidのActivityが破棄される仕組み〜

この記事は、LIFULLその2 Advent Calendar 2020の23日目の記事です。:christmas_tree:

今回は、Androidの低レイヤーな話を取り上げてみようと思います。
具体的には、OOM Killerでプロセスがkillされるのを未然に防ぐLowMemoryKillerの仕組みについてです。

ネイティブアプリの開発は、よりメモリの事を意識した開発が必要だなと日々感じていたので、もっと低いレイヤーで何が行われているかをちゃんと理解したいと思ったのがきっかけで、勉強してきた内容になります。

読んでいただけたらこの辺の内容を理解できる内容になっていると思います。

  • LowMemoryKillerの仕組み
  • Activityが破棄される基準
  • onSaveInstanceState()Bundleに保存するデータは実際どこに保存されているのか

Androidエンジニアじゃなくても、Androidの世界を少し覗いた気になれるような記事になればいいなと思います。:eyes:

はじめに

Androidには、OOM Killerによってプロセスがkillされるのを未然に防ぐLowMemoryKillerという仕組みがあります。
AndroidでActivityが突如破棄されてしまうことがありますが、その仕組みに深く関係しているのがLowMemoryKillerです。

LowMemoryKillerの必要性

LowMemoryKillerの仕組みに入る前に、LowMemoryKillerがどんなときに必要になるか話をします。

まず前提知識になりますが、Androidアプリ開発でまず画面を用意するときは、Activityというコンポーネントを使用します。1つのActivityが1つの画面というイメージでここでは大丈夫です。(Activity上で複数の画面をFragmentで管理する設計もありますが、今回はFragmentは話に必要ないので省きます)
一般的に、1つのアプリには複数のActivtityが含まれ、1アプリは1プロセスで実行されます。

デフォルトでは、同じアプリのすべてのコンポーネントは同じプロセスで実行され、ほとんどのアプリでこの動作を変更する必要はありません。

アプリを閉じてもユーザーがアプリをkillしない限りバックグラウンドに残り続けます。Activityのインスタンスもプロセス内に残り続けます。

そのため、あるアプリがバックグラウンド時に他のアプリがメモリを要求した場合、アプリ側からみると以下のような段階を踏んで空きメモリを確保しようとします。

  1. メモリの開放
    • Activity#onLowMemory()Activity#onTrimMemory(int)が呼び出される
    • (Fragmentの破棄などのタイミングがここにあたる)
  2. Activityの破棄
    • Activity#onDestroy()が呼び出される
    • ただし、Activity#onSaveInstanceState(Bundle)が事前に呼び出され、必要な状態は保存される
  3. プロセスの終了
    • プロセスが終了される
    • この場合、実行中のActivityは終了するがActivity#onDestrory()は呼び出されない
    • アプリがActivity#onStop()で停止している状態であれば、onSaveInstanceState(Bundle)は呼び出された後となるため、Activityの状態復元は可能

このようにメモリが足りなくなると最終的にはアプリのプロセス終了に到ります。実際にプロセスの終了を行うカーネル側からみると、これがOOM Killerによってプロセスがkillされるタイミングですと、今ユーザーにとって重要なアプリでも問答無用でkillされることになってしまいます。

こんな事態にならないよう未然に防ぐのがLowMemoryKillerです。

OOM Killerよりもっと前の段階で、そのActivityが重要か重要じゃないかを判断して、重要度の低いActivityを持つプロセスからkillしていき、メモリを空けるように動きます。

  • OOM Killer
    • どのプロセスがkillされるかは運任せに近く、重要なプロセスでも問答無用でkillする
  • LowMemoryKiller
    • 重要度が低いActivityを持つプロセス(ex. バックグラウンドにいるActivity)から段階的にkillすることを試みる

このようにLowMemoryKillerが裏で不要なプロセスをよしなにkillしてくれるおかげで私達は、普段快適にAndroidのスマートフォンを利用できているわけです。

LowMemoryKillerの全体像

本題のLowMemoryKillerの仕組みについてです。

まずAndroidは、LinuxカーネルをベースとするOSであり、LowMemoryKillerは、Linuxカーネルに既にある機能をうまく活用して実現されている仕組みになります。ざっくり書くと、OOM Killerによってプロセスがkillされる前に重要度が低いActivityを持つプロセスをkillしてメモリを空ける仕組みです。

LowMemoryKillerの登場人物は主に3つです。

  • ActivityManagerService
    • 現在どのActivityが重要かを判断するActivityの管理人のような役割をする
    • ActivityStackというスタックでActivityを管理
    • Activityの重要度を示すスコアの更新依頼をカーネル側に行う
  • lmkdデーモン
    • oom_score_adj というスコアを更新する
  • lowmemorykillerドライバ
    • メモリの空きが少なくなってきたタイミングで表に出ていない重要度の低いアプリのプロセスのkillを試みる
    • 実際にここでプロセスをkillする仕組みには、Linuxのディスクキャッシュの開放を行うshrink_slab()を活用している

例としてA画面 → B画面へのActivity遷移が起きたときの全体像をざっくり図に表すと以下のようになります。
全体図.png

  1. まず、ActivityManagerServiceがActivity BをActivityStackに積む
  2. ActivityManagerServiceは、各Activityの重要度を示すスコアをカーネル側に伝達するため、スコアの更新依頼を行う
  3. lmkdデーモンがスコア(oom_score_adj)を更新する
  4. lowmemorykillerドライバは、端末のメモリに空きが少なくなってきたときに、lmkdで更新されるスコアを参照して、スコアの高いActivityを持つプロセスからkillしていき、メモリを解放することを試みる

※ただし、カーネル4.12の時点で、lowmemorykillerドライバはカーネルから削除されており、lmkdがメモリのモニタリングとプロセス強制終了タスクを実行するように変わっています。基本的に機能としてはlowmemorykillerドライバと同じ機能をサポートしていますが、Android10以降では、メモリプレッシャーの検出にカーネルプレッシャーストール情報(PSI)モニターを使用する、新しいlmkdモードがサポートされていたりします。詳しくは、ローメモリ キラー デーモン(Imkd)を参照ください。

Activityの重要度を示すスコア(oom_score_adj)

lmkdが更新するスコアに従って、メモリの空きが少なくなってきたときにActivityの破棄やプロセスのkillを段階的に行うので、このスコアをActivityが破棄される基準といえそうです。

このスコアは、oom_score_adjと呼ばれ、OOMKillerがプロセスをkillするときにも使用されます。

oom_score_adjは、プロセスとActivityのライフサイクルの状態によって決められるkillされる閾値(メモリ空き容量)を表します。

実際に自分の端末でこのスコアを確認してみました。
(闘値は固定ではなく端末の状態で上下します。)

$adb shell dumpsys activity oom

OOM levels:
    -900: SYSTEM_ADJ (   73,728K)
    -800: PERSISTENT_PROC_ADJ (   73,728K)
    -700: PERSISTENT_SERVICE_ADJ (   73,728K)
      0: FOREGROUND_APP_ADJ (   73,728K)
     100: VISIBLE_APP_ADJ (   92,160K)
     200: PERCEPTIBLE_APP_ADJ (  110,592K)
     250: PERCEPTIBLE_LOW_APP_ADJ (  129,024K)
     300: BACKUP_APP_ADJ (  221,184K)
     400: HEAVY_WEIGHT_APP_ADJ (  221,184K)
     500: SERVICE_ADJ (  221,184K)
     600: HOME_APP_ADJ (  221,184K)
     700: PREVIOUS_APP_ADJ (  221,184K)
     800: SERVICE_B_ADJ (  221,184K)
     900: CACHED_APP_MIN_ADJ (  221,184K)
     999: CACHED_APP_MAX_ADJ (  322,560K)
  • 先頭についている3桁の数字:システム内で生かすべき優先順位
  • [XXXX_XXX_ADJ]の語尾の()の中の数字:プロセスを終了する具体的なメモリの空き容量の闘値

システム内で生かすべき優先順位の数字がより小さいほどプロセスがkillされづらくなり、高いほどkillされやすくなります。システムや永続化サービスを除いた時、一番この中でkillされる可能性が低いのは、level:0のフォアグラウンドいるActivityということになります。

ちなみに、XXXX_XXX_ADJ の意味は、ProcessList.javaのソースコードを読めばわかります。

ActivityがフォアグラウンドにあればそのActivityのscoreはFOREGROUND_APP_ADJで0、1つ前に使っていたならPREVIOUS_APP_ADJで700、ホームのアプリのActivityならHOME_APP_ADJで600といった形で、Activityのライフサイクルの状態などによってスコアが変化します。
例えばActivityの画面遷移が起きた時のスコアの変遷は以下のようになります。

score.png

このようにActivityのライフサイクルが変わるたびに、ActivityManagerServiceが各Activityの重要度を判断して、スコアを更新するということです。実際にスコアを更新するのはlmkdです。

Androidを触っていると、”いつの間にかアプリがkillされている”ってことがありますが、内部的にはこのようにActivityのスコア更新を行って、メモリの空きが少なくなったときにはこのスコアの基準に従ってActivity破棄やプロセスのkillが行われているようです。

Bundleは実際どこに保存されているのか

アプリのプロセスがLowMemoryKillerによってkillされる話をここまでしてきましたが、Androidでは、破棄されたActivityを復元させることもできます。

LowMemoryKillerの必要性でも軽く触れましたが、その際は、onSaveInstanceState()でデータをBundleに保存しておき、Activityを復元するときには保存しておいたBundleのデータを取り出すことで、Activityが突然破棄された場合に対処します。これに関してはAndroidエンジニアなら常識になっていると思いますが、

このときBundleは実際どこに保存されているのでしょうか?:thinking:

その答えは、
SystemServerのプロセス上にあるActivityStackです。

先ほどのoom_score_adjの話を振り返ると、SystemServerのプロセスのoom_score_adjは-900になります。
アプリをバックグラウンドに移動すると、まず前回使っていたActivityとしてスコアは0から700になり、さらに時間が経つにつれてスコアが増えていくことを踏まえると、スコア-900のSystemServerのプロセスにデータを保存しておけばシステムから非常にkillされにくいということがいえます。

ただし、もちろんですがSystemServerのプロセスのメモリを使用するので、Bundleにあまり大きなデータを保存してはいけません。SystemServerのメモリが大きくなりすぎると、メモリを空けようとして、裏のActivityをkillする頻度が上がるため、タスクの切り替えがすごく遅くなったように感じたり、メモリ不足によるアプリの異常終了などが多くなってしまうようです。

またBundleにあまり大きなデータを保存しようとすると、TransactionTooLargeExceptionという例外も発生します。

The Binder transaction buffer has a limited fixed size, currently 1Mb, which is shared by all transactions in progress for the process.

Activity間の画面遷移を行う際に、Intentに載せられるExtraのペイロードの上限が1MBであることはこのようにドキュメントに書かれていますが、手元で何度かこのエラーに遭遇したときは実際は1MBいってなくても発生していました。

個人的な解釈としては、onSaveInstanceState()で保存するBundleにはidやBooleanなどだけで、大きなデータはViewModelか永続化領域に保存か、ネットワークから再取得するか、という使い方が適切なんだろうなと思います。
便利ですがBundleには、なんでもかんでもツッコまないようにしましょう。

おわりに

Linuxカーネルのより詳細な話も書こうとしたのですが、長くなりすぎるので本記事では省略しました。でも今回説明した仕組みをより詳細に知るためにはLinuxの知識も必要となります。気になる方はぜひ参考文献に載せている書籍などをご覧ください。:green_book:

普段何気なく書いてるコードもこうして裏では動いてくれているんだと思いをはせながら、来年は今年よりメモリに優しい開発をしていきたいなと思います。

メリークリスマス。よいお年を。:innocent:

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OpenVPN 纏め + Amazon Linux2

概要

  • OpenVPNは、IP/TCPなどのL2、L3プロトコルを、SSL/TLSでカプセル化し、OpenVPNサーバーとOpenVPNクライアントとの間を安全に接続します。

  • OpenVPNは、PKI を利用します。

  • OpenVPNに必要なファイル

    • CA(認証局):CA証明書、CA秘密鍵
    • OpenVPNサーバー:CA証明書、サーバー証明書、サーバー秘密鍵、DHパラメーター
    • OpenVPNクライアント:CA証明書、クライアント証明書、クライアント秘密鍵
  • 接続形態

ルーティング接続 ブリッジ接続
仮想トンネルネットワーク経由で異なるネットワークに接続する方法です。
ルーティング処理を介して接続するので、 OpenVPNサーバーとOpenVPNクライアントは別々のネットワークが利用できます。
LAN同士を相互に接続する用途に向いており、大規模なアクセス制御に向いている。
仮想インタフェースで接続する方法です。
接続ネットワークと同じネットワークセグメントのIPアドレスをOpenVPNクライアントの仮想インタフェースに割り当てることで、OpenVPNクライアントは、接続先ネットワークに参加できます。
ブロードキャストが届くので、SambaやWindowsサーバーなどが利用できます。小規模なネットワークや個人で利用するのに手軽である。

OpenVPNサーバー側

easy-rsaパッケージを使って、認証局などを作成

# /usr/share/easy-rsa/3/easy-rsa init-pki
# /usr/share/easy-rsa/3/easy-rsa build-ca

サーバー証明書を作成

# /usr/share/easy-rsa/3/easy-rsa build-ca build-server-full vpnsrv nopass

クライアント証明書を作成

# /usr/share/easy-rsa/3/easy-rsa build-ca build-client-full vpncli nopass

DHパラメータを作成。

# /usr/share/easy-rsa/3/easy-rsa gen-dh

作成した証明書ファイルを、etc/openvpn/serverにコピーする。

# cp -r /usr/share/easy-rsa/3/pki etc/openvpn/server

TLS鍵を作成する。

# openvpn --genkey --secret /etc/openvpn/server/pki/ta.key

/etc/openvpn/server/server.conf の設定。
/usr/share/doc/openvpn/2.4.9/sample/sample-config-files からサンプルをコピーして利用するといい。

/etc/openvpn/server/server.conf
#ポート番号
port 1194

#プロトコル
proto udp

#ブリッジ接続
dev tap

#CA秘密鍵
ca ca.crt

#サーバー証明書
cert issued/vpnsrv.crt

#サーバー秘密鍵
key private/vpnsrv.key

#DHパラメータ
dh dh.pem

#VPNで利用するネットワーク
server 192.168.250.0 255.255.255.0
push "route 192.168.250.0 255.255.255.0"

#TLS認証鍵
tls-auth ta.key

#接続中のクライアントのリストを出力するファイル
status /var/log/openvpn-status.log

#ログを出力するファイル(指定しなければsyslogに出力)
log /var/log/openvpn.log

OpenVPNを起動する。

# systemctl start openvpn-server@server
# systemctl -w net.ipv4.ip_forward=1

OpenVPNクライアント側

OpenVPNクライアント側へ以下のファイルを転送します。

転送必要なファイル 説明
クライアント証明書 /etc/openvpn/server/pki/issued/vpncli1.crt
クライアント秘密鍵 /etc/openvpn/server/pki/private/vpncli1.key
CA秘密鍵 /etc/openvpn/server/pki/ca.crt
TLS認証鍵 /etc/openvpn/server/pki/ta.key
/etc/openvpn/client.conf
#クライアントであることの指定。
client

#ブリッジ接続
dev tap

#プロトコル
proto udp

#接続先サーバー、ポート
remoto server.naata.com 1194

#認証局証明書のファイル
ca     ca.crt
#クライアント証明書のファイル
cert   issued/vpncli1.crt
#クライアント秘密鍵のファイル
key    private/vpncli1.key

#tls認証鍵
tls-auth ta.key

OpenVPNを起動します。

# /sbin/openvpn /etc/openvpn/client.conf

OpenVPN を構築する(Amazon Linux2)

1.インストール

EPEL リポジトリを有効化

OpenVPN のインストール

sudo yum install openvpn -y 

easy-rsa のインストール

sudo yum install easy-rsa --enablerepo=epel -y

2.認証局と鍵の作成

認証局の初期化

sudo -s
cd /usr/share/easy-rsa/3
./easyrsa init-pki

init-pki complete; you may now create a CA or requests.
Your newly created PKI dir is: /usr/share/easy-rsa/3/pki

認証局の作成

./easyrsa build-ca

DHパラメータの作成

./easyrsa gen-dh

TLS認証鍵の作成

openvpn --genkey --secret /etc/openvpn/ta.key

サーバー証明書と秘密鍵の作成

./easyrsa build-server-full server nopass

クライアント証明書と秘密鍵の作成

 ./easyrsa build-client-full client

3./etc/openvpn/server.confの設定

/etc/openvpn/server.confの編集

cp /usr/share/doc/openvpn-2.4.9/sample/sample-config-files/server.conf /etc/openvpn/
/etc/openvpn/server.conf
port 1194
proto udp
dev tun
ca /usr/share/easy-rsa/3/pki/ca.crt 
cert /usr/share/easy-rsa/3/pki/issued/server.crt
key /usr/share/easy-rsa/3/pki/private/server.key
dh /usr/share/easy-rsa/3/pki/dh.pem
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
push "route 10.0.0.0 255.255.255.0"
keepalive 10 120
#tls-auth ta.key 0
cipher AES-256-CBC
comp-lzo
persist-key
persist-tun
status openvpn-status.log
verb 3
explicit-exit-notify 1

4.openVPNの開始と確認

フォワーディングの設定

/etc/sysctl.conf
net.ipv4.ip_forward = 1

network の再起動

systemctl restart network 

openvpnの再起動

systemctl start openvpn@server
systemctl enable openvpn@server

openvpnのステータス確認

systemctl list-unit-files -t service | grep openvpn

5.AWS側の設定

  • OpenVPNをインストールしたインスタンスのセキュリティグループで 1194 番ポートを許可する

  • Privateネットワーク

    • ルートテーブル:VPNクライアント側のセグメントから、OpenVPNをインストールしたサーバへのルーティングを設定する。
    • セキュリティグループ:VPNクライアント側のセグメントからはいってくるパケットの許可
  • OpenVPNをインストールしたサーバで送信元/送信先の変更チェックを無効に設定する

※送信元/送信先の変更チェック無効化とは
EC2インスタンスはデフォルトで送信元/送信先チェックを有効化しています。つまり、対象のEC2インスタンス宛てでないパケットはデフォルトで弾く仕様ということです。今回はインターネット宛てのパケットをEC2で経由させる必要があるためこの機能を無効化し、パケットを弾かせないように変更する必要があります。

参考:https://it.hirokun.net/entry/ec2-openvpn-easyrsa3#OpenVPN-6

6.Tunnelblick(MAC)で検証

Tunnelbrick で、接続する。

client
dev tun
proto udp
remote OpenVPNサーバIPアドレス 1194
resolv-retry infinite
nobind
persist-key
persist-tun
ca ca.crt
cert client.crt
key client.key
cipher AES-256-CBC
keepalive 10 60
verb 3
mssfix 1280
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cで作るコンテナもどき #2

はじめに

この記事は、FUNアドベントカレンダー2020の22日目の記事になります。

昨日はともかさんの学内限定初心者に優しいハッカソンFunLocksを運営した話でした。
今年はpart1とpart2それぞれ記事を書いているのですが、2つともともかさんの次になります。なんででしょうね。不思議。

環境

  • debian buster
  • kernel 4.19.0

さくらのクラウド上で実行しています。

今回で扱う範囲

namespace,chroot,cgroupを実装します。
今回、cgroupではcpuの制御しか行いません。

namespaceの切り分け

コンテナを作成するために、namespaceの切り分けを行う必要があります。これに対応したシステムコールはunshareとなります。
似たような機能を持つシステムコールにcloneがあります。
せっかくですので、ここでunshareとcloneの違いを簡単にまとめてからどちらを使うか考えましょう。

unshare

現在のプロセスに対して名前空間共有の制御を行う。

引数

  • int flags
    与えられたflagに対応した資源を共有しません。 つまり、何も指定しないとすべて共有されるってことかな。
返り値 : int

成功時0, 失敗時-1

clone

子プロセスの作成と同時に名前空間共有制御を行う。
fork + unshareが混ざったイメージ

引数
  • unsigned long flags
    与えられたflagに対応した資源を共有します
  • void *child_stack
    割愛
  • void *ptid
    割愛
  • void *ctid
    割愛
  • struct pt_regs *regs
    割愛

なんかいっぱいありますね。
unshareとは違い子プロセスも作成するので、子プロセスの使用するメモリやその他諸々が必要になります。(めんどくさくて調べていないので詳しくは各自で)

返り値 : long

子プロセスのスレッドidが返されます。失敗時-1、子プロセスは作成されません。

結局どっち使うの?

すこし長くなってしまいましたが、今回はunshareを使用したいと思います。
理由としては以下となります。

  • 引数と返り値がわかりやすい。
  • cloneの説明にスレッドに使うことが多いみたいなこと書いてある。

実装してみる  

#define _GNU_SOURCE
#include<sched.h>
#include<unistd.h>
#include<stdio.h>
#include<errno.h>

int main(){
        const unsigned int UNSHARE_FLAGS = ( CLONE_FILES | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_NEWPID);
        if (unshare(UNSHARE_FLAGS) < 0){
                perror("unshare");
        }
        printf("ok\n");
        return 0;
}

で名前空間を分離することができるとおもいます。
unshareの実行しかしておらず、エラーが出なかったことだけしか確認していないので、この段階ではちゃんと動いたかわかりません。

flagの説明は割愛します。UNSHAREに書いてあるので各自見てみてください。

rootの変更

namespaceを切り分けたあとはrootを変更してあげます。
システムコールのchrootを使います。

chroot

ルートディレクトリの変更。
引数で指定したpathが以降で'/'として扱われます。

引数

  • const char* path
    変更先のパス

返り値

成功時0, エラーの場合-1

実装してみる

#include<unistd.h>
#include<errno.h>
#include<stdio.h>

int main(){
        char *argv[3];
        argv[0] = "/";
        argv[1] = NULL;

        if( chroot("./test") < 0 ){
                perror("chroot");
                return 1;
        }
        if ( chdir("./test") < 0 ){
                perror("chdir");
                return 1;
        }
        if ( execve("/bin/ls", argv, NULL) < 0){
                perror("execve");
                return 1;
        }
        printf("ok\n");
        return 0;
}

testというディレクトリを作成し、そこにchrootを行います。
その後/bin/lsを実行するコードを書きました。
testは空のディレクトリなので、もちろん、/bin/lsなんてないのでうまくいきません。
これでchrootがうまく機能しているというのが確認できます。(賢い確認方法ではない)
ちなみに、ホストマシンのものではないdebianのrootディレクトリを用意して、chrootした場合は正常に動きました。

リソースの制限

コンテナとして成り立たせるためにはリソースの制限を行う必要があります。例えば、メモリの使用量だとか、CPU利用の制限とか、いろいろです。  
リソースの制限に関しては、システムコールがあるわけではなく、cgroupというLinuxの機能があります。
cgroupはv1とv2がありますが、今回はv2を使用します。
cgroup v2の細かい説明や仕様については長くなるのでここでは説明しません。
興味あるかたはぜひこちらをご覧になってください。

cgroupを用いたリソースの制限

cgroupを使えるようにするには以下の手順を踏む必要があります。
1. cgroupfsのマウント
2. cgroupの作成(ディレクトリの作成)
3. cgroup.procsへ制御するプロセスIDの書き込み
4. サブシステムの登
5. サブシステムへの制限

実装してみる

今回僕の環境では/sys/fs/cgroupにすでにマウントされていたので行いません。

#include<sys/mount.h>
#include<fcntl.h>
#include<unistd.h>
#include<errno.h>
#include<stdio.h>

int main(){
        //make cgroup
        if( access("/sys/fs/cgroup/container", F_OK) < 0){
                if( mkdir("/sys/fs/cgroup/container", 0644) < 0){
                        perror("mkdir");
                        return -1;
                }
        }
        //set pid
        int fd;
        fd = open("/sys/fs/cgroup/container/cgroup.procs", O_WRONLY);
        if( fd < 0 ){
                perror("cgroup open");
                return -1;
        }
        int _pid = getpid();
        char buff[6];
        snprintf(buff, 6 , "%d", _pid);
        write(fd, buff, 6);
        close(fd);

        //set subsystem
        fd = open("/sys/fs/cgroup/container/cgroup.subtree_control", O_WRONLY);
        if( fd < 0 ){
                perror("subsystem open");
                return -1;
        }
        write(fd, "+cpu", 5);
        close(fd);

        //set cpu max
        fd = open("/sys/fs/cgroup/container/cpu.max", O_WRONLY);
        if( fd < 0 ){
                perror("cpu open");
                return -1;
        }
        write(fd, "10000", 6);
        close(fd);
        while(1){
                fd = open("/dev/null", O_WRONLY);
                write(fd,"hello world\n", 12);
                close(fd);
        }
}

現在のプロセスに対して、CPU最大10%までの制限をかけて、"hello world"を/dev/nullに無限に出力するものです。
(yesコマンドみたいな?)
これを実行した状態でプロセスのCPU使用率を確認してみましょう。
screen-capture-_online-video-cutter.com_-_3_.gif
おおよそ10%に固定されていることがわかります。
多分上手く動いてますね。

補足

        //set cpu max
        fd = open("/sys/fs/cgroup/container/cpu.max", O_WRONLY);
        if( fd < 0 ){
                perror("cpu open");
                return -1;
        }
        write(fd, "10000", 6);
        close(fd);c

writeで"10000"とありますが、これはCPU時間のことです。今回、CPU時間の最大が100000なので1/10の"10000"となっています。
これでCPU使用率の設定ができます。

おわり

今回はnamespace,chroot,cgroupを実装しました。
コードの質問や、挙動が違う部分、間違ってる部分あればTwitterまでお願いします。
次回はいつになるかわかりませんが、capabilityを実装しようと思います。

参考にしたところ

Cで作るコンテナもどき #1
LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術
Man page of UNSHARE
Man page of CLONE
Man page of CHROOT

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Homebrew + rbenv + ruby-build の関係性(後編)実際にrubyとRailsの導入もできます!

前回の記事ではbrewでインストールしたプログラムが/usr/local/Cellerディレクトリなどに入りシンボリンクを作成することでパスが通ること、各ディレクトリ構成について説明しました。今回は実際にrbenvruby-buildをインストールして、それぞれのコマンドで何をしているのかを説明していきます。

rbenv, ruby-buildのインストール

まずはbrewを最新の状態にしておきます。

% brew update

次にrbenvruby-buildのインストールです。ruby-buildはrbenvのプラグインの1つです。rbenv installというRubyのバージョンをインストールするコマンドを提供しています。rbenvをインストールする際に、セットでインストールします。

% brew install rbenv ruby-build

RBENV_ROOT とは

次のコマンドの説明に必要なのでこの用語の説明をします。RBENV_ROOT というのは rbenv の shims (後述) と versions (Ruby のインストール先) がある場所(パス)を指し示すための環境変数です。 デフォルトではホームディレクトリに直下になっており、以下のように確認できます。

% rbenv root
/Users/shuntagami/.rbenv

rbenv initの実体

話を戻します。rbenv のインストール手順によると以下のコマンドを実行するようにあります。

% echo 'eval "$(rbenv init -)"' >> ~/.zshrc  #(`~/.zshrc`の部分は使っているシェルに応じて変えてください。)

これは何を行うのでしょうか? 答えは書いてあるとおり、shimsautocompletion を有効化します。shimsには何が入っているのか確認してみましょう。

% cd .rbenv
% ls
shims    version  versions

% cd shims
% ls
bundle bundler erb gem irb rake rdoc ri ruby testrb,,,

bundleやらrakeやらrailsを使ったことのある人ならおなじみのコマンドが入っています。つまり、shimsを有効化することにより、$RBENV_ROOT/shimsPATH に入れ,これらのコマンドがどこからでも使えるようになるということです。最後にシェルの設定ファイルの変更を以下のコマンドで読み込みましょう。

source ~/.zshrc  #(`~/.zshrc`の部分は使っているシェルに応じて変えてください。)

rbenvでrubyをインストールする

ここまででrbenvのインストールが完了したのでrubyをインストールしていきます。インストール可能なバージョンは以下のように確認しましょう。

% rbenv install --list

特に理由がなければ最新のバージョンをインストールしましょう。

% rbenv install 2.7.2

インストールしたバージョンを使うために以下のコマンドを実行します。

% rbenv global 2.7.2

最後にrbenvを読み込み、変更を反映させます。新しいバージョンの Ruby を入れたときや、実行ファイルを提供する gem を入れたあとには実行するようにしましょう。

% rbenv rehash

Railsを用意する

rubyの導入が終わり、せっかくなのでRailsの導入手順も説明していきます。

bundlerのインストール

bundlerとは、gemのバージョンやgemの依存関係を管理してくれるgemです。bundlerを使うことで、複数人での開発やgemのバージョンが上がってもエラーを起こさずに開発できます。

% gem install bundler

bundler自体もgemなのでインストールが完了すると1 gem installedと表示されるはずです。

Railsのインストール

% gem install rails --version='6.0.0'

バージョンを指定してインストールします。完了したら先ほど説明したrbenv rehashコマンドを実行しましょう。

参考

rbenv + ruby-build はどうやって動いているのか
https://takatoshiono.hatenablog.com/entry/2015/01/09/012040

rbenv公式
https://github.com/rbenv/rbenv#basic-github-checkout

まとめ

rubyとRailsのインストール手順とそれぞれのコマンドの意味を見ていきました。あとはデータベース,yarn,Node.jsをインストールすればRailsを動かすことができるようになります

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Linuxの先読みキャッシング read_ahead_kb

この記事面白すぎる!

先読み

必要なデータをユーザから要求があるたびに同期的に読み込んでいると、ディスクの I/O 待ちに時間がかかる。そのため、 I/O 要求がシーケンシャルリードだと判断した時点で要求されていない後続のブロックを先読みしておき、キャッシュを充実させる。先読みしたデータは使われるかもしれないし、無駄になるかもしれない。
先読みすることでどの程度効率化されているかを動作確認する。
https://www.kimullaa.com/entry/2019/12/01/130347

#// 先読みが有効であることを確認する
cat /sys/block/sda/queue/read_ahead_kb

# cache削除
echo 3 > /proc/sys/vm/drop_caches

# (先読みあり) 読み込み速度計測
time dd if=/dev/sda of=/dev/null bs=10M count=100

#先読みを無効にする
echo 0 > /sys/block/sda/queue/read_ahead_kb

# cache削除
echo 3 > /proc/sys/vm/drop_caches

# (先読みなし)読み込み速度計測
time dd if=/dev/sda of=/dev/null bs=10M count=100

result

image.png

[root@localhost test]# cat /sys/block/sda/queue/read_ahead_kb
4096
[root@localhost test]# echo 3 > /proc/sys/vm/drop_caches
[root@localhost test]# time dd if=/dev/sda of=/dev/null bs=10M count=100
100+0 records in
100+0 records out
1048576000 bytes (1.0 GB) copied, 1.53053 s, 685 MB/s

real    0m1.566s
user    0m0.000s
sys 0m1.074s
[root@localhost test]# #先読みを無効にする
[root@localhost test]# echo 0 > /sys/block/sda/queue/read_ahead_kb
[root@localhost test]# echo 3 > /proc/sys/vm/drop_caches
[root@localhost test]# time dd if=/dev/sda of=/dev/null bs=10M count=100
100+0 records in
100+0 records out
1048576000 bytes (1.0 GB) copied, 37.3734 s, 28.1 MB/s

real    0m37.386s
user    0m0.001s
sys 0m25.442s
[root@localhost test]#
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vmstat コマンドの buff と cache の違い

buff と cache の違い

ページキャッシュとバッファキャッシュ
ほとんどのファイルシステムでは、ディスクから読み込んだデータをキャッシュする。データへの アクセス方式によって 、ページキャッシュとバッファキャッシュに分けられる。
https://www.kimullaa.com/entry/2019/12/01/130347

image.png

x 「何のデータか」
o 「どうやって取得したか」

cacheを増やす方法

ページキャッシュ
ファイルシステム経由 でアクセスしたデータのキャッシュ。

file system(HDD/SSD)からファイルを読む

# でかいファイルを作る
 dd if=/dev/zero of=/tmp/large.txt count=100 bs=10M

# cache削除
 echo 3 > /proc/sys/vm/drop_caches

# cacheが減った状態
 vmstat

# 1回目
time cat /tmp/large.txt > /dev/null

# cacheが載った状態
 vmstat

この記事参照
https://qiita.com/uturned0/items/3eab2a8d03180677f768

cache result

image.png

[root@localhost test]# # でかいファイルを作る
[root@localhost test]#  dd if=/dev/zero of=/tmp/large.txt count=100 bs=10M

100+0 records in
100+0 records out
1048576000 bytes (1.0 GB) copied, 1.24035 s, 845 MB/s
[root@localhost test]#
[root@localhost test]# # cache削除
[root@localhost test]#  echo 3 > /proc/sys/vm/drop_caches
[root@localhost test]#
[root@localhost test]# # cacheが減った状態
[root@localhost test]#  vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 1550208      0 221652    0    0  2181  2180  144  159  2  2 96  0  0
[root@localhost test]#
[root@localhost test]# # 1回目
[root@localhost test]# time cat /tmp/large.txt > /dev/null

real    0m1.950s
user    0m0.012s
sys 0m1.639s
[root@localhost test]#
[root@localhost test]# # cacheが載った状態
[root@localhost test]#  vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0      0 688060      0 1083692    0    0  2471  2179  147  161  2  2 96  0  0
[root@localhost test]#
[root@localhost test]#

buffを増やす方法

バッファキャッシュ
raw I/O 経由 でアクセスしたデータをキャッシュする。実装上は、ブロックデバイスに対するページキャッシュになっている。

ファイルではなく、block device /dev/sd* を直接読み込む。

# buff削除
 echo 3 > /proc/sys/vm/drop_caches

# buffが減った状態
 vmstat

# buff を載せる
time dd if=/dev/sda of=/dev/null count=100 bs=10M

# buffが載った状態
 vmstat

buff result

image.png

[root@localhost test]# # buff削除
[root@localhost test]#  echo 3 > /proc/sys/vm/drop_caches
[root@localhost test]#
[root@localhost test]# # buffが減った状態
[root@localhost test]#  vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 1718432      0  53296    0    0  2430  2176  146  159  2  2 96  0  0
[root@localhost test]#
[root@localhost test]# # buff を載せる
[root@localhost test]# time dd if=/dev/sda of=/dev/null count=100 bs=10M

100+0 records in
100+0 records out
1048576000 bytes (1.0 GB) copied, 1.51047 s, 694 MB/s

real    0m1.515s
user    0m0.001s
sys 0m1.122s
[root@localhost test]#
[root@localhost test]# # buffが載った状態
[root@localhost test]#  vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0      0 690092 1028096  53344    0    0  2771  2175  149  163  2  2 96  0  0
[root@localhost test]#
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LinuxがPage cacheを使って高速化するのを体験してみる

こちらの超素敵な記事をまんま真似するだけです。
https://www.kimullaa.com/entry/2019/12/01/130347

rootで実行します。

# でかいファイルを作る
 dd if=/dev/zero of=/tmp/large.txt count=100 bs=10M

# 今のcache状態
 vmstat

# cache削除
 echo 3 > /proc/sys/vm/drop_caches

# cacheが減った状態
 vmstat

# 1回目
time cat /tmp/large.txt > /dev/null

# cacheが載った状態
 vmstat

# 2回目は早くなるはず
time cat /tmp/large.txt > /dev/null

# お掃除
rm -rf /tmp/large.txt

result

10倍になりました。

image.png

[root@localhost test]# # でかいファイルを作る
[root@localhost test]#  dd if=/dev/zero of=/tmp/large.txt count=100 bs=10M
100+0 records in
100+0 records out
1048576000 bytes (1.0 GB) copied, 1.29639 s, 809 MB/s
[root@localhost test]#
[root@localhost test]# # 今のcache状態
[root@localhost test]#  vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0  79216      0 1692164    0    0  1084  2053  166  195  3  2 95  0  0
[root@localhost test]#
[root@localhost test]# # cache削除
[root@localhost test]#  echo 3 > /proc/sys/vm/drop_caches
[root@localhost test]#
[root@localhost test]# # cacheが減った状態
[root@localhost test]#  vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 1592220      0 179268    0    0  1086  2053  166  195  3  2 95  0  0
[root@localhost test]#
[root@localhost test]# # 1回目
[root@localhost test]# time cat /tmp/large.txt > /dev/null

real    0m1.455s
user    0m0.008s
sys 0m1.075s
[root@localhost test]#
[root@localhost test]# # cacheが載った状態
[root@localhost test]#  vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0      0 567092      0 1204456    0    0  1565  2053  170  200  3  2 95  0  0
[root@localhost test]#
[root@localhost test]# # 2回目は早くなるはず
[root@localhost test]# time cat /tmp/large.txt > /dev/null

real    0m0.139s
user    0m0.003s
sys 0m0.135s
[root@localhost test]#
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Homebrew + rbenv + ruby-build の関係性(前編)

はじめに

最近rubyの管理をrbenvからanyenv経由でrbenvをインストールする形式に変えようと思っていました。というのも、.zshrc(bashを使う人は.bash_profile)にanyenvのパスを通すだけで済むのでファイルをきれいに保てるというのが1番の理由です。その移行方法について調べている際に「rbenvをよくわかってなかったなあ」と感じたので記事を書くことにしました。また、今回の記事は「homebrewでインストールしたとき」を想定しています。

brewでインストールしたものはどこにある?

まず私はこんな基本的なこともわたしは理解していませんでした。homebrewでインストールしたものは/usr/local/Cellerに入るようになっています。「/usr/local/Cellerにパスを通したことなんてない!」と思ったのですが、homebrew の仕組みにしたがって、適切なバージョンの実行可能ファイルに /usr/local/bin からのシンボリックリンク(ショートカット)が作成されるみたいです。つまり、/usr/local/binへのパスが通っていればhomebrewでインストールしたコマンドは使えるようになります。以下のような感じです。

$ ll /usr/local/bin/{rbenv,ruby-build}
lrwxr-xr-x  1 shuntagami  admin    31B  8  3 15:26 /usr/local/bin/rbenv -> ../Cellar/rbenv/1.1.2/bin/rbenv
lrwxr-xr-x  1 shuntagami  admin    44B 12 20 08:13 /usr/local/bin/ruby-build -> ../Cellar/ruby-build/20201210/bin/ruby-build

ちなみに、PATHは環境変数$PATHに設定されており、echo $PATHで確認できます。以下は私の例です。

$ echo $PATH
/Users/shuntagami/.anyenv/envs/nodenv/shims:/Users/shuntagami/.anyenv/envs/nodenv/bin:/Users/shuntagami/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

パスは:で区切られるので見やすくしてあげます。

$ echo $PATH
/Users/shuntagami/.anyenv/envs/nodenv/shims
/Users/shuntagami/.anyenv/envs/nodenv/bin
/Users/shuntagami/bin
/usr/local/bin 
/usr/bin
/bin
/usr/sbin
/sbin

/usr/local/binへのパスが通っていますね!補足で、$PATHに同じパスが通っていたらtypeset -U path PATHで解消できます。

binがいっぱいあるけどどういう意味?

先ほどの環境変数$PATHには/usr/,/binなどがありましたがそれらの違いについて説明します。そもそもbinとはBinary codeのことで実行可能プログラム置き場ということです。 コマンドも実態はプログラムなので権限や用途に合わせてわけて保存されます。

/bin

シングルユーザモードでも利用できるコマンドを置きます。逆の言い方をすると、「/usr/bin」や「/usr/local/bin」に置かれているコマンドなどはシングルユーザモードで利用できないということになります。シングルユーザモードは、基本的にOSが壊れて正常に起動できないなど非常時に利用するものですので、「/bin」にはごく基本的かつ非常時に利用するコマンドが置かれることになります。

/sbin

ここにはシステム管理用のコマンドが保存されます。多くのシステム管理用のコマンドはrootユーザーで(管理者になってから)実行されます。

/usr/bin

/usr/binには「シングルユーザモードで利用しない」かつ「RPMやdebなどのパッケージ管理システムによって、システムに管理されるコマンドやプログラム」が置かれます。非常時に利用するものではないが、システムを構成する重要なコマンドやプログラムはここに置かれることになります。また、ここには1000以上のコマンドがあるのでls -l /usr/bin | lessコマンドを使うと確認しやすいです。

/usr/sbin

/binと/usr/binの違いと同様。

/usr/local/bin

/usr/local/binは自分でインストールしたコマンドや自作のコマンドを置くのに使います。

まとめ

brewでインストールしたプログラムが/usr/local/Cellerディレクトリなどに入りシンボリンクを作成することでパスが通ること、各ディレクトリ構成について説明しました。後編では実際にrbenvとruby-buildをインストールして詳細を解説していきます!

参考

「/bin」「/usr/bin」「/usr/local/bin」ディレクトリの使い分け
https://linuc.org/study/knowledge/544/

Linux標準教科書
https://linuc.org/textbooks/linux/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む