simotin13's message

simotin13といいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

Linux C言語でミリ秒の計算をする

Linux系OSのC言語ではtimeval構造体を使った時間表現があります。

しかしながら、あくまでgettimeofdayによる時間の取得・参照を目的としているからなのか、時間計算のAPIはありません。

ms秒単位で経過時間の加算をする関数が必要になったので書いてみたのですが、テストのために書いた検証コードを動かしてみたら、結果が面白かったので記事として挙げておきたいと思います。

ミリ秒加算するコード

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>

void add_ms(struct timeval *t, long ms) {
	long add_us = ms * 1000;
	long tmp_us = t->tv_usec + add_us;
	if (1000000 <= tmp_us) {
		t->tv_sec++;
		t->tv_usec = tmp_us - 1000000;
	}
	else {
		t->tv_usec += add_us;
	}
}

int main(int argc, char **argv) {
	struct timeval calc, now;
	gettimeofday(&now, NULL);

	calc = now;

	printf("------------------------------------------\n");
	printf("Start Test\n");
	printf("now  [%ld-%ld]\n", now.tv_sec, now.tv_usec);
	printf("calc [%ld-%ld]\n", calc.tv_sec, calc.tv_usec);
	printf("------------------------------------------\n");
	while(1) {
		usleep(1000 * 1);
		add_ms(&calc, 1);
		gettimeofday(&now, NULL);
		printf("------------------------------------------\n");
		printf("now  [%ld-%ld]\n", now.tv_sec, now.tv_usec);
		printf("calc [%ld-%ld]\n", calc.tv_sec, calc.tv_usec);
		printf("------------------------------------------\n");
	}
	return 0;
}

汎用OSの限界

加算処理が正しく動くか確認するため、現在時刻と書いてみた関数(add_ms)で計算した時刻の2つを縦に並べて表示しました。

usleep関数はマイクロ秒単位でのスリープ処理になります。

私の手元の環境では、上記の1ms単位での確認では徐々に計算した時刻の方が遅れていきました。
100msとか1secだとほぼ同じくらいに進んでいきますが、だいたい数十ミリ秒程度はcalcの方が遅れていました。

やはり、タイムシェアリングで動作する汎用OSだと、当然ですが精度的には限界があるようです。

usleepの精度

試しにusleepとgettimeofdayを繰り返すコードに修正して試してみましたが、

usleep(10);

としても大体100usの誤差が出ていました。

usleep関数はマイクロ秒オーダーのスリープ関数ですが、名前を信じて

usleep(1);

とかしてその通りに動くことは期待してはいけません

名前負けしてる関数なんです。

試した環境はCentOSとWindows10上のcygwinを使いましたが汎用OSで他のプロセスやカーネルの処理、ディスパッチにミリ秒かからないのであれば、むしろ頑張っているなぁという気もします。

感想

ということで実際に検証してみて、書いてみた関数の挙動的には問題はなさそうですが、こういった時間計算の関数が標準ライブラリに含まれない理由がなんとなく分かった気がしました。

日付や時間の分野は、扱うのが簡単見えてすごく難しいですね。

それにしてもusleepの実装が気になるところではあります。

後で見てみよう・・・

Elixir入門 ~2日目 リストとタプルを試す~

昨日はインタラクティブな環境iexを立ち上げてみたり、ファイルに書いたElixirのHello Worldを動かした。
まだ大してElixirの魅力とか特徴がつかめていない。

今日は、適当にデータ型をいくつか触ってみたり、昨日は触らなかったコンパイルを試してみる。

データ型

iexで少し遊んでみる。
iexの終了はCtrl-Cでabortを選べばいいみたい。

リスト

リストには何でも入る

[1, "abc", 3.14, false]

リストは内部的には、連結リストになっている。lispのconsセルと同じだ。
なので先頭に要素追加は速く、末尾への追加は遅いということらしい。

[0] ++ [1, 2, 3]

は早いけど、

[1, 2, 3] ++ [4] 

は遅い。

以下のような、指定したインデックスのオブジェクトも遅くなるそうだ。

 Enum.at(["a", "b", "c", "d"], 1)

みたいな書き方をする。

タプル

リストは連結リスト構造なのに対してタプルは配列という理解でよさそう。

tp = {1,2,3}

配列なので

tp[0]

でインデックスアクセスができそうだがそれはできない。
一般的な言語のインデックスアクセスに相当するのは

elem(tp,0)

のようなコードになる。

何をするにも関数を使う必要がある。関数プログラミングらしさを感じてきた。

コンパイル

Elixirのコードのコンパイル

$ elixirc ファイル名

コンパイルできる。

コンパイルすると、Elixir.ファイル名.BEAM

というファイルができる。


疲れたので続きは、また明日以降・・・

Elixir入門 ~1日目 導入~

