join コマンドの練習
join コマンドを使ったファイルの結合を練習してみます。
テスト用にファイル breeds と dogs を用意しました。
2 つとも 1 行目はヘッダです。データはソートされていないです。
$ cat breeds id breed 4 maltese 1 poodle 3 frenchbulldog 2 pomeranian 5 husky $ cat dogs id name breed_id 2 koro 5 1 sofia 4 3 louis 3
結合できなかった行を表示しない場合(内部結合?)
後ほど出てきますが -a
オプションを付けないと
結合できなかった行は表示されません。
まずは dogs の breed_id で結合してみます。
join コマンドでは対象データがソートされている必要があるので
sort した結果を join に渡しています。ヘッダ行は邪魔なので
sort 前に sed '1d'
で消しています。
join の -1 3 -2 1
は 1 つめのファイルの 3 つめのフィールドと
2 つめのファイルの 1 つめのフィールドを結合するという意味です。
$ join -1 3 -2 1 <(sed '1d' dogs | sort -k3) <(sed '1d' breeds | sort) 3 3 louis frenchbulldog 4 1 sofia maltese 5 2 koro husky
join で -o
を使用すると表示するフィールドを選択できます。
1.1
は 1 つめのファイルの 1 つめのフィールドという意味です。
$ join -1 3 -2 1 -o 1.1 1.2 1.3 2.2 <(sed '1d' dogs | sort -k3) <(sed '1d' breeds | sort) 3 louis 3 frenchbulldog 1 sofia 4 maltese 2 koro 5 husky
今度は breeds の id で結合してみます。-a
オプションが無いので
poodle, pomeranian は表示されません。
$ join -1 1 -2 3 -o 1.1 1.2 2.1 2.2 <(sed '1d' breeds | sort) <(sed '1d' dogs | sort -k3) 3 frenchbulldog 3 louis 4 maltese 1 sofia 5 husky 2 koro
結合できなかった行を表示する場合(外部結合?)
上記の poodle, pomeranian が表示されなかったコマンドに -a 1
を付けて
実行してみます。-a 1 -a 2
という使い方もできるらしいです。
-e
で一致するフィールドが無い時に表示する文字列を指定します。
$ join -a 1 -1 1 -2 3 -o 1.1 1.2 2.1 2.2 -e NULL <(sed '1d' breeds | sort) <(sed '1d' dogs | sort -k3) 1 poodle NULL NULL 2 pomeranian NULL NULL 3 frenchbulldog 3 louis 4 maltese 1 sofia 5 husky 2 koro
参考にさせていただいたサイト
サービスのログが journald に収集されるか試す
journald について勉強しています。systemd が起動するサービスの標準出力と標準エラー出力が journald に収集されるか、 テスト用のログを出力するサービスを作り、試してみました。
テスト用のログ出力スクリプト
以下のスクリプトを /home/pi/testsh/test.sh
に配置しました。
10 秒ごとに標準出力と標準エラー出力に書き込みます。
#!/bin/bash while true do sleep 10 echo "hello stdout" echo "hello stderr" >&2 done
ユニットファイルを作る
test.sh
を実行するサービスのユニットファイルを /etc/systemd/system/test.service
に作ります。
参考にさせてもらったサイト: https://qiita.com/DQNEO/items/0b5d0bc5d3cf407cb7ff
$ sudoedit /etc/systemd/system/test.service $ cat /etc/systemd/system/test.service [Unit] Description = test [Service] ExecStart = /home/pi/testsh/test.sh Restart = always Type = simple [Install] WantedBy = multi-user.target
test.service がサービスとして認識されたか確認する
OK ですね。
$ sudo systemctl list-unit-files --type=service | grep test test.service disabled
サービスを起動して、journald のログを確認する
サービス起動前に journalctl | grep hello
で journald のログに hello stdout と hello stderr が無いことを確認します。
$ journalctl | grep hello
サービスを起動します。
$ sudo systemctl start test.service
再度ログを確認すると、ちゃんと出力されています。
$ journalctl | grep hello 4月 04 17:24:28 raspberrypi test.sh[13035]: hello stdout 4月 04 17:24:28 raspberrypi test.sh[13035]: hello stderr 4月 04 17:24:38 raspberrypi test.sh[13035]: hello stdout 4月 04 17:24:38 raspberrypi test.sh[13035]: hello stderr
確認が終わったら、サービスを止めておきます。
$ sudo systemctl stop test.service
systemd-cat
systemd-cat を実行しても journald にログを記録できるみたいです。
参考にさせてもらったサイト: https://serverfault.com/questions/573946/how-can-i-send-a-message-to-the-systemd-journal-from-the-command-line
$ journalctl | grep bowwow $ echo 'bowwow' | systemd-cat $ journalctl | grep bowwow 4月 04 17:54:27 raspberrypi cat[13153]: bowwow
「Linuxのしくみ」を読んだメモ
試して理解 Linuxのしくみ 実験と図解で学ぶOSとハードウェアの基礎知識 第3刷
を読み終えました。自分用に整理したメモを残しておきます。
勉強用リポジトリ: https://github.com/hiroygo/linux-in-practice
調査用のコマンドやファイル
/proc/cpuinfo ファイル
cat /proc/cpuinfo
で CPU 情報を取得できる。
taskset コマンド
プロセスが動作する論理 CPU を指定できる。
$ taskset -c 0,4 ./fuga
time コマンド
プロセスの処理時間を計測できる。
real が経過時間、user がユーザモードの時間、
sys がカーネルがシステムコールを実行していた時間。
$ time ps PID TTY TIME CMD 41058 pts/0 00:00:00 bash 41067 pts/0 00:00:00 ps real 0m0.039s user 0m0.005s sys 0m0.006s
strace コマンド
プロセスのシステムコールを確認できる。
strace の出力は 1 行が 1 つのシステムコールに対応する。
free コマンド
メモリ状況を確認できる。開放可能なカーネルメモリ領域のサイズと free の合計が available になる。 開放可能なカーネルメモリ領域とはバッファキャッシュやページキャッシュなど。 Swap 行がスワップ領域の情報。
$ free total used free shared buff/cache available Mem: 7820180 1660916 3412396 387088 2746868 5450456 Swap: 2097148 0 2097148
ps コマンド
min_flt と maj_flt でページフォルトの総数が取れる。
$ ps -o pid,comm,min_flt,maj_flt PID COMMAND MINFL MAJFL 5617 bash 1524 0 8884 ps 178 0
readelf コマンド
ELF フォーマットの実行ファイルの情報を表示する。以下のサンプルでは、メモリマップ開始アドレスは 0000000000002580
と 000000000000a000
になっている。
$ readelf -S /bin/sleep There are 30 section headers, starting at offset 0x91d8: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align ... [16] .text PROGBITS 0000000000002580 00002580 0000000000003692 0000000000000000 AX 0 0 16 ... [26] .data PROGBITS 000000000000a000 00009000 0000000000000080 0000000000000000 WA 0 0 32 ...
CPU のカーネルモードとユーザモード(第1章, 第2章)
プロセスから直接デバイスやプロセス管理システムなどにアクセスできないようにするため、 CPU にはカーネルモードとユーザモードがある。カーネルモードの時だけ、 デバイスやプロセス管理システムなどにアクセスできる。プロセスがシステムコールを発行すると、 CPU で割り込みというイベントが発生し、CPU はユーザモードからカーネルモードに遷移する。 システムコール処理が終わると CPU はユーザモードに戻る。 デバイスドライバはカーネルモードで動作する。
システムコール(第2章)
システムコールはアーキテクチャ依存のアセンブラコードで呼ぶ必要がある。 (特定のレジスタに実行したいシステムコールの番号を設定するなど) C だとインラインアセンブラという機能がある。これだと手間がかかるので、 OS にはシステムコールを呼び出すラッパー関数が用意されている。 CPU がカーネルモードに遷移すると、システムコールの番号から システムコール・テーブルを参照して、対応するカーネルの処理を呼び出す。
- 参考にさせてもらったサイト
プロセスの状態(第4章)
プロセスの状態には実行中、実行待ち(CPU で実行されるの待ち)、スリープ(イベント待ち)、ゾンビがある。
OOM Killer(第5章)
どうやってもメモリが不足するような場合、Out of Memory(OOM) という状態になる。 この場合、OOM Killer が実行され、適当なプロセスが kill されることがある。
仮想記憶(第5章)
プロセスは仮想記憶というメモリを使う。 仮想記憶のアドレスと物理メモリのアドレスの対応はカーネルメモリ領域のページテーブルで ページという単位で管理される。 仮想記憶を使うことで以下を解決する。
- メモリ断片化
- 物理メモリでは断片化しているメモリ領域を、あたかも連続しているように見せることが出来る。
- 他のプロセスやカーネルメモリ領域へのアクセス制御
- 仮想記憶はプロセスごとに作られ、ページテーブルで管理される。このため他のメモリ領域が見えなくなる。
- 他のプロセスとのアドレス衝突
- プロセスを起動するために、ディスクなどからメモリにプロセスをマッピングする時に、他のプロセスとメモリマップ開始アドレスなどが衝突する可能性がある。仮想記憶はプロセスごとに作られるので回避できる。
- メモリマップ開始アドレスなどは readelf から確認できる。
不正な仮想アドレスにプロセスがアクセスすると、ページフォルトという割り込みが発生し、カーネルのページフォルトハンドラが実行され、
カーネルはプロセスに SIGSEGV
を通知する。
mmap と malloc(第5章)
mmap ではページ単位でメモリを確保する。malloc でバイト単位で確保できるのは、glibc が中間管理してくれているから。
デマンドページング(第5章)
プロセスが mmap でメモリ確保した段階では、実際に物理メモリが割り当てられているわけではない。 確保した仮想アドレス(ページ)に最初にアクセスした時に、ページフォルトが走り、物理メモリが割り当てされる。
コピーオンライト(第5章)
fork で子プロセスを作成した段階では子プロセス用のメモリ領域は確保されていない。ページテーブルだけコピーされる。 この段階では親プロセスと子プロセスはメモリ領域を共有している。 親プロセスまたは子プロセスのどちらかがメモリに書き込むと、対応するページだけページフォルトが走り、 親プロセス用のページと子プロセス用のページに分かれる。
スワップ(第5章)
ストレージにメモリを退避して、空きメモリを確保する仕組み。 スワップイン、スワップアウトが頻繁に繰り返される状態をスラッシングという。
ページキャッシュ(第6章)
ストレージ上のデータをメモリ上にキャッシュする仕組み。プロセスがデータを変更するとページキャッシュも変更され、後でストレージ上のデータも更新される。 メモリが不足してくると、カーネルはページキャッシュを開放する。 ページキャッシュは複数のプロセスからアクセスされる可能性がある。
ファイルシステム(第7章)
ファイルシステムが無いとユーザは自分で、そのデータについて、ストレージデバイス上でのアドレス、サイズを憶えておかないといけない。
ファイルシステムにはデータ本体(ファイル)とメタデータ(データ本体のアドレス、サイズ、名前、作成日時など)がある。
tmpfs はメモリ上のファイルシステムでアクセスが高速、再起動で消去されるというもの。/tmp
などに使われる。
ジャーナリング(第7章)
ファイルシステムの不整合を防ぐ仕組み。ファイルシステムのジャーナル領域にアトミックで実行されるべき複数のファイルシステム操作を保存してから、操作を実行する。
キャラクタデバイスとブロックデバイス(第7章)
- キャラクタデバイス: キーボード、マウスなど。シークできない
- 参考にさせてもらったサイト: https://qiita.com/koara-local/items/6484723d29afad4c3afb
- ブロックデバイス: HDDなど
golang で URL を結合する
golang で URL を結合する
- url.Parse と path.Join を使う
- path.Join だけだと正しく結合できない場合がある1
- 実行結果の
https:/golang.org/pkg/net/url
のようにスラッシュが削除される
- 実行結果の
サンプルコード
package main import ( "fmt" "net/url" "path" ) const ( srcURL = "https://golang.org/pkg/" path1 = "net" path2 = "url" ) func main() { fmt.Printf("path.Join だけで結合: %s\n", path.Join(srcURL, path1, path2)) u, err := url.Parse(srcURL) if err != nil { panic(err) } u.Path = path.Join(u.Path, path1, path2) fmt.Printf("url.Parse と path.Join で結合: %s\n", u) }
実行結果
path.Join だけで結合: https:/golang.org/pkg/net/url url.Parse と path.Join で結合: https://golang.org/pkg/net/url
参考にさせていただいたサイト
golang で UUID を使う
golang で UUID を使う
- ライブラリは https://github.com/google/uuid を使います
- GoDoc: https://pkg.go.dev/github.com/google/uuid
サンプルコード
package main import ( "fmt" "github.com/google/uuid" ) func main() { // uuid.NewRandom() はランダムなバージョン 4 の UUID を返す id1, err := uuid.NewRandom() if err != nil { panic(err) } fmt.Printf("id1, %s, %s, %s\n", id1.Variant(), id1.Version(), id1) // uuid.New() はランダムな UUID を返すか、パニックを起こす // uuid.New() は uuid.Must(uuid.NewRandom()) と等価 id2 := uuid.New() fmt.Printf("id2, %s, %s, %s\n", id2.Variant(), id2.Version(), id2) }
サンプルコード実行結果
id1, RFC4122, VERSION_4, ce33574c-5716-4c02-b833-2651b2a90a84 id2, RFC4122, VERSION_4, 5775e7ad-ee61-48f9-9e35-fd7d890b40f5
Raspberry Pi 4 でローカル DNS サーバを立てる
Raspberry Pi 4 と BIND 9 で自宅ネットワーク用の DNS サーバを立てました。
ラズパイに sv.homenetwork
というドメイン名を付けてみます。
ラズパイ情報
$ cat /etc/os-release PRETTY_NAME="Raspbian GNU/Linux 10 (buster)" NAME="Raspbian GNU/Linux" VERSION_ID="10" VERSION="10 (buster)" VERSION_CODENAME=buster ID=raspbian ID_LIKE=debian HOME_URL="http://www.raspbian.org/" SUPPORT_URL="http://www.raspbian.org/RaspbianForums" BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
ラズパイに BIND 9 をインストールする
$ sudo apt install bind9 $ named -v BIND 9.11.5-P4-5.1+deb10u2-Raspbian (Extended Support Version)
BIND 9 を設定する
192.168.1.0/24
を homenetwork
というゾーンにします。
ラズパイには sv.homenetwork
というドメイン名を付けてみます。
/etc/bind/named.conf
このファイルから /etc/bind/named.conf.options
と
/etc/bind/named.conf.local
をインクルードするみたいです。
このファイルはデフォルトのままで、設定変更しませんでした。
$ cat /etc/bind/named.conf // This is the primary configuration file for the BIND DNS server named. // // Please read /usr/share/doc/bind9/README.Debian.gz for information on the // structure of BIND configuration files in Debian, *BEFORE* you customize // this configuration file. // // If you are just adding zones, please do that in /etc/bind/named.conf.local include "/etc/bind/named.conf.options"; include "/etc/bind/named.conf.local"; include "/etc/bind/named.conf.default-zones";
/etc/bind/named.conf.options
このファイルに DNSサーバの設定を書いていきます。
$ cat /etc/bind/named.conf.options options { // サーバの作業ディレクトリ // このディレクトに後述するゾーンファイルを置くみたいです directory "/var/cache/bind"; // 自身が属するネットワークからのクエリのみ許可する allow-query { localnets; }; // ゾーン転送するセカンダリサーバは存在しないので無効にする allow-transfer { none; }; // 自身で解決できない問い合わせを別のフルリゾルバに転送する // 自宅ルータの 192.168.1.1 がフォワーダとして動作し // プロバイダのフルリゾルバに転送してくれます forwarders { 192.168.1.1; }; // フォワーダから回答が得られない場合はエラーとなる forward only; // DNSSEC を無効 dnssec-validation no; // IPv6 のクエリに対応する listen-on-v6 { any; }; };
/etc/bind/named.conf.local
このファイルでゾーンファイルを指定するみたいです。
$ cat /etc/bind/named.conf.local zone "homenetwork" IN { // 権威サーバの情報 // セカンダリサーバ(slave)は存在しないので // プライマリサーバ(master)とします type master; file "homenetwork.zone"; }; zone "1.168.192.in-addr.arpa" IN { type master; file "homenetwork.rev"; };
/var/cache/bind/homenetwork.zone
正引き用のゾーンデータを記述するゾーンファイルです。
refresh, retry, expire はセカンダリサーバが存在しないので、
使われないような気がします。
$ cat /var/cache/bind/homenetwork.zone $TTL 3600 @ IN SOA sv.homenetwork. root.homenetwork. ( 2015123105 ; serial 3H ; refresh 15M ; retry 1W ; expire 1H ) ; minimum IN NS sv sv IN A 192.168.1.10
/var/cache/bind/homenetwork.rev
逆引き用のゾーンデータを記述するゾーンファイルです。
$ cat /var/cache/bind/homenetwork.rev $TTL 3600 @ IN SOA sv.homenetwork. root.homenetwork. ( 2015123105 ; serial 3H ; refresh 15M ; retry 1W ; expire 1H ) ; minimum IN NS sv.homenetwork. 10 IN PTR sv.homenetwork.
設定ファイルのチェックを行う
$ sudo named-checkconf $ sudo named-checkzone 1.168.192.in-addr.arpa /var/cache/bind/homenetwork.rev zone 1.168.192.in-addr.arpa/IN: loaded serial 2015123105 OK $ sudo named-checkzone homenetwork /var/cache/bind/homenetwork.zone zone homenetwork/IN: loaded serial 2015123105 OK
ラズパイが問い合わせる DNS サーバを自分自身にする
自宅ネットワークでは DHCP を使用しているので
ラズパイの /etc/dhcpcd.conf
に以下を追記します。
static domain_name_servers=127.0.0.1 static domain_name=homenetwork
BIND 9 の再起動
BIND 9 を再起動して、ステータスに異常がないか確認します。
$ sudo systemctl restart bind9 $ sudo systemctl status bind9
DHCP サーバが配布する DNS サーバのアドレスをラズパイのアドレスに変更する
DHCP を使用している場合は DHCP サーバ の設定を変更します。
正引きテスト
作業用 PC からテストします。まずは直接 192.168.1.10 に問い合わせてみます。
status: NOERROR
で flags
に aa
があります。
大丈夫そうです。
$ dig @192.168.1.10 sv.homenetwork ; <<>> DiG 9.16.1-Ubuntu <<>> @192.168.1.10 sv.homenetwork ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61550 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1 ... ;; ANSWER SECTION: sv.homenetwork. 3600 IN A 192.168.1.10 ;; AUTHORITY SECTION: homenetwork. 3600 IN NS sv.homenetwork. ...
外部のドメインも試してみます。今度は flags
に ra
があります。
大丈夫そうです。
$ dig @192.168.1.10 www.google.com ; <<>> DiG 9.16.1-Ubuntu <<>> @192.168.1.10 www.google.com ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 63049 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ... ;; ANSWER SECTION: www.google.com. 42 IN A 172.217.24.132 ...
次に systemd-resolved を経由して問い合わせてみます。
こっちも大丈夫そうです。
$ dig sv.homenetwork ; <<>> DiG 9.16.1-Ubuntu <<>> sv.homenetwork ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30774 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ... ;; ANSWER SECTION: sv.homenetwork. 3038 IN A 192.168.1.10 ...
逆引きテスト
dig @192.168.1.10 -x 192.168.1.10
と dig -x 192.168.1.10
を試して、
大丈夫なことを確認しました。
systemd-resolved 経由の問い合わせがうまくいかないとき
systemd-resolve --status
してCurrent DNS Server
を確認するsystemctl restart systemd-resolved
で systemd-resolved を再起動してみる
参考にさせていただいたサイト
Raspberry Pi 4 のセットアップ
ラズパイ4スターターキット 4GB版 を秋月電子で購入しました。
行った設定などを書いておきます。
OS 情報
ラズパイ4スターターキット 4GB版
には Raspbian インストール済 microSD が付属してます。
そのため OS は付属の Raspbian を使うことにしました。ラズパイに microSD を挿すだけで OK です
$ cat /etc/os-release PRETTY_NAME="Raspbian GNU/Linux 10 (buster)" NAME="Raspbian GNU/Linux" VERSION_ID="10" VERSION="10 (buster)" VERSION_CODENAME=buster ID=raspbian ID_LIKE=debian HOME_URL="http://www.raspbian.org/" SUPPORT_URL="http://www.raspbian.org/RaspbianForums" BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
OS の GUI を無効化する
GUI は使わないので無効にします。
sudo raspi-config
を実行します1 System Options
>S5 Boot / Auto Login
>B1 Console
またはB2 Console Autologin
を選択して、ラズパイを再起動します- 私は
B1 Console
を選択しました
- 私は
Wi-Fi を無効化する
有線で接続するので Wi-Fi を無効にします。
/etc/rc.local
にiwconfig wlan0 txpower off
を追記して、再起動します
起動中に LED を点滅させる
起動してるのか分かりにくいので LED を点滅させます。
/boot/config.txt
にdtparam=act_led_trigger=heartbeat
を追記して、再起動しますact_led_trigger
で緑色 LED 、pwr_led_trigger
で赤色 LED が点滅します
ブラウザからラズパイを管理できるようにする
Cockpit をインストールすると、ブラウザからラズパイの状態確認やシャットダウン、再起動が行えます。
Cockpit インストール
$ sudo apt install dirmngr $ sudo apt install cockpit
Cockpit で HTTPS を無効化する
/etc/cockpit/cockpit.conf
に AllowUnencrypted = true
を設定します。
$ cat /etc/cockpit/cockpit.conf [WebService] AllowUnencrypted = true
ブラウザからログインする
http://ラズパイの IP アドレス:9090
にアクセスします