ラベル Linux の投稿を表示しています。 すべての投稿を表示
ラベル Linux の投稿を表示しています。 すべての投稿を表示

2019年5月14日火曜日

ruby+gtk3 → require load error

rubyか、久しぶりだな.わたしはPerlが馴染みなのでね.
LinuxでGUIを動かしたくてgtkを動かそうと、まずはこういうサイトを読んでるところ.
https://qiita.com/kojix2/items/d9da8e08757dcc6b1a7d

だがトラブル発生.
この簡単なrubyコードを動かそうとするが、pathが通らない系のトラブルでerrorになる.
require 'gtk3'
window = Gtk::Window.new
window.show
Gtk.main

たとえばこんなerrorである.
% ruby simple.rb        ←上のcodeがsimple.rbとする
require': cannot load such file -- gtk3 (LoadError)
いきなり1行目のrequireでload errorになっている.

ちなみにいまわたしが使っているOSはLinuxだ.
Linux rtl 4.9.0-8-amd64 #1 SMP Debian 4.9.144-3.1 (2019-02-19) x86_64 GNU/Linux

当errorの回避にはいくつかのポイントがあったので読者諸氏がお困りの際には以下のプロセスを試してみてちょ.

gtk3が入ってないのかなぁと思ってinstallを試みるが、わたしの環境では既存だったのでこれは空振りだった.
% apt-get install gtk3

net情報ではちゃんとpathを切るためにはgemでinstallしろと書かれてたりするのでやってみるが、このgemが全然動かない.リビルドができませんとかいうerrorがでてすぐに止まる.ちなみにgemはruby libraryをinstallするのに使うコマンドだそうだ.
% gem install gtk3

net情報ではこれが必要だとか、、、
% apt-get install libgtk-3-dev

再度gemしてみる.さっきよりもリビルドが先まで進むがそれでも同じ症状で止まってしまう.
% gem install gtk3

さらにこいつもinstall、、、だがまだgemだめ~
% apt-get install ruby-dev

rakeが見つからないとかまだ怒っているので、、、rakeを入れてみる.
% gem install rake

こんどはどうだ?とgemをやってみると、、、やっと通った.
% gem install gtk3

ようやくこれも動くようになったよ.これで先へ進める.
% ruby simple.rb

症状をまとめると、3つのトラブルだったようだ.
1)rubyがgtk3のpathを知らない
2)path設定も含め、gemでgtk3をinstallすべきなのだがgemがerrorで止まる
3)gemを動かすに必要なものが不足していた

かしこ

2019年3月6日水曜日

OpenCVにて、USB CAM複数接続が動かないビョーキ(未解決)

OpenCV、画像処理には欠かせない便利なライブラリ.たまにお世話になっている.

今回はOpenCVのトラブルで死んだという話だ.詰んでしまった.

トラブルは、1台のPCにUSB CAMを3台接続したいが、ダメ~と怒られる.
(IP CAMではなくUSB接続のカメラである)

諸条件により、以前は無問題で使えていたが、最新のOpenCVでは複数台接続が出来なくなった.この諸条件というのがクセ者なので以下に諸々を記す.

【原因のあらまし】    (環境はLinuxにて)
1)USB CAMを3台接続してもちゃんと動く場合.
PCIバスにUSBホストコントローラが3つぶら下がっている.各USBホストコントローラ当りUSBCAMを1台接続する.これなら正常.しかしこういう接続はしたくない.
2)ダメな場合.
USBホストコントローラに複数のUSBCAMを接続した場合.「デバイスに空きがありません」系のメッセージで止まる.
3)いうまでもなくダメな場合.
USBHUBを介して複数台のUSBCAMを接続した場合でももちろんダメと云われる.
【トラブル詳細】
エラーメッセージの全容はこうだ.
libv4l2: error turning on stream: No space left on device
VIDIOC_STREAMON: No space left on device
OpenCV Error: Assertion failed (scn == 3 || scn == 4) in cvtColor, file /build/opencv-00QkEr/opencv-2.4.9.1+dfsg1/modules/imgproc/src/color.cpp, line 3737
terminate called after throwing an instance of 'cv::Exception'
  what():  /build/opencv-00QkEr/opencv-2.4.9.1+dfsg1/modules/imgproc/src/color.cpp:3737: error: (-215) scn == 3 || scn == 4 in function cvtColor

V4L2という、OpenCVではよく見かけるライブラリが、「そんな空きはありません」と怒っている.

このエラーが生じる場面は、
  cam0 = cv2.VideoCapture(0)
  cam1 = cv2.VideoCapture(1)     ←ここ
のように2台目のUSBCAMをopenしようとした時点だと思われる.

ここで、システム面の条件を.
OS: Kona-linux4.0
Linux 4.9.0-8-amd64 #1 SMP Debian 4.9.144-3.1 (2019-02-19) x86_64 GNU/Linux

OpenCV: 2.4.9.1
OpenCV: 3.4.5       ←upgradeしてみたけど症状は同じ

USBCAM駆動ソフト: C++、Python、 Python3      ←同じ症状

USBCAM: 中華製名無し製品
以前この投稿で書いたもの「画角の狭いUSB CAMが多いとお嘆きの貴兄に、広角のUSB CAMを贈ります

Python3+OpenCV3.4.5コード: C++でも状況は同じなので、簡単なPython3のコードを示す.USBCAMを2つOPENして新しいウインドウ2つにカメラ画像を表示するものだ.OpenCV3になって小さな地雷がたくさんあって困るわ、プンスカ!
import cv2
cam0 = cv2.VideoCapture(0)
cam1 = cv2.VideoCapture(1)    ←ここで死ぬ
if cam0.isOpened() is False:
    raise("CAM0_NG")
if cam1.isOpened() is False:
    raise("CAM0_NG")
#fourcc = cv2.CV_FOURCC('M','J','P','G')     <---NG
fourcc = cv2.VideoWriter_fourcc('M','J','P','G')
print(format(fourcc,'0x'))
cam0.set(cv2.CAP_PROP_FOURCC,fourcc)
cam0.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cam0.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
cam1.set(cv2.CAP_PROP_FOURCC,fourcc)

print( cam0.get(cv2.CAP_PROP_FRAME_WIDTH))
print( cam0.get(cv2.CAP_PROP_FRAME_HEIGHT))
print( cam1.get(cv2.CAP_PROP_FRAME_WIDTH))
print( cam1.get(cv2.CAP_PROP_FRAME_HEIGHT))

while True:
    ret, img0 = cam0.read()
    ret, img1 = cam1.read()
    cv2.imshow('0 PUSH SPAVE KEY to CLOSE', img0)
    cv2.imshow('1 PUSH SPAVE KEY to CLOSE', img1)
    if cv2.waitKey(1) == 32: break
cam0.release()
cam1.release()
cv2.destroyAllWindows()


【トラブル詳細2】
PCだけでなく、Rapsberry-piでも試してみたら、症状は同じだった.
OpenCV: 2.4.9.1


【トラブル回避策1】
上で述べたとおり、こういう接続にすれば問題が生じない.
しかしPCなら可能だがRapsberry-piはUSBホストコントローラが1つだけのように見える(lsusb)ので、この回避策は使えないし、たとえPCであってもこれは嫌だよぅ.


【トラブル回避策2】
USBCAMを別の製品に変えると治ってしまうのだ.LogiCoolの古い製品だ.
2つのUSBCAMの素性をlsusb -vで調べてみる.

名無し中華製品:
解像度:640x480、160x120、320x240、352x288、800x600、1280x720、1280x1024、1600x1200
IF:上の各々に、非圧縮/MJPEG が選択可

Logitech, Inc. Webcam B500:
最大解像度が1280x1024までという他は名無し中華と同じ.非圧縮/MJPEGも同じ.

この状況、いかがだろうか?
V4L2が「そんなリソースないわ」と怒る原因は、1600x1200の高解像度ゆえのwork area確保のためか、1600x1200の高解像度ゆえの帯域不足か、そんな匂いがしないだろうか?

しかし、上のPython3コードはそれらに対処したつもりなのである.この部分だ.
  fourcc = cv2.VideoWriter_fourcc('M','J','P','G')
  cam0.set(cv2.CAP_PROP_FOURCC,fourcc)
  cam0.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
  cam0.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
MJPEGで帯域を下げ、解像度下げで帯域を下げたつもりだ.しかしそれでもダメなのである.

