コスモリサーチの凄腕エンジニアたちが日々、難題と格闘した記録
Copyright Cosmo Research Corp.

pthreadの仮想メモリ解放

LinuxPosix Thread(pthread)で仮想メモリが解放されない時があるので 条件を調べてみました。 環境はZYNQのLinuxなのでPCのLinuxとは動作が違う部分があるかもしれません。

結果的にはpthread_createで作成したスレッドではスレッド終了時には仮想メモリを解放されませんが、 再度pthread_createをした時に前回確保したメモリを再利用しているようです。 (=新たな確保をしていない)

pthread_createで確保されるメモリはスレッドのスタックサイズに依存するのですが 初期状態だと8MBがスタックサイズになっているので組み込みのプログラムとしては大量の メモリを使用しているように見えてしまいます。 解放しないのは仮想メモリだけで物理メモリが解放されず残っているわけではないです。 プログラム動作的には問題ないのですがPSコマンドやtop/htopコマンドを使用してメモリ使用量を見るとき、 スレッドでで自動的に確保されるサイズが加算されて見えるていることには注意しないといけません。

動作例

スレッドを6つ作成、終了後、新たにスレッドを2つ作成するプログラムです。 6つスレッドが終了するとpthread_createで確保されたメモリは全て解放される動作を期待するのですが、 実際に動かしてみると6つのスレッドが終了した時点で仮想メモリが確保されたままになります。

メモリの使用量がわかるよう動かしている途中でコンソールからhtopコマンドを使用して VIRT(仮想メモリ)とRES(物理メモリ)を見てみます。

ステップ1  初回のpthread_create

スレッドを6個作成すると1スレッド当たり約8MB程度、6スレッド作成完了した時点でこのプログラムでは 50MBのメモリが仮想メモリとして確保されます。 6つのスレッドが終了し、joinsした段階でメモリは解放されるはずですが、topのVIRTを見るとまだ42Mbyte程度使用しています。

ステップ2  2回目以降のpthread_create

この後、2つのスレッドを再度作成するとVIRTは42MBのままです。 これは前回確保した仮想メモリを流用しているため、新たなメモリ確保が行われてなかったものと考えられます。 2つのスレッドが終了後もVIRTは42MBを維持し、プログラム終了で解放されます。

//スレッド本体関数
void *func_null(void *arg){
    printf("new thread created\n");
    sleep(10);
}


int main()
{
    pthread_t th_cli1;
    pthread_t th_cli2;
    pthread_t th_cli3;
    pthread_t th_cli4;
    pthread_t th_cli5;
    pthread_t th_cli6;

    pthread_attr_t th_cli_attr;
    int status;


    //--------------------------------------------------------------------------------//
    // スレッド属性を作ってスレッド作成の準備
    //--------------------------------------------------------------------------------//
    pthread_attr_init(&th_cli_attr);
    //--------------------------------------------------------------------------------//
    // TEST1 スレッドを6個作成
    //--------------------------------------------------------------------------------//
    status = pthread_create(&th_cli1,&th_cli_attr,func_null,(void*)0);
    status = pthread_create(&th_cli2,&th_cli_attr,func_null,(void*)0);
    status = pthread_create(&th_cli3,&th_cli_attr,func_null,(void*)0);
    status = pthread_create(&th_cli4,&th_cli_attr,func_null,(void*)0);
    status = pthread_create(&th_cli5,&th_cli_attr,func_null,(void*)0);
    status = pthread_create(&th_cli6,&th_cli_attr,func_null,(void*)0);

    pthread_join(th_cli1,NULL);
    pthread_join(th_cli2,NULL);
    pthread_join(th_cli3,NULL);
    pthread_join(th_cli4,NULL);
    pthread_join(th_cli5,NULL);
    pthread_join(th_cli6,NULL);

    printf("6 thread finished\n");
    sleep(10);
    //--------------------------------------------------------------------------------//
    // TEST2 スレッドを2個作成
    //--------------------------------------------------------------------------------//
    status = pthread_create(&th_cli1,&th_cli_attr,func_null,(void*)0);
    status = pthread_create(&th_cli2,&th_cli_attr,func_null,(void*)0);

    pthread_join(th_cli1,NULL);
    pthread_join(th_cli2,NULL);
    printf("2 thread finished\n");
    sleep(10);
    return;
}

