mysqldumpでフルバックアップとリストア

MySQLのバックアップにはいくつか方法があります。その中でも最もオーソドックスな方法であるmysqldumpを使った方法についてまとめておきます。

mysqldumpを使った方法の特徴としては

  • 手軽である
  • 無停止でバックアップが取れる(オンラインバックアップ)
  • データのダンプを出力する(論理バックアップ)
  • リストアに時間がかかる

などがあります。

1. セットアップ

こちらの記事にしたがってMySQLサーバーを用意します。

2. ダンプの取得

以下のようにmysqldumpコマンドを使いダンプデータを取得します。

$ sudo mysqldump example > backup/backup-$(date +%Y%m%d).sql

以上です。簡単。

リストア

リストアのためDROP TABLEしておきます。

リストアの際には先ほどのダンプデータをリダイレクトで入力するだけ。

$ sudo mysql example < backup/backup-20181029.sql

リストアも簡単。

全てのデータベースをバックアップしたい

--all-databasesオプションを使います。

$ sudo mysqldump --all-databases > backup/backup-$(date +%Y%m%d).sql

これならDROP DATABASEした状態でも以下でリストア可能。

$ sudo mysql < backup/backup-20181029.sql

バックアップを圧縮して取りたい

gzipにパイプすれば圧縮も簡単。

$ sudo mysqldump --all-databases | gzip > backup/backup-$(date +%Y%m%d).sql.gz

リストアもgunzip経由で。

$ gunzip < backup/backup-20181029.sql.gz | sudo mysql

cronで定期的にバックアップしたい

最後にcronの設定をしてみます。MySQL接続のため.my.cnfは事前に設定しておきましょう。 例えば毎日午前3時にバックアップを取りたかったらcrontab -eに以下を追加します。

0 3 * * * /usr/bin/mysqldump -u root --all-databases > /path/to/home/backup/full-$(date +\%Y\%m\%d).sql

参考

MySQLサーバーの設置

チュートリアルなどでMySQLサーバーが欲しいことがよくあるので手順をまとめておきます。OSはUbuntu18.04TLSです。

【参考】

1. インスタンスの作成

自分の環境に合わせてインスタンスをセットアップします。今回はGCP上に設置します。

