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 stdouthello 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 フォーマットの実行ファイルの情報を表示する。以下のサンプルでは、メモリマップ開始アドレスは 0000000000002580000000000000a000 になっている。

$ 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 を通知する。

mmapmalloc(第5章)

mmap ではページ単位でメモリを確保する。malloc でバイト単位で確保できるのは、glibc が中間管理してくれているから。

デマンドページング(第5章)

プロセスが mmap でメモリ確保した段階では、実際に物理メモリが割り当てられているわけではない。 確保した仮想アドレス(ページ)に最初にアクセスした時に、ページフォルトが走り、物理メモリが割り当てされる。

コピーオンライト(第5章)

fork で子プロセスを作成した段階では子プロセス用のメモリ領域は確保されていない。ページテーブルだけコピーされる。 この段階では親プロセスと子プロセスはメモリ領域を共有している。 親プロセスまたは子プロセスのどちらかがメモリに書き込むと、対応するページだけページフォルトが走り、 親プロセス用のページと子プロセス用のページに分かれる。

スワップ(第5章)

ストレージにメモリを退避して、空きメモリを確保する仕組み。 スワップイン、スワップアウトが頻繁に繰り返される状態をスラッシングという。

ページキャッシュ(第6章)

ストレージ上のデータをメモリ上にキャッシュする仕組み。プロセスがデータを変更するとページキャッシュも変更され、後でストレージ上のデータも更新される。 メモリが不足してくると、カーネルはページキャッシュを開放する。 ページキャッシュは複数のプロセスからアクセスされる可能性がある。

ファイルシステム(第7章)

ファイルシステムが無いとユーザは自分で、そのデータについて、ストレージデバイス上でのアドレス、サイズを憶えておかないといけない。 ファイルシステムにはデータ本体(ファイル)とメタデータ(データ本体のアドレス、サイズ、名前、作成日時など)がある。 tmpfs はメモリ上のファイルシステムでアクセスが高速、再起動で消去されるというもの。/tmp などに使われる。

ジャーナリング(第7章)

ファイルシステムの不整合を防ぐ仕組み。ファイルシステムのジャーナル領域にアトミックで実行されるべき複数のファイルシステム操作を保存してから、操作を実行する。

キャラクタデバイスとブロックデバイス(第7章)

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 を使う

サンプルコード

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 というドメイン名を付けてみます。

f:id:arrowislandnai:20201231170609p:plain

ラズパイ情報

$ 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/24homenetwork というゾーンにします。
ラズパイには 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: NOERRORflagsaa があります。
大丈夫そうです。

$ 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.

...

外部のドメインも試してみます。今度は flagsra があります。
大丈夫そうです。

$ 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.10dig -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.localiwconfig wlan0 txpower off を追記して、再起動します
    • sudo iwconfig wlan0 txpower off を実行しただけでも Wi-Fi は無効になります。ただし再起動後にまた Wi-Fi が有効になります
    • このため /etc/rc.localとかに書いておいた方がいいと思います

起動中に LED を点滅させる

起動してるのか分かりにくいので LED を点滅させます。

  • /boot/config.txtdtparam=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.confAllowUnencrypted = true を設定します。

$ cat /etc/cockpit/cockpit.conf
[WebService]
AllowUnencrypted = true

ブラウザからログインする

  • http://ラズパイの IP アドレス:9090 にアクセスします f:id:arrowislandnai:20201230165332p:plain

参考にさせていただいたサイト