【社内向け情報共有例】3Dプリンタの使い方

以前コスモリサーチ社内向けに展開した3Dプリンタ使用方法の簡易的な手順を記載する.
これは一例であるが,社内では情報共有を積極的に行っている.
なおこの内容単体ですぐ3Dプリンタを使用できる程の情報量にはなっていないので注意.


・BCN3D”Sigma R19”について
https://dddjapan.com/products/bcn3d-sigma-r19

・使用の手順
① 制作する形の検討
② 3D CADで設計
③ ファイルの変換
④ 3Dプリント

① 制作する形の検討

f:id:cosmo-blog-admin:20200525184834j:plain

まず、紙ベースでどんな形にするか決める.
詳細な寸法は一度プリントして現物で確認したほうが良いので,悩むくらいなら暫定で決めてしまったほうが良い.

以下,ポイント箇条書き

  • 3D CADは始めると楽しくなってしまうので,発散しないように事前に紙ベースで検討を⾏う.
  • 45°以上のオーバーハングはサポートがないと製造できないため,積層方向を意識して検討する
  • 体積が大きいと値段や印刷時間が増えるので可能なら小さく作る.
  • 高さ方向に伸ばすと印刷時間が延びるので,可能なら低くするor短い方向に積層する.

② 3D CADで設計

STL形式で出⼒できれば何でもいいが,初心者でも使いやすいRSコンポーネンツの無料3D CAD”DesignSpark Mechanical”がおすすめである.
https://www.rs-online.com/designspark/mechanical-software-jp
STEPデータの読み込みとSTLファイルの書き出しが可能で,ググって情報も拾いやすい.

以下,DesignSparkのポイント箇条書き

  • STEP書き出し不可
  • 図形への文字の書き込みができない
  • 修正がSolidWorksに比べて難しい.
  • AltiumのSTEPデータを読み込めるが,AltiumのSTEP出力には全部品の外形とvia等が付いてくるので重くなりやすい. PCBファイルをSTEP出力用にコピーし,viaを削除する,部品点数が多い場合は必要のない実装部品は消すなどした方が良いかもしれない.

③ ファイルの変換

STL形式で出⼒したファイルを3Dプリンタ付属の”BCN3D Cura”で読み込みGcodeに変換する.
https://www.bcn3d.com/bcn3d-cura/

④3Dプリント

  1. ベースガラスが汚れていたら水で洗いきれいにする
  2. 平⾯出しのキャリブレーションを⾏う
  3. ベースプレートを加熱する(10分程度)
  4. GcodeをSDカードから3Dプリンタに読み込ませる
  5. 印刷する

3Dプリンタの使用例

・基板スペーサー

f:id:cosmo-blog-admin:20200525184915j:plain f:id:cosmo-blog-admin:20200525184929j:plain f:id:cosmo-blog-admin:20200525184947j:plain

筐体に基板を取り付けるためのスペーサー.
両面テープとホットボンドで取り付ける.
もともとはゴムシートを切って貼っていたがその手間が削減,また位置合わせも兼ねている.

設計時間:4h
印刷時間:1h
材料費:100円程度

・基板スタンド

f:id:cosmo-blog-admin:20200525184959j:plain f:id:cosmo-blog-admin:20200525185009j:plain

展示会で基板を斜めに置くためのスタンド.
部品とぶつからないようにAltiumで形を決めてから3DCADで製作した.
斜めの部分も45°に納めているのでスペーサーをプリントせずに作れた.
基板がずり落ちてしまうとICに負荷がかかるので穴径はタイトにした.
内部密度20%.

設計時間:4h
印刷時間:24h
材料費:1500円程度

UARTを使ってSDカードを書き換える

 装置の蓋を開けずにデザインを交換したいという要求があった。該当の装置は外部へのデバッグ用I/FがUSBを使ったUARTのみしかなく、UART経由でデザインを更新することを考える。

lrzを使用することで、TeratermのZMODEというものを使用してファイルを送信し、SDカードを更新できる。

方法

lrzコマンド

通常、petalinuxでは、

# petalinux-config -c rootfs