$ gcloud compute instances create mysql-example3 --machine-type=f1-micro --image=ubuntu-1804-bionic-v20181029 --image-project=ubuntu-os-cloud
Created [https://www.googleapis.com/compute/v1/projects/playground-192621/zones/europe-west1-b/instances/mysql-example3].
NAME            ZONE            MACHINE_TYPE  PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP     STATUS
mysql-example3  europe-west1-b  f1-micro                   10.132.0.5   35.205.118.215  RUNNING

2. 作成したインスタンスSSHする

$ gcloud computh ssh mysql-example3

3. mysqlのダウンロード

$ sudo apt-get update && sudo apt-get install -y mysql-server

これだけ。一応ps aux | grep mysqlで確認しておきます。

4. .my.cnfの設置

便利のためにホームディレクトリに.my.cnfファイルを設置しておきます。中身はこんな感じ(userとpassは適宜変えてね)。

[client]
user = root
password = 

5. ダミーデータの作成と導入

ここのサイトでダミーのデータを簡単に作れます。

6. .sqlファイルを作成したらインスタンスに送る。

$ gcloud compute scp data.sql mysql-example3:~

7. データベースの作成

$ sudo mysql -e 'create database example;'

7. リダイレクトでmysqlコマンドに繋ぐ

$ sudo mysql -- example < data.sql

ここまで5分でできるようになろう。

【メモ】AWKについて

AWKについて調べた際のメモおよび「『シェル芸』に効くAWK処方箋」の読書メモです。

  • マニュアルはここ(あとで読む)
  • 【 awk 】コマンド(基本編)――テキストの加工とパターン処理を行う
    • awk ‘pattern {action}’ filename
    • $番号でフィールドを指定
    • スクリプトをファイルに保存して実行することもできる
    • 入力の一行目を削除したい場合はパイプラインの前段にsed 1dを通しておく
    • いくつか組み込み変数がある
    • デフォルトで拡張正規表現でパターンの位置に/pattern/で記述する
    • &&||で条件をつなぐことができる
    • ~はマッチ演算子で例えば$4 ~ /pattern/というようにどのフィールドのマッチングをとるか指定できる
    • BEGIN { … }で全行処理前のアクション、END { … }で全行処理後のアクションを記述できる
    • アクション内では複数の処理を実行してもいい
    • shebang#! /usr/bin/awk -f
    • 一行単位の処理では不可能な条件分岐は制御構文を使う
    • if, switch ~ case, while, do ~ while, forなど一通り使える
    • いくつかの組み込み関数が使える
  • [初心者向け]Awkの使い方
  • AWKのまとめ
    • 関数定義はfunction name(args) { … }で記述できる
  • AWK リファレンス
    • 独立したスクリプト言語なのでawkスクリプト/インタプリタというのが正確
    • 複数のパターン・アクションが指定された場合、入力の行ごとに上から順に評価・実行される
      • next構文を使うと以下のパターン・アクションを無視して次の行にいける(for分のcontinue的役割)
      • そのほかnextfilereturnexitもある
    • —assign var=“value”awkスクリプトに変数を渡すことができる
    • あるいはスクリプト内から$変数名で直接シェル変数を参照してもいい
    • 正規表現に変数を使いたい場合はmatch関数を使う
  • AWK - wikipedia
    • もともとはテキスト処理プログラムとして開発された
    • プリミティブ型は数値と文字列のみ
    • 変数は関数の引数を除いてはグローバルスコープを持つ
      • 関数の引数を余分に定義してローカル変数とするきな臭いプラクティスがある
  • awk とはどんな言語か
  • AWKコマンドの使い方
    • getline関数で次の行を取得できる
  • 実例でわかる awk: 第 1 回
    • 区切り文字は-Fオプションで指定
    • スクリプトで実行する場合は区切り文字を(オプションではなく)スクリプト内で設定するのがよい
    • 変数に数値を文字列として代入しても算術演算が適用される
  • はじめてのAWK
    • 配列はA[2,4,6,8]で表現できる
    • grepawkで書いたコードがあるのであとで読む
  • Getting Started with awk
  • なるべく書かないawkの使い方
    • awkスクリプトとは 'パターン{アクション}' の集合”
    • cutとの違いは単純に使いやすさの問題
      • デフォルトの区切り文字が一文字以上の空白であったりするところ
    • awkとは、表計算コマンドなのかもしれない。”
    • 組み込み変数は7つ
  • 実用 awk ワンライナー
    • awkAWKの処理系のことを指しており、別物なので注意
    • パターンをカンマで区切ると”その範囲内はマッチする”という動作になる
    • レシピ集になっているので必見
  • 「シェル芸」に効くAWK処方箋
    • 第1章:導入
      • シェル芸:ワンライナーで文字列加工すること
      • パターンは条件式そのもの
      • 比較・マッチング演算子の返り値は数字の0または1
      • 代入の戻り値は左辺値
      • 戻り値が0の場合でも数値を表示したいときはダブルクオートをつけて文字列として処理させる
    • 第2章:行操作
      • AWKでtailを作りたいときはtacを使った方がいい
      • カンマは範囲指定演算子と呼ばれ、スイッチのような働きをする
      • カンマの後半を偽な値にすることで、マッチした行以降という表現になる
    • 第3章:列操作
      • AWKとcutの最大の違いは区切り文字に正規表現が使えるかどうか
      • コマンドラインで組み込み変数を指定するときは-vオプションを使う
      • OFSで期待通りに出力するには$1 = $1でレコードの再構築をする必要がある
    • 第4章:文字列関数
      • AWKのインデックスは1から始まるので注意
      • substr関数とindex関数の組み合わせで文字列を抜き出す
      • match関数は組み込み変数に結果を代入するので後から参照できる
        • grep -oと等価なので文字数の少ないそっちがおすすめ
      • sub/gsubの返り値は置換した個数であり置換結果ではないので注意
    • 第5章:数値演算
      • 数字は全て倍精度不動少数点数扱い
    • 第6章:配列・連想配列
      • 配列はインデックスが数値、連想配列は文字列なだけで同じデータ構造
      • for i in arrayでループ処理できるがiに入るのは値ではなくインデックス
      • split関数で手軽に配列を作成できる
      • if “key” in arrayで配列の検査ができる
    • 第7章:GNU AWK拡張機能
      • match関数の第3引数でマッチした値を参照できる
    • 第8章:パイプ
      • かつてsrandを使って時刻を取得するという裏技があった
      • gensub関数を使えば置換後の文字列を返り値として取得できる。後方参照もできる
      • 双方向パイプを使ってAWK内で他のコマンドの実行、値の受け渡しができる
      • (xargsの本質は列の行への変換)
      • 双方向パイプでソケットに直接書き込むことでインターネット通信すらできる
        • まあ、curl/wgetを使おうね……
    • 第9章:GNU拡張とCSVファイルの扱い
      • フィールドの中のパターンを指定するFPAT変数を使うことでカンマをフィールドに含むCSVも簡単に扱える
      • gawkは多次元配列を正式にサポートする
      • BEGINFILE部によりファイルが存在するかを確認できる
      • @変数()で変数を関数として呼び出す
    • 第10章:もっとGNU拡張
      • MPFRによる浮動小数点数演算の精度向上
      • @loadによりライブラリ・ファイルの読み込みができる
      • fork関数でforkシステムコールを呼び出せる
      • in-placeオプションでファイルの更新ができる
    • 第11章:実践
      • コマンドをAWKで書き換えていくという方法がある
      • さらに応用としてサーバを立てたり、CMSを作ることもできる
    • 第12章:Podcastダイジェスト

まとめ

  • awkAWKという処理系の実装であり両者は別物
  • awk ‘pattern {action}’ [filename]、特にパターン・アクションの組み合わせの集合が全て
  • 独立したスクリプト言語なのでサーバプログラムも作れる

とりあえずなんとか読むことはできるはず。書けるようになるのはまた今度頑張る。

【読了】新しいシェルプログラミングの教科書

一介のソフトウェアエンジニアとしてシェルスクリプトくらいさらさらと書けますよね?......僕は書けません。というわけで「新しいシェルプログラミングの教科書」を読んだのでその際のメモです。

www.amazon.co.jp

  • Chapter01: シェルとは
    • ログインシェルの確認は$ cat /etc/shells
    • 新しいシェルの起動は$ bash、終了は$ exit
    • シェル=コマンドインタプリタ
    • 「どれだけ便利にコマンドを実行するか」
    • コマンドインタプリタの機能をカーネルから取り出したもの → 「1つのことをうまくやらせる」
    • コマンド実行はforkし、子プロセス上でexecする
  • Chapter02: シェルスクリプトとは
    • シェルスクリプトを書く = シェルでプログラミングする
    • コマンドを組み合わせて新しいコマンドをつくる
    • シェルスクリプトのメリット
      • 再利用性
      • typoの防止
    • デメリット
    • 利用例
      • 新たなコマンドの作成
        • コマンドが実行可能フィアルなのかシェルスクリプトなのかはfileコマンドで確認できる
  • Chapter03: 基本
    • 改行=エンターキー = 実行
    • 複数行に書きたいときは\
    • 一つの行にまとめたいときは;
    • 複数行コメントは存在しない
    • bashコマンドは引数をスクリプトとして読み込んで実行→実行権限がいらない
      • スクリプトがどのシェルで書かれているかを知っていないといけない
      • ./scriptはいつでも有効だが、bash scriptはscriptが例えばバイナリだと動かない
  • Chapter04: 変数
    • 値は文字列として解釈される
    • $をつけて参照
    • 明示的に参照するときは${}で囲む
      • 変数のすぐ後ろに入力がある時使える
    • 環境変数 = コマンドに引き継がれる変数
      • コマンドから参照できるかどうか
      • exportで変数を環境変数にする
      • 慣例として変数名を大文字にする
    • $1, $2, $3 … でスクリプトに渡された引数を参照できる
      • $*, $@で全てを一度に参照できる
      • 通常は$@で参照
    • IFSは区切り文字を表し、参照する際はecho "$IFS" | od -aとする
    • 特殊パラメータ
      • $# ... 引数の個数
      • $? ... 前回コマンドの終了ステータス
      • $$でプロセスIDを取得、tmpのファイル名などに利用
    • declareコマンドで変数の型を宣言できる
      • 整数型の場合代入の右辺は算術評価される
    • 配列の宣言はvar=(a b c)、要素の参照は${var[i]}、追加はvar+=(value1 value2)、削除はunset var[i]、要素数の取得は${#var[@]}
    • 連想配列を作る際はdeclare -Aで明示する必要がある
  • Chapter05: 展開・クオーティング
    • 展開の種類 ... パス名展開、ブレース展開、チルダ展開、パラメータ展開、コマンド置換、算術評価・展開、プロセス置換、履歴展開
    • パス名展開
      • マッチするファイルが存在する場合のみ有効
      • 正規表現とまた違うマッチング規則なので注意
      • ドットで始まるファイルを展開したいときは明示する必要がある
    • ブレース展開
      • マッチするファイルがなくてもいい
        • 新しいファイルを作るときなどに利用
    • チルダ展開
    • パラメタ展開
      • $varとか${var}とかのこと
      • ${name:-value} ... デフォルト値の指定
      • ${name:=value} ... デフォルト値を代入して返す
      • ${name:?value} ... 値がない時のエラーメッセージの指定
      • それぞれ:を除くと値が未設定の時のみの動作になり、空文字のときは空文字がそのまま返る
      • ${name:index:length}で文字列のスライシングができる
      • ${#name} ... 文字数が返る
      • ${name#pattern} ... 先頭から最短マッチでpatternを切り落とす
        • ファイル名の取得 ${path##*/}
      • ${name%pattern} ... 末尾から最短マッチでpatternを切り落とす
      • それぞれ記号を二つ重ねると最長マッチになる
      • ${name/pattern/substring} ... マッチするパターンを置換して返す
        • 使い所不明
    • コマンド置換
      • $()と``は等価
    • 算術評価・展開
      • ((算術式)) ... 終了ステータスが返る
      • $((算術式)) ... 評価結果が返る
      • exprコマンドでもいいけどこっちの方が早い
      • letも等価だが、読みやすさを考えると代入のみに使うのがおすすめ
    • プロセス置換
      • <()で標準入力が二つ欲しい時にパイプラインの代わりとして使える
      • (パイプラインの本質は一時ファイルなんだなー...。)
    • 履歴展開
      • !から始まる入力でコマンドの実行履歴を参照できる
      • スクリプトで使うことはまずない
    • クオーティングで特殊文字を無効化する
      • \特殊文字エスケープ
      • ''で全ての展開のエスケープ
      • ""は特殊文字を一部だけ無効化する
        • 「パラメタ展開」「コマンド置換」「算術展開」
  • Chapter06: 制御構造
    • 条件分岐
      • if (条件); then ~ elif ~ else ~ fi
      • 条件はコマンドとして実行され、終了ステータスが0のとき真、0以外のとき偽に評価される
      • []はtestという"コマンド"
      • :はヌルコマンドといい、必ず正常終了する
      • command1 && command2 ... command1の終了ステータスが0のときのみcommand2を実行する
      • command1 || command2 ... command1の終了ステータスが0以外のときのみcommand2を実行する
      • []に対して[[]]を使う最大のモチベーションは"読みやすさ"
        • [[]]はコマンドではなく構文
        • パターンマッチも使える
    • ループ
      • for (変数) in (単語リスト) do ~ done
        • break、continueが使える
      • case (文字列) in (パターン1) ~ ;; (パターン2) ~ ;; ... esac
        • *)でデフォルト句を表す
      • whileとその逆のuntilもあるよ
  • Chapter07: リダイレクト・パイプ
    • 標準入出力がファイルだからリダイレクト(元|先)つまり標準入出力の切り替え先もファイルだよ
    • (ファイルディスクリプタ番号)> (接続先ファイル)が基本形
    • &(ファイルディスクリプタ番号)で他の接続先を指定できる
      • 標準出力にエラー出力を繋ぐ2>&1は定型文
    • 出力を捨てるときは/dev/nullを接続先に指定
      • 標準出力を捨てるのはifの条件部など
      • 標準エラーを捨てるのはエラーを無視する時
      • 両方捨てる決まり文句は> /dev/null 2>&1
    • >で上書き、>>で追記
    • (コマンド) <<< (終了文字列) ~ (終了文字列)でヒアドクが書ける
      • 終了文字列をクオーティングすることでクオーティングのルールを適用できる
    • パイプラインは一時ファイルが本質
    • {}()で複数コマンドのグループ化ができる
      • 複数コマンドの出力をまとめて出力したいときとか
      • {}は同一プロセス内、()はサブシェル内で実行される→変数を参照する時注意
  • Chapter08: 関数
    • 三通りの書き方の中で(関数名)() { ~ }が一般的
    • ローカルスコープで変数を宣言したいときは必ずlocalを使う
    • 引数の参照は同様に位置パラメータを使う
    • returnで終了ステータスを明示的に返すことができる
  • Chapter09: 組み込みコマンド
    • :
      • 無限ループが作れる
    • printf
      • printf '%s\n' "~"をよく使う
    • command, builtin
      • commandは組み込みコマンドと実行可能ファイル、builtinは組み込みコマンドから探す
    • type
      • コマンドの種別を調べたい時
    • shift
      • 解析済みの位置パラメータを削除するとき
      • オプションの解析とかで意外とよく使う
    • set
      • 目的が複数あるという意味で一番UNIXらしくないコマンド
      • 1)シェルのオプションの表示・変更、2)位置パラメータの設定
    • unset
      • 変数を削除したい時
    • read
      • 標準入力から一行取り出すとき
      • ループのリスト部によく使う例えばwhile IFS= read -r line do ~ done
        • 入力行がlineに代入される、変数名を複数指定したときはIFS区切りでそれぞれ代入される
    • trap
      • 指定したシグナルを補足して指定の処理をする
      • 終了前の後処理を記述したいときとか
    • wait
      • バックグラウンドで実行した処理の終了を待ちたいとき
    • exec
      • 現在のシェルをコマンドのプロセスに差し替えたいとき
      • 現在のシェルのリダイレクト先を変更するときにも使う
    • eval
      • コマンドを動的に組み立てて実行したいとき
      • 実行シェルの環境変数も設定したいスクリプト、具体例はssh-agent
  • Chapter10: 正規表現・文字列操作
    • grep, sedそれからawkも不自由なく使えるようになろうね
    • grepはパターンの検索、sedは置換に使う
  • Chapter11: シェルスクリプトの実行
    • shebangはexecシステムコールの仕様
      • ファイルの先頭2バイトが#!だった時に内容を(バイナリではなく)テキストファイルとして解析する
      • #!以降をコマンドとしてファイル名を引数に実行する
        • → 実行コマンドをスクリプト内に記述できる = 呼び出し側はどのコマンドかを気にせずただ実行するだけでいい
    • sourceは引数ファイルを現在のシェル内で実行する
      • 変数など環境のセットアップを一つのファイルにまとめておける
  • Chapter12: 具体例
  • Chapter13: シェル補完の書き方とか
  • Chapter14: テスト・デバッグ
    • 静的解析
      • noexecオプション ... 構文チェック
      • ShellCheck ... Webサイトおよびコマンドラインツール
    • Batsというツールでテストスクリプトが書ける
    • デバッグ
      • シェルオプション
        • verbose ... 実行するコマンドラインの出力
        • xtrace ... 各種展開の結果を出力
        • nounset ... 未定義変数参照時にエラーとともにexitする
        • errexit ... 終了ステータスが0以外のコマンドを実行した直後にexitする
    • パイプラインで繋いだ後ろはサブシェルなので変数を参照できない点に注意

まとめ

「新しいLinuxの教科書」が読みやすかったので期待しましたが期待通りでした。シェルスクリプトを学ぼうという方には非常におすすめです。文法がわかったのでどんどん書いて身につけていこうというところですが実務ではあんまり使わないのでどうしたものか......。

7~9月振り返り

久し振りになってしまいました。

やったこと

今回はテーマが月ごとに1つずつありました。それぞれ7月テスト/TDD・8月オブジェクト指向・9月セキュリティです。このテーマで勉強を始めるにあたって結構な不安がありました。それは設計という正解のないテーマに対して、成長を実感できるか、本を読んだだけで何も変化を感じずモヤモヤして終わるのではないかというものです。結論から言うと心配していたことは全く起こりませんでした。つまり、3ヶ月前の自分と比べて今書いているコードが段違いにきれいになっていることを実感しています。具体的にどうしているかは別の機会に紹介したいと思いますが、TDDを積極的に取り入れています。主なメリットは 1) 仕様漏れを実装前に発見できる 2) 内部実装を知る前にあるべきインターフェイスを検討できる 3) テストさえ通れば中身がどうあれ動くことを確認できるなどです。また今年の頭に読んだもののモヤモヤしていたパターンという分野があります。そのモヤモヤへの答えはケントベックのTDD入門にありました。これまで"良い設計"が実装されるものだとずっと思い込んでいたのですが「設計と実装は互いにフィードバックする」というきれいなコードを書く人にとっておそらく常識といえる考え方に出会えたのはこの三ヶ月で最も価値のあることでした。パターンとの付き合い方がわかったことで本に書かれていることと実際とのギャップが埋まった感覚があります。

