imadedede のブログ

今出川潤の出張所。

「12ステップで作る組み込みOS自作入門」環境構築した

「12ステップで作る組み込みOS自作入門」始めてはや数日。 6章まで終わってようやく折り返し地点。 環境構築がいろいろ大変だったのでその記録をしていく。 基本的には書籍とサポートページを行ったり来たりしながら環境構築をすすめる。

kozos.jp

概要

VirtualBox + Vagrant で仮想環境を使って開発環境を構築していく。

わざわざ仮想化せんでもええやん。ただ環境構築はミスって不可逆的な変更でいろいろ汚してしまった時、気持ちが辛くなる。仮想化することで、間違ったらいつでも捨てることができると自分に言い聞かせ、作業の心理的安全性を確保する。

ええんや! メモリも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。いつの間にか入っていた。

ロスコンパイラの構築

binutilsgcc の tar を取ってきて解凍。

書籍のサポートページによると、クロスコンパイラに関しては最新ではないほうがいいらしい。なので、書籍にて検証済みのバージョンを使用する。

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

以下の記事を参考にした。

これで 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章まで来た。お疲れ様でした。

この環境でこれまで問題なかったので、たぶんこの先も大丈夫じゃないかな……。

残り半分。頑張っていきましょう。

とか言っている間に、最後まで終わりました。非常に有意義な時間を過ごせました。感想はまた別の記事で。

学習中にコミットしていたリポジトリはこちら。

github.com

お疲れさまでした。

VS Code の Pylint で venv 仮想環境に追加したモジュールを読み込む

先日の記事の続き。

imadedede.hatenablog.com

使用環境は 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 の流儀なので、普段からこのうるささを黙らせ続けるくらいの方が丁度いいのかもしれない。

参考

qiita.com

Python3 の csv.writer で改行コードを LF にしたいときは lineterminator を変更する

ここ読んで見つけた。

stackoverflow.com

使用環境は 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 CodePython 拡張機能を入れて 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 が追加パッケージの利用をいちいち赤線で注意してくる問題に対応する方法。

imadedede.hatenablog.com

Apache + pukiwiki と Tomcat + GitBucket をポート80番で共存させる

Linuxデスクトップ環境で、ゆるく wiki と git を運用したい。というわけで、 localhostpukiwiki と GitBucket をインストールして起動してみる。

方針として、ゆるく運用&構築したいので、なるべく手軽にインストールして、デフォルト設定に近い状態で使う。

環境は Linux Mint 19 Tera の Mate で。それでは行ってみよう。

Apache + pukiwiki

まず Apache による wiki から設定していく。

インストールするパッケージ

必要なパッケージのインストールは Synaptic で行う。 GUI で楽したい。下記のパッケージをインストール指定して適用する。

インストールが終わったら http://localhost がすでに動いている。アクセスして動作を確認。

設定ファイル確認

主な設定があるファイルは /etc/apache2/apache2.conf にある。今回はこれは特にいじらないが、ざっくり見ておく。

また /etc/apache2/envvars を見て APACHE_RUN_USERAPACHE_RUN_GROUP が何になっているかを確認しておく。Linux Mint は元々は UbuntuDebian 系なので、これは 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番で ApacheTomcat を共存させる

さて、ここからが本番。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/

動作確認

apachetomcat の再起動を行う。

sudo systemctl restart apache2 tomcat8

http://localhost/gitbucket/ にアクセスしてみる。アクセスできるならOK。

もちろん、既存の http://localhost/pukiwiki/ の方へも問題なくアクセスできることも確認しておく。

感想

apachetomcat もちゃんと使うのは初めてだったため、とても大変だった。 所有権に関するトラブルでだいぶ悩んだんだけど、検索する限りよくあるトラブルらしいので、今後とも気をつけていきたい。

それはそれとして、 Linux MintLinux デスクトップ環境としてかなり使いやすく感じた。元が 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言語で(あるいは彼らがなじんでいる言語なら何でもいい)小さな関数を書くように求める。
以下に、私がよく出す問題の例を挙げる。

  1. 文字列をその場で逆にする
  2. 連結リストを逆にする
  3. バイトデータの中で経っているビットの数を数える
  4. 二文探索
  5. 文字列の中で同じ文字が一番長く続くところを見つける
  6. atoi
  7. 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分ほどかかってしまった。
まだまだ精進が足りませんなあ。
まあでも、たまにはこういうことを考えてみるのも一興ということで。

追記:最新版はこちら