して、「Filesystem Package」->「console」->「network」の「lrzsz」を有効にすると、lrzコマンドが使用可能になる。 容易にSDカードが取り出せない状態で、何回も更新する必要があるような場合、便利なのかもしれない。(ただし速度が遅く11KB/sec)

*参考 http://aster-ism.hatenablog.com/entry/2017/07/11/223550

今回はこのlrzszを有効にしていない状態なので、力技でlrzコマンドを使えるようにし、ファイル転送を行う。lrzコマンドをテキストエディタでバイナリモードでASCIIデータとして開く。このASCIIデータをechoコマンドを使ってTeratermから転送し、lrzコマンドを作る。

f:id:cosmo-blog-admin:20200423152816p:plain
lrzコマンドのバイナリデータ(lrz_bin_echo.txt)

lrz_bin_echo.txtを送る際に、一度に70行ぐらいを転送しようとすると、Teratermの制限か何かで、途中までしか転送されないことがあるので、30行づつぐらいに刻んでコピペをするとよい。(1行に何文字あるか、にも依存すると思う。)

*参考 https://nagayasu-shinya.com/bash-echo-binary/

TeratermのZMODEを使用してファイル転送

あとはlrzコマンドを叩き、

# chmod 777 /path/to/lrz
# /path/to/lrz
lrz waiting to receive.**B0100000023be50

となるのでTeraterm上の「ファイル」->「転送」->「ZMODEM」->「送信」でファイル選び、転送する。

※ちなみに
前述したように転送速度が11KB/secとなり、今回転送したかったBOOT.BIN(約17MB)を転送するのに30分ぐらいかかる。

AXI Chip2Chipを使用した系のデバッグと対応

前置き

 Zynqにぶら下がっている2台のFPGAレジスタ設定用にAXI Chip2Chip IPを使用しようとしたところ、IPが期待した通りに動かなかったためそのデバッグと対策を行った。

現象

 下記系で設計し実機デバッグを行ったところ、SoC-FPGA1間はLinkが立つが、FPGA1-FPGA2間のIPがLinkもErrorも立たない現象が発生した。

f:id:cosmo-blog-admin:20200413132407p:plain
当初想定していた系

IPの外部I/FはSelectIO DDR(Compact 4-1)、AXIバスの接続はAXI Interconnectを使用している。 なお1台のFPGAにAXI Chip2chip IPを2つ置いてインプリを行うとエラーが発生するため、Xilinxのフォーラムなどを参考にIPのMaster/SlaveでIDELAYCTRLとIDELAYE3をグループ分けしている。

調査内容

問題の調査に当たって下記を確認した。

  • XilinxのフォーラムやAnswer Recordなどで同様の症状が報告されていないか確認。→なかった。
  • 社内実績のあるプロジェクトでFPGAがUltrascaleではなく7シリーズの場合と、Ultrascaleを使用しているが外部I/Fにトランシーバーを使用している場合は同様の問題が発生していなかった。→今回の系特有の問題?
  • IP間の信号の接続方法を疑い、シミュレーションをやり直したがそれは問題なかった。→信号の接続の仕方が悪いわけではなさそう。
  • FPGA間の信号線が物理的に途切れていることを疑ったが、使用しているIOで別途テスト信号を送受信すると正常にできた。→物理的な信号は問題ない。
  • IPの動作モードがCommon/Independentと二つ存在するため、両方試してみたが、どちらでもリンクしなかった。
  • IP用のクロックとリセットの仕方の組み合わせをいろいろ変更してみたがどの場合もリンクしなかった。
  • IPのバグを疑い、インプリに使用するVivadoのバージョンやAXI Chip2chip IPのバージョンを変更してみたが状況は同じであった。
  • IDELAYCTRLとIDELAYE3をグループ分けしないとインプリが通らない事やSoC-FPGA1間はリンクしていることから、1つのFPGAにIPが1つなら問題ないのかと考え、試しにSoC-FPGA1間のIPを削除してみたらFPGA1-FPGA2間のIPがリンクした→IPが複数あることが原因?
  • IPからはクロックとデータが入出力されるが、クロックは問題なく出ている。ただしIP起動時のリンク用のデータ出力が途中で止まってしまっている。→IP内部の起動シーケンスがおかしくなっていそう。

 フォーラムなどを見ると「FPGAが7シリーズで外部I/FがSelectIO」か「FPGAがUltrascaleで外部I/Fがトランシーバー」のケースしか見かけられなかったため、「FPGAがUltrascaleで外部I/FがSelectIO」という組み合わせで発生するバグか、もしくはその組み合わせの際に特別に何か設定する必要があるかのどちらかが原因と思われるが、情報が少なくまたIP内部のシーケンスの問題の場合は対処の方法がないためこれ以上のデバッグは断念した。