コンパイラの授業でお世話になった石浦先生に教えて頂いたelixirを勉強してみることにしました。
(コンパイラの授業を受けた話はまだ書けていないのでそのうち書きたいと思います)

ちなみに石浦先生の研究室ではerlangFPGAの高位合成をする研究をしているそうです。
普段の記事は「です・ます調」で書いていますが、elixirの勉強の記事は個人的な記録としての意味合いが強いので、この勉強の記録は独白・口語体で書くことにします。
あと学習の過程を書いているのでその時点で間違ったことを書いている可能性はあります。
理解の度合いや指摘があれば直すと思いますが、もしここに書いている内容を参考にされる方は自己責任でお願いします。

はじまり

とりあえずがっつりelixirの勉強だけはできないだろうから毎日数行のコードを書いて行くことにする。
こういうのは本当に少しでもいいから毎日やった方がいいかもしれない。
コンパイラの勉強とかLinuxカーネルの勉強とか、やりたいことはいろいろあるし仕事関係の勉強もあるので時間はどうせたくさんは取れない。

なぜerlangにしなかったのか。

Elixirの文法がrubyに近いという噂を聞いたから。コツがつかめらたらerlangとかBEAMのレイヤも勉強したい。

参考書籍

プログラミングElixir

プログラミングElixir

サンプルプログラムのダウンロードもできる。

導入

elixir-lang.org

でインストール方法を調べる。

windows環境

とりあえずwindows用にインストーラを走らせてみた。
何も考えず「次へ」を押したので何が入ったとかよく分かっていない。

インストールが終わるとErlangがインストールされていた。elixirはErlang用のBEAMという仮想マシン上で動くらしいので、まぁそういうことなんだろう。
cygwinのコンソールからiexしてみても動かなかった。

後でパスを通そう。

Linux環境

VMLinux Mint上で、

$ sudo apt-get install elixir

してみた。

iexでインタラクティブなシェルが立ち上がったので多分大丈夫だろう。

Hello World

拡張子

elixirの拡張子は

.ex もしくは .exs にするのが習慣らしい。

.exはコンパイル済みのファイルに付け、 .exs はスクリプトに書くらしい。

感覚としては、Elixirは中間言語による仮想マシン方式とのことなので
Javaの .class と .java みたいな感じだろうか。
Javaと違ってコンパイルしなくても動かせる。LispHaskellと同じ。

IO.puts "Hello, World!"

スクリプト実行

コンパイルなしの実行はRubyPython等と同じで

$elixir ファイル名

とすればよい。

上記のコードをhello.exs に保存して動かす。

$ elixir hello.exs
Hello, World!

ちゃんと動いた。

iexでの実行

c ヘルパーという機能でiexからも実行できる

c "hello.exs"
Hello, World!
[]

評価された後[]が出ている。パッと見は空の配列に見えるけど、全てはリストで表現されているんだろうか。


コンパイルについてはなんか長くなりそうなので今日はこれまでにしておく。

CMakeでARM用にクロスコンパイルする

最近、オープンソースなどで配布されているソフトのビルドシステムがCMakeになっているのをよく見かけるようになりました。

CMakeはこれまでも何回か使ったことがありましたが、クロスコンパイルをしたいときの使い方を忘れてしまっていて、思い出すのに時間がかかったので備忘録として書いておきたいと思います。

CMakeでHello World

ロスコンパイルの前にC言語Hello WorldプログラムをCMakeを使って(セルフコンパイル)ビルドしてみます。

ビルドするコード

#include <stdio.h>

int main(int argc, char **argv)
{
	printf("hello world\n");
	return 0;
}

CMakeLists.txt を書く

CMakeLists.txtを最小限の構成で手っ取り早く書くとこんな感じです。

cmake_minimum_required(VERSION 2.8)
add_executable(helloworld main.c)

cmake_minimum_required は文字通りcmakeのバージョンです。
add_executable でターゲット名とソースファイルを指定します。

ビルド

cmakeの便利な点として、ソースファイルのディレクトリとオブジェクトの出力ディレクトリを分けてビルドできる点があります。
CMakeLists.txt があるディレクトリで、

$ mkdir build
$ cd build
$ cmake ..

とすると、作成したbuildディレクトリ内にMakefileなどビルドに必要なファイルが生成されます。

実行例はこんな感じです。

-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done

$ ls
CMakeCache.txt  CMakeFiles  Makefile  cmake_install.cmake

あとはこのbuildディレクトリでmakeコマンドを実行すればビルドできます。
私の環境だとコンパイラGCCが使われたようです。

$ make
Scanning dependencies of target helloworld
[ 50%] Building C object CMakeFiles/helloworld.dir/main.c.o
[100%] Linking C executable helloworld
[100%] Built target helloworld

$ ./helloworld 
hello world

ロスコンパイルを試す

