ToDo:
Fortran pointer経由で、mmap(2)経由で割り当てた匿名共有メモリを扱うためのユーティリティmoduleを導入した
局所スコープで、共有メモリ配列を割り当てているコード群を近代化する
まずは、tlspect@src/tbrad.fとtftrack@src/tftack.fのmapalloc/mapfreeを置き換えた
OpenShared系は暫く放置するとして…
itmmapp経由でrlist内に割り当ててる動的配列utwissの実装をFortran pointerベースの実装に置換した
tffsmatch内部に残っているのは、ifqu/ifqu0でindexしている、 実数型2次元配列 qu(nqcol,nvar)である
rlistから参照するコードなので、読みづらい…
関連するコードフロー内で、nvarは定数であり、nqcolはtffscale等で動的に決定され最大値はmaxcond(正確にはmaxcondを越えるとエラー)
技術的には、条件毎の値を保管する配列が長さmaxcondで確保されたものが渡ってくる事に起因する
Fortran pointer渡しにすれば動的に拡張可能となるが、関連するfunction/subroutineのインターフェースを 明示的に宣言することが必要(読み出し・格納のみでリサイズを行わない場合は、宣言なしの配列渡しが可能 Ex. tfshow)
配列渡しによる参照側が(nqcol,nvar)で参照しているので、nqcolが変更された場合アドレッシングレイアウトが変更されるために、アドレッシングの整合性を維持するためにnqcolが減少するケースでもpointerの再割り当てが必要になる
また、mmap_utilsのAPI設計上、munmapに必要な割付長が配列sizeに相関する必要がある
可変長割り当てを考慮すると(nvar,nqcol)の方が良いが、バックエンドに存在する線形代数ルーチンでJacobianを解く際のインターフェースに依存性があり転置するのは面倒が多い
メモリ再割り当て動作を減らすには、mmap_shared/munmap APIを直に使い自前でpointer割り当てを管理するのが最適?
2種類の導関数計算エンジンの外側で、配列割り当てを行う形にコードを整理すべきと思われる
必要なもの
qu/qu0 arrayのallocation/deallocationをtffsmatch()へ局所化、mmap_sharedベースに書き換え完了
qu/qu0配列の受け渡しを見ていると…なんか変な感じがするというか、バグってるぽぃ 要調査
tgradとtfsolvで勾配配列と作業領域を渡す順番が異なっている上に、subroutine側の仮引数名の逆になっているという極めて紛らわしい設計になっていた
更に、tgradとtfsolvの呼び出しは、並列スコープ外なので、作業領域(qu0 @ tffsmatch)が共有メモリ上にある必要が無い
現状、CaMonitorのValueCB経由の読み出しは、フル実装になったはず
残っているのは、
従って、以下の方針で修正する
CaOpen系とCaClose系のコードは、EPICS\CaOpenとEPICS\CaClearChannelに統合できそうなので、動作仕様の違いをまとめておく
関数 | 引数の数 | callback | ca_pend_io | retry | chidリーク |
CaOpen1 | 1 | なし | あり | なし | ca_pend_ioタイムアウト時 |
CaOpen2 | 1 or more | なし | あり | あり | ca_pend_ioタイムアウト・リトライ時 |
EPICS\$CaOpen | 1 | あり | なし | なし | なし |
関数 | 引数の数 | ca_pend_io |
CaClose1 | 1 | あり |
EPICS\$CaClearChannel | 1 | なし |
調査結果のまとめ
実装は、nfCaOpen @ src/tfefun2.f (casearch_/capendio_)
実装は、tfefun2 @ src/tfefun2.f (casearch_/capendio_)
引数 | 基本タイムアウト |
1 | 0.5 |
2 | 0.5 |
3 | 0.6 |
4 | 0.8 |
8 | 1.6 |
12 | 2.4 |
16 | 3.2 |
20 | 4.0 |
24 | 4.0 |
レコード名に長い文字列を与えて、ca_create_channelがECA_BADSTRを返す状況にすると、SEVCHKマクロでSEGVする
当該エラーは、nciu::nciu(コンストラクタ)内のnameLengthTempチェックからthrowされるcacChannel::badString例外に起因している
レコード名の長さがMAX_UDP_SEND - sizeof(caHdr)を超過している際に発生し、libca内の構造体の整合性が破壊されている模様(試験環境でのしきい値は、1008bytes)
ca_create_channelに投げる前に長さをチェックすべき
2017/12/08追記
従って、1024 - 16 → 1008文字のレコード名(NULL文字を除く)で cacChannel::badString例外が発生する
既存実装の動作解析まとめ
関数 | タイムアウト |
caputreal_ | 100 |
caputsyring_ | 0.1 |
caputrealarray_ | 0.5 |
caputstringarray_ | 0.5 |
CaWrite2[arguments__] := Scan[Check[EPICS$CaPut@@#, Null]&, {arguments}]
マニュアルを読んだ範囲では、handler無しのca_create_channelとca_getのrequestの完了を待つ際の ca_pend_ioの戻り値に付いてまとめると
ca_pend_ioに先行するhandler無しの ca_create_channelとca_get要求全ての処理が完了している
ca_pend_ioに先行するhandler無しの ca_create_channelとca_get要求のなかに、未完了のものが含まれる。
=未完了の要求のうち、ca_get要求は再実行されるがca_create_channel要求は再実行されない=(2017/12/11訂正)
ca_pend_io以前に投入されたhandler無しのca_create_channel/ca_get要求は、終了・キャンセルされている。 ただし、CA APIレベルで状態を持たないca_get要求の成否は不明となる (2017/12/11追記)
ca_create_channelに関しては、ca_pend_ioからECA_TIMEOUTで戻った時点で接続状態が確定し、ca_stateが cs_never_connもしくはca_prev_conn状態となっており、ca_clear_channelするまで変わらない。 また、ca_create_channelで生成したchidオブジェクトをca_stateで調べれば、接続確立に失敗しているchannelを特定できる(未接続で確定しているので、以降のca_get/ca_putがECA_DISCONNで失敗するので、ca_clear_channelで捨てる以外することが無いはず)
=一方、ca_get要求は再投入されるとのことなので、ca_getへ渡したbuffer pointerは、CA client library内で生存しており、ca_pend_ioがECA_NORMALを返すまで開放することが出来ない=(ca_pend_ioから戻った時点で、参照が開放される 2017/12/11訂正)
また、どのca_get要求が未完了なのかを調べるAPIが存在しないので、 以前に未完了で放置したca_get要求が有った場合、直前のca_getが成功したか否かを判定するには、DBR_TIME型などの付加情報の付いた要求を行いbufferが上書きされてるかを判定することになる。
=TCP/IPの接続タイムアウトで接続が遮断すれば、channel状態がDISCONNへ移行し、当該channelへのca_get要求が廃棄されるはずであるが、それまでの間、ca_getへ渡したbufferを安全に回収出来ない=(2017/12/11削除)
=従って、外部ネットワークに悪意があると考えた場合、ca_getはcallback経由の実装を行うべきと思われる=(2017/12/11削除)
callbackへ任意の参照を渡せ、通信が途中で切断された場合はcallbackはbad statusを伴って呼び出されるとの記載があるので、 callbackへ以下の情報を記録したヒープ上のオブジェクトを渡す。
同期変数の状態は以下の4種
callbackは、同期変数が状態(1)であることを確認し後、データを書き込み、同期変数を状態(3)もしくは(4)へ遷移させる
callbackが、同期変数が状態(2)であることを確認した場合は、ヒープ上のオブジェクトを開放する
main programは、ca_get_callback後に同期変数が状態(3)もしくは(4)へ変わるのをca_pend_ioを行いながら待ち、受け取りに成功した場合は、ヒープ上のオブジェクトを開放する
main programがtimeoutした場合は、同期変数を状態(2)へ遷移させ、受け取り先の割り当てを解除する
callback動作が、プリエンプトな場合は同期変数の操作はアトミックである必要があり、main programとcallbackが併走する場合は、callback側が動作中であることを示す遷移状態かロックを追加する必要がある
ISO C11で同期変数を実装するならatomic_intとか?
ca_get_callbackによる要求に対して、ca_pend_ioはブロッキング動作しないとの事なので、同期待ちがspin waitもしくはsignalベースとなりあまりうれしくないことが判明
ca_sg_create/ca_sg_array_put/ca_sg_blockを使うべきか?
ca_cg_resetで、同期グループ内でissueされている要求をキャンセルできるようなので、安全にリソースを回収できる
ただし、同期グループに複数のI/Oが含まれる場合、どのI/Oが未完で終わったかを識別するAPIが存在しない
従って、ca_get要求が処理されたかを個別に確認するには、以下の2種に絞られる
コード解析のまとめ
取り合えず、実装の一本化が完了
残っている作業
オリジナル実装の送信バリアのまとめと考察
CaOpenX[pv:{__String}] := Module[{chl = EPICS$CaOpen/@pv, dt = 10e-3, timeout = EPICS$CaOpenTimeout[]}, While[timeout > 0, Sleep[dt]; timeout -= dt; EPICS$CaPendEvent[]; Print[timeout]; If[And@@(CaConnectedQ/@chl), Break[]]]; Map[If[CaConnectedQ[#], #, EPICS$CaClearChannel[#]; $Failed]&, chl]];
tffsmatchで割り当てた領域がtffscalcに渡しているケースで、tffscalc内部で並列化する場合に、並列リージョンからの書き戻し動作の挙動が変わっていないかソースを精査する必要あり
tfefundef@src/tfefun.f冒頭のget_func_id(ia1)にて、SEGVするケースを発見した
irtc=0 narg=isp-isp1 it1=itastk(1,isp1) ia1=itastk(2,isp1) 1 if(it1 .eq. ntfoper)then if(ia1 .gt. mtfend .and. get_func_id(ia1) .ge. 1000)then call tfefunref(isp1,itx,iax,vx,.true.,irtc) if(irtc .eq. 0 .and. $ itx .eq. ntfreal .and. iax .ne. 0)then call tfclrtparaed else if(irtc .eq. 0)then irtc=itfmessageexp(999,'General::invset',it1,ia1,0.d0) endif endif go to 6900 elseif(ia1 .eq. mtfatt)then call tfatt(isp1,itx,iax,vx,.false.,irtc) go to 6900 else
発生ケースでは、it1が ntfoperにも関わらず ia1が無効なポイントをさしている。 ただし、itastk(2, isp1)は健全な状態で、後続のコードでit1がntfsymbolのケースで、Symbolの参照先がOperatorになっている場合にitastk(*, isp1)とit1を書き換えて、go to 1で再帰するケースで発生する
また、当該コードで、call tfclrtparaedに達するケースは、LINE(tfline)がパラメータブロックの参照をiax経由で返してくる場合で、LINE[...] = valの代入式の左辺値評価を行うケースに相当する。従って、tfefunrefを呼び出す事前条件はもう少し限定出来ると思われる(左辺値参照の伝搬を制限するケースでは、isp1にLINEが来ていないケースの評価が省略できるはず…)
ifブロック内で、General::invsetを返す条件に irtc == 0(tffunrefの評価が成功している)を要求しているのも謎なので要解析
Tk_CreateFileHandler wrapperに関して、Tkinterのloading前に登録手続きのQeueingサポートを必要と考えていたが、Tcl/Tkのインタプリタインスタンスの生成(Packages/TkinterCore.n)と関係なくlibtkがバインドされていれば十分であることが分かった。
従って、バックエンドモジュールであるSAD/Tkinter.soがロード済みで有れば十分である。
builtinの場合を考えると、sadDefFunc_EPICSCA時点ではLibrary@Requireは要求出来ないので、SADScriptインタープリタからの関数評価で初期化シングルトンeca_initが走ったタイミングで、必要なbackend moduleを要求すれば良い
実際に、ポーリングを走らせる段階ではTkWait/TkSenseを評価したタイミングで、Packages/Tkinter.nがロードされTkイベントループ内からTkinter backendに登録済みの eca_fd_callbackが起動される
必要なものをまとめると
CPUS lpqの件で取り上げたnet.inet6.ip6.v6only sysctl変数であるが、net.inet6.ip6.v6only=1(ipv6_ipv4mapping="NO")な運用下では、TCPOpen[0]で生成するServer Side TCP SocketがIPv6専用となり、IPv4で接続してくるクライアントからの接続を受けられなくなる。
TCPOpen[0]が返すポート番号で再度TCPOpen[port]を実行すると、TCP/IPv4ポートが空いていれば割り当てる事が出来る。
ただし、現行のTCPAcceptは単一のSocketのみを受け付けるので、SelectUnit/PollでIPv6/IPv4ソケットを待ち、要求が有ったソケットからTCPAcceptするという処理が必要となる
TCPOpen・TCPAcceptに手を加えて、プロトコル毎のソケットをリストで返す案もあるが、TCPOpen[0]が返すソケットをそのままSelectUnit/Pollに流し込むコードで後方互換性が破壊される。 また、TCPAcceptでBlocking Waitさせるケースでは、TCPAccept内部でselect/pollでBlockingさせることが必要となり面倒が増える
TCPAcceptによるSocket Listのサポートは後方互換性を破壊しないので実装すべきである
Network/StreamIO libraryに関しては、早急に対策が必要
クラス定義で、Undefinedを初期値にしたインスタンス変数を定義すると、当該変数は初期化されない
Packages/Class.nの内部実装で、未定義時の特異値としてUndefinedを使っている模様
2017/12/18 r456561適用後のportupgrade後にX server再起動でjisx0208フォントを認識しない状況が発生
更新前のlibXfont-1.5.2へ巻き戻すと元に戻るので、1.5.2から1.5.4の差分で退行している?
1.5.4に入っているCVE-2017-16611の修正で、フォントファイルオープン(src/fontfile/fileio.c)時にO_NOFOLLOWが付与された結果、.pcf.gzがシンボリックリンクなものが表示できなくなっている
portsから入れているフォントに関しては、ports側を修正すべき (copyにするかハードリンクに切り替える)
IEEE_VALUEは、intrinsicモジュール内の関数であるが、intrinsic関数では無いので、parameterの初期化式に記述できないのが難点 (現状も初期化は、tfinfint_が行っている)
REAL型のXに対して、floor(X)はX以下の最大のINTEGERを返す関数なので、正の無限大に対しては、INTEGER表現域の最大値をを返すべきだと思うのですが、gfortran 6.4だとなぜか-2147483648 = 0x80000000を返してくる
内部的には、REAL->INTEGER変換で符号付き2の補数形式の32bit INTEGERの最小値-2147483648.0d0を変換できるようになっていて、符号付きの浮動小数点なので正の無限大が2147483648.0d0に打ち切られてから整数変換によって0x80000000へ変換が起こってるように考えたのだが、有限値2147483648.0d0のfloorは表現域の上限2147483647 = 0x7fffffffを返している
INTEGER型の表現域に解が存在しないtfloor(-Infinity)に対して2147483647が返ってくるのも謎である
実装バグっぽい気がする
use IEEE_ARITHMETIC use ISO_FORTRAN_ENV real(real64) :: a, pinf, ninf, nan pinf = IEEE_VALUE(dinf, IEEE_POSITIVE_INF) ninf = IEEE_VALUE(dinf, IEEE_NEGATIVE_INF) nan = IEEE_VALUE(nan, IEEE_QUIET_NAN) a = 2147483648.d0 b = -2147483649.d0 write(*,*)a,' -> ',floor(a) write(*,*)b,' -> ',floor(b) write(*,*)pinf,' -> ',floor(pinf) write(*,*)ninf,' -> ',floor(ninf) write(*,*)nan,' -> ',floor(nan) write(*,*)sign(nan,-1.d0),' -> ',floor(sign(nan,-1.d0)) end
コンパイラドライバ(devel/llvm50側)をゴソゴソ修正して、-Lオプション無しで、libflangmain, libflang, libflangrti等のランタイムライブラリをリンクできる所まで修正完了
まだ、-Iオプション無しでintrinsic moduleが使えないので、コンパイラドライバもしくはflang1/flang2に埋め込まれているmodule include pathに修正が必要な模様…
tools/clang/lib/Driver/ToolChains/FreeBSD.cppに環境依存のintrinsic module pathを追加するコード(flang1に対して-stdincオプションを設定する)を追加して、追加オプション無しでintrinsic module付きのコードをコンパイル・リンクできる所まで完了
SADのコンパイルを試すのは来年かな?
カテゴリー: Admin | Emacs | EPICS | Fortran | FreeBSD | GCC | hgsubversion | IPv6 | KEKB | LHC | Lisp | LLVM | MADX | Ryzen | SAD | samba | tDiary | unix | WWW | YaSAI | お仕事 | イベント | 出張 | 宴会 | 数学 | 艦これ | 買いもの | 追記 | 雑記