行った対応

 SoC-FPGA1間の動作は問題なかったことと、FPGA1-FPGA2間の接続はレジスタの設定/読み出しのみで遅延をあまり考える必要がなく複雑な処理も必要なかったため、FPGA1-FPGA2間のみ外部I/Fを自作のシリアル通信I/Fに変更して対処した。

f:id:cosmo-blog-admin:20200413145711p:plain
最終的な系

 今回の系では外部I/FにSelectIOを使用していたが、トランシーバーを使用した方がFPGA間の信号の接続本数が少なくて済みいろいろと考える必要が少ないため、今後同じ問題に当たる可能性は低いかもしれないが参考のため記録に残しておく。

FX3(CYUSB30xx)のブートに関するメモ

FX3のコンフィグ

USBブートの場合、USB2.0接続でないと動作しない。
基板上の接続をUSB3.0/USB2.0ともに有効にする必要がある。

また、PMODEの設定はZ11としてUSBブート状態にする必要がある。

参考: EZ-USB® FX3™/FX3S™ブートオプションについて(AN76405)

USB Control Centerを使用する場合

FX3 SDKをインストールするとついてくるControl Centerを使用してコンフィグ出来る。

  1. PMODEの設定をZ11にして電源ONする。
  2. Program > FX3 > RAMにて、RAM上に任意プログラムを書き込んで起動する。
  3. RAMではなく、SPI FLASHも動作する。

この操作はGUIでの操作になるので、何度も実行するのは面倒である。

※SPIに書き込むイメージファイルは、通常のイメージファイルとは違うため、注意が必要である。

elf2img.exe -i XXXX.elf -o XXXX.img               # 通常(RAM書き込み用)イメージ作成
elf2img.exe -i XXXX.elf -o XXXX.img -i2cconf 0x00 # SPI書き込み用イメージ作成

libusb-1.0付属のfxload.cを使用する場合

事前準備その1(zadigによるドライバの変更)

事前の準備として、USB boot状態のFX3のドライバをWinUSB(libusb-1.0)に変更する必要がある。
この変更にはzadigを使用する。
恒久的にドライバが変更されるため、注意すること。
具体的な操作は、zadigにて、VendorID="04B4" ProductID="00F3"のドライバをWinUSBに変更する。

事前準備その2(libusb-1.0付属のfxloadのコンパイル)

msys2にて、コンパイル環境を構築し、pacmanにてlibusb-1.0のライブラリをインストールしておく。
その後、fxloadをコンパイルする。

git clone https://github.com/libusb/libusb.git
cd libusb/examples
gcc fxload.c exusb.c -o fxload.exe -lusb-1.0 -I/mingw32/include/libusb/libusb-1.0 -static

fxload.exeにてコンフィグ実行

  1. PMODEの設定をZ11にして電源ONする。
  2. fxload.exe -t fx3 -i <fx3 image file>

実行結果

C:\Users\cosmo\Desktop>fxload.exe -t fx3 -i SlaveFifoSync\Debug\SlaveFifoSync.img
found device 'Cypress FX3' [04b4:00f3] (3,8)
microcontroller type: fx3
SlaveFifoSync\Debug\SlaveFifoSync.
img: type Cypress IMG format
open firmware image SlaveFifoSync\Debug\SlaveFifoSync.img for RAM upload
normal FW binary executable image with checksum
FX3 bootloader version: 0x000000A9
writing image...
transfer execution to Program Entry at 0x4000c71c

一度ドライバをインストールしておけば、上記を実行するだけでFX3に任意のプログラムがロードされる。

XilinxのDDR4メモリコントローラについてのメモ

目的

DDR4メモリコントローラのオーバーヘッドに影響がある設定ついて調べる。 どの設定にしたらアクセス速度があがるか?

先に、内容のまとめ