セキュリティに関してはテーマは非常に面白かったものの時間不足でした。せめてCTFに参加してみたかったです。

良かった点・反省点・改善点

  • ✅いっぱい読んだ
    • オブジェクト指向に関わる有名な本は一通り触れることができたと思います。おそらくこの分野の本であればどんな本でもすっと読めるだけのバックグラウンドが揃ったのではないでしょうか。
  • ✅即実践
    • 仕事にダイレクトに活かせる分野だったのでフィードバックが早く、学んだことを効率よく身につけることができました。
  • ❌本しか読んでない
    • インプットのチャンネルを増やしたいと思う次第です。
  • ❌学習のプランが大雑把だった
    • 目標を設定するのが難しかったし、前提の知識が少なかったため想像しにくい部分もありましたが、やはり目標があってこそ効率的に学習できるので、プランニングの時間をしっかりとるべきでした。
  • ❌アウトプットできなかった
    • 正解のないテーマに対してどの角度で切り込んでいけば、本に書いていることをただ紹介するだけではない切り口を見いだせるのかずっと考えていたものの結局思いつくことができませんでした。この先もコードを書く以上常に向き合っていくテーマなのでいつか自分の言葉で語れればと思います。
    • それと同時にアウトプットを仕組化する必要を感じています。

この三ヶ月は旅行に熱心になっていて、勉強が途切れ途切れになってしまったのがやや寂しくもあります。今年残り三ヶ月も頑張っていきます。

