【メモ】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 - wikipedia
- awk とはどんな言語か
- AWKコマンドの使い方
- getline関数で次の行を取得できる
- 実例でわかる awk: 第 1 回
- はじめてのAWK
- Getting Started with awk
- なるべく書かないawkの使い方
- 実用 awk ワンライナー
- 「シェル芸」に効くAWK処方箋
- 第1章:導入
- 第2章:行操作
- 第3章:列操作
- 第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章:パイプ
- 第9章:GNU拡張とCSVファイルの扱い
- 第10章:もっとGNU拡張
- 第11章:実践
- 第12章:Podcastダイジェスト
まとめ
- awkはAWKという処理系の実装であり両者は別物
awk ‘pattern {action}’ [filename]
、特にパターン・アクションの組み合わせの集合が全て- 独立したスクリプト言語なのでサーバプログラムも作れる
とりあえずなんとか読むことはできるはず。書けるようになるのはまた今度頑張る。
【読了】新しいシェルプログラミングの教科書
一介のソフトウェアエンジニアとしてシェルスクリプトくらいさらさらと書けますよね?......僕は書けません。というわけで「新しいシェルプログラミングの教科書」を読んだのでその際のメモです。
- Chapter01: シェルとは
- Chapter02: シェルスクリプトとは
- Chapter03: 基本
- 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を切り落とす
- ディレクトリ名の取得
${path%/*}
- ディレクトリ名の取得
- それぞれ記号を二つ重ねると最長マッチになる
- ${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
- :
- Chapter10: 正規表現・文字列操作
- Chapter11: シェルスクリプトの実行
- Chapter12: 具体例
- Chapter13: シェル補完の書き方とか
- Chapter14: テスト・デバッグ
まとめ
「新しいLinuxの教科書」が読みやすかったので期待しましたが期待通りでした。シェルスクリプトを学ぼうという方には非常におすすめです。文法がわかったのでどんどん書いて身につけていこうというところですが実務ではあんまり使わないのでどうしたものか......。
7~9月振り返り
久し振りになってしまいました。
やったこと
- 書籍
- はじめて学ぶソフトウェアのテスト技法
- 知識ゼロから学ぶソフトウェアテスト
- ソフトウェアテスト技法
- テスト駆動開発
- 実践テスト駆動開発
- The Art of Software Testing
- リファクタリング
- エクストリームプログラミング
- レガシーコード改善ガイド
- Agile Testing
- オブジェクト指向でなぜつくるのか
- クリーンコード
- アジャイルソフトウェア開発の奥義
- パターンハッチング
- ソフトウェアアーキテクチャ(POSA)
- エンタープライズアプリケーションアーキテクチャパターン
- ドメイン駆動開発
- 不正アクセス対策
- 体系的に学ぶ 安全なWebアプリケーションの作り方
- おうちで学べる セキュリティのきほん
- この一冊で全部わかるセキュリティの基本
- 動かして学ぶ セキュリティ入門講座
- セキュリティコンテストチャレンジブック
- サイバーセキュリティプログラミング - Pythonで学ぶハッカーの思考
- ハッカーの学校
- HACKING:美しき策謀
- Object oriented software construction
今回はテーマが月ごとに1つずつありました。それぞれ7月テスト/TDD・8月オブジェクト指向・9月セキュリティです。このテーマで勉強を始めるにあたって結構な不安がありました。それは設計という正解のないテーマに対して、成長を実感できるか、本を読んだだけで何も変化を感じずモヤモヤして終わるのではないかというものです。結論から言うと心配していたことは全く起こりませんでした。つまり、3ヶ月前の自分と比べて今書いているコードが段違いにきれいになっていることを実感しています。具体的にどうしているかは別の機会に紹介したいと思いますが、TDDを積極的に取り入れています。主なメリットは 1) 仕様漏れを実装前に発見できる 2) 内部実装を知る前にあるべきインターフェイスを検討できる 3) テストさえ通れば中身がどうあれ動くことを確認できるなどです。また今年の頭に読んだもののモヤモヤしていたパターンという分野があります。そのモヤモヤへの答えはケントベックのTDD入門にありました。これまで"良い設計"が実装されるものだとずっと思い込んでいたのですが「設計と実装は互いにフィードバックする」というきれいなコードを書く人にとっておそらく常識といえる考え方に出会えたのはこの三ヶ月で最も価値のあることでした。パターンとの付き合い方がわかったことで本に書かれていることと実際とのギャップが埋まった感覚があります。
セキュリティに関してはテーマは非常に面白かったものの時間不足でした。せめてCTFに参加してみたかったです。
良かった点・反省点・改善点
- ✅いっぱい読んだ
- オブジェクト指向に関わる有名な本は一通り触れることができたと思います。おそらくこの分野の本であればどんな本でもすっと読めるだけのバックグラウンドが揃ったのではないでしょうか。
- ✅即実践
- 仕事にダイレクトに活かせる分野だったのでフィードバックが早く、学んだことを効率よく身につけることができました。
- ❌本しか読んでない
- インプットのチャンネルを増やしたいと思う次第です。
- ❌学習のプランが大雑把だった
- 目標を設定するのが難しかったし、前提の知識が少なかったため想像しにくい部分もありましたが、やはり目標があってこそ効率的に学習できるので、プランニングの時間をしっかりとるべきでした。
- ❌アウトプットできなかった
- 正解のないテーマに対してどの角度で切り込んでいけば、本に書いていることをただ紹介するだけではない切り口を見いだせるのかずっと考えていたものの結局思いつくことができませんでした。この先もコードを書く以上常に向き合っていくテーマなのでいつか自分の言葉で語れればと思います。
- それと同時にアウトプットを仕組化する必要を感じています。
この三ヶ月は旅行に熱心になっていて、勉強が途切れ途切れになってしまったのがやや寂しくもあります。今年残り三ヶ月も頑張っていきます。
4~6月振り返り
やったこと
- 書籍
- 30日でできる! OS自作入門
- はじめてのOSコードリーディング ~UNIX V6で学ぶカーネルのしくみ
- プログラムはなぜ動くのか 第2版 知っておきたいプログラムの基礎知識
- オペレーティングシステムの仕組み
- Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識
- はじめて読む486―32ビットコンピュータをやさしく語る
- Linuxカーネル2.6解読室
- BareMetalで遊ぶ Raspberry Pi
- コンピュータの構成と設計 第5版 上 (途中まで)
- UNIXネットワークプログラミング〈Vol.1〉ネットワークAPI:ソケットとXTI (途中まで)
- Unix考古学 Truth of the Legend
- Raspberry Pi
- 書いたもの
- その他
学んだことに関する振り返りは以下にまとめました。
当初はOSの勉強というテーマで、Linuxディストリビューションや運用など上の方に伸びていくことを想定していましたが、CPUやハードウェアなど低いレベルの方向に向かってしまいました。実務に直接役立つ知識は得られませんでしたが結果オーライだと思っています。動機は記事にも書いたように「基礎を知れば最新技術に素早く対応できる」という説を確かめることです、がもう少し掘り下げたいと思います。学生時代の陸上で経験したことの一つに「身体能力を十分高めた(雑に言えば筋トレを頑張った)人の方が技術面の向上も早く、記録の伸びが大きい」という発見、もっと言えば反省がありました。プログラマとしてこれまで新しいことを学んでも上滑りする、思ったように理解が深まらない、と感じる原因の一つとしてプログラミングにおける基礎能力の不足があるのではないかという疑問を抱いていました。コンピュータサイエンスの知識を基礎能力とする妥当性はさておきこれが今回システムを学んだ背景です。3ヵ月の勉強ではこれが正しい(あるいは間違っている)と確信するには至りませんでしたが、「システムプログラミングを学ぶのは楽しい」という発見がありました。前回学んでいた機械学習は「面白いけどもういいかな」という分野でしたがシステムの勉強はまだ興味が尽きないという意味で割に自分に向いている、と言えるかもしれません。コンパイラなど触れていないトピックはたくさんあるので、一度時間を置いてまた帰ってきたいと思います。
良かった点・反省点・改善点
- 実際に手を動かした
- 実際に手を動かすことが学習効率に影響することは認識していたため、当初から題材を探していました。Raspberry Piという案を閃き、実際にハードウェアと格闘しながらシステムについて学べたのは非常に大きかったです。最終的な成果を出すところまでは至りませんでしたが、手を動かして学ぶことの重要性を再認識しました。今後も強く意識して続けて行きたいと思います。
- 中だるみしてしまった
- Raspberry Piに実際システムを組んでみる段階に至って、ハードウェア開発の難しさからモチベーションを維持するのが難しくなってしまいました。加えて旅行が立て込んである期間すっぽり抜けてしまったため最後中途半端な形で終えてしまったのは非常に残念です。旅行が続いたのは季節柄なので仕方ないとして、モチベーションを維持する工夫の必要性を感じました。定期的なアウトプットを自分に課す、など思い浮かびますがみなさんどうしているのでしょう。
- より多角的に学べた
- 前回の反省を活かし、なるだけ多くの資料を手元に用意して、読んだり読まなかったり一部だけ読んだりといろんな角度から学ぶことで立体的な知識を得ることができました。
- (ついに)英語でのアウトプットを始めた
- たったのブログ一本、しかも日本語で書いたことの翻訳ですが英語でのアウトプットを果たしました。今後もコンスタントに続けていけたらいいなと思います。
【Raspberry Pi】システムタイマ
Raspberry PiでLチカ - 技術について語るときに僕の語ることでLED点滅をしたときにdelay(50000)
という関数を使いましたが、「CPU50000クロックってどんだけやねん」という話なのでシステムタイマを利用して秒で指定できるようにしたいと思います。
なお主に以下の書籍の第5章およびサンプルコードを参考にしています。
- BareMetalで遊ぶ Raspberry Pi - 達人出版会
- RPi_Micon_C85book/timer.c at master · jitomesky/RPi_Micon_C85book · GitHub
システムタイマ
システムタイマのレジスタも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つのレジスタに分かれており、メモリ上アドレスはそれぞれ0x20203004
と0x20203008
になります。それぞれ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関数を作りたいと思います。流れは以下の通りです。
- 現在時刻+delayを目標時刻とする。
- ループ条件で現在時刻を再度取得し、目標時刻を過ぎていたらループを抜ける。
関数は以下の通りです。
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を反転させます。
【メモ】Makefile
Makefileについて調べた際のメモです。
- Microsoft nmake、Borland make、GNU make、Solaris makeなどいろんな種類があるらしい
- フォーマットは
ターゲット名: 依存ファイル名
を書いて次の行からコマンドを書いていく。- 「作りたいもの、必要な材料、作り方」の順
- コマンド行の先頭はタブで空ける必要がある(
ほんとに?本当だった。vs codeの場合勝手にタブにしてくれた。) - ターゲットが複数の場合別々に書けば変更があったファイルだけビルドし直すので効率がいい。
make
だけで実行した場合、先頭にあるターゲットが実行される。- 先頭のターゲットとして
all: 本当のターゲット
を書く習慣がある。
- 先頭のターゲットとして
TOOLCHAIN = arm-none-eabi
というように変数を定義できる。これをマクロと呼ぶ。- 必ずしもターゲットで指定した名前のファイルがコマンドにより作られる必要はない。
clean
やinstall
などの命名でターゲットが定義されることも多い。
- 依存ファイルがない場合はなにも書かなくていいし、ターゲットと同じ行に書いてもいい。
- コマンド行のないターゲットを書くこともでき、主に複数のターゲットをまとめる際に使われる。
CFLAGS
という変数が定義されている場合、*.c
を*.o
にコンパイルするときに暗黙的に使用される。- その他デフォルトのルールや変数は山ほどあり、
make -p
で確認することができる。 - ビルドし直すべきかどうかをファイルの更新日時を使って判断する。
- 再ビルドを矯正したいときはターゲットのファイルに対してtouchコマンドを実行すればいい。
- 必ずしもC言語のプログラムである必要はなく、材料とそれから作りたいものがあるときにはかなり汎用的に使えるコマンドになっている。
-f
オプションで対象のファイルを指定し、省略した場合GNUmakefile
、makefile
、Makefile
の順で探す。- 依存ファイルがなく、生成するファイルもないターゲットのことをphony targetと呼ぶ。phony tagetは同名のファイルが存在すると動かなくなってしまうため、
.PHONY: ターゲット名
を書くことでこれを回避できる。.PHONY
はまとめてどこか一箇所に書いておけばオッケー。
- ヘッダファイルを依存ファイルに含めることで、ヘッダファイルだけの更新があったときでもリビルドを走らせることができる。
- コマンド内で
$@
によりターゲット名を参照できる。これを内部マクロという。また$<
では依存ファイルの先頭のファイル名を参照できる。- 一覧はここ: Special macros
.SUFFIX: 依存先の拡張子 依存元拡張子
のフォーマットで拡張子ごとの依存関係を記述できる。- 発展形としてOSごとの差異を吸収したツールも存在する。
- シェルスクリプトと同様に
\
で改行できる。 - ターゲット名は複数書いてもよく、これは単に複数のルールをまとめて書いたことになる。
- 関数が用意されている。see: GNU make: Functions
- shell関数を使うことでマクロ定義の際などでコマンドを実行することができる。
- コマンドの先頭に
@
をつけるとその行の出力を省略できる。-
をつけるとコマンド失敗を無視できる。 - 慣例として変更の可能性があるマクロは大文字、Makefile内で閉じているマクロは小文字で書く。
- マクロ宣言の際にコロンをイコールの前につけて
:=
とすると、定義内のマクロが即時展開される。それ以外では実行の際に展開される。 ?=
により値が定義されていないときだけ新たに定義する、という動作にできる。makedepend
コマンドよりルールを動的に生成することができる。include
で他のMakefileをインクルードできる。ifdef
やifeq
などの分岐分を使える。
Refs
- 今更聞けないmakefileの書き方
- Makefileの解説
- Makefileの書き方 - $ cat /var/log/shin
- - 自動化のためのGNU Make入門講座 - Makefileの基本:ルール
- トリビアなmakefile入門
- Makefileの書き方に関する備忘録 - minus9d's diary
- Makefileの書き方 - Web就活日記
- Make Tutorial: How-To Write A Makefile - Michael Safyan
- c - How do I make a simple makefile for gcc on Linux? - Stack Overflow
- C Programming Tutorial: Make and Makefiles
- Make と Makefile の説明
Raspberry PiでLチカ
ハードウェア版Hello, WorldであるところのLチカ(LED点灯)をRaspberry Pi(以下RPI)でやってみたいと思います。使用するのはRaspberry Pi B+モデルです。
やることは2ステップです。
- LEDに対応するピンを出力モードに設定する。
- 対応するピンの出力ビットを立てる。
がその前にRPIにおけるハードウェア制御について触れます。
なおビルドや実機での動かし方などは割愛するので以下のサイトを参考にしてください。
- Department of Computer Science and Technology – Raspberry Pi: Baking Pi – Operating Systems Development
- Tutorial
RPIでのハードウェア制御
ハードウェアはそれぞれ自身を制御するための記憶領域を持っており、これをレジスタと呼びます。このレジスタにCPUから値を読み書きすることで制御を行います。例えばLEDの場合「このレジスタの値がxxxxの時にLEDがつく」とハードウェアの仕様で決められているので、この値をCPUから書き込むことで点灯させます。
CPUからレジスタの値を読み書きするにはいくつか方法があるらしいのですが、RPIはメモリマップトIOと呼ばれる方式を採用しています。 これはハードウェアのレジスタをメモリに対応づけることで、メモリの読み書きを通してハードウェアを制御する方式です。レジスタに値を書き込みたいときはそのレジスタに対応するメモリ上のアドレスに値を書き込むだけで同時にハードウェアのレジスタを書き換えることができます。読み出しに関しても同様にメモリ上の値を読むだけです。メモリに対する通常のロード・ストア命令でハードウェアを制御できるシンプルさが利点です。 LEDのレジスタもこの方式に従いメモリ上にマップされているので、まずはじめにこれがどこにあるのかを調べることからはじめます。
ところでRPIにはいろんなハードウェアを接続するための汎用的な入出力ピンが存在します。この汎用IOピンの集合をGPIOと呼び全部で53のピンがあります。RPIにはじめから組み込まれているLEDもこの汎用ピンの一つを利用しており、53あるピンのうち47番目(RPI B+の場合)に接続されています。 つまり「LEDを制御するレジスタがメモリ上のどこにマップされているか」は言い換えれば「GPIOの47番ピンがメモリ上のどこにマップされているか」という問題になります。
どのレジスタがメモリ上のどこにマップされているのか、というのはARMのペリフェラル(周辺装置)マニュアルに書いてあります。これのp.89以降にGPIOに関する記述があり、GPIOがアドレス0x20200000
の位置(RPI B+の場合)からはじまることがわかります1。今回必要なレジスタはモードの設定用のGPSELレジスタおよび出力用のGPSETレジスタです。書き込む値については実際にコードを見ながら触れたいと思います。
実際にLEDを点灯してみます。先にkernel_mainのコード全体は以下のとおりです。
void kernel_main(uint32_t r0, uint32_t r1, uint32_t atags) { (void) r0; (void) r1; (void) atags; *(volatile uint32_t *) 0x20200010 = (1 << 21); *(volatile uint32_t *) 0x20200020 = (1 << 15); while (1) { } }
出力モードの設定
GPIOピンの制御モードには1ピンあたり3bit与えられているため、一つのレジスタ(32bit)で管理できるのは32 / 3 = 10
ピンです。よって53ピンあるGPIOの制御用レジスタGPSELは0~5の6つに分かれており、それぞれ10ピンずつ割り当てられています。
今回の目的である47番ピンはSEL4ということになり、対応する3bitは21~23番目ということになります。SEL4はメモリ上0x20200010にあり、出力のモードは0b001ということになっているため21番目のビットを立てれば完了です。
*(volatile uint32_t *) 0x20200010 = (1 << 21);
(volatileというのが気になるかもしれません。この行はメモリに値を書き込むだけの処理のため、コンパイラが勝手に「この処理いらん」と判断するなどの最適化がはたらき、意図しているのと違うコードが生成されてしまう可能性があります。volatileはそれを回避するために付け足しています。)
出力レジスタのビットをたてる
ピンをHIGHにするGPSETレジスタは2つあり、1つめが0~31番ピン、2つめが32~53番ピンに対応します。今回47番ピンなので、2つ目のレジスタの47 - 32 = 15
番目のビットを立てれば良いです。SET1レジスタはメモリ上0x20200020にあります。
*(volatile uint32_t *) 0x20200020 = (1 << 15);
以上をビルドして実機で動かすとLEDが点灯するのが確認できると思います。
Refs
- Department of Computer Science and Technology – Raspberry Pi: Baking Pi – Operating Systems Development
- Tutorial
- BareMetalで遊ぶ Raspberry Pi - 達人出版会
もし点灯だけではなく点滅もしたい場合も手順は同じで、今度はピンをLOWにするGPCLRレジスタを使います。CLR1レジスタは0x2020002Cにあるため、
*(volatile uint32_t *) 0x2020002C = (1 << 15);
をするだけです。
点滅しているのががわかるように、CPUを空回しするdelay関数を以下のように用意します。
// https://jsandler18.github.io/tutorial/boot.html より void delay(int32_t count) { asm volatile("__delay_%=: subs %[count], %[count], #1; bne __delay_%=\n" : "=r"(count): [count]"0"(count) : "cc"); }
これをつかったkernel_main全体は、
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(500000); *(volatile uint32_t *) 0x2020002C = (1 << 15); delay(500000); } }
になり、これを実行するとLEDが点滅するのが確認できるはずです。