ちなみに、ネットの質問コーナーに核心的なことが書かれている.曰く、
・原因は、コントローラに十分なUSB帯域幅がないことです
・カメラにMJEPGを使用させることができれば、問題は解決します
・OpenCVは取得を行うためにFFMPEGを使用しているかもしれませんし(linuxビルドは通常FFMPEGをサポートしています)、そしてOpenCVのドキュメントはFFMPEGが使用されているなら取得フォーマットを変更できないと認めています
・FFMPEGの設定を変更する必要があります
・カメラを別のUSBコントローラに移動してみてください

この優秀な回答者は、わたしの疑いのほとんど全てを語りつくしている....


【トラブル回避策3】
上で、ラズパイでも同じ症状だったと書いたが、実はそうでもない.
3年ぐらい前のラズパイにinstallしたOS等の環境なら無問題なのである、名無し中華UABCAMであっても

試しに同じラズパイでapt-get updateしたら複数USBCAMがダメになった.(logicoolでOKかどうかは未確認)

ちなみにOpenCVのrevisionはapt-get update前後で同じ 2.4.9.1 だった.
V4L2がどうかは未確認だが、OpenCVに連携している何らかのライブラリ群が原因と推測する.


【MJPEGの使用】
質問コーナーの回答をこう読む.
 ・FFMPEGが非圧縮伝送/MJPEGのどちらを使っているか不明
 ・FFMPEGは選択を拒む

ゆえに、FFMPEGを殺してOpenCV3.4.5をビルドしたら治らんだろうか?
OpenCVのビルドはこちらのページを参考にさせてもらった.
CMAKEのoptionの中にある、FFMPEGをOFFにしてビルドした.
   -D WITH_FFMPEG=ON
   -D WITH_FFMPEG=OFF    ←これ

これもやったけど、上のPython3コードで結果はダメなままだったのだ.


【撤退宣言】
これ以上の探索は無理だなぁ.V4L2のソースをたぐるとかはちょっと能力オーバーだ.

ライブラリ群を数年前のversionに先祖返りさせるっていう後ろ向き作業も虚しい.

中華製名無しUSBCAMから1600x1200なんつう使わないプロファイルを消してしまいたいけど、それはメーカーじゃないと無理無理.

OpenCV3系では3.4.5が最終版のようだ.
そして最新版は4.0.1が2018.12にリリースされたらしい.何が変わったんだろうね.

皆さんのお役に立てず申し訳ありません、切腹.

2019年1月2日水曜日

sambaをkona-linux 4.0 proに入れる

なんと、sambaについて書いたことがなかったようだ.備忘録として書いた気がしてたんだけど...なので書いとくね.(わりと最近まで忘備録かと思ってた)

当記事を公開した直後にapt-get upgradeしたらsambaが動かなくなってしまい、一旦非公開にした.fixできたので再度公開する.fixについては後述する.

【やりたいこと】
ファイルサーバを屋根裏に置いて、リモートログインしてごにょごにょ、WakeONLanでごにょごにょ、、、などしたいと思う.動画ファイル置場が欲しい.
OSはkona linux 4.0を使う.   →こちら
HDDはマシンに3台接続される.80GB,3TB,2TBの3台である.
家族がwindowsからnetworkで見れるようにするためにファイル共有のsambaを使う.

【やること】
手始めに考えることは、kona linux 4.0 proのデフォでsambaって動くようになっているのかな?
   cat /etc/samba/smb.conf
これは存在するのだが、
   ps aux | grep smb
では何も出てこない.sambaが走っていない.
つまりsambaが入ってないのだった.というわけで、sambaを入れる.
   apt-get update
   apt-get install samba

たぶん不要だけど念のため再起動してから、
   ps aux | grep smb
と打つと、smbdが走っていることがわかる.  →不幸だと起動しない(後述)
   root   <中略>   /usr/sbin/smbd

しかしまだwindowsからは見えない.

ここで、わたしのマシンにおけるHDDのマウント状況はこうなっている.fstab等の解説はここではしない.
   /dev/sdb: 80GB     /
   /dev/sdc: 3TB       /mnt/sdc
   /dev/sda: 2TB       /mnt/sda

/etc/smb.confをこちらのファイルで置き換える.これはわたしがカスタマイズしたsmb.confなので参考にどうぞ.このsmb.confは、3TBと2TBの2つのHDDを共有するために末尾にこう書いてある.読者の環境に応じてpath=の行を書き変えてくれ.
   [share]
   path = /mnt/sdc/share
        <中略>
   [share2]
   path = /mnt/sda/share2

このpathに沿うために/mnt/sdc配下にshareを作っておく必要がある.
   cd /mnt/sdc
   mkdir share
   chmod 777 share
/mnt/sdaにも同様.
   cd /mnt/sda
   mkdir share2
   chmod 777 share2

smb.confで一点注意がある.apt-get upgradeした後に、systemctl start smbdと打ってもsambaが起動しなくなってしまうトラブルだ.既知のトラブルで、こちらなどに書かれている.トラブル回避のために、smb.confを一行だけコメントアウトしたら治った.何故これが有効なのかは不明.
   ;   guest account = guest


マシンを再起動すると、windowsから3TB,2TB HDDが見えるようになる.

とりあえずこれでおしまい.


追記: fstabにHDDを追加するためにUUIDを知りたいときには、blkidと打てば表示される.
% blkid
/dev/sda2: UUID="515e2e1d-dfc1-4b03-833b-8342c9fcba6c" TYPE="ext4" PARTUUID="18560a89-02"
fstabにはこんな感じでHDDを追加したりする.
UUID=515e2e1d-dfc1-4b03-833b-8342c9fcba6c /mnt/sda2 ext4 defaults 0 0

------
失敗事例を記しておく.

当初、80GB HDDにsmb.confのpathを紐付けした.
そして、そのpathに3TB HDDへのリンクを張ったのだ.
しかしこういったリンク方式はダメだった.
windowsから見えるHDDサイズが80GBに見えてしまうのである.これでは3TBをつかえない.
sambaはリンク先空き容量までは考慮してくれないのね.ご注意のほどを.


なおいつもの通り、この情報でyouが死んでも知らんがなー、自己責任でどうぞ.

かしこ

2018年11月9日金曜日

【ALSA】PCオーディオ、フルビットsin波を出力し、それをキャプチャしてみた

最終的にはRapsberry Piでやりたいのですが、まずはLinuxで動かしてみたのが、
  ・PCのオーディオ出力端子にフルビットsin波を出力
  ・同信号をPCのオーディオLINE入力端子に接続し、キャプチャする
という動作です.

現在のLinuxのオーディオは、ALSAというドライバで実装されているらしい.いろんな事を出来るみたいだけどよくわかりません.
ともかく、今時のLinuxのディストリをinstallすればALSAが動くようになってるんじゃね?
「じゃね?」っていうのは、apt-get install alsa-dev みたいなことをやったのかどうか、もう忘れちゃったんだよね.なにせ、6月からこっち忙しくて自分の時間を持てなかったので、ALSA弄りで遊んでいるのは5ヶ月ぶりなんですよ.5ヶ月前に何をインストしたかなんて覚えてませんわ.

そんなわけで5ヶ月前までALSAの資料漁りをしとったわけですけど、結局のところALSAを解説する日本語書籍は無いっぽいです.なのでネットの諸情報をネチネチとこねくり回して「あ~動いたっぽいなぁ」と思ったので以下に記します.

とても参考になったサイトはこちらでした(日本語).ありがとうございます.
GitHubにも参考になるcodeがあったのでチラチラと見ました.

下記情報で貴方が損害を蒙ったとしてもわたしは知らないので、やるなら自己責任でやってくださいね.フルビット出力でスピーカ鳴らすとスピーカーや耳が故障するかもです.劇薬ですので.(わたしはオシロで波形観測だけしてます)

【環境】
Kona-Linux 4.0 pro
全てterminal windowでの操作.
ハードウエアは当ブログのこの記事で書いた中古Gateway
オンボードのIntel audioを使用   (audio cardは搭載せず)
PCオーディオ端子はこういう配線   (LINE入力をキャプチャする)
PCオーディオ出力(緑)→ボリウム→PCオーディオLINE入力(青) という信号の流れ.
ボリウムでレベルを下げないとキャプチャがサチるため.
サウンドデバイスファイルはこうなっていて、出力pcmC0D0p  キャプチャpcmC0D0c を使っている.(わたしの環境ではということで)
   ls /dev/snd
   by-path/   hwC0D2  pcmC0D0c  pcmC0D2c  seq
   controlC0  hwC0D3  pcmC0D0p  pcmC0D3p  timer