4~6月振り返り

やったこと

学んだことに関する振り返りは以下にまとめました。

qiita.com

当初はOSの勉強というテーマで、Linuxディストリビューションや運用など上の方に伸びていくことを想定していましたが、CPUやハードウェアなど低いレベルの方向に向かってしまいました。実務に直接役立つ知識は得られませんでしたが結果オーライだと思っています。動機は記事にも書いたように「基礎を知れば最新技術に素早く対応できる」という説を確かめることです、がもう少し掘り下げたいと思います。学生時代の陸上で経験したことの一つに「身体能力を十分高めた(雑に言えば筋トレを頑張った)人の方が技術面の向上も早く、記録の伸びが大きい」という発見、もっと言えば反省がありました。プログラマとしてこれまで新しいことを学んでも上滑りする、思ったように理解が深まらない、と感じる原因の一つとしてプログラミングにおける基礎能力の不足があるのではないかという疑問を抱いていました。コンピュータサイエンスの知識を基礎能力とする妥当性はさておきこれが今回システムを学んだ背景です。3ヵ月の勉強ではこれが正しい(あるいは間違っている)と確信するには至りませんでしたが、「システムプログラミングを学ぶのは楽しい」という発見がありました。前回学んでいた機械学習は「面白いけどもういいかな」という分野でしたがシステムの勉強はまだ興味が尽きないという意味で割に自分に向いている、と言えるかもしれません。コンパイラなど触れていないトピックはたくさんあるので、一度時間を置いてまた帰ってきたいと思います。

