「12ステップで作る組み込みOS自作入門」環境構築した
「12ステップで作る組み込みOS自作入門」始めてはや数日。 6章まで終わってようやく折り返し地点。 環境構築がいろいろ大変だったのでその記録をしていく。 基本的には書籍とサポートページを行ったり来たりしながら環境構築をすすめる。
概要
VirtualBox + Vagrant で仮想環境を使って開発環境を構築していく。
- ホスト Linux Mint 19.2 Tina
- ゲスト VirtualBox Ubuntu 16.4 32bit
わざわざ仮想化せんでもええやん。ただ環境構築はミスって不可逆的な変更でいろいろ汚してしまった時、気持ちが辛くなる。仮想化することで、間違ったらいつでも捨てることができると自分に言い聞かせ、作業の心理的安全性を確保する。
ええんや! メモリもCPUも資源は潤沢なんやから! これが富豪的ソフトウェア開発や!
ホストにはエディタとセルフコンパイラとシリアル接続。そして Git でバージョン管理。
ゲストにはセルフコンパイラとクロスコンパイラをインストール。
VirtualBox の設定で、ゲストにUSBシリアル変換アダプタからの接続をバイパスすることはできたけど、あまりメリットがなかった(後述)。
64bit OS だといろいろ不都合があるので 32bit OS を使う。
具体的には GCC ビルド時にパッチを当てる必要がある。gcc3.4.6だとコンパイル時に負数の即値があると警告が出てくる。警告であってエラーではないので、もしかしたら動作に問題はないのかもしれない(そこまで詳しく調査してない)が、ビルド時に警告を無視し続けるのは精神衛生上あまりよろしくないので、64bit を避ける。
たぶん、64bitOS ならGCCも新しいバージョンにすればいいんだけど、書籍との環境を変えすぎると、問題が起こったときに原因の切り分けがとても大変になるので、なるべく書籍での環境に合わせる。
ゲストOS設定
VirtualBox + Vagrant 設定を作っていく。
まず vagrant-vbguest プラグイン導入。これがないと起動直後の共有フォルダ同期でコケる。
できたらゲスト OS を Vagrant Cloud からダウンロードする。
Vagrant box ubuntu/xenial32 - Vagrant Cloud
vagrant init ubuntu/xenial32 vagrant up
これでダウンロードが始まるのでしばらく待つ。無事起動したら、Vagrantfile
に共有フォルダ設定を追記する。
設定ファイルではホストゲスト間の共有フォルダを設定していく。ホスト OS の Vagrantfile
と同じディレクトリにある workspace
ディレクトリをゲスト OS と共有する。ソースファイル編集の作業は主にこの共有フォルダ内で行う。ちなみにこの設定は46行目あたりにあった。
config.vm.synced_folder "./workspace", "/home/vagrant/workspace"
設定したら vagrant reload
してゲスト OS を再起動。
セルフコンパイラは最新でOK。いつの間にか入っていた。
クロスコンパイラの構築
binutils と gcc の tar を取ってきて解凍。
- GCC Releases - GNU Project - Free Software Foundation (FSF)
- Binutils - GNU Project - Free Software Foundation
書籍のサポートページによると、クロスコンパイラに関しては最新ではないほうがいいらしい。なので、書籍にて検証済みのバージョンを使用する。
binutils
書籍の手順に従ってインストール。./configure
時のオプションに --disable-werror
がないと make install
でコケる。
gcc
インストール前にパッチを当てる。パッチはサポートページから入手。パッチコマンドで当てる。
書籍の手順にしたがってソースコードを修正し ./configure
して make
して sudo make install
する。これでインストールはOK。
ビルド後の後片付け
両方のインストール後は使用したファイルをそのままにしてはいけない。
GCC のディレクトリが、なぜかあとで vagrant reload
または vagrant up
したときに、起動直後の rsync でエラーを起こしてしまう。そしてホストゲスト間のフォルダ同期に失敗してしまう。
対策は、gcc
フォルダを削除する。あるいは、Vagrant 設定ファイルがあるディレクトリ以下からどこか別の場所に移動する。
これだけで何事もなかったように動く。
なんで GCC の解凍後のディレクトリが rsync を邪魔するのかはわからん。沼が深そうなので、調査は一旦後回し。
シリアル接続
バイナリイメージ作成はゲストOSでやって、マイコンボードとの通信はホストOSでやる。
今回使ったのは USBシリアル変換コード。秋月電子で買った。ドライバの追加なしでいきなり使えた。
接続するとコマンドで /dev/ttyUSB0
が出てるのが確認できる。
ただしこのままでは通信できない。/dev/ttyUSB0
には一般ユーザのアクセス権がないため。なので権限設定をしていく。
とりあえず接続できるようにするには chmod 666 /dev/ttyUSB0
でいい。しかしこれだと、USB 接続を外すたびに権限がリセットされる。なので接続ごとに毎回設定をするようにする。
lsusb
コマンドでベンダ ID とプロダクト ID を見る。そして /etc/udev/rules.d/50-usb-serial.rules
に追記する。
以下の記事を参考にした。
これで USB を挿し直しても無事に権限設定ができている。
ぶっちゃけ、なんで ttyUSB0
でなんで 0
なのかはわからん。1
かもしれないし 2
かもしれないけど、それを確認するコマンドもわからん。とりあえず ttyUSB0
で決め打ちして動いてるからOKとした。あとで調べる。
そして Hello, World! へ……
シリアル通信は ckermit で行う。初見ではボードに電源をつないでも何も反応がなかったので、本当に動いているのか不安だったけど、ちゃんと動いていた。
kermit の設定は書籍に従う。自分の環境だと set carrier-watch off
をつけないとうまくいかなかった。
kermit 設定は ~/.kermrc
に記述すればいい。
# ckermit デフォルト設定 set line /dev/ttyUSB0 # 接続先 # 接続設定 set speed 9600 # ボーレート set terminal bytesize 8 # ビット set stop-bits 1 # ストップビット set parity none # パリティ set carrier-watch off # キャリア検出 set flow-control none # フロー制御 # 4章以降で必要(後述) # lrzsz を利用したファイルの送受信プロトコル設定 set protocol xmodem rx {rx -b} {sx %s} {sx -b %s} rx rx
以下の記事を参考にした。
- 組込み開発の常識!?C-Kermitの使い方を調べたメモ | Futurismo
- tools/kermit - NORK's "HOW TO..." Wiki ç¥ãã¦ãã®ãã¯ãWikiã
- Kermit 95 - The SET TERMINAL Command
これで kermit 起動後は connect
で接続できる。
書籍1章に従って正しくプログラムを転送できているなら、これでリセットボタンを押すごとに Hello, World! を受信できる。
たまに出力される文字列が化けて出てくることがある。そんなときは、とりあえず USB シリアルケーブルの USB-PC 間を一度抜き挿しすると直る。
ゲストOSにUSBシリアル接続をバイパスする場合
最終的には見送ったが、ゲストにUSBをバイパスする方法もある
VirtualBox のゲスト OS 設定画面でシリアルポートのパスに /dev/ttyUSB0
を設定。これでゲストOSの /dev/ttyS0
に接続することでシリアル通信ができる。
……はずなのだけど、なぜか自分の環境では受信できたりできなかったり、画面上の文字列表示がずれてたりで、うまくできなかった。これがバイパス設定を見送った主な理由。
他にも単純な問題はある。ホストOSのデバイスをゲストOSに常にバイパスする設定だと、USBが挿さっていない状態でゲストOSを起動すると常にエラーが起きる。これがはっきり言って面倒くさい。
このふたつの理由から、USBシリアルはホストから通信したほうが手間かからずにいいな、となった。
2章以降での問題と解決など
3章以降でのリンカスクリプトの修正について
Google グループ によると、書籍3章のリンカスクリプトは厳密な記述ではないらしく、根本的な解決方が紹介されていた。なので適用した。
さらに、
www.slideshare.net
51ページ以降にて、ビルド時の問題とその解決方法が載っている(GCC 4.7)。このコードの変更も適用した。
4章以降でのファイル転送のオプション変更
4章以降、シリアル通信でファイル転送を行うようになる。
そのとき kermit で接続するなら lxzsz を追加にインストールして送受信するのだけど、その設定コマンドは書籍から以下のように変更している。
set protocol xmodem rx {rx -b} {sx %s} {sx -b %s} rx rx
-a
オプションから -b
オプションへ変更している。man ページによると -a
オプションは ascii モードで、-b
オプションは binary モードで送信する。この違いは何かというと、-a
モードでは改行コードの LF が CRLF に変換されてしまう。
これだと4章でのテキストファイル送信時に、ローカルでのファイルダンプと送信済みファイルのダンプが一致しない。送信過程で LF が CRLF に変換されてしまうため。ちなみに改行コードを CRLF で送信すると CRCRLF になる。そのため binary モードで送信するよう、オプションを変更している。
感想
こんな感じの設定で6章まで来た。お疲れ様でした。
この環境でこれまで問題なかったので、たぶんこの先も大丈夫じゃないかな……。
残り半分。頑張っていきましょう。
とか言っている間に、最後まで終わりました。非常に有意義な時間を過ごせました。感想はまた別の記事で。
学習中にコミットしていたリポジトリはこちら。
お疲れさまでした。
VS Code の Pylint で venv 仮想環境に追加したモジュールを読み込む
先日の記事の続き。
使用環境は LinuxMint 19.1 tessa で apt インストールの Python3.6.8 + Pylint3 1.8.3 + astroid 1.6.0。Microsoft Visual Studio Code 1.36.0 には Python 拡張機能 2019.6.22090 を追加している。
問題
今作ってる Python プロジェクトでは venv で仮想環境を作って pip3 でパッケージを追加している。しかし、先日の方法でシステムの Pylint を VS code に設定すると、仮想環境にインストールした PyPI パッケージを読み込んでくれず、 VS Code 上で常に赤線が引かれてしまう。非常に鬱陶しい。ではどうするか。
解決策
Pylint が仮想環境のパッケージも読み込むように設定する。
1. コマンドライン上で仮想環境をアクティブにする
標準ライブラリの venv
を使って仮想環境を作りアクティブ化する。例では仮想環境のフォルダを project
としている。
$ python3 -m venv project $ . project/bin/activate
2. プロジェクトのルートに .pylintrc を作る
プロジェクトに pylint の設定ファイルを追加する。
(project) $ pylint --generate-rcfile > .pylintrc
3. init-hook を編集
Pylint 設定を編集する。
# init-hook のコメントアウトを外してライブラリを読み込むよう設定 init-hook="import sys; sys.path.append('./project/lib/python3.6/site-packages')"
Pylint の起動前動作に仮想環境のパッケージのインストール先ディレクトリを追加する。今回の環境では ./project/lib/python3.6/site-packages
になったが、各自の環境で読み替えてもらえれば。
これで、仮想環境のパッケージを読み込んだ状態で VS Code を起動できる。余計な赤線も入らない。
余談
Pylint のデフォルト設定を追加すると、今度はちょっとした記述に緑線が引かれるようになり、これはこれで少しうるさく感じてしまう。しかし、まあ、お行儀良く書くのが Python の流儀なので、普段からこのうるささを黙らせ続けるくらいの方が丁度いいのかもしれない。
参考
Python3 の csv.writer で改行コードを LF にしたいときは lineterminator を変更する
ここ読んで見つけた。
使用環境は LinuxMint 19.1 tessa で Python3.6.8。
デフォルトの csv.writer の改行コードは CR/LF で出力される
例えば以下のようなコードがある。
import csv with open('hoge.csv', 'w', newline='') as f: writer = csv.writer(f) writer.writerow(['one', 'two', 'three'])
Python3 の標準ライブラリを使った簡単な CSV 書き出しコードだが、これでそのまま書き出すと改行コードは CR/LF になる(Excel 互換のため?)。これを LF にしたい。しかし公式ドキュメントでは newline=''
を使えと書いてある。ではどうするか。
lineterminator='\n' を指定して LF に変更する
以下のように lineterminator という終端処理パラメータを変更する(ドキュメントはこちら)。
import csv with open('hoge.csv', 'w', newline='') as f: writer = csv.writer(f, lineterminator='\n') # LF にしたいので '\n' を指定する。 writer.writerow(['one', 'two', 'three'])
これで改行コードが LF の CSV ファイルが出力される。
VS Code で Python 拡張機能を使うときの Pylint 設定
ここを読めばわかる。 stackoverflow.com
今回はここの解決策から引用している。
使用環境は LinuxMint 19.1 tessa で Python, Python3 共に apt から最新版をインストール済み。Microsoft Visual Studio Code 1.36.0 には Python 拡張機能 2019.6.22090 を追加している。
VS Code で Pylint の設定をしていく
VS Code で Python 拡張機能を入れて Python のソースファイルを開くと Pylint の設定を求められる。無いなら入れろ、と言われる。しかし LinuxMint 19.1 では Pylint は apt にある。できればこれを使いたい。その設定を VS Code にしていく。
1. Pylint の場所を探す
which pylint
する。例えばそれが /usr/bin/pylint
だったとする。
2. VS Code の設定を変更する
VS Code で設定を開き、検索窓に python.linting.pylintpath
と入れる。
そしてその項目に、さっき which pylint
した場所(今回は /usr/bin/pylint
)を入れる。
これで OK 。pip install pylint
を毎回しなくてもいい。
自分は Python3 をメインに使っているので Pylint3 を設定してます。
7/12 追記
venv で仮想環境を作ったとき Pylint が追加パッケージの利用をいちいち赤線で注意してくる問題に対応する方法。
Apache + pukiwiki と Tomcat + GitBucket をポート80番で共存させる
Linuxデスクトップ環境で、ゆるく wiki と git を運用したい。というわけで、 localhost に pukiwiki と GitBucket をインストールして起動してみる。
方針として、ゆるく運用&構築したいので、なるべく手軽にインストールして、デフォルト設定に近い状態で使う。
環境は Linux Mint 19 Tera の Mate で。それでは行ってみよう。
Apache + pukiwiki
インストールするパッケージ
必要なパッケージのインストールは Synaptic で行う。 GUI で楽したい。下記のパッケージをインストール指定して適用する。
- Apache2
- PHP
インストールが終わったら http://localhost がすでに動いている。アクセスして動作を確認。
設定ファイル確認
主な設定があるファイルは /etc/apache2/apache2.conf
にある。今回はこれは特にいじらないが、ざっくり見ておく。
また /etc/apache2/envvars
を見て APACHE_RUN_USER
と APACHE_RUN_GROUP
が何になっているかを確認しておく。Linux Mint は元々は Ubuntu で Debian 系なので、これは www-data
になっている。
念の為、
cat /etc/group
しておいて、 www-data
ができていることを確認しておく。
pukiwiki のダウンロードと所有権の変更
pukiwiki をここからダウンロード。現在の最新版は1.5.1。 pukiwiki.php.ini の変更などインストールに関することは該当ページを参照。動作確認するだけならいじる必要はない。
解凍後にできたフォルダの名前を pukiwiki
に変更。そして pukiwiki
フォルダを Apache のデータ用フォルダにコピーする
sudo mv pukiwiki /var/www/html/
そして /var/www
以下の所有権の変更。
sudo chown -R www-data.www-data /var/www
動作確認
sudo systemctl restart apache2
で apache を再起動。 http://localhost/pukiwiki/ にアクセスして、 pukiwiki が動いていることを確認する。
Tomcat + GitBucket
Apache の次は、 Tomcat で Git サーバを構築する。
インストールするパッケージ
Synaptic で以下のパッケージをインストール指定して適用。
- default-jre
- Tomcat8
- Tomcat8-admin
Tomcat Web アプリケーションマネージャも一緒に入れる。なくてもいいけど、ゆるく運用するにはこういうの欲しい。
設定ファイル変更
/etc/tomcat8/tomcat.users.xml
を編集する。コメントアウトを有効化して、管理人ロールとアカウントを追加。
<role rolename="admin-gui"/> <user username="tomcat" password="password" roles="tomcat,admin-gui"/>
ポート設定を変更。 /etc/tomcat8/server.xml
を編集して、デフォルトのポートを 8080
から 8081
に変更。
<Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
これは必要ないけど、8080のままだと他のアプリのデフォルト設定とこの先ガンガンぶつかっていってしまうため。
GitBucket のダウンロードと所有権の変更
GitBucket の Release から gitgucket.war
のダウンロード。http://localhost:8081/manager/ にアクセスしてWebアプリケーションマネージャから gitgucket.war
ファイルをアップロード。そして現段階では起動しないことを確認しておく。
念の為、ウェブAPPマネージャからアップロードされた先のフォルダを見ておく。
ls -al /var/lib/tomcat8/webapps
で gitbucket.war
がちゃんとコピーできていて、かつ所有者が tomcat8
になっていることを確認する。
次に GitBucket のデータ用フォルダを作って、所有権をtomcat8に明け渡す。
sudo mkdir /var/lib/tomcat8/.gitbucket
sudo chown tomcat8:tomcat8 /var/lib/tomcat8/.gitbucket
こうしてからだとWebアプリケーションマネージャでGitBucketを起動できる。
GitBucketがどこをデータ用フォルダとするかは環境によって異なるらしい。Ubuntu 16.04なら /usr/share/tomcat8/.gitbucket
とからしい(こちらのブログを参照)。
Webアプリケーションマネージャから一度起動に失敗しておくと、 /var/log/tomcat8/localhost.*.log
にエラーログが残る。そのエラーログを読めば、どこにフォルダを作ろうとして失敗したのかがわかる。わかったら、あとはそこに作って所有権を渡せばいい。
まあ普通はいちいち失敗させて確認するより、予め環境変数によってフォルダを設定すると思う(こちらのブログ参照)。今回はゆるく運用ということでこのやり方はしないけど、ちゃんと運用するならちゃんとやろう。
動作確認
sudo systemctl restart tomcat8
で tomcat を再起動。 http://localhost:8081/gitbucket/ にアクセスしてみる。動作していたらOK。
ポート80番で Apache と Tomcat を共存させる
さて、ここからが本番。Tomcat から GitBucket を起動したものの、このままだといちいちURL にポート番号をいれる必要がある。これは面倒。なので、http のデフォルトポート80番からアクセスできるようにする。
方針としては、特定の URL にアクセスしたときのみ、Apache から Tomcat に切り替えてアクセスする。
Tomcat 側の設定
/etc/tomcat8/server.xml
を編集する。AJP に関する部分がコメントアウトされているので、これを外して有効化する。
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
Apache 側の設定
いくつかのモジュールを有効化する。
sudo a2enmod proxy proxy_http proxy_ajp
有効化したら、 /etc/apache2/mods-enabled/proxy.conf
にファイルができているので、アクセスして編集する。 <IfModule>
タグの内側に以下のように記述。
ProxyPass /gitbucket/ ajp://localhost:8009/gitbucket/ ProxyPassReverse /gitbucket/ ajp://localhost:8009/gitbucket/
動作確認
sudo systemctl restart apache2 tomcat8
http://localhost/gitbucket/ にアクセスしてみる。アクセスできるならOK。
もちろん、既存の http://localhost/pukiwiki/ の方へも問題なくアクセスできることも確認しておく。
感想
apache も tomcat もちゃんと使うのは初めてだったため、とても大変だった。 所有権に関するトラブルでだいぶ悩んだんだけど、検索する限りよくあるトラブルらしいので、今後とも気をつけていきたい。
それはそれとして、 Linux Mint は Linux デスクトップ環境としてかなり使いやすく感じた。元が Ubuntu でさらにその元は Debian なので、情報が検索してヒットしやすいし、様々なツールが既に用意されている。強力なバックアップツールまでついているので、ゆるく運用するにはもってこい。
というわけで、しばらくは Linux Mint を使ってみようと思った。
続・C言語で文字列を逆にする
昨日の記事の続き。
寝る前にふと、文字列長を取得する部分を改善できるのではないか、と思いついた。
しかしそれなら長さ0の文字列にどう対応するべきか。
しかたないので関数にして括り出して対応することにした。
以下、改善版のコード。
// 文字列を逆にする // どうせなら string.h なしで #include <stdio.h> // 文字列のポインタを受け取って、その文字列の並びを逆にする void strrev(char s[]){ char temp = '\0'; char *head = s; char *tail = s; // 長さ0の文字列なら操作せず返す if(*head == '\0'){ return; } // 文字列の最期の文字のポインタを取得する while( *(tail+1) != '\0'){ tail++; } // 逆にする while(head != tail){ temp = *head; *head = *tail; *tail = temp; head++; if(head == tail){ break; } tail--; } return; } // a.out の後に続く文字列を逆順にする int main(int argc, char* argv[]){ char *s = argv[1]; // 引数が足りないなら比較しない if(argc <= 1){ return 0; } // 操作前 printf("Input: %s\n", s); // 文字列を逆にする strrev(s); // 操作後 printf("Output: %s\n", s); return 0; }
主な変更点は、文字列の長さを測らなくなったこと。
これによって int 型1つ分のメモリ消費がなくなった(その分文字列ポインタが2つ増えた)のと、入れ替えられる文字列の長さが int 型最大値に依存しなくなった。後者は明確な利点ではなかろうか。
計算量は特に変わらず O(n) のオーダのまま。
実行結果はこちら。
imadedede$ ./a.out a Input: a Output: a imadedede$ ./a.out ab Input: ab Output: ba imadedede$ ./a.out abc Input: abc Output: cba imadedede$ ./a.out abcd Input: abcd Output: dcba
問題ない。これでよし。
追記:最新版はこちら
C言語で文字列を逆にする
最近 Joel on Software を紙で読んでいる。
その中に気になる一節があった。20章「採用面接ゲリラガイド」の中のこんな文章だ。
(※書籍版での更新日時は2004年6月4日となっており少し新しい。さらに更新された web 版の日記はこちら)
4.プログラミングの質問
面接のこの部分には一番時間をかけるべきだ。私は候補者にC言語で(あるいは彼らがなじんでいる言語なら何でもいい)小さな関数を書くように求める。
以下に、私がよく出す問題の例を挙げる。
- 文字列をその場で逆にする
- 連結リストを逆にする
- バイトデータの中で経っているビットの数を数える
- 二文探索
- 文字列の中で同じ文字が一番長く続くところを見つける
- atoi
- itoa(スタックやstrrevを使う必要があるため、良い問題だ)
「文字列を逆にするのか・・・strlen() で計って、 malloc() をして・・・」
と考えながら読み進めると、まさにそれを先読みしたかのようなこんな記述が。
(中略)
1.の「文字列をその場で逆順にする」については、私がこれまで面接した候補者はみんな1度目には間違ったやり方をした。例外なく、彼らはもう1つのバッファを用意して、そこに逆順の文字列を作ろうとする。問題は、そのバッファのメモリ領域を誰が割り当てるのか? 誰がそのバッファを開放するのか?
間違ったやり方らしい。
そしてこう続いた。
この質問を何ダースもの候補者に出して気づいた興味深い事実がある。C言語を知っていると思っている人たちの多くが、メモリやポインタを本当には理解していない。彼らは単にそれが分からないのだ。そういう人たちがプログラマとして働いているというのも驚きだが、実際そうなのだ。この質問で候補者を判定する方法はいくつかある。
・・・と、いうことは、ポインタを使えばもっと早くメモリ消費も短くなるのかな?
そんなわけで、書いてみた。
// 文字列を逆にする // どうせなら string.h なしで #include <stdio.h> // a.out の後に続く文字列を逆順にする int main(int argc, char* argv[]){ int len = 0; char temp = '\0'; char *s = argv[1]; char *head = NULL; char *tail = NULL; // 引数が足りないなら比較しない if(argc <= 1){ return 0; } // 操作前 printf("Input: %s\n", s); // 文字列の長さを取得する len = 0; head = s; while(*head != '\0'){ head++; len++; } // 逆にする head = s; tail = &s[len-1]; while(head != tail){ temp = *head; *head = *tail; *tail = temp; head++; if(head == tail){ break; } tail--; } // 操作後 printf("Output: %s\n", s); return 0; }
実行結果はこんな感じ。
imadedede$ ./a.out a Input: a Output: a imadedede$ ./a.out ab Input: ab Output: ba imadedede$ ./a.out abc Input: abc Output: cba imadedede$ ./a.out abcd Input: abcd Output: dcba
これだと、文字数が1文字でも奇数でも偶数でも入れ替えられる。
元の文字列が破壊されてしまうけど、特に制限はなかったからこれでも間違いではないはず。
・・・実のところ、書き始めでは文字列の長さをとった後で head ポインタの初期化を忘れており、そのデバッグに30分ほどかかってしまった。
まだまだ精進が足りませんなあ。
まあでも、たまにはこういうことを考えてみるのも一興ということで。
追記:最新版はこちら