出力とキャプチャを同じデバイスで実行できるものなのか不明だったが、やってみたら動いた.

【いきなりソースコード】
2つあります.わたしのcodeはゲスなのを前もってお詫びしとく.動いたことを確認してからupしたのでたぶん動くと思います.大音響が出るからむやみにスピーカーを鳴らさないで.

1)sin.c
sin波をPCオーディオ出力端子に出すプログラム.(fs=48kHz)
エラー処理を割愛した病気codeだけど許して.
コンパイルはこれ→   gcc -lasound -lm sin.c -o sin
うごかすにはこんなかんじ↓
   sin      →Lch 1000Hz    Rch 333Hz    フルスケール
   sin 2000 1500 0.5        →Lch 2000Hz  Rch 1500Hz フルスケールの半分
止めない限り永久に出力しつづける.
オーディオデバイス指定は"default"にしてあるが、わたしの環境では"hw:0,0"と指定したのと同等である.hw:0,0はpcmC0D0p/pcmC0D0c のALSAにおける呼び名であり、上でリンクした日本語サイトの解説を読んだら理解できるかもよ.

2)adc.c
PCオーディオLINE入力端子をキャプチャするプログラム.(fs=48kHz)
エラー処理を割愛した病気codeだけど許して.
指定サンプル数でキャプチャを打ち切り、stdoutにLch,Rchの順でtab区切りで出力する.
コンパイルはこれ→   gcc -lasound adc.c -o adc
うごかすにはこんなかんじ↓
   adc                       →default動作、1024サンプルで打ち切り、hw:0,0
   adc 4096               →4096サンプルで打ち切り、hw:0,0
   adc 2048 hw:1,0    →2048サンプルで打ち切り、hw:1,0
リダイレクトでファイルに落とすとかは好きにしてね.ex.  adc > mycapfile
ファイル名をcapture.cにすりゃよかった気がする.

追記
3)周波数チャープ出力プログラムも作ってみた.   charp.c
4)M系列を出力するのも作ってみた.   Mseq.c


【フルビット出力】
このような結果を得ました.このMBDでは、フルビットかつMixer volume最大だと3.3Vppが出てきました.
なんで3.3Vppなのでしょうか? ありがちなのは、ADCのVrefに3.3Vを突っ込んでいるだけだろっていうやつかしら.

軽く調べたのだけど、PCのオーディオ出力レベルに厳密な規格は無いみたい.
この3.3Vppというのは1.167Vrmsです.コンスーマオーディオにありがちと云われる0dBV=1Vrmsとは異なるし、プロ用機器の0dBu=0.775Vrmsとも異なる.+4dBu=1.228Vrmsとも異なる.いずれとも似て非なるレベルです.
(0dBuは600Ωに1mWだってさ)

考えるだけ無駄だと思ったので放置プレイにしときます.

【alsamixerを使え】
出力が出ないとか、キャプチャがオールゼロだとか、そういうトラブルに見舞われたらミキサーを疑ってみたらいいかもしれません.

まず出力のミキサー調整は、alsamixer と打って出て来るこの画面で調整します.
(画面の様子が変だったら alsamixer -c 0 としたら良いかも)
PCオーディオ出力端子(緑)のレベルは、Master volumeとFront volumeで決まるようになっていました.他のMBDでも同じかどうかは知りません.

次にキャプチャのミキサー調整は、alsamixer -V capture と打って出るこの画面で調整します.
PCオーディオLINE入力端子に接続しているのだから、Input Source=Lineに設定する必要があります.これを怠るとシステムノイズがキャプチャされるばかりになってしまいます.
わたしの環境では、Capture volumeがキャプチャレベルの調節でした.他のMBDでも同じかどうかは知りません.
Capture volumeを最大にしてしまうと、フルビット入力レベルが10mVppとかいう極小電圧になってしまいます.アンプゲインが大きすぎるのです.
Capture volumeを24とか19まで下げて使うことにしました.ところが19の時ですら、150mVppでフルビットになってしまうのですからそれでもLINE入力と呼べるのかと疑問に思います.LINE入力なら2Vppでフルビットなどというオーダーが通常だと思いますから.
ちなみに、Capture volumeを5などに下げたら450mVppでフルビットなどと改善が期待されますがダメでした.なぜならMBDのLINE入力アンプが150mVppを超えるとサチってしまうのです.妙なレベル設定です.
マイク出力を拾うに適したレベル設定を志向しているのではないかと推測するのですが、考えるだけ無駄なので放置プレイにしときます.

【フルビットキャプチャ】
Capture volume=19
PCオーディオLINE入力=150mVpp
Lch=1000Hz     Rch=333Hz
キャプチャしたファイルを描画した波形.前半がLchで、後半がRchになっている.
振幅は、±30000を超えているので、ほぼ16bitフルビットです.

フィルタかけたりして遊べますね.遊んだらまたレポートします.

追記【サンプリング周波数の自由度】
Intel audioのサンプリング周波数設定の下限/上限はどうなっているのでしょうか?
APIの
    snd_pcm_hw_params_set_rate_near()
でサンプリング周波数をセットします.
いろいろなサンプリング周波数を試みた結果、4000~2999999Hzまでちゃんと動いているようです.上限がほぼ3MHzというのは驚きです.また、1Hz単位でセットできた風に見えるのですが、DDSの対応する周波数STEPがそんなに細やかであるというのも信じ難いです.なんだかなーと思いつつこれも放置プレイ.

ここで例えばサンプリング周波数を200kHzにしたら、PCオーディオ出力端子に100kHzを出せるようになるのでしょうか? やってみたけど、LPFのせいで約20kHzまでしか出ませんでした.

かしこ

2018年5月24日木曜日

linux kernel 4.xでパラレルポートのLEDを制御してみる (非module版)

ひとつ前の記事では、パラレルポートの先につけたLEDをON/OFFさせるために、わざわざmoduleをこしらえて、丁寧なやり方で実装しました.けれど正直言ってかったるい.

今回は、moduleを使わずに、ユーザープログラムからパラレルポートへ出力させます.
ソースコードは正味たったの9行です.アホかってくらい簡単.

-----
その前に長い経緯を書かねばなるまいて...

こちらの記事に書きましたとおり、わたしのLinuxマシンにはCH382という謎のchipの載った中華基板が刺さっています.CH382がパラレルポートのchipなのですが、これのdetasheetがネットに無くて、レジスタ仕様が判らんのです.

もしもそれが判ったなら、ioperm(),outb()を使ったユーザープログラムでパラレルポート制御なんか簡単にできてしまうはずです.

ちくしょうな事にch382が仕様不明なのでmodule programmingしたのがひとつ前の記事だったのです.

moduleで動かせたので、今一度ch382の仕様を調べてみようとしたけれどムダでした.でも、いろんなことをして次第に手掛かりはつかめました.

【dmesg】
dmesgに次の文字が見えます.
parport0: PC-style at 0xe100, irq 16 [PCSPP(,...)]
parport0のIOアドレスは0xe100であるらしい.

【/proc】
cat /proc/ioports | grep parport
      e100-e102 : parport0
ここにもe100が登場する.しかもたったの3バイトと表示される.

【lspci】
lspciの表示の抜粋です.ここにもe100が見られます.
01:00.0 Serial controller: Device 1c00:3050 (rev 10) (prog-if 05 [16850])
        Interrupt: pin A routed to IRQ 16
        Region 0: I/O ports at e000 [size=256]
        Region 1: Memory at f0000000 (32-bit, prefetchable) [size=32K]
        Region 2: I/O ports at e100 [size=4]

これらより、e100から3バイトがパラレルポートIOであるらしい.

【wiki】
wikiパラレルポートを見ると、IBM PCのIO仕様は、SPPモードで3バイトだと書かれている.SPPモードとは、セントロニクスとしても知られる最も素朴なモードです.
レジスタbitとコネクタの相関関係は、この図がわかりやすい.
D0~D7のIN/OUTを指定するレジスタは無いのだろうかと勘繰りたくなるが、SPPモードでは出力オンリーなので、directionレジスタは存在しない.(SPP,ECP,EPPの選択レジスタは何処に在るのかという問題は依然として不明)


以上の情報から、わたしのLinuxマシンでは、e100番地を書き換えてやれば、パラレルポートの先端につけたLEDをON/OFFできると推測されました.