良かった点・反省点・改善点

  • 実際に手を動かした
    • 実際に手を動かすことが学習効率に影響することは認識していたため、当初から題材を探していました。Raspberry Piという案を閃き、実際にハードウェアと格闘しながらシステムについて学べたのは非常に大きかったです。最終的な成果を出すところまでは至りませんでしたが、手を動かして学ぶことの重要性を再認識しました。今後も強く意識して続けて行きたいと思います。
  • 中だるみしてしまった
    • Raspberry Piに実際システムを組んでみる段階に至って、ハードウェア開発の難しさからモチベーションを維持するのが難しくなってしまいました。加えて旅行が立て込んである期間すっぽり抜けてしまったため最後中途半端な形で終えてしまったのは非常に残念です。旅行が続いたのは季節柄なので仕方ないとして、モチベーションを維持する工夫の必要性を感じました。定期的なアウトプットを自分に課す、など思い浮かびますがみなさんどうしているのでしょう。
  • より多角的に学べた
    • 前回の反省を活かし、なるだけ多くの資料を手元に用意して、読んだり読まなかったり一部だけ読んだりといろんな角度から学ぶことで立体的な知識を得ることができました。
  • (ついに)英語でのアウトプットを始めた
    • たったのブログ一本、しかも日本語で書いたことの翻訳ですが英語でのアウトプットを果たしました。今後もコンスタントに続けていけたらいいなと思います。