これでCMakeによるセルフコンパイルの準備はできました。
ロスコンパイルを行う場合は、更にツールチェインファイルというファイルを作成する必要があります。
といっても大げさなものではなくて、文字通りクロスコンパイル時に使用するコンパイラの指定をするだけです。

今回はRaspberrypi でも実行できるので、クロスコンパイラとして arm-linux-gnueabihf-gcc を使ってみました。
私が試したビルド環境は64bit版の Linux mint です。

$ uname -a
Linux mint 4.4.0-21-generic #37-Ubuntu SMP Mon Apr 18 18:33:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

Ubuntu系のOSであればインストールは、

$sudo apt install g++-arm-linux-gnueabihf

でインストールできると思います。

ツールチェインファイルの書き方

さて、本題のツールチェインファイルは以下のようになります。

ファイル名を arm-toolchain.cmake として、以下のような記述をします。

SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc)

ツールチェインファイルを書いたら、後はセルフコンパイルの時と同様にビルド用のディレクトリを作ってcmakeコマンドを実行します。

$ mkdir cross-build
$ cd cross-build
$ cmake .. -DCMAKE_TOOLCHAIN_FILE=../arm-toolchain.cmake

セルフコンパイルの時との違いとして、

  • DCMAKE_TOOLCHAIN_FILE=../arm-toolchain.cmake

のようにツールチェインファイルのパスを指定してあげる必要があります。

-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/arm-linux-gnueabihf-gcc
-- Check for working C compiler: /usr/bin/arm-linux-gnueabihf-gcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done

$ make
Scanning dependencies of target helloworld
[ 50%] Building C object CMakeFiles/helloworld.dir/main.c.o
[100%] Linking C executable helloworld
[100%] Built target helloworld

$ file helloworld
helloworld: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=9bd916148cf9880f2494f47199e8c001244a450d, not stripped

ちゃんとARM用にクロスコンパイルできました。
かなりミニマムな構成でのビルドの例なので、実際にオープンソースのプロジェクトなどをビルドする際はもう少しオプションやパスの指定などが必要になってくるかと思います。

自分の雛形用として、Githubにおいています。もし参考にされる方がいらっしゃいましたらどうぞ。

gist.github.com

[2017/10/2 追記]
クロス環境向けのインクルードパスとかのプレフィックスはどうするんだろう・・・ってなったので追記しておきます。

クロス開発環境のプレフィックスについて

クロス開発環境を使う際には、クロスコンパイラが使用するインクルードパス・ライブラリパスの指定が必要になってきます。

cmakeを使ってプレフィックスパスを指定する場合は、

 -DCMAKE_INSTALL_PREFIX:PATH=クロス環境のプレフィックスパス

とすればよいようです。

上記の Raspberry pi の例だと、

$ cmake .. -DCMAKE_TOOLCHAIN_FILE=../arm-toolchain.cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr/arm-linux-gnueabihf/

のような感じになります。

感想

CMakeは書き方を覚えてしまえばMakefileを直接書くよりは楽といえば楽ですね。
昔はMakeの本を読んで一生懸命Makefileを書き方を勉強したものですが、Makefileは書かないとすぐに忘れてしまいます。

Makefileを書きたくないものぐさな私は、

$ echo "gcc main.c hoge.c -o target" > build.sh
$ chmod +x build.sh
$ ./build.sh

みたいなことをよくしています。

小規模な実験的なコードを書く場合はこのやり方でもよいのですが、プロジェクトが育ってくるとやはりビルド管理したくなります。

Makefileさえ知らない新人のころのあるプロジェクトでは、

ソースコード書いたらmfmkってコマンドを実行すればいいから」

と親切な先輩が教えくれた現場がありました。

あの便利な mkmf はLinux系のOSには標準では入っていないんですね。

依存関係とかを気にせずに特定のディレクトリのソースコード調べてMakefile生成するだけであれば簡単に作れそうに思います。気が向いたら作ってみたいです・・・

ウイルス性のいぼを自宅で治療した

最近、子供の足にウイルス性のイボができていたのですが、病院に行かずなんとか自力で治すことができました。

写真は治りかけの子供の患部の状態です。

削った後なのでかなりきれいになった状態ですが、(写真ではわかりづらいですが)赤いぶつぶつがまだ少し残っています。
*1

f:id:simotin13:20170904215656j:plain

[2017-11-18 追記]
この記事ですが、割と沢山の方に読んで頂いているようですので完治後の子供の写真を載せておきたいと思います。

f:id:simotin13:20171119005703p:plain

ウイルス性いぼによる赤い点々は完全になくなっています。
針で削ったこともあってか、皮膚は少し荒れていますが、イボ自体は完全に無くなりました。

*1:記事を書こうとは思っていなかったこともあり治療前から写真はとっていませんでした

続きを読む