------
やってみたソースコードはたったのこれだけ.IOを直接触っているので劇薬です.またマシンによってはe100番地じゃないケースもあり得るので注意してください.しくじってもわたしはしらんよー.
#include <sys/io.h>
#include <linux/unistd.h>
#define PARADRS 0xe100
int main(int argc, char *argv[]){
  if(argc!=2) return -1;
  if(ioperm(PARADRS, 8, 1)!=0) return -1;
  outb( argv[1][0], PARADRS );
  return 0;
}

ioperm()はe100番地の8bitをアクセスする許可を得ています.
outb()はe100番地をargvで上書きしてます.

ビルド方法
gcc -I/lib/modules/4.16.7-rt1-kona-rt/build/include leddirect.c
linux versionの部分は各位の環境によりけりで書き換えて下さい.

使い方
a.out 1
これで、パラレルポートのLEDが'1'のascii code=$31=00110001で発光します.
でも、これはroot権限者じゃないと動かないと思いますので注意、注意.
温故知新

linux kernel 4.xでパラレルポートのLEDを制御してみる (module版)

Linuxでパラレルポートを制御するmoduleをもぞもぞと弄ろうとしています.しかしわたしはmoduleは良く判らんのです...

残念なコトに、linux moduleについて解説された書籍は2000年頃に出版された古い本が多く、そういう本はkernel2.4をベースに書かれていたりします.ところがkernel2.6でパラレルポートmodule関連libraryが刷新されたようで、2.4の書籍に掲載されたsample codeはそのままでは動かなかったりします.
2.6以降は4.xに至るまでさほど変わってない印象です.ヘッダファイルの置き場所が変わってたりはしたけど.

新しい情報を求めてnetを徘徊しまして、パラレルポートに取り付けた8ヶのLEDを任意に点灯できるようになったので以下に書き記しておきます.

ここで使っているLinuxは、Kona-Linux 4.0で、kernel versionは4.16です.
Linux 4.16.7-rt1-kona-rt #1 SMP PREEMPT RT
あれれ、RT patchあてたっけかなぁ? まぁいいや.

※誤りのため貴殿が損害を受けても知らんので素直に死んでくださいね.

【パラレルポートのハードウエア的なこと】
延長ケーブルの末端にLEDを8ヶつけています.この写真は'1'=$31を出力している場面です.00110001が発光しています.
回路はこうなってます.470Ωじゃなくて1kΩにしましたが.
【ソースコード】
こちらに置いときます.実験用なのでprintk()がたくさん入ってます.

参考にしたのはこちらのページですが、同ページのcodeそのままではちょっち不具合があります.正しくrmmodできないので、2度目のinsmodで死んでしまうんです.何か追加したんだけど忘れちゃった.

なお、module programmingについてはこちらなどを参考にどうぞどうぞ.

【Makefile】
obj-m := led.o
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

$(PWD)じゃ良くないとかあるみたいですがとりあえずこれでよろしく.

Makefileって何?という方は申し訳ないですが自力でなんとかしてください.

わたしのマシンは自分でKernelをビルドした環境なのでこのMakefileでmakeできますけど、ディストリをinstallしただけの環境だと動かないのかも.自力でなんとかしてください、よろしく.

【当moduleの使い方例】
% make                      ビルド
% insmod led.ko      当moduleの登録
% echo 1 > /dev/parportled         parallel portへ出力
% rmmod led            当moduleの削除

・insmodコマンドでled.koをkernelに登録します.自動的に/dev/parportledが作られます.
・当moduleの仕様は、/dev/parportledに文字を流し込むと、パラレルポートに出てくるというものです.上の例では、キャラクタを1つだけの'1'を/dev/parportledに流し込んでいるので、ASCIIコードで'1'=$31=00110001で、上の写真の様にLEDが点灯する結果になります.
・最後にrmmodして当moduleをKernelから削除しています.それと同時に/dev/parportledも削除されます.

【いきなりですが、当moduleがどんな動きをしているのか?】
当moduleのsource codeには、様々なcallback関数が記述されています.どの関数がどのtimingで呼び出されるのかを実際に調べてみました.dmesgコマンドの表示結果を脚色したものを以下に示します.

・moduleを登録する際に以下の関数が呼ばれています.
% insmod led.ko
=>ENTER led_init()
 alloc_chrdev_region()
 class_create()
 device_create()
 cdev_init()
 cdev_add()
 parport_register_driver()
=>ENTER led_attach()
 parport_register_device()
=>CMPL led_attach()
=>CMPL led_init()

・'1'=$31を出力する際に以下の関数が呼ばれています.
% echo 1 > /dev/parportled
=>led_open() (nothing to do)
=>ENTER led_write()
 parport_write_data()
=>CMPL led_write()
=>led_release() (nothing to do)

・moduleの削除の際に以下の関数が呼ばれています.
% rmmod led
=>ENTER led_exit()
 parport_unregister_driver()
=>led_detach() (nothing to do)
 device_destroy()
 class_destroy()
 cdev_del()
 unregister_chrdev_region()
=>CMPL led_exit()


【ソースコードについて】
いろんなライブラリ関数が登場して幻惑されてしまいます.

当moduleは、たぶん以下のような構造になっているのだと思っています.飽くまでもわたしの想像ですので.
・ハードウエアのパラレルポートに近いところにparportというデバドラが居る.
・その上位にキャラクタデバイスを意味するcdevというデバドラが居る.kernel2.6以降ではcdevを使うよう推奨されているらしい.
・両デバドラに、/dev/parportledというデバイスファイルを紐付けする必要がある.
・/dev/parportledは、下図の3つの関数を使って生成される.(mknodは使わない)
・parportに、led_attach(),led_detach()を紐付けする必要がある.
・cdevに、led_open(),led_write(),led_release()を紐付けする必要がある.
・moduleがkernelに登録・削除されたらled_attach(),led_detach()がcallされる.
・/dev/parportledに文字が流し込まれるとled_open(),led_write(),led_release()が順番にcallされる.

これを念頭に置いて、以下のソースコードを眺めてみます.
なお、エラー処理は割愛してありますんでー.

↓デバイスファイル名を/dev/parportledとする.
#define DEVICE_NAME  "parportled"

↓たかがパラレルポートを動かすのに何かいろいろと必要でめんどくさー.
static dev_t dev_number;               デバイスファイルのmajor/minor番号
static struct class *led_class;     デバイスファイルの属性
struct cdev led_cdev;                   キャラクタデバイス
struct pardevice *pdev;               Parallel portデバイス

↓/dev/parportledに文字が流し込まれた時にcallされopenする.中身無し.
int led_open(struct inode *inode, struct file *file){ return 0; }

↓openの後にcallされる.文字をパラレルポートにwriteする.
ssize_t led_write(struct file *file, const char *buf, size_t count, loff_t *ppos){
  char kbuf;
  copy_from_user(&kbuf, buf, 1);
  parport_claim_or_block(pdev);
  parport_write_data(pdev->port, kbuf);
  parport_release(pdev);
  return count;
}

↓writeが終わったらcallされcloseする.中身無し.
int led_release(struct inode *inode, struct file *file){ return 0;}

↓上記led_open/led_write/led_releaseを紐付けるところ.
static struct file_operations led_fops = {
  .owner = THIS_MODULE,
  .open = led_open,
  .write = led_write,
  .release = led_release,
};

以上は、デバイスファイル/dev/parportledをwriteするための関数達でした.

以下に登場するのは、moduleの登録・削除時にcallされる関数達です.

↓他のデバドラがparalell portを占有していて困ったときにcallされる.中身無し.
static int led_preempt(void *handle){  return 1; }

↓当moduleの登録時にcallされる.デバドラparportと/dev/parportledを接続する役目と思う.他のデバドラがparallel portを占有中の際はled_preempt()がcallされる.
static void led_attach(struct parport *port){
  pdev = parport_register_device(port, DEVICE_NAME, led_preempt, NULL, NULL, 0, NULL);
}

↓当moduleの削除時にcallされる.中身無し.
static void led_detach(struct parport *port){ ; }

↓led_attach(),led_detach()をデバドラparportに紐付けるところ.
static struct parport_driver led_driver = {
  .name   = "parportled",
  .attach = led_attach,
  .detach = led_detach,
};