【Raspberry Pi】システムタイマ

Raspberry PiでLチカ - 技術について語るときに僕の語ることでLED点滅をしたときにdelay(50000)という関数を使いましたが、「CPU50000クロックってどんだけやねん」という話なのでシステムタイマを利用して秒で指定できるようにしたいと思います。

なお主に以下の書籍の第5章およびサンプルコードを参考にしています。

システムタイマ

システムタイマのレジスタもGPIOと同じようにメモリマップト方式でメモリ上にマップされています。ペリフェラルマニュアルのp.172を参照するとシステムタイマが0x20203000に存在することがわかります1

システムタイマもいくつかレジスタを持ちますが今回は64bitのフリーランニングカウンタを使いたいと思います。

get_systime()関数

システムタイマの値を読む、get_systime()関数を定義します。

uint64_t get_systime(void)
{
    uint32_t clo, chi;

    chi = *(volatile uint32_t *) 0x20003008;
    clo = *(volatile uint32_t *) 0x20003004;

    return (chi << 32) + clo;
}

先ほどのフリーランニングカウンタは32bitずつLOWとHIGHの2つのレジスタに分かれており、メモリ上アドレスはそれぞれ0x202030040x20203008 になります。それぞれclo、chiに読み込んだのち、HIGHを32bit分繰り上げLOWを足して返しています。

