Rustで高階関数を使う
はじめに
高階関数は便利だ。
なので、rustでも使いたい。
よって、使い方を検証した。
下記にrust環境構築方法をまとめている。
dockerとvscodeでrust環境を構築する on Windows10 - イヌツムリのメモ
結論
引数になる関数をBox
でラップすると、関数を引数に取れる。
戻り値になる関数をimpl
を指定すると関数を戻り値に取れる。
dyn
のありなしで何が変わるのか調べる。 -> traitオブジェクトを引数に取るときはdynをつける。
(2021/04/07 追記)
関数を引数にとるサンプルコード
fn map(f: Box<dyn Fn(i32) -> i32>, a: &Vec<i32>) -> Vec<i32>{ let mut b = vec![]; for &num in a{ b.push(f(num)); } b } fn twice(x: i32) -> i32{ x + x } fn main() { let arry = &vec![4, 5, 3, 2]; let v4 = map(Box::new(twice),arry); println!("{:?}", v4); }
引数に渡すときはBox::new(関数名)
で渡せる。
受け取り側の関数定義はBox<dyn Fn(i32) -> i32>
で受け取れる。
(2021/04/07 追記)
dynをつけないと下記エラーでコンパイラに怒られる。
trait objects without an explicit
dynare deprecated
トレイトを引数にとるのでなく、トレイトオブジェクトを引数に取るので、ということらしい。
トレイトではどんな関数が実装されているか不明で、メモリ確保ができないから、と理解した。
オブジェクト化されていれば、内容が明確なのでメモリ確保が可能になる。
関数を返すサンプルコード
fn map(f: Box<dyn Fn(i32) -> i32>, a: &Vec<i32>) -> Vec<i32>{ let mut b = vec![]; for &num in a{ b.push(f(num)); } b } fn add_x(x: i32) -> impl Fn(i32) -> i32{ move |a: i32| -> i32{ a + x } } fn main() { let arry2 = &vec![4, 5, 3, 2]; let f = add_x(4); let v2 = map(Box::new(f),arry2); println!("{:?}", v2); }
関数を戻り値に指定するにはimpl
を指定する。
関数を変数に束縛する際に引数を与える場合、move
しないと与えた変数の所有権を移せない。
Boxよりもimplを使ったほうがよい?
(2021/04/07 追記)
dynはトレイトオブジェクトを返すときに明示するとわかった。
なので、add_xの戻り値は、implでなく下記のようにBox
fn add_x(x: i32) -> Box<dyn Fn(i32) -> i32>{ let f = move |a: i32| -> i32{ a + x }; Box::new(f) } fn main(){ let arry2 = &vec![4, 5, 3, 2]; let f = add_x(5); let v2 = map(f,arry2); println!("{:?}", v2); }
というか、下記のようにimpl使ったほうがスッキリとかける。
(i32をTに変えてるのは気にしないで...)
map()の宣言時や、mapの引数にtwiceを与えるときにBoxを使わなくて良いのでスッキリする。
ただ、dynがあとからrustに実装されたことを考えると、私の理解に間違いがあるように思う。
fn map<T>(f: impl Fn(T) -> T, a: &Vec<T>) -> Vec<T> where T: Copy + std::ops::Add<Output = T> + std::ops::Sub<Output = T> + std::ops::Mul<Output = T> + std::ops::Div<Output = T> { let mut b = vec![]; for &num in a{ b.push(f(num)); } b } fn twice(x: i32) -> i32{ x + x } fn main(){ let arry1 = &vec![4, 5, 3, 2]; let v4 = map(twice, &arry1); println!("{:?}", v4); }
あんまり変わんないけど、pythonで巨大数を速く作る
はじめに
最大値を求めるコードを書くときに、最大値を仮おきしたい。
その際、どのようにしたら高速に作れるか調べた。
結果、どの方法もあんまり変わらなかった。 もっと値を大きく設定した場合に差が出るかもしれないが、必要なさそうなので検証しない。
検証
ビット演算、累乗、floatのinfで比較した。
def gettime(): return time.perf_counter() s = gettime() INF = 1 << 30 e1 = gettime() - s print(e1) s = gettime() a = 10**9 e2 = gettime() - s print(e2) s = gettime() c = float('inf') e3 = gettime() - s print(e3)
ちなみに、ビット演算は30回シフトすれば109を超える。
結果
1.3999999999847468e-06 2.6000000000192536e-06 1.8399999999973993e-05
結果、floatのinfが一番遅く、bit演算が一番速かった。
しかし、floatのinfのほうがbit演算で算出した値より遥かに大きいので
bit演算で更に大きな値を求めた場合、floatのinfと比べてどうなるのか検証が必要である。 (やらない)
しかし、そんなに速度に変わりがないように思われるので、気にならなければfloatのinfを使うのが良さそう。
まとめ
巨大な値を作成する速度を比較した。
結果、bit演算が一番高速だった。
しかし、速度の差が小さいので、常に最大値を取れるfloatのinfが良さそうではある。
virtualboxにcentos8を入れたかったができなかったので久しぶりにvagrantに頼ってみた話
はじめに
kernel driverをいじりたかったのでcentos8を入れることにした。
(弊社環境は未だにcentos6... 誰が自動化されていない環境でウン百台もアップデートするんだ...)
centos8のisoを用いたインストールがterminalからできなかったので、
vagrantを使って環境を用意した。
vagrant
vagrant 1.7とかvirtualbox 4.xとか入ってたのでアンインストールして下記を入れた。
- vagrant_2.2.14_x86_64
- VirtualBox-6.1-6.1.16_140961_el6-1.x86_64
プロキシ環境下なので設定
$ export http_proxy="http://proxy.server:8080" $ export https_proxy="https://proxy.server:8080" $ vagrant plugin install vagrant-proxyconf
$ vagrant box add https://app.vagrantup.com/generic/boxes/centos8 $ vagrant init generic/centos8
まとめ
vagratを使って、プロキシ環境下にcentos8環境を作成した。
開発環境を整えたらboxファイルを作っておこうと思う。
(centos8そのうち使えなくなるので更新しなくちゃあ...)
vagrantのインストール手順もそのうちまとめよう。
linux kernelのテスト方法
linux kernelのテスト方法
linux kernelの開発でテストしたかったので調べた。
まずは項目だけ。
そのうち、使い方等を追記したい。
静的コード解析
- sparse
- smatch
- coccinelle 自動パッチ
自動テスト
- ktest
- kselftest
- LTP
- autotest テストインフラ
- kunit ユーザレイヤで試験できる
- syzkaller ファジングテスト
まとめ
smatch、kunitがあればよさげ
kunitは新しめ5.xのカーネルじゃないと無理っぽい?
autotestがあると試験で楽ができそう
syzkallerはある程度開発が進んだら脆弱性ないか調べるのに導入したい
参考
pythonの多次元リストの罠にハマった
はじめに
久しぶりにpythonを触ったら、ハマった。
多次元リストを生成して、そのうちの1つ値を変更すると、
他の列の値も変更されてしまった。
初期化方法がまずく、1つの列をコピーして多次元リストとしていたことが原因だった。
リスト内包表記すれば、それぞれの列を別のオブジェクトとして生成してくれるので同じ問題が発生しない。
(そういえばそうやった...)
ハマった例
よくあるリストの初期化方法として下記の方法がある。
dp = [0] * 3 print(dp) # [0, 0, 0] dp[0] = 1 # [1, 0, 0]
しかし、これがハマりどころで、同じようにして3 * 3のリストを生成すると、
[0] * 3
のリストをコピーして多次元リストを生成してしまう。
それにより、[0][0]
の値を変更したつもりが他の列の値も変更してしまう。
dp = [ [0] * 3 ] * 3 print(dp) # [ [0, 0, 0] , [0, 0, 0], [0, 0, 0] ] dp[0][0] = 1 # [ [1, 0, 0] , [1, 0, 0], [1, 0, 0] ]
解決策
多次元リストを生成するときは、下記のようにリスト内包表記を用いると リストオブジェクトをコピーしないため、トラブルにならない。
dp = [ [0] * 3 for _ in range(3) ] print(dp) # [ [0, 0, 0] , [0, 0, 0], [0, 0, 0] ] dp[0][0] = 1 # [ [1, 0, 0] , [0, 0, 0], [0, 0, 0] ]
おわりに
[0] * 3
で、0番目の値が1番目や2番目にコピーされているけど、
[ [0, 0, 0] ] * 3
ではリスト[0, 0, 0]
のアドレスがコピーされているということがわかった。
pythonで何が値で、何がアドレスなのか上手に隠蔽してもらっているためにハマったようだ。
アドレスを意識するような言語を触っているから納得感あるが、
アドレスの概念がない人に教えるのが大変そうだ。
linux kernelドライバでタイマー割り込みを実現するのにhrtimerを使ったのでメモする
はじめに
hrtimerはhigh resolusion timerの略。
従来のtimer_list
より高解像度なタイマーを提供する。
もとのドライバソフトウェアでは、timer_list
を用いたブロックデバイスを使用していた。
timer_list
では割り込みできる最小単位がmili secであったため、
nano secで割り込み可能なhrtimer
を使用するコードに変更した。
変更する過程で調べたことを下記にまとめる。
16666666nano secで割り込むようにして、printkで割り込んだ時刻を記録したが
誤差が発生することがわかった。
誤差が発生する要因の1つに仮想マシン上で実行していることが挙げられる。
実機でもそのうち評価できたらいいな。
環境
- virtualbox上
- CentOS 7.9.2009
- 3.10.0-1160.15.2.el7.x86_64
使用できるか確認する
下記で調べられる。
後で追記する。
cat /proc/timer_list
.resolution: 1 nsecs
event_handler: hrtimer_interrupt
初期化
調べたことを記録するに当たり、要素だけを抜き出したコードもどきを下記に示す。
(計測に使ったコードそのままではないという意味)
ドライバの初期化関数で、wait_q
およびhrtimer
の初期化を行う。
hrtimer_init
の引数は下記の通り。
void hrtimer_init(struct hrtimer *timer, clockid_t which_clock, enum hrtimer_mode mode);
- struct hrtimer : タイマー
- clockid_t which_clock:
CLOCK_MONOTONIC
かCLOCK_REALTIME
、CLOCK_BOOTTIME
、CLOCK_TAI
を選べる - enum hrtimer_mode mode:
HRTIMER_MODE_ABS
かHRTIMER_MODE_REL
を選べる
hrtimerのメンバであるfunctionにタイマーが期限切れになったときに発火する関数を与える。
hrtimer_start ()で、タイマーの計測を開始する。
int hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode)
ktime_t には、ktime_get()で現在時刻を取得し、そこにnanosecだけ進めた時刻をktime_add_ns()で計算して設定する。
ktime_t で設定した時刻に到達すると、functionに与えた関数が発火する。
static struct mymod_device{ struct hrtimer timer; wait_queue_head_t waitq; }mymod_dev; int __init mymod_init(void){ init_waitqueue_head(&vsync_dev.waitq); hrtimer_init(&mymod_dev.timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); mymod_dev.timer.function = &mymod_interrupt; long nanosec = 1000; hrtimer_start(&mymod_dev.timer, ktime_add_ns(ktime_get(), nanosec), HRTIMER_MODE_ABS); }
割り込み
初期化関数内でタイマーが期限切れになったときにmymod_interrupt()を発火するように設定した。
mymod_interrupt()を下記に示す。
wake_up_interruptible_all()でwaitqで待っている関数を発火する(後述)。
hrtimer_forward()で次にtimer.functionに与えられている関数(mymod_interrupt())を発火する時刻を設定する。
u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval)
ktime_t now に現在時刻を、ktime_t intervalに待機する時間を指定する。
ここで、発火してから現在時刻を取得する処理時間分の遅延が誤差になっている?
return HRTIMER_RESTART; で再度タイマーをスタートしている。
1回だけ実行したい場合は、 return HRTIMER_NORESTART;とする。
以上により、wait_event_interruptible()で待機している関数を、intervalごとに起床する。
enum hrtimer_restart mymod_interrupt(struct hrtimer *timer){ wake_up_interruptible_all(&mymod_dev.waitq); long nanosec = 16666666; ktime_t currtime, interval; currtime = ktime_get(); interval = ktime_set(0,nanosec); interval = ktime_set(0,nanosec); hrtimer_forward(timer, currtime , interval); return HRTIMER_RESTART; }
割り込み待ち
mymod_interrupt()で割り込むまで、待機する関数の説明をする。
wait_event_interruptible()で待機する。
mymod_interrupt()内のwake_up_interruptible_all()で起床する。
起床すると、copy_to_user()でユーザランドのデバイスファイルへへ書き込む。
ssize_t mymod_read(struct posix_clock *pc, uint rdflags, char __user *buf, size_t count){ wait_event_interruptible(mymod_dev.waitq, true); copy_to_user((void *)buf, "hello ", len); return (len); }
計測
上記ではhelloを送るように書いているが、実際はgetnstimeofday()で時刻を取得して送っている)。 カーネルレイヤの結果と、ユーザレイヤの結果をそれぞれ示す。
printkで出力した、割り込み時刻と前回からの差を下記に示す。
差の期待値は016666usecであったが、最大約1.5msecの誤差がある。
一方、ほぼ設定したタイマーどおりに動作しているパターンも存在する。
[ 143.477075] dddd 1617174009.033413, 033413 [ 143.493679] dddd 1617174009.050083, 016670 [ 143.510381] dddd 1617174009.066785, 016702 [ 143.527836] dddd 1617174009.084235, 017450 [ 143.543590] dddd 1617174009.099994, 015759 [ 143.560254] dddd 1617174009.116658, 016664 [ 143.576927] dddd 1617174009.133331, 016673 [ 143.593592] dddd 1617174009.149996, 016665 [ 143.611618] dddd 1617174009.168155, 018159 [ 143.626930] dddd 1617174009.183335, 015180 [ 143.643595] dddd 1617174009.200000, 016665 [ 143.660191] dddd 1617174009.216595, 016595 [ 143.678135] dddd 1617174009.234729, 018134 [ 143.693585] dddd 1617174009.249989, 015260 [ 143.710252] dddd 1617174009.266657, 016668 [ 143.726976] dddd 1617174009.283381, 016724 [ 143.743596] dddd 1617174009.300001, 016620
ユーザランドで出力した値を下記に示す。
kernelから受信した時刻と、前回からの差を出力している。
差の期待値は016666usecであったが、最大約1.5msecの誤差がある。
sec 1617174009, usec 033413, 7:0:9.33 ,sa 033413 sec 1617174009, usec 050083, 7:0:9.50 ,sa 016670 sec 1617174009, usec 066785, 7:0:9.66 ,sa 016702 sec 1617174009, usec 084235, 7:0:9.84 ,sa 017450 sec 1617174009, usec 099994, 7:0:9.99 ,sa 015759 sec 1617174009, usec 116658, 7:0:9.116 ,sa 016664 sec 1617174009, usec 133331, 7:0:9.133 ,sa 016673 sec 1617174009, usec 149996, 7:0:9.149 ,sa 016665 sec 1617174009, usec 168155, 7:0:9.168 ,sa 018159 sec 1617174009, usec 183335, 7:0:9.183 ,sa 015180 sec 1617174009, usec 200000, 7:0:9.200 ,sa 016665 sec 1617174009, usec 216595, 7:0:9.216 ,sa 016595 sec 1617174009, usec 234729, 7:0:9.234 ,sa 018134 sec 1617174009, usec 249989, 7:0:9.249 ,sa 015260 sec 1617174009, usec 266657, 7:0:9.266 ,sa 016668 sec 1617174009, usec 283381, 7:0:9.283 ,sa 016724 sec 1617174009, usec 300001, 7:0:9.300 ,sa 016620
おわりに
hrtimerを導入してnano sec単位で割り込みタイマーを仕掛けられるようにした。
しかし、誤差が大きく、安定してnano secで割り込みできない。
今後は実機で試して精度を確認する。
また、hrtimer_initのclockid_t やhrtimer_mode を他の値に変えて精度がどのように変わるか調査する。
参考
modprobe: ERROR: could not insert 'module_name': Exec format error
はじめに
modprobeしたが、モジュールをロードできなかった。
問題を解決できなかったが、別アプローチでロードすることはできた。
事象
$ make -C /lib/modules/$(uname -r)/build M="drivers/module_name" modules # make -C /lib/modules/$(uname -r)/build M="drivers/module_name" modules_install # modprobe module_name modprobe: ERROR: could not insert 'module_name': Exec format error $ dmesg module: x86/modules: Skipping invalid relocation target, existing value is nonzero for type 1, loc ffffffffc05af400, val ffffffffc05ad168
対策
makeファイルを自分で書いてmodprobeしたところ、ロードすることができた。
なぞ。
調査
loc = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr
+ rel[i].r_offset;
./arch/x86/kernel/module.cのapply_relocate_add()内で、locが0でないことにより、エラーを吐いている。
elf bss
Virtualboxの仮想マシンとlinuxホストマシン間でpingが通らない問題
はじめに
virtualboxにcentos8を入れたがpingが通らない問題が発生した。
NATを介してホストマシンから仮想マシンへ接続することはできた。
しかし、仮想マシンからホストマシンへ、ホストマシンから仮想マシンへブリッジを介した通信ができなかった。
くだらない理由だがハマったので解決方法を記録しておく。
結論
ブリッジアダプタに接続しているホストの物理IFがリンクアップしていなかった。
その物理IFのネットワークアドレスとと仮想マシンのIFのネットワークアドレスは揃える必要がある。
現象が発生したときのネットワーク
手元のローカルマシンからホストマシンへはsshで接続した。
ホストマシンから仮想マシンへはNATを介してsshすることができた。
しかし、仮想マシンのEth1、Eth2からホストマシンのEth1へ、ホストマシンのEth1から仮想マシンのEth1、Eth2へpingを通すことができなかった。
調査
それぞれでarpを試したところ、macアドレス解決できていないことがわかった。
nmcliコマンドを用いてOSがネットワークIFを認識していることを確認した。
そこで、ホストマシンのイーサネットケーブルを確認しに行ったところ、ホストマシンのeth1に接続しているケーブルが抜けているということがわかった。
ケーブルをさして、pingを実行したところホストと仮想マシン間で相互に通信することができた。
ついでに
ネットワークアドレスの異なる物理IFへブリッジしたところ、相互にpingが通らなくなった。
物理IFのリンクアップだけでなく、ネットワークアドレスを揃えることも必要だとわかった。
おわりに
お粗末な原因でげんなりした。
なぜリンクアップしていないといけないのかを今後調べる。
番外編(2021.06.28追記)
ネットワーク設定のケーブル接続チェックボックのチェックが外れていることが原因のときもある。
この設定は仮想マシンのイーサネットポートにケーブルが接続されているか、抜いているかをエミュレートしている。
チェック有りがケールブルを接続している、チェックなしがケーブルを抜いている状態を示す。
vboxmanageコマンドで設定変更が可能。