↓当moduleの登録時に最初にcallされる関数.
int __init led_init(void)
{
  ↓/dev/parportledを自動的に作る.major番号はお任せ.かつてはmajor番号決め撃ちでデバイスファイルを別途作る必要があったが、kernel2.6以降では動的なmajor番号が推奨されるらしい.そのためにここがある.
  alloc_chrdev_region(&dev_number, 0, 1, DEVICE_NAME);
  led_class = class_create(THIS_MODULE, DEVICE_NAME);
  device_create(led_class, NULL, dev_number, NULL, DEVICE_NAME);

  ↓led_open(),led_write(),led_release()をキャラクタデバイスcdevに紐付ける.
  cdev_init(&led_cdev, &led_fops);
  led_cdev.owner = THIS_MODULE;

  ↓cdevに/dev/parportledを紐付ける.dev_numberには/dev/parportledのmajor,minor番号が格納されている.
  cdev_add(&led_cdev, dev_number, 1);

  ↓デバドラparportを登録する.
  parport_register_driver(&led_driver);
  return 0;
}

↓当moduleの削除時にcallされる関数.諸々を清算してます.
void __exit led_exit(void){
  parport_unregister_driver(&led_driver);
  device_destroy(led_class, MKDEV(MAJOR(dev_number), 0));
  class_destroy(led_class);
  cdev_del(&led_cdev);
  unregister_chrdev_region(dev_number, 1);
  return;
}

module_init(led_init);     init()を登録
module_exit(led_exit);     exit()を登録


以上でソースコードはおしまいです.
moduleというものは、callback関数を記述して組み込むのが主な作業なので、慣れると比較的追いやすいように思いました.

かしこ

2018年4月30日月曜日

自宅のNetworkについて (23台いる)

外出先から家庭内LANに接続できたら便利かもと、VPNを試してみました.Linux serverを建ててゴニョゴニョとした設定で苦労したのではありません.NTTの光モデムにVPNサーバ機能があるのでそれをチョイと設定すりゃおしまいです.こんな簡単にVPNが出来ちゃうのならJKでも主婦でも誰でもVPNできちゃいます.
だがしかし、VPNしてみて判ったのは、外出先からアクセスしたいdataってそんなに多くないです.逆にsecurityが心配なのでVPNの窓口はすぐに閉鎖しました.さよならVPN.

VPNのついでに、家庭内LANに接続されている機器を調査しました.
pingを撃ちまくるscriptはかったるいのでやめといて、netdiscoverというコマンドが便利です.こんなコマンド↓
netdiscover -r 192.168.0.0/16     (Linuxにて)

netdiscoverにぞろぞろと23台もnetwork機器が出てきやがりました.そんなに居るの? 萩尾望都の「11人いる!」を想起しました.

netdiscoverでscanしたってWIFIチップのベンダー名ぐらいしか判らないので、デバイスを特定するには現物のMAC addressを見なくちゃいけません.家の中をうろついてチェックしました.家族3人分のデバイスが多数参加しています.
・network機器  3台           NTT光モデム、WIFI-AP(2台)
・windows    4台
・MacBook   1台
・Linux         3
rapsberry pi   1台
・android     6台               スマホなど
Game        2台             PS3、PS-VITA
・AV機器       3台      amazon fire stick、android AV player、SONY AV amp

将来、多数の家電機器がIoTデバイスとしてnetworkに参加するようになったら機器の特定なんかやってられなくなるんじゃなかろうか? 隣家のエアコンが我が家のnetworkに紛れ込んでいても気づかなっかたりして.そういうの困るぞー

かしこ

2018年4月18日水曜日

骨董品、EeePCが我が手に → Linuxマシンへ


古のPCでEeePCというのがありました.2008年発売だったそうです.もっと古かったかと思ってた.

ASUSがEeePCを出したのと同時期に、MSIはWind Notebook U100を出しました.これらはIntelがAtom CPUを使った小型PCを提唱して開発されたPCだと思います.間もなくIntelがやる気を失ったのか知らんけど、一時的なブーム?で終わりました.

わたしはU100を買ったんです.それも3台も買ったの.うち1台は子供が小学生になったので「これを使いなさい」と与えました.

EeePCもU100もOSはwinXPでした.Vistaが2006年リリースでしたから、XPの末期に発売されたPCだったのですね.EeePCにはAtom N270 1.6GHzが載っていまして、なんとsingle coreなんです.

↓今までご縁のなかったEeePCでしたが、なぜか今、わたしの手元にあります.知人に要る?と言われて、クレクレタコラになりました.骨董品だ.U100よりも作りはしっかりしています.さすがはASUS.
↓いまさらwinXPを使う気にはならないので、Kona Linux 4.0をインストしてみましたら、デバドラ等に何の問題もなく動きました.wifiも繋がっています.
↓底板を外すと、HDD,wifi,DDRが見えます.
↓DDR2 667 2GBです.秋葉のJUNK屋のレジ脇に二束三文で転がっているレベルの骨董品です.

EeePCには第二の人生を送ってもらおう.

かしこ

2018年4月9日月曜日

kona linux 4.0 jack リアルタイムカーネル (RTLinux) を試す (3) パラレルポート

今回はパラレルポートをトグルして遊びます.RTLinuxだと時間精度が良いかどうかを確かめます.

その準備で死に掛かりましたが崖っぷちで踏みとどまりました.

死に掛かったのは、CH382というICが載ったパラレルポート増設PCIeカードのドライバが、Linux Kernel4.xに対応していないため動かなかった.module sourceを書き換えることで使えるようになりました.

なお、当情報により貴殿がいかなる損害を受けてもわたしは知らんのであった.

【経緯】
ある日のこと、¥5000ぐらいの小型中古PCを買おうと秋葉へ出かけました.RTLinuxをインストするマシンとして使うためです.
一昨年ごろには、企業が除却した小型PCが秋葉のJUNK屋でたくさん売られていました.¥5000ぐらいで.
しかし今では、そういうお手ごろな中古PCは売られてないですね.想像ですけど、途上国へ大量に流れてるんじゃないかな.北海道の港で見かけるロシア向け国産自動車みたいに.

「安いPCがないー、どこにもないー」と徘徊し、じゃんぱらにて、食指の動くブツを発見しました.
GATEWAY/Celeron G540 2.50GHz
MEM4GB/HDD1TB/DVD不調/KBDなし
USB3.0/埃なし美品/¥4900(税込)
どーせDVDは使わないのでそれでもいいやと日比谷線でハンドキャリーしました.DVDの不調はトレイ機構がたまに引っかかる程度でした.ま、いいか.

RTLinuxの記事はそのマシンで遊んだ成果を記したものです.

その後、parallel portを弄って遊ぼうと思いマシンのリアパネルを見て「パラレルがないー、どこにもないー」とアタマを抱えてしまいました.というのは嘘で、今時のマシンにはシリアルもパラレルも無いのがフツーですから動揺なぞしません.
そこで、parallel portを追加するPCIeカードを中華通販で買いました.WCH社のCH382というICが載っています.流通量は多いようです.¥600ぐらいだったかな.

ところがこのカードがわたしのLinuxマシンで動かないでやんの.
・ネットの情報によると、このページはkernel4.xでは使えなかったと書いています
・その一方で、Kernel4.1.19への対応方法というページもある.これを書いている時点で最新のKernel4.14.29には同ページの変更が反映されている

だとすると、CH382のカードはKernel4.14.29で使えてもいいじゃないかと思われますが、、、症状は、、、
・/dev/parport0も/dev/lp0も存在しない.mknodしても焼け石
・dmesgに、lp: driver loaded but no devices found という不吉な文字がある
・lsmodによると、ppdevは動いている
・lspciによると、カードは認識できている
これらから、デバドラは起動しているが、デバドラがカードを認識してないような気がします.


【試行】
CH382パラレルカードに付属のCDROMにLinux用デバドラが入っています.ところが、それはKernel3.xまでしか対応していません.悪い状況.

謎のサイトからCH382のドライバをDLしてみたけど、Kernel2.6までしか対応してません.もっと悪い状況.

Kernel4.14.29 sourceを探すとそれらしいファイルなら在ります.
   /usr/src/linux-4.14.29/drivers/parport/parport_serial.c
この中に、こういう記述があります.
   { 0x1c00, 0x3250, 0x1c00, 0x3250, 0, 0, wch_ch382_2s1p}
この行はCH382カードを識別するPCI-IDと思われます.

一方で、lspciで表示される同カードの情報はこうなっています.
   01:00.0 Serial controller: Device 1c00:3050 (rev 10)  (prog-if 05 [16850])