ここでもしHIGHを読んだ直後、LOWを読む前にLOWがオーバーフローした場合、HIGHの値がインクリメントされてしまうので以下のようにもう一度読み直すことでこれに対応します。なおオーバーフローの間隔は70分程度あるので一回チェックすれば十分です。

uint64_t get_systime(void)
{
    uint64_t t;
    uint32_t clo, chi;

    chi = *(volatile uint32_t *) 0x20003008;
    clo = *(volatile uint32_t *) 0x20003004;

    if (chi != *(volatile uint32_t *) 0x20003008) {
        chi = *(volatile uint32_t *) 0x20003008;
        clo = *(volatile uint32_t *) 0x20003004;
    }

    t = (chi << 32) + clo;

    return t;
}

delay_s()関数

このget_systime()関数を使って秒で指定するdelay関数を作りたいと思います。流れは以下の通りです。

  1. 現在時刻+delayを目標時刻とする。
  2. ループ条件で現在時刻を再度取得し、目標時刻を過ぎていたらループを抜ける。

関数は以下の通りです。

void delay_s(uint32_t delay)
{
    uint64_t target = get_systime() + delay * 1000 * 1000; // 1
    while (get_systime() < target); // 2

    return;
}

再びLチカ

点灯と消灯の間にdelay_s()関数を挟んでLEDを点滅させます。