DDR4メモリコンポーネントについては、8Gb:x4,x8,x16 DDR4 SDRAM Datasheetを参考にした。

  • x16デバイスでは、バンクグループは1bit、バンクが2bit存在する。
  • 違うバンクグループへのアクセスが早く、同じバンクグループへのアクセスは遅い。
  • データマスクが有効だと遅くなる。

メモリコントローラIPについては、UltraScaleアーキテクチャFPGAメモリIP v1.2 LogiCORE IP 製品ガイド(PG150)を参考にした。

  • 単調にインクリメントする場合はROW_COLUMN_BANKが有効だが、短いバースト長の場合はROW_COLUMN_BANK_INTLVが有効らしい。
  • DM/DBIのオプションは、NO_DM_DBI_WRがベターかもしれない。

DDR4メモリコンポーネント

DDR4メモリコンポーネントのTimingパラメータの概要

-083E Device overview

0.937ns @ CL = 15 (DDR4-2133)
Data Rate    = 2133 MT/s
tRCD         = 14.06 ns
tRP          = 14.06 ns
CL           = 14.06 ns

Addressing(component)

Bank Group : BG0 (1bit only)
Bank       : BA[1:0] (per Bank Group)
Row        : A[15:0]
Column     : A[9:0]
Page Size  : 2KByte (per Bank)

DDR4メモリコンポーネントのモードレジスタ設定(メモリコントローラIPと照らし合わせ)

x16デバイスには8個のバンク(2バンクグループで各々4バンク)が存在する。
MIGではMT40A512M16HA-083Eを選択して938psのクロックサイクルとすると、以下の設定になる。

  • MR0 = 13'b0011100110000,

    • BL8(Mode : Burst Length 8)固定
      • tWR(WRITE recovery) = 16
        ここから、最小のWRサイクルは以下となる。
        roundup(tWR / tCK) = roundup(16 / 0.938) = roundup(17.057...) = 18 cycle
        ただし、DM(Data Mask)が有効な場合は上記から更に遅くなる。
      • tRTP(READ-to-PRECHARGE) = 8
        ここから、最小のRTPサイクルは以下となる。
        roundup(tRTP / tCK) = roundup(8 / 0.938) = roundup(8.428...) = 9 cycle
        同じバンクにアクセスする際のACTタイミングがこれで決まる。
  • MR1 = 13'b0001100000001,

    • AL(Additive Latency) = 0(AL disable)
      WL(WRITE latency) = AL + CWL(CAS WRITE latency) = CWL
      RL(READ latency) = AL + CL(CAS latency) = CL
  • MR2 = 13'b0000000010000,

    • CWL = 11
      WL = AL + CWL = 0 + 11 = 11
  • MR4 = 13'b0000000000000,

    • WRITE preamble setting = 1tCK
    • READ preamble setting = 1tCK
    • CAL(CMD address latency) = 0
  • MR5 = 13'b0010000000000,

    • DM(Data mask) enable ... 実際には使用しないがIPで有効になっているとWRには影響する?
      これがオーバヘッドに影響するかどうかは、要確認。
  • MR6 = 13'b0100000010100,

    • tCCD_L = 6 clocks

DDR4メモリコンポーネントのBank Access Operation

違うバンクグループへのアクセス(WRITE/READ)は、tCCD_S(or short)分遅延する。
同じバンクグループへのアクセス(WRITE/READ)は、tCCD_L(or long)分遅延する。

tCCD_S = 4 clocks (DDR4-2133) ... CAS_n-to-CAS_n command delay to different bank group
tCCD_L = 6 clocks             ... CAS_n-to-CAS_n command delay to same bank group

違うバンクグループのACTIVATEは、tRRD_S分遅延する。
同じバンクグループの違うバンクのACTIVATEは、tRRD_L分遅延する。

tRRD_S = 5.3 ns (DDR4-2133, page size 2KByte)  ... ACTIVATE-to-ACTIVATE command period to different bank groups
tRRD_L = 6.4 ns (DDR4-2133, page size 2KByte)  ... ACTIVATE-to-ACTIVATE command period to same bank groups

tWTR_S/L(WRITEからREADの切り替わり時間)については、とりあえず無視する。

メモリコントローラ

グループFSM