1c00:3050の部分が、vendor id/device idを意味していると思われ、sourceとは3050の部分が食い違っています.これは怪しい.

試しに、parport_serial.c をこのように書き換え、Kernelをビルドします.
   { 0x1c00, 0x3050, PCI_ANY_ID, PCI_ANY_ID, 0, 0, wch_ch382_2s1p}
RT Kernelビルドのやり方は、本連載その1に記した流れで行います.RT patchも当てます.


【結果】
/dev/parport0が作られました.
dmesgに、パラレルポートが認識されたような表示が出ました.
   parport0: PC-style at 0xe100, irq 16 [PCSPP,TRISTATE]
   lp0: using parport0 (interrupt-driven)
うまくいったかんじー.


【アプリ走行】
こちらを参考にして、簡単なプログラムを作りました.sourceをここに置きました.

このプログラムは、50usec毎にパラレルポート出力データの1→0を入れ替えてる(トグル)つもりの動作をします.

ビルドは、gcc pp.c でOKでした.ライブラリoptionは不要.

a.outを動かします.パラレルポートをオシロで観測します.
↓なんじゃーこりゃぁ、、、全然100usecじゃないよ.1周期226usecになっちゃってる.それだけじゃなくて波形がチラチラしているのは周期が跳んでるからです.でもこの低性能はscheduler設定をなにもやってないので無理もありません.
↓次にschedulerを最強にして動かします.chrt -r 99 a.out というコマンドを使います.chrtはscheduler優先度を変えて実行する作用があります.周期が119usecになり、周期跳びも少し減りました.リアルタイム性が改善しました.
RTでもこんな程度かと少しがっかりしました.


【まとめ】
・使えなくてお金をムダにしたかと思ったパラレルカードを使えました
・パラレルポートをトグルするプログラムを書きました
・scheduler優先度を強化することによりRTLinuxの性能を発揮できたようです

その2へ

かしこ

2018年4月1日日曜日

kona linux 4.0 jack リアルタイムカーネル (RTLinux) を試す (2) スケジューラ優先度

前回はkona linux4.0にRTlinuxをインストした顛末を書きました.RTLinuxでは周期的割り込みの時間精度が高いことを確認できました.  →前回へ

ただ、RTLinuxで周期割り込みthreadをどうやって実装してるんだろという点についてはさっぱりわからんちんでした.

今回はアプリソフトを弄ってみて、どうやってるんだろの疑問に自分で答えます.

印象としては、1chip-CPUのtimer割り込みの単純さに比較すると、鬼のようにめんどくさいことをしてます.はぁ~というかんじー.

参考にしたのはcyclictestというtoolです.周期実行threadの時間誤差を統計してくれるtoolです.これの肝の部分を抜き出してどんな動きをさせているのかを調べました.

なお、本稿はLinux kernel 4.14.24と、リアルタイムパッチrt19の組み合わせでのハナシです.本稿を書いている時点では新しいversionかと思います.

間違いがあるかもしれません.その際は素直に死んでくださいませ.

-----
【timer割り込みと、周期実行threadの大きな違い】
わたしはOSに詳しい人ではないので、周期割り込みというと1チップCPUのtimer割り込みしか知りません.timer割り込みの仕組みは、カウンタを廻しておいてカウント値が所定の値になったらピキッと割り込みがかかるようになっています.

ところが周期実行threadの場合は、誤解を恐れずに述べるならば、
2018.4.1.16:39:49:0293840
のような実時間時計を参照します.(正確にはUNIX時刻だと思われる=1970年1月1日午前0時0分0秒からの経過秒数)
例えば1秒後にthreadが起動されたいなら、
2018.4.1.16:39:50:0293840
に目覚まし時計をセットして眠ります.
眠るという表現は的を得ていて、clock_nanosleep(時刻)という関数を呼び出して眠ります.寝ている間はOSにCPUを大政奉還しているものと思います.

threadでは実時間時計を使うのですから、system callで現在時刻を読む→未来の目覚まし時刻を計算→目覚ましセット→寝る、という仕事が生じます.

ふ~んそうなんだ、、、正直言ってめんどくさいですかねぇ.もしかしたら、Kernel2.xの頃のRTLinuxにはXXmSec毎に目覚まし鳴らせ!的な関数が提供されていたのかもしれません.また、Kernel名人ならmoduleを駆使して簡単に処理する技があるのかもしれません.わたしにはそんな技はありません.


【通常KernelとリアルタイムKernelの扱い方の違い】
呼び出す関数が異なる、などのような本質的な違いは無いようです.周期実行threadのexeファイルは、通常KernelでもRT Kernelでも動きましたから.ただし結果には差が生じます.RT Kernelの方が周期誤差が少ないです.

大雑把にはそういう理解なのですが、もう少し細かい理解が必要です.周期誤差を改善するためにschedulerに根性を注入する必要があります.たとえRT Kernelであってもschedulerが根性なしだと周期誤差はだらしないままです.
つまりこう云えます.
  1)RT Kernelを意識せずにthreadのコードを書くのが基本だが、
  2)高優先度のscheduler設定にするのがRTさを享受するために必須

ヤレヤレ、schedulerと来たもんだ...

PC関連書籍には流行り廃りがあるようで、今本屋へ云ってもpthreadの書籍はほとんど売られていません.10年以上前でしたかね、windowsのスレッドの本がたくさん在ったのは.絶版で古本でしか入手できない書籍を除くと、今出回っているのはこれだけのように思います.帯に書かれた「レガシーな技術を今あえて学ぶ!」があまり売れないジャンルを示しています.
この書籍はpthreadの生成消滅についてとても判りやすく書かれていて好感が持てるのですが、初級者向けだからかschedulerについては解説が無いんです.立ち読みしたけど買ってません.

もう一つ、1998年刊行で絶版と思われるのがこちらの怖い本.毛虫の表紙だなんてどうかしています.怖くて触れません.古本を買ってすぐに表紙を切って捨てました.この毛虫本にはschedulerについて少しですが解説されていますので有り難い.しかし英文和訳文は読みにくいので有り難くない.


以下ではソースコードの説明などです.ソースは300行ぐらいです.

【ソースコードとビルドと実行】
2coreのceleronマシンでしか動作確認しておりません.
ソースはこちら.GPLとか判らなくてゴメン.
ビルドはこれで出来ています.RTライブラリは指定してません.
cc -c jitter.c -Wall -O3 -o jitter.o -lpthread
cc -Wall -O3 -o jitter jitter.o -lpthread
root権限のひとにしか実行できないと思いますのでsu rootなどよしなにやってください.

動作例1 (scheduler最強)  =RT Kernelの能力発揮できてる状況
$ jitter -p99
CPUs = 2      policy = fifo
Th: 0 CPU:0 Pol: 1 Pri:99 Intvl: 1000 Cyc:10000 Min: 4 Act:  9 Avg:  8 Max:  38
Th: 1 CPU:0 Pol: 1 Pri:99 Intvl: 1100 Cyc: 9091 Min: 3 Act: 10 Avg: 14 Max:  64
Th: 2 CPU:0 Pol: 1 Pri:99 Intvl: 1200 Cyc: 8334 Min: 3 Act: 10 Avg:  9 Max: 107
Th: 3 CPU:0 Pol: 1 Pri:99 Intvl: 1300 Cyc: 7693 Min: 4 Act:  8 Avg: 18 Max:  66
Th: 4 CPU:0 Pol: 1 Pri:99 Intvl: 1400 Cyc: 7143 Min: 4 Act: 36 Avg:  8 Max:  61
-p99はscheduler優先度が最高である意味.
CPUs=2は2coreである意味.
policy=fifoはschedulerの動作モードがfifoモードである意味.fifoは最強かと.
Th:0-4はスレッドを5つ起動している意味.
CPU:0は0番目のCPUで走行している意味.
Pol:1はscheduler動作モードfifoの意味.=最強
Pri:99はscheduler優先度99の意味.=最強
Intvl:1000はthread周期=1000uSecの意味 (2番目以降のthread周期は10%増し)
Cyc:10000はthreadを10000回呼び出す意味.
Min:Act:Avg:Max:はIntvlからの誤差uSecの意味.小さいと優秀