void kernel_main(uint32_t r0, uint32_t r1, uint32_t atags)
{
    (void) r0;
    (void) r1;
    (void) atags;

    *(volatile uint32_t *) 0x20200010 = (1 << 21);

    while (1) {
        *(volatile uint32_t *) 0x20200020 = (1 << 15);
        delay_s(1);
        *(volatile uint32_t *) 0x2020002C = (1 << 15);
        delay_s(1);
    }
}

ちなみにPWM的に制御したい時には以下のコードを参考にします。

実装にあたりdelay関数のマイクロ秒版を用意しました。

void kernel_main(uint32_t r0, uint32_t r1, uint32_t atags)
{
    (void) r0;
    (void) r1;
    (void) atags;

    uint32_t brightness = 255, speed = 16, up = 0;

    *(volatile uint32_t *) 0x20200010 = (1 << 21);

    while (1) {
        if (brightness > 0) {
            // turn off LED for "brightness" us
            *(volatile uint32_t *) 0x2020002C = (1 << 15);
            delay_us(brightness);
        }

        if ((255 - brightness) >= 0) {
            // turn on LED for "255 - brightness" us
            *(volatile uint32_t *) 0x20200020 = (1 << 15);
            delay_us(255 - brightness);
        }

        // decrement speed
        speed--;
        if (speed == 0) {
            // recover it
            speed = 16;

            // if the brightness is getting brighter
            if (up) {
                // not maximum yet
                if (brightness < 255)
                    // increment brightness
                    brightness++;

                // hit the maximum
                if (brightness == 255)
                    // go downward
                    up = 0;
            } else { // LED is getting dimmer
                // not minimum yet
                if (brightness > 0)
                    // decrement brightness
                    brightness--;

                // hit the minimum
                if (brightness == 0)
                    // go upward
                    up = 1;
            }
        }
    }
}

明るさを255段階に分け、255マイクロ秒のうちbrightnessマイクロ秒だけLEDを点灯させます。これをspeed回繰り返したのちup = 1であれば明るさを一つあげ、up = 0であれば一つさげ、明るさが0または255に達した時にはupを反転させます。


  1. 0x3F203000じゃんと思うかもしれませんが前回記事の補足を参照してみてください。