基本的にはバンクグループがコントローラのグループFSMに割り当てられ、各グループFSM間でタイミングを協調してアクセス効率を高める働きをする。
基本的には、と書いたのはDDR4のx16デバイスの場合に、バンクグループは1bitだが、グループFSMは2bit(4つ)存在するからである。

アクセス効率を高めるためには、グループFSMへのアクセスを均等にすることが必要である。

アドレスオーダー

  • ROW_COLUMN_BANK
    app_addr[4:3]がグループFSMに割り当たる。
    長期間、単調にインクリメントする場合に最も効率的なアドレス配置となる。
    単調にインクリメントしない場合は、この配置を維持したまま、下位bitにトグルしやすいbitを持ってくると良い。

  • ROW_COLUMN_BANK_INTLV
    バースト長が短い場合にパフォーマンスを向上させる。
    この並びは、効果があるかもしれないが、書き込みが連続している場合は効果がなさそうではある。

  • ROW_BANK_COLUMNおよびBANK_ROW_COLUMNは推奨しないようだ。

データマスクとDBI(Data Bus Inversion)

データマスク(DM)は、特定のbitだけマスクしたりする機能であり、DBIはトグル回数(割合)を減らす機能である。
DBIの機能を使用することでノイズ低減や、消費電力の低減を期待できるらしい。
書き込みにおいて、DMとDBIは同時に使用することはできない。

DDR4メモリコンポーネントのデータシートを見る限り、書き込みにおいて、DBIが遅延を発生させることはないようだ(書き込みと同時に調整を行う)。
読み出しに関しては、レイテンシが2クロック増える。

MIGで選択できるDM/DBIのオプションは、以下から選ぶことができるが、レイテンシを考慮するとNO_DM_DBI_WRがベターかもしれない。

  • DM_NO_DBI(デフォルト)データマスク有効でDBI無効
  • DM_DBI_RDデータマスク有効で読み出しのみDBI有効
  • NO_DM_DBI_RDデータマスク無効で読み出しのみDBI有効
  • NO_DM_DBI_WRデータマスク無効で書き込みのみDBI有効
  • NO_DM_DBI_WR_RDデータマスク無効で読み出し/書き込みともDBI有効

Windows 10でWSLを有効時、Quartus PrimeのDDR3 Memory Controller IP生成でエラー

前置き

Windows 10でWSLを有効時、Quartus PrimeのDDR3 Memory Controller IP生成でエラーになって、IPの生成ができない現象に遭遇した。
ひとまずはWSLを無効にすれば正常に生成できるが、気になったので調査してみた。

エラー内容

Quartus Primeのバージョン16.1のエラー表示(17.1でも同様)。

Error: Error during execution of "{C:/intelfpga/16.1/quartus//../nios2eds/Nios II Command Shell.bat} make all 2>> stderr.txt": child process exited abnormally
Error: Execution of command "{C:/intelfpga/16.1/quartus//../nios2eds/Nios II Command Shell.bat} make all 2>> stderr.txt" failed

Quartusで自動生成されたMakefileの下記部分でエラーになっている。

D:/intelfpga/17.1/quartus//../nios2eds/sdk2/bin/nios2-bsp hal sequencer_bsp .. --default_sections_mapping sequencer_mem --use_bootloader DONT_CHANGE

調査内容

nios2-bspでエラーになるようだが、これの中身はシェルスクリプトであり、Quartus(Nios II)のmakeからシェルスクリプトを呼ぶとbash nios2-bspが実行される事がわかった。

このbashは、WSLが有効な環境ではC:\Windows\System32\bash.exeに展開されてしまうのでエラーになってしまう。本当はNIOS II Command Shellのbashを呼んでほしい。

対策

かなり無理矢理だが、自作のnios2-bsp.exeをオリジナルのnios2-bspと同じフォルダに置くことで解決した。

nios2-bsp.exeについては、特に理由はないが今回はgolangで作ってみた。(shebang書き換えでスマートに対応できたかもしれない)

package main

import (
    "fmt"
    "os"
    "os/exec"
)

func main() {
    cmd := exec.Command(
        os.Getenv("QUARTUS_ROOTDIR")+`\bin64\cygwin\bin\bash.exe`,
        append([]string{os.Getenv("SOPC_KIT_NIOS2") + "/sdk2/bin/nios2-bsp"}, os.Args[1:]...)...,
    )
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if err := cmd.Run(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}