動作例2 (scheduler最弱) =これじゃ通常Kernelと変わらないよな状況
$ jitter
CPUs = 2      policy = other
Th: 0 CPU:0 Pol: 0 Pri: 0 Intvl: 1000 Cyc:10000 Min:18 Act: 68 Avg: 66 Max: 5281
Th: 1 CPU:0 Pol: 0 Pri: 0 Intvl: 1100 Cyc: 9097 Min:10 Act: 63 Avg: 70 Max: 1088
Th: 2 CPU:0 Pol: 0 Pri: 0 Intvl: 1200 Cyc: 8338 Min:12 Act: 74 Avg: 69 Max: 1768
Th: 3 CPU:0 Pol: 0 Pri: 0 Intvl: 1300 Cyc: 7697 Min: 6 Act: 65 Avg: 60 Max: 1001
Th: 4 CPU:0 Pol: 0 Pri: 0 Intvl: 1400 Cyc: 7145 Min: 8 Act: 61 Avg: 67 Max: 5387
動作例1よりも誤差uSecが悪化しています.その理由は、
Pol:0はscheduler動作モードがotherの意味.otherは最弱.
Pri:0はscheduler優先度0の意味.=最弱

動作例3 (thread 20個、1000回で終了)
$ jitter -t20 -l1000
CPUs = 2      policy = other
Th: 0 CPU:0 Pol: 0 Pri: 0 Intvl: 1000 Cyc: 1000 Min:  5 Act: 63 Avg: 63 Max: 1103
Th: 1 CPU:0 Pol: 0 Pri: 0 Intvl: 1100 Cyc:  910 Min:  6 Act: 82 Avg: 69 Max:  885
Th: 2 CPU:0 Pol: 0 Pri: 0 Intvl: 1200 Cyc:  835 Min: 10 Act: 91 Avg: 52 Max:  668
Th: 3 CPU:0 Pol: 0 Pri: 0 Intvl: 1300 Cyc:  770 Min:  9 Act: 70 Avg: 61 Max:  490
  :
Th:16 CPU:0 Pol: 0 Pri: 0 Intvl: 2600 Cyc:  384 Min: 26 Act: 63 Avg: 74 Max: 4408
Th:17 CPU:0 Pol: 0 Pri: 0 Intvl: 2700 Cyc:  371 Min:  6 Act: 60 Avg: 58 Max:  144
Th:18 CPU:0 Pol: 0 Pri: 0 Intvl: 2800 Cyc:  357 Min: 14 Act: 67 Avg: 77 Max: 4338
Th:19 CPU:0 Pol: 0 Pri: 0 Intvl: 2900 Cyc:  344 Min: 18 Act: 63 Avg: 74 Max: 3920

動作例4   (その他のoption)
-?         ヘルプ表示
-i456     thread0周期=456uSec   (2番目以降のthread周期は10%増し)
-s25      25秒で自動停止


【main()】
流れの主要箇所だけ抜き出します.エラー処理も割愛します.

int main(int argc, char **argv)
{
        options(argc, argv);      コマンドoptionの解読

        parametersはthreadの諸元を記述します.statisticsは測定結果を格納します.
        それらのメモリエリアを確保します.
        num_threadsには造るthread数が入ってます.
        parameters = calloc(num_threads, sizeof(struct thread_param *));
        statistics = calloc(num_threads, sizeof(struct thread_stat *));

        このfor()でnum_threads個のthreadを生成します.
        for (i = 0; i < num_threads; i++) {
                各threadの諸元を格納する構造体の確保
                parameters[i] = par = calloc(1,sizeof(struct thread_param));

                各threadの測定結果を格納する構造体の確保
                statistics[i] = stat = calloc(1,sizeof(struct thread_stat));

                構造体のデータ設定
                par->prio = priority;        プロセス優先度
                schedulerポリシー、FIFOは最強、OTHERは最弱で通常
                if (priority && (policy == SCHED_FIFO)) par->policy = policy;
                else    par->policy = SCHED_OTHER;
                par->interval = interval + interval*0.1*i;      thread周期
                par->max_cycles = max_cycles;            thread loop回数
                par->stats = stat;                測定結果格納場所の指定
                par->tnum = i;                   thread番号
                stat->min = 1000000;
                stat->max = 0;
                stat->avg = 0.0;

                thread生成.timerthread()を紐付け.
                pthread_create(&stat->thread, &attr, timerthread, par);
        }     thread生成ここまで

        全threadがon goingゆえshutdown=0である限りloopする
        while (!shutdown) {
               usleep(100000);      100mS毎に測定結果を表示する
               for (i = 0; i < num_threads; i++){
                      printf(.....)       測定結果を表示
               }
        }

        for (i = 0; i < num_threads; i++)     全threadの終了を待つ
               pthread_join(statistics[i]->thread, NULL);
}


【timerthread()】
周期実行threadの本体.主要部分だけ.
loopの動作はこうなっています.
目覚ましセット→寝る→目覚める→現在時刻を読む→次の目覚まし時刻を計算

static void *timerthread(void *param)
{
        scheduler設定
        memset(&schedp, 0, sizeof(schedp));
        schedp.sched_priority = par->prio;          優先度
        sched_setscheduler(0, par->policy, &schedp);      ポリシー

        threadの周期時間(秒)を計算する.整数秒とnsec単位の少数秒で表現
        interval.tv_sec = par->interval / USEC_PER_SEC;
        interval.tv_nsec = (par->interval % USEC_PER_SEC) * 1000;

        clock_gettime(0, &now);     現在時刻を採取

        目覚まし時計時刻を計算= next = now + interval
        next = now;
        next.tv_sec += interval.tv_sec;
        next.tv_nsec += interval.tv_nsec;
        tsnorm(&next);           少数秒が1を超えてたら桁上げ

        N秒後に自動停止する時刻を計算= stop = now + N
        memset(&stop, 0, sizeof(stop));
        if (duration) {
          stop = now;
          stop.tv_sec += duration;
        }

        周期loop
        while (!shutdown) {      測定終了したthreadがshutdown=1にする
                目覚まし時計をセットして寝る
                ret = clock_nanosleep(0, TIMER_ABSTIME, &next, NULL);

                ret = clock_gettime(0, &now);       目覚めた時刻を採取

                diff = calcdiff(now, next);      diff = 目覚めた時刻 - 目覚まし時刻

                if (diff < stat->min)   stat->min = diff;       測定値update
                if (diff > stat->max)   stat->max = diff;
                stat->avg += (double) diff;
                stat->act = diff;

                停止時刻ならオシマイにする
                if (duration && (calcdiff(now, stop) >= 0)) shutdown=1;

                stat->cycles++;          loop回数カウント

                目覚まし時刻を計算 = next = next + interval
                next.tv_sec += interval.tv_sec;
                next.tv_nsec += interval.tv_nsec;
                tsnorm(&next);        少数秒が1を超えてたら桁上げ

                loop回数満了ならオシマイにする
                if (par->max_cycles == stat->cycles) shutdown=1;
        }     周期loopここまで
        pthread_exit(&ret);       threadの自己終了
}


【まとめ】
ふーん、RT Kernelってそうなのかぁ、なんだかなぁ...
でもやり方が判ったからまぁいいや.

その1へ           その3へ

かしこ

2018年3月21日水曜日

kona linux 4.0 jack リアルタイムカーネル (RTLinux) を試す (1) 導入

リアルタイムOSってのを使ってみたくありませんか?

あのォ・・・
RT-LinuxというOSがあるんですけどォ、使い方がよく判らないんですゥ.

"rtlinux"を検索すると、HOWTOなどに混じってサンプルプログラムも散見されます.しかしそれらは2000年前後の古い内容であって、これを書いている2018年のRT-Linuxには合わないんです.例えば、rtl.h/rtl.mkなんていうファイルは現在のRT-Linuxには在りませんから.
最新のRT-Linuxを入手してインストは出来ても、RT APIの仕様が判らない.APIのヘッダファイルがどれかすら判らない.そんな状況です.

どうやらその答えは、たぶんですけどこういう事情じゃないかと思います.
・かつてはrtl.hに定義されているような、RT-Linux固有のAPIが在った
・2018年時点ではそのようなRT-Linux固有のAPIは無いらしい  →追記参照
・LinuxにもRT-Linuxにも標準装備のpthreadがある
・pthreadには、スレッド優先度という機能がある
・スレッド優先度を高くするとリアルタイム性が改善される
・スレッド優先度=最高設定にしたとき、Linuxならベストエフォートだが、RT-Linuxならリアルタイム性が保証される

つまり、RT-Linuxについて学び使うにはpthreadそのものが対象となる(らしい).

追記:/lib/x86_64-linux-gnu/librt-2.24.so というのがあり、これはリアルタイムライブラリのようです.こちらにlibrtの解説があります.
nm librt-2.24.soでは関数名は表示されませんでした.
strings librt-2.24.soではそれっぽい関数名が出てきました.一例を示します.
pthread_self
pthread_getschedparam
pthread_cond_signal
pthread_setschedparam
__clock_getres
__clock_gettime
__clock_settime
__clock_nanosleep

以下では、kona linux 4.0 でRT-Linuxをインストールして動作テストしてみる辺りまでをレポします.


【Linuxディストリ】
Kona-Linux 4.0 Jackを使います.JackはRT-Linux対応版です.(自分でごにょごにょやればJack以外でもRT化はできます.Jackならお膳立てされとると、そんなかんじー)


【リアルタイムカーネルへのupgrade】
Kona-Linux 4.0 Jackをinstallした直後は、リアルタイムカーネルはinstallされていません.install後に管理者自身ががやらなくちゃいけません.

どうしたらリアルタイムカーネルをinstallできるのか?

これらはやってもムダでした.
apt-get update
apt-get upgrade
メニュー → システムツール → パッケージアップデーター
メニュー → システムツール → GDbiパッケージインストーラー

rebootすると現れることのある「リアルタイムカーネルにupgradeするよう促すダイアログ」から入ってkernel upgradeするのがJackのお膳立てです.しかし、このダイアログが出るかどうかが気まぐれで困りました.強制的に同ダイアログを出すには、こうします.
メニュー → 実行 → initdesktop.sh
upgradeが完了するまでには1時間ぐらいかかります.

リアルタイムカーネルがインストされると、このような名前のdirectoryができているはずです.
/usr/src/linux-headers-4.14.24-rt19-kona-rt
また、uname -a ではこのように表示されます.
Linux rtl 4.14.24-rt19-kona-rt #1 SMP PREEMPT RT Mon Mar 12 19:05:16 JST 2018 x86_64 GNU/Linux
(これらはaptがupdateされると数字が変わります)


【同じリアルタイムカーネルをビルドしてみる】
ムダな行為ではありますが、同じversionのkernelをコンパイルしてみます.

1)linux kernel 4.14.24 source   (これ自体は通常Kernel)
ここからLinux sourceを辿れます.
https://www.kernel.org/pub/
下へ降りてゆきます.
https://www.kernel.org/pub/linux/kernel/v4.x/
大量のファイルがあるので、中間ぐらいまでscrollすると linux-4.14.24.tar.gz がありますのでDLします.保存directoryは/usr/src/です.tar xvfzで解凍します.

2)kernel 4.14.24をRT化するpatch
uname表示結果から4.14.24かつrt19を探します.
kernel sourceをDLしたご近所にRT化patchがあります.
https://www.kernel.org/pub/linux/kernel/projects/rt/4.9/older/
rt19はこのファイルです.
patch-4.14.24-rt19.patch.gz
DLします.保存directoryは/usr/src/です.gzip -dで解凍します.

3)patchをあてる
cd linux-4.14.24
cat ../patch-4.14.24-rt19.patch | patch -p1

4)configする
通常はここでコンパイルの前にconfigするのですが、わたしのマシンにはRT-Linuxをインスト済みなので既存のconfigをコピってそれで良しとしちゃいます.
cp /usr/src/linux-headers-4.14.24-rt19-kona-rt/.config .config
古い.configを読み込ませます.
make oldconfig

5)ビルドする
make
make modules_install
make install
もしかしたらncursesが無いと文句言われるかも.その際はこれで回避.
apt-get install ncurses-dev

リブートすると、、、RT-Linux kernelになってるはずです.

わざわざ再コンパイルしてみた理由は、rtl.hやrtl.mkが生成されるかなと期待したからなのですが、そんなファイルは生成されませんでした.フッやっぱりな、とシニカルな笑みを浮かべたわたしなのでしたー


【リアルタイム性の動作チェック】
rt-testsというtoolがあるのでそれを使います.
apt-get install rt-tests
apt-get install libnuma-dev          ←念のため

実行例1 最高優先度 RT-Linux
cyclictest -t 5 -i 10000 -l 1000 -c CLOCK_REALTIME -p 99
T:0 (16648) P:99 I:10000 C:1000 Min: 9 Act:12 Avg:15 Max:  30
T:1 (16649) P:99 I:10500 C: 952 Min: 8 Act:16 Avg:16 Max:  51
T:2 (16650) P:99 I:11000 C: 909 Min:10 Act:18 Avg:17 Max:  61
T:3 (16651) P:99 I:11500 C: 869 Min: 8 Act:29 Avg:22 Max:  57
T:4 (16652) P:99 I:12000 C: 833 Min: 7 Act:17 Avg:16 Max:  49

実行例2 最抵優先度 RT-Linux
cyclictest -t 5 -i 10000 -l 1000 -c CLOCK_REALTIME
T:0 (25417) P: 0 I:10000 C:1000 Min: 7 Act:15 Avg:16 Max: 335
T:1 (25418) P: 0 I:10500 C: 952 Min: 7 Act: 8 Avg:16 Max:1004
T:2 (25419) P: 0 I:11000 C: 909 Min: 7 Act:24 Avg:19 Max: 513
T:3 (25420) P: 0 I:11500 C: 869 Min: 8 Act:18 Avg:19 Max: 493
T:4 (25421) P: 0 I:12000 C: 833 Min: 8 Act:15 Avg:18 Max: 545

cyclictestコマンドは、周期実行されるスレッドのjitterを測定してるらしいです.
P:99が最高優先度で、P:0が最低優先度の意味だと思います.
Max:の数字がjitter最大値uSecです.
知見は、
P:99だとjitterが少ない
P:0だとjitterが大きい
・でもRTのおかげなのかどうかは不明

なお、ここでCPUに負荷を与える方法は、裏でLinux Kernelをビルドするコトでやってます.yes > /dev/null & では負荷が軽すぎて性能差が出ませんでした.

cyclictestコマンドのoption説明、
 -t 5    スレッドを5つ起動する
 -i 10000      周期実行サイクル10mSec
 -l 1000      1000回で停止
 -c             clock source
 -p            優先度

次に、起動時のGRUB画面で通常Kernelをセレクトして、cyclictestをやってみました.果たしてRTとの性能差は見られますかどうか?

実行例3 最高優先度 通常Kernel
cyclictest -t 5 -i 10000 -l 1000 -c CLOCK_REALTIME -p 99
T:0 (11947) P:99 I:10000 C:1000 Min: 2 Act:13 Avg:15 Max: 972
T:1 (11948) P:99 I:10500 C: 952 Min: 2 Act:12 Avg:13 Max: 802
T:2 (11949) P:99 I:11000 C: 909 Min: 2 Act:10 Avg:11 Max:  42
T:3 (11950) P:99 I:11500 C: 869 Min: 3 Act:14 Avg:12 Max: 240
T:4 (11951) P:99 I:12000 C: 833 Min: 3 Act:11 Avg:12 Max: 244

いや~これは通常Kernelですと優先度最高でもjitterが大きくてだいぶやられちゃってますね.RTの恩恵がひしひしと伝わってきます.他の条件でも試しましたが、煩雑になるので割愛します.


【まとめ】
Kona Linux 4.0にRT-Linuxを無事インストできました.

裏でkernelをビルドするという過大なCPU負荷をかけた状態でなら、RT-Linuxの恩恵を実感できました.
RTOSですと音楽再生音質が優れるという説を耳にしますが、それはCPU負荷が大きいときには顕著ですが、CPU負荷が小さいケースではRTOSでも音質改善は少ないと想像します.

これを書いている時点でのRT-Linuxには、RT-Linux固有のAPIは存在せず、専らプロセスの優先度設定によってRT性を強化する仕組みのように思われます.
cyclictest.cがincludeしているヘッダファイルを調べましたが、thread関連はいつもお馴染みの pthread.h sched.h ぐらいで、RT-Linux patchで追加されたヘッダファイルは見当たりません.

残念ながらpthreadの使い方はあまり判っていません.
情報は古いですが、pthreadの関数一覧はこれらが参考になるかと.
 pthread.h
 sched.h
timer割り込みしたいなぁ、、、

追記:このページにSCHED_FIFOなどのスケジューラ優先度設定についての解説があり、参考になります.FIFOが最強でOTHERが最弱.
https://linuxjm.osdn.jp/html/LDP_man-pages/man7/sched.7.html


その2へ

かしこ