20191222のLinuxに関する記事は7件です。

【Ubuntu】GAN用の教師データ拡張法【ImageMagick】

・はじめに

 GANを用いて画像生成を行おうとしたが、手作業で集め画像だけではデータ数が少なすぎて学習がうまく進まなかった。
 そこで少ない元画像を加工することにより教師データを水増しする方法を備忘録として書く。

・ImageMagickのインストール

今回はUbuntu 18.04 LTS上にImageMagickをインストールした。

$ sudo apt install imagemagick

※ImageMagickには多数の脆弱性が報告されているため、サーバーにインストールする場合は注意してください。
参考:さようなら ImageMagick - https://blog.cybozu.io/entry/2018/08/21/080000

・画像水増しのための手法

 GANにおいて教師データを水増しする際に有効な画像変換としては、以下のようなものが挙げられる。

・反転
・コントラスト調整
・ノイズ増加

 画像水増しの手法は他にも多数ありますが、人の顔の画像を上下反転させるなど、画像変換を行った結果、人間が見て違和感を感じるような画像にならないように注意する必要がある。
 画像を左右反転させて2倍に増やした後、コントラスト調整やノイズ増加、明るさ調整などでさらに倍々に増やしていく。
 今回は下のいらすとやの画像を加工する。
tv_video_hensyu_hard.png

・左右反転

$ convert *.png -flop flop.png

alt
 *.pngとすることでカレントディレクトリに存在するすべてのpngファイルを左右反転させることができる。上下反転させたいときは-flopを-flipに書き換える。最後のflop.pngは変換後の画像名になる。

・コントラスト調整

convert *.png -contrast -contrast -contrast increasecontrast.png

コントラストを増加させる場合は-contrastを使う。また-contrastの数を増やすほどコントラストが増加する。
increasecontrast-1.png
 逆にコントラストを減少させる場合は+contrastを使う。こちらも+contrastの数を増やすことでコントラストの減少率を上げることができる。

convert *.png +contrast +contrast decreasecontrast.png

 コントラスト増加の割合と減少の割合を変えて実行することにより、より多くの教師画像を作成できる。

・ノイズ増加

 ガウシアンノイズを増加させる。

convert *.png -noise 10 +noise Gaussian Gaussiannoise.png

Gaussiannoise.png
 -noiseでradiusの大きさを指定し、+noiseでどのタイプのノイズを入れるかを指定する。指定できるノイズの種類には、Gaussianの他にImpulse、Laplacian、Multiplicative、Poisson、Random、Uniformがある。

・水増し効果

 以上の画像変換(反転、コントラスト増加、減少、ノイズ増加)を行うことで2×2×2×2=8倍に教師画像を増やすことができる。
 今回説明した変換方法以外にもImageMagickでは様々な画像変換が可能なため、敵対的生成ネットワークの教師データを手軽に増やすことができる。
 特にGANを用いる場合、元となる教師データが少ないことも多いため、画像変換は教師データ拡張に有効な手法であると思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【開発環境】Operation not permittedでphp artisan migrateできないときは権限周りを確認!

permission.png

どうも、たかふみです。

現在携わっているシステムの開発が終わり、「さぁそろそろステージング環境の準備をしようかなぁ」と思い、テーブルを作成するコマンド php aritisan migrate を実行しようとしたそのとき。

Operation not permitted

「migrateができない!」

今日の記事は、AWSのEC2を使って用意したステージング環境でmigrateができない問題を解決します!

解決策

自分が所有ユーザーになる&所有グループに書き込み権限を与える

やったこと

1. まずエラーメッセージを読む。

UnexpectedValueException  : The stream or file "/var/www/mcfhfs/storage/logs/laravel-2019-11-20.log" could not be opened: failed to open stream: Permission denied
  at /var/www/mcfhfs/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php:111
    107|             restore_error_handler();
    108|             if (!is_resource($this->stream)) {
    109|                 $this->stream = null;
    110|
  > 111|                 throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: '.$this->errorMessage, $this->url));
    112|             }
    113|         }
    114|
    115|         if ($this->useLocking) {
  Exception trace:
  1   Monolog\Handler\StreamHandler::write()
      /var/www/mcfhfs/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php:120
  2   Monolog\Handler\RotatingFileHandler::write()
      /var/www/mcfhfs/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php:42
  Please use the argument -v to see more details.

エラーメッセージを見てみると「ログファイルへの書き込み権限がありません。」となっていました。どうやらログファイルへの書き込み権限が無いようです。

2. ログファイルの権限を確認&変更

ls -la で権限を確認すると、rw- - r -- - r --となっていました。

この場合「所有ユーザ(読/書) - 所有グループ(読) - その他のユーザ(読)」であり、
僕は所有ユーザーではなく、所有グループに属するので読み取り権限しかありません。

「そしたら、所有グループの権限に書き込み権限(w)を追加すれば良いのでは?」

ということでchmod 664 laravel-2019-11-20.log を実行。
結果、 Operation not permitted が表示されました。

調べてみると、ファイルの権限はファイル所有者でないと変更できないようです。
(参考:https://marunouchi-tech.i-studio.co.jp/3341)

3. ファイルを作り直す

「そしたら、自分がファイルの所有ユーザーになればいいのでは?」

該当のログファイルを一旦削除して再度作成。
ファイル所有者が自分になっていることを確認した後、再度migrateを実行。

php artisan migrate:fresh
Dropped all tables successfully.
Migration table created successfully.

「成功!」

4. webサーバーからも書き込みができるように権限を変更

これで解決だと思い、ブラウザからステージング環境に接続してページを表示しようとしたところ、再び「ログファイルに書き込み権限がありません」との表示がされました。
どうやらwebサーバーからの書き込み権限を与える必要があるようです。

chmod 664 laravel-2019-11-20.log
ls -la
-rw-rw-r--

これで所有ユーザー、所有グループに書き込み権限を付与できたので再度サイトへアクセス。無事に表示されていました!

■まとめ

・権限はファイル所有者でないと変更できない。
・webサーバーからの書き込み権限付与を忘れずに!
・「Operation not permitted」「Permission denied」のときには権限を確認!

今まで権限でエラーが起きたことがなかったので、これを機に知ることができて良かったです。これでPermission deniedが出ても安心ですね。それでは!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Linux】複数あるファイルの中から特定の文字列を検索したいとき

はじめに

Railsでカラム名を変更した際、各ファイルに書いていた、変更前のカラム名を探して、変更後のカラム名に治すのがめんどくさかったので今回この記事を書きます。

コマンド

$ find ./ -type f -print | xargs grep 'hoge'

上記のコマンドの説明

find
find の次で指定したディレクトリ以下のファイルを検索する。
ファイル検索の構文は「find [path] [検索条件] [アクション]」

./
今いるディレクトリ以下が検索対象。「~/」とするとホームディレクトリ以下が検索対象となる。
./ の代わりにフルパスでも可。この場合も指定したディレクトリ以下が検索対象になる。

-print
検索結果を標準出力する。このとき結果をフルパスで表示する

-type f
指定したファイルタイプを検索する。fが通常ファイルを,cまたはdとするとディレクトリを,lとするとシンボリック・リンクを検索します。

xargs
標準入力からコマンドラインを作成し、それを実行する

grep
ファイルから文字列を検索する。grep の後に検索したい文字列を指定する。

参考

http://kawatama.net/web/1141

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ROSで移動したワークスペースごとに自動でsetup.bashを読み込むための環境構築

概要

本記事ではdirenvの簡単な使い方と,導入方法について紹介します.

動機

ROSで複数のワークスペースを使い分けていると,ワークスペースごとにsource devel/setup.bashをする必要があり地味に面倒なので,自動化できないかなあと調べていました.

環境

Ubuntu 16.04
他のバージョンでも多分できます.

方法

direnvなるものがあり,これをを使用すると,それぞれのワークスペースルートディレクトリに移動するだけで,setup.bash自動で読み込んでくれるらしい.

direnv導入方法

とても参考になる記事(direnvを使おう)を発見したので,基本的にはこの記事通りに設定すれば環境構築はできそうです.

go command not found

direnvを使おうを参考にインストールしようと「goコマンドが見つかりません」なるエラーが発生.

確かにGoは導入した記憶がないので,新しく入れる必要がありそうです.

Go のインストール

ここでは,direnvで使用しているgoの最新版をapt installで導入するための方法を紹介します.

通常のapt-repositoryではgoの最新版が手に入らない可能性がある

今話題のGoはapt installで簡単にインストールすることができます. しかし,apt-repositoryにあるバージョンに注意しなければなりません

筆者の環境で何も考えずに下記のようにgoをインストールした場合...

$ sudo apt update
$ sudo apt install golang-go

インストールされたのはgo 1.6. 2019年12月現在の最新版はgo 1.13のため,かなり古いバージョンがインストールされてしまっています.
direnvでは,最低でもgo 1.8以降のバージョンが必要なため,これではdirenvを使用することができません.

golang-goの最新版をインストールするためにapt-repositoryを更新

そこで,最新バージョンを手に入れるために,ここを参考に apt-repositoryを更新し,再度インストールします.

$ sudo add-apt-repository ppa:longsleep/golang-backports
$ sudo apt-get update
$ sudo apt-get install golang-go

念の為,バージョンを確認してみると

$ go version
go version go1.13.4 linux/amd64

となり,無事最新版をインストールすることができました.

direnvのインストール

ついでにdirenvのインストール方法も記載しておきます.

$ git clone https://github.com/direnv/direnv
$ cd direnv
$ sudo make install

~/.bashrcに下記を追記

~/.bashrc
export EDITOR=vim  # 好きなエディタを指定できます.今回はvimを使用.
eval "$(direnv hook bash)"

使い方

例えば/home/user/以下にcatkin_wshoge_wsがあったとすると,それぞれのディレクトリにて,下記を実行します.

// ワークスペースに移動
$ cd ~/{ws_roos}

// .envrcの作成/編集
$ direnv edit .

作成した.envrcを下記のように編集.

~/{ws_root}/.envrc
source deve/setup.bash
## outputが欲しい場合は下記を追加
# echo Auto sourcing source deve/setup.bash

これでワークスペースに移動するだけでsetup.bashを自動で読み込んでくれます.

まとめ

「ROS 複数ワークスペース setup.bash 自動」で検索してもなかなか欲しい答えにたどり着けないものですね…

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Seurat in Linux (インストール)

概要

SeuratをLinuxで動かそうとしたら、インストールに結構苦戦してしまったのでメモ
OS : Linux mint 19.3 Tricia

やる

チュート通り、install.packages("Seurat")してみるが、依存関係パッケージのインストールができない。
ちょっと調べてみると、どうもsudo apt install r-baseでインストールできるRのバージョンは3.4と古く、対応してないパッケージがあるみたい。新しいバージョンを入れるにはレポジトリと公開鍵を追加する必要がある。

#一旦、Rをアンインストールする
$ sudo apt purge r-base* r-recommended r-cran-*
$ sudo apt autoremove
$ sudo apt update

#公開鍵とレポジトリを追加する
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9
$ sudo add-apt-repository https://cloud.r-project.org/bin/linux/ubuntu bionic-cran35/

これで追加すると追加したhttps://cloud.r-project.org/bin/linux/ubuntu bionic-cran35/が何故かhttps://cloud.r-project.org/bin/linux/ubuntu mainとなってしまっているので、[システム設定]→[ソフトウェアソース]から正しいものに変更する。

そして、再びRをインストール

$ sudo apt update
$ sudo apt install r-base

これでR 3.6のインストールができた。

これで再度install.packages("Seurat")すると、またひっかかる。ldコマンドのオプションがないそう。
ターミナルからこれを入れる。Opensslも入ってなかったので一緒に入れる。

$ sudo apt install liblapack-dev liblapack3 libopenblas-base libopenblas-dev gfortran libssl-dev

これでも引っかかる。BioManagerからmulttestを入れる。

if (!requireNamespace("BiocManager", quietly = TRUE))
    install.packages("BiocManager")

BiocManager::install("multtest")

これでinstall.packages("Seurat")するとSeuratをインストールできた。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像が一覧表示できるlsコマンド lsix の紹介

lsix について

画像付きでファイルを一覧表示出来るコマンドです。
こんな感じでターミナル上で lisx と打つと、ディレクトリにある画像ファイルを一覧表示してくれます。
lsix.gif

ローカルだけではなく、サーバ上にインストールしておけば ssh 越しに Webアプリケーションの image ディレクトリの画像をサクっと確認する様な用途にも使えて便利です。

動作環境と必要なものについて

各OSで動作します。必要なのは

  • SixelGraphics で画像表示が可能なターミナルエミュレータ
  • ImageMagick

だけです。
ターミナルについては mlterm / Xterm /urxvt / iTerm2 等を使ってください。
Windows10 では WSL で wsltty を使うのが簡単です。こちらの記事を参考にして頂ければと思います。

今の ImageMagick は標準で sixel への変換処理が出来る様になっています。普通にOSのリポジトリからインストールしましょう。Ubuntu 等であれば、

~% sudo apt-get install imagemagick

でOKです。

lsix のインストール

こちらのリポジトリから、cloneするなり、zip をダウンロードするなりして展開します。
lsix の実体は Imagemagick の convertコマンドを使用したシェルスクリプトなので、展開したディレクトリ配下のlsixというファイルを、パスが通った場所にコピーするだけでインストール完了です。

使い方

画像があるディレクトリ上で lisx と打つだけで、オプションはありません。
こんな gif アニメの場合は
310776_245.gif

展開して表示されます。
pic2.png

PDF も表示可能ですが、少し変換に時間がかかる為デフォルトではスルーされるので、 lsix *pdf 等と明示指定するとOKです。

以上、是非試してみてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

scsi_mid_low_api.txtの訳

自分の勉強用にscsi_mid_low_api.txtを訳したのでメモ。

Introduction

この文書はLinuxのSCSI mid levelドライバとSCSI lower levelドライバの間のインターフェースの概略を説明するものである。Lower level ドライバ(以後、LLDs)はhost bus adapter(以後、HBA)ドライバとhostドライバ(以後、HD)など様々なものから呼ばれます。この文脈における"host"はIOバス(例. PCI または ISA)とSCSIトランスポート上に存在する一つのSCSI Initiator Portをつなぐブリッジを意味します。”initiator"ポート(SCSIの専門用語です。SAM-3を参照ください)は"target" SCSI port(例. ディスク)にSCSIコマンドを送ります。稼働中のシステムではたくさんのLLDsが存在するかもしれませんが、ハードウェアタイプにつき一つのみ適用されます。大半のLLDsは一つ以上のSCSI HBAを制御できます。いくつかのHBAは複数のhostを含みます。

SCSI transportはLinuxにおいて既にそれ自身のsubsystemをもつ外部バスである場合もあります。(例. USBとieee1394).そのような場合、SCSI subsystem のLLDは他のドライバのsubsystemへのソフトウェアブリッジとなります。例はusb-storageドライバ(dirvers/usb/storageにあります)とiee1394/sbp2ドライバ(drivers/ieee1394 にあります)です。

例えば、aic7xxx LLDはその会社の7xxx chipシリーズを採用しているAdaptec SCSI parallel interface(SPI)コントローラを制御します。Aic7xxx LLDはカーネルに組み込む、またはモジュールとして読み込むことができます。Linuxシステムにおいて稼働するaic7xxx LLDは一つだけですが、それはたくさんのHBAを制御するかもしれません。これらのHBAはPCIのドーターボードとして存在するかマザーボード上に組み込まれているかもしれません(あるいは両方の場合もあります。)いくつかのaic7xxxベースのHBAはデュアルコントローラです。従って2つのhostを表します。最新のHBAと同様に、aic7xxxのそれぞれのhostは自身のPCIデバイスアドレスを持っています。[SCSI hostとPCIデバイスの1対1対応は一般的ですが、必須ではありません。(例. ISA adpterを使うとき)]

SCSI mid層はLLDをSCSI upper層のドライバやblock層のドライバのような他の層から隔離します。

この文書はLinux kernel version 2.6.8に大体一致しています。

Documentation

SCSIの文書ディレクトリはLinxuソースツリーの中にあり、通常はDocumentation/scsiになります。大半の文書はplainテキスト(すなわちASCII)です。このファイルはscsi_mid_low_api.txtというファイル名で、該当ディレクトリで見つけられます。この文書のより最近のコピーは
http://web.archive.org/web/20070107183357rn_1/sg.torque.net/scsi/
で見つかるかもしれません。たくさんのLLDsはそこで文書化されています。(例. Aic7xxx.txt)。SCSI mid levelはLinux Kernel 2.4系のSCSI subsystemについて述べている文書へのurlを含むscsi.txtで簡単に述べられています。2つのupper levelドライバはそのディレクトリに文書を持っています。すなわち st.txt(SCSI tape driver)とscsi-generic.txt(sg driverのためのもの)です。

いくつかのLLDsに関する文書またはurlはCソースコード中あるいは、Cソースコードと同一のディレクトリで見つかるかもしれません。例えば、USB mass storage driverについてのURLを見つけるには、/usr/src/linux/drivers/usb/storage ディレクトリを見てください。

Driver structure

従来、SCSI subsystem用のLLDはdrivers/scsiディレクトリ中の少なくとも2つのファイルからなります。例えば、xyzと呼ばれるドライバはヘッダーファイルのxyz.hとソースファイルのxyz.cを持ちます。[実際には、一つのファイルにすべて記述できない明確な理由はありません。ヘッダーファイルが余分なこともあります]。様々なOSに移植されるいくつかのドライバは2つ以上のファイルを持ちます。例えば、aic7xxxドライバはgeneric用とOS特有のもの用(例. FreeBSDとLinux)に分割されたファイルを持ちます。そのようなドライバはdrivers/scsiディレクトリ以下にそれら自身のディレクトリを作る傾向にあります。

新しいLLDをLinuxへ追加するときは、以下のファイル(drivers/scsiディレクトリで見つかります)は注意する必要があります。MakefileとKconfigです。既存のLLDがどのように構成されているか調べることがおそらく最善です。

2.5系のdevelopment kernelが2.6系のproduction系に進化していく過程で、変更はこのインターフェースに導入されています。この例の一つが2つのモデルを有効にしているドライバの初期化コードです。古い初期化は、HBAドライバのロード時に検出されたhostに基づくものです。類似のものは2.4系で見つかります。これは"passive"初期化モデルと呼ばれます。新しいモデルはLLDの生存期間中にHBAにhotplugとunpluggedを許します。そしてこれは"hotplug"初期化モデルと呼ばれます。新しいモデルは、永続的に接続される従来のSCSIデバイスとhotplugされる最新のSCSIデバイス(例. USBまたはIEEE1394接続のデジカメ)を両方同じように扱えるため好ましいです。両方の初期化モデルは以下のセクションで議論します。

SCSI subsystemmへのLLDインターフェースは様々な方法があります:
a) Mid levelから直接関数を実行する
b) Mid levelから提供される関数登録用の関数ポインタを経由する。Mid levelは将来どこかのタイミングでこれらの関数を実行します。LLDはこれらの関数の実装を提供します。
c) Mid levelによって維持されているよく知られているデータ構造のインスタンスに直にアクセスする

a)グループの関数は以下の”Mid level supplied functions "の章でリスト化しています。

b)グループの関数は以下の”Interface functions"の章でリスト化しています。それらの関数ポインタは"struct scsi_host_template"の中に存在し、scsi_host_alloc()(*注1)によって渡されます。LLDが提供したくない関数インターフェースはscsi_host_templateの対応するメンバをNULLに設定します。ファイルスコープでのscsi_host_templateのインスタンスの定義は、明示的に初期化されていない関数ポインタをNULLに設定します。

c)グループの使い方は、特に"hotplug"環境のときは注意が必要です。LLDはmid levelや他のレイヤーと共有しているインスタンスの生存期間に気づかなければなりません。

LLD内で定義されているすべての関数とファイルスコープで定義されているすべてのデータはstaticであるべきです。例えば、xxxという名称のLLDで定義されているslave_alloc()関数は、"static int xxx_slave_alloc(struct scsi_device sdev) { / code */}"と定義することができます。

(*注1) scsi_host_alloc()関数はほとんどの場合、scsi_register()関数と置き換えられます。scsi_register()とscsi_unregister()関数はpassive初期化モデルのサポート維持用に残っています。

Hotplug initialization model

このモデルではLLDがSCSI subsystemからSCSI hostを導入するときと、取り除くときを制御します。Hostはドライバ初期化と同じくらいの速さで導入され、またドライバがshutdownされるのと同じくらい遅く取り除かれます。通常、ドライバはHBAが検出されたことを示すsysfs probe()コールバックに応答します。新しいデバイスがLLDが制御したいものであることを確認後、LLDはHBAを初期化しそれからSCSI mid levelに新しいhostを登録します。

LLDの初期化中に、ドライバはHBAが発見されるのを期待する適切なIOバスにドライバ自身を登録すべきです。(例. PCIバス)。これはおそらくsysfs経由で可能です。いくつかのドライバパラメータ(特にドライバがロードされた後に書き込み可能なもの)はこの時点でsysfsにも登録可能です。SCSI mid levelはLLDが最初のHBAを登録したとき、LLDを認識するようになります。

しばらくしてから、LLDはHBAとそれに続くLLDとmid levelの間の一般的な呼び出しシーケンスを認識します。この例は3つのSCSIデバイス中、最初の2つのみが応答を返す新しく導入されたHBAをスキャンするmid levelを示します。

        HBA PROBE: assume 2 SCSI devices found in scan
LLD                   mid level                    LLD
===-------------------=========--------------------===------
scsi_host_alloc()  -->
scsi_add_host()  ---->
scsi_scan_host()  -------+
                         |
                slave_alloc()
                slave_configure() -->  scsi_adjust_queue_depth()
                         |
                slave_alloc()
                slave_configure()
                         |
                slave_alloc()   ***
                slave_destroy() ***
------------------------------------------------------------

もしLLDがデフォルトのqueueの設定を調節したい場合は、slave_configure()ルーチンの中でscsi_adjust_queue_depth()を起動します。

*** mid levelがスキャンを試しても応答がないscsi deviceに対しては、slave_alloc(), slave_destroy()のペアが呼ばれます。

HBAが取り外されるときは、アンロードされたLLDモジュールに関連付けられた通常のシャットダウン処理の一部か、sysfsのremove() コールバックが起動されたことによって意図された"hot unplug"への応答かもしれません。いずれの場合にしろシーケンスは同じです。

    HBA REMOVE: assume 2 SCSI devices attached
LLD                      mid level                 LLD
===----------------------=========-----------------===------
scsi_remove_host() ---------+
                            |
                     slave_destroy()
                     slave_destroy()
scsi_host_put()
------------------------------------------------------------

Scsi_Host構造体のインスタンス(scsi_host_alloc()の戻り値のポインタのことです)を追いかけるのはLLDにとって役に立つかもしれません。そのようなインスタンスはmid-levelによって所有されます。Scsi_Host構造体のインスタンスはリファレンスカウントが0になったときにscsi_host_put()経由で解放されます。

マウントしたファイルシステム上のSCSIコマンドを処理中のdiskを制御するHBAをHot unplugするのは興味深い状況です。リファレンスカウントのロジックはmid-levelに関係するたくさんの問題に対処するためにmid-levelに導入されています。以下のリファレンスカウントの章を見てください。

hotplugの概念はSCSIデバイスに拡張されるかもしれません。現在、HBAが追加されたとき、scsi_scan_host()関数はHBAのSCSI transportに取り付けられたSCSI deviceへのスキャンを行います。より新しいSCSI transportでは、scan処理が完了したあとにHBAが新しいSCSIデバイスに気づくようになるかもしれません。LLDはmid-levelにSCSIデバイスを気づかせるために以下のシーケンスを利用可能です。

             SCSI DEVICE hotplug
LLD                   mid level                    LLD
===-------------------=========--------------------===------
scsi_add_device()  ------+
                         |
                    slave_alloc()
                    slave_configure()   [--> scsi_adjust_queue_depth()]
------------------------------------------------------------

同様の仕方で、LLDはSCSIデバイスの取り外されたか、SCSIデバイスへの接続が中断されたことに気づけるようになるかもしれません。いくつかの既存のSCSIトランスポート(例.SPI)はmid-levelによって発行されるSCSIデバイスをofflineにするSCSIコマンドが失敗するまで、SCSIデバイスが取り外されたことを気づかないかもしれません。SCSIデバイスの取り外しを検知するLLDは以下のシーケンスによってupper layerからの取り外しを開始できます。

              SCSI DEVICE hot unplug
LLD                      mid level                 LLD
===----------------------=========-----------------===------
scsi_remove_device() -------+
                            |
                     slave_destroy()
------------------------------------------------------------

LLDにとってscsi_device構造体のインスタンスを追いかけることは役に立つかもしれません(ポインタはslave_alloc()とslave_configure()コールバックへのパラメータとして渡されます)。そのようなインスタンスはmid-levelによって所有されます。scsi_device構造体はslave_destroy()のあとに解放されます。

Passive initialization model

これらの古いLLDはソースコードの中に"scsi_module.c"と呼ばれるファイルを含んでいます。そのファイルを機能させるために、driver_templateと呼ばれるscsi_host_template構造体のインスタンスを定義する必要があります。このモデルの典型的なコードシーケンスの使い方は以下です:
static struct scsi_host_template driver_template = {
...
};
#include "scsi_module.c"
Scsi_module.cファイルは以下の2つの関数を含みます:
- Init_this_scsi_driver() LLDが初期化されるときに実行されます(i.e 起動時 or モジュールロード時)
- Exit_this_scsi_driver() LLDがシャットダウンされるときに実行されます(i.e モジュールアンロード時)
注意: これらの関数はinitとexit修飾詞でタグ付けされているので、LLDは明示的にコールすべきではありません。(カーネルが実行するので)

これは2つのホストが認識され(detect()が2を返す)、それぞれのホスト上でSCSIバススキャンを実行して1つのSCSIデバイスを発見する例です。(2個めのSCSIデバイスが応答を返さなかった)。

LLD                      mid level                 LLD
===----------------------=========-----------------===------
init_this_scsi_driver() ----+
                            |
                         detect()  -----------------+
                            |                       |
                            |                scsi_register()
                            |                scsi_register()
                            |
                      slave_alloc()
                      slave_configure()  -->  scsi_adjust_queue_depth()
                      slave_alloc()   ***
                      slave_destroy() ***
                            |
                      slave_alloc()
                      slave_configure()
                      slave_alloc()   ***
                      slave_destroy() ***
------------------------------------------------------------

Mid-levelはtagged queuingをoffにしてscsi_adjust_queue_depth()を呼び出し、そのホスト用にqueue lengthとして"cmd_per_lun"を設定します。これらの設定はLLDによって提供されるslave_configure()で上書き可能です。
*** mid levelがスキャンを試みて応答をかえさなかったSCSIデバイス用に、slave_alloc()とslave_detroy()のペアが呼ばれます。

以下はLLDがシャットダウンするシーケンスです。

LLD                      mid level                 LLD
===----------------------=========-----------------===------
exit_this_scsi_driver() ----+
                            |
                     slave_destroy()
                         release()   -->   scsi_unregister()
                            |
                     slave_destroy()
                        release()   -->   scsi_unregister()
------------------------------------------------------------

LLDはslave_destroy()を定義する必要はありません(i.e. 定義するかはオプショナルです)。

"passive initialization"モデルの欠点はホストの登録と解除がLLDの初期化とシャットダウンに結びついていることです。いったんLLDが初期化されると、ドライバのシャットダウンや再初期化なしに新しく出現したホスト(e.g hotplug経由)を簡単に追加することができません。両方の初期化モデルに対応したLLDを書くことは可能かもしれません。

Reference Counting

Scsi_Host構造体にはリファレンスカウントインフラが追加されました。これによって、それらを利用する様々なSCSIレイヤを通してScsi_Hostインスタンスの所有権が効果的に広がります。以前は、そのようなインスタンスはmid levelによって排他的に所有されていました。LLDはこれらのリファレンスカウントを直接操作する必要は普段はありませんが、いくつかのケースでは操作する必要があります。

Scsi_Host構造体に関連した興味深い3つのリファレンスカウントの関数があります。
- scsi_host_alloc(): リファレンスカウントを1に設定したScsi_Host構造体の新しいインスタンスへのポインタを返します。^^
- scsi_host_get(): 与えられたインスタンスのリファレンスカウントに1加算します。
- scsi_host_put(): 与えられたインスタンスのリファレンスカウントを1減算します。もしリファレンスカウントが0になったら、与えられたインスタンスを解放します

Scsi_device構造体にはリファレンスカウントインフラが追加されました。それらを利用する様々なSCSIレイヤを通してScsi_deviceインスタンスの所有権が効果的に広がります。以前は、そのようなインスタンスはmid levelによって排他的に所有されていました。Include/scsi/scsi_device.hの終わりにむかって宣言されたアクセス関数を見てください。もしLLDがScsi_deviceインスタンスへのポインタのコピーを持ち続けたいなら、そのリファレンスカウントを押し上げるためにscsi_device_get()を使うべきです。そのポインタが終了したときは、そのリファレンスカウントを減らすためにscsi_device_put()を使用できます(そして潜在的にはそれを削除します)。

^^ Scsi_Host構造体は実際にはこれらの関数によって並行に操作される2つのリファレンスカウントを持ちます。(shost_devとshost_gendevのこと?)

Conventions

まずリーナス・トーバルズCのコーディングスタイルについての考えはDocumentation/CodingStyleファイルで見つけることができる。

次に、struct tagsのために類義語を導入したtypedefを禁止する動きがあります。SCSI subsystemにおいてはまだ両方とも見つけることができますが、そのようなtypedefは将来の削除をより簡単にするために一つのファイルscsi_typedefs.hに移されています。例えば:
"typedef struct scsi_cmnd Scsi_Cmnd;"

また、大部分のC99のエンハンスは関連するgccのコンパイラによってそれらがサポートされる拡張を奨励します。そのため、C99スタイルの構造体と配列の初期化子は必要な場合には奨励されます。度を越さないでください、VLAsはまだ適切にサポートされていません。これに対する例外は、"//"スタイルのコメントを使うことです。/* */スタイルのコメントがLinuxではまだ好まれます。

よく書かれ、テストされ文書化されたコードは上記の慣例に従うように再整形する必要はありません。例えば、aic7xxxドライバはFreeBSDとAdaptecの研究所からLinuxへやって来ます。きっとFreeBSDとAdaptecは彼ら自身のコーディングの慣例を持っています。

Mid level supplied functions

これらの関数はLLDによって利用されるためにSCSI mid levelによって提供されます。これらの関数の名前(すなわちエントリポイント)はLLDがそれらにアクセスできるようにexportされます。カーネルはどんなLLDが初期化されるより前にSCSI mid levelをロードし初期化されるように整えます。以下の関数はアルファベット順に並べられ、それらの関数名はすべて"scsi_"から始まります。

Summary:
scsi_activate_tcq - turn on tag command queueing
scsi_add_device - creates new scsi device (lu) instance
scsi_add_host - perform sysfs registration and set up transport class
scsi_adjust_queue_depth - change the queue depth on a SCSI device
scsi_bios_ptable - return copy of block device's partition table
scsi_block_requests - prevent further commands being queued to given host
scsi_deactivate_tcq - turn off tag command queueing
scsi_host_alloc - return a new scsi_host instance whose refcount==1
scsi_host_get - increments Scsi_Host instance's refcount
scsi_host_put - decrements Scsi_Host instance's refcount (free if 0)
scsi_partsize - parse partition table into cylinders, heads + sectors
scsi_register - create and register a scsi host adapter instance.
scsi_remove_device - detach and remove a SCSI device
scsi_remove_host - detach and remove all SCSI devices owned by host
scsi_report_bus_reset - report scsi _bus_ reset observed
scsi_scan_host - scan SCSI bus
scsi_track_queue_full - track successive QUEUE_FULL events 
scsi_unblock_requests - allow further commands to be queued to given host
scsi_unregister - [calls scsi_host_put()]

Details:
省略

Interface Functions

インターフェース関数はLLDによって定義され、それらの関数ポインタはscsi_host_alloc()[または scsi_register()/init_this_scsi_driver()]によって割り当てられたscsi_host_template構造体のインスタンスに配置されます。いくつかは必須です。インターフェース関数はstaticで宣言されるべきです。受け入れられた慣例は"xyz"というドライバはそれのslave_configure()関数を static int xyz_slave_configure(struct scsi_device *sdev)として宣言します。そして、以下に並べられているすべてのインターフェース関数も同様です。

この関数へのポインタはscsi_host_template構造体インスタンスのslave_configureメンバに配置されるべきです。そのようなインスタンスへのポインタはmid levelのscsi_host_alloc()[または scsi_register()/init_this_scsi_driver()]へ渡されるべきです。

インターフェース関数はまたinclude/scsi/scsi_host.hファイルのscsi_host_template構造体の定義箇所のすぐ上に記述されています。場合によっては、詳細はscsi_host.h内のそれより下に記述されています。

インターフェース関数はアルファベット順に以下に記載します。

Summary:
bios_param - fetch head, sector, cylinder info for a disk
detect - detects HBAs this driver wants to control
eh_timed_out - notify the host that a command timer expired
eh_abort_handler - abort given command
eh_bus_reset_handler - issue SCSI bus reset
eh_device_reset_handler - issue SCSI device reset
eh_host_reset_handler - reset host (host bus adapter)
info - supply information about given host
ioctl - driver can respond to ioctls
proc_info - supports /proc/scsi/{driver_name}/{host_no}
queuecommand - queue scsi command, invoke 'done' on completion
release - release all resources associated with given host
slave_alloc - prior to any commands being sent to a new device 
slave_configure - driver fine tuning for given device after attach
slave_destroy - given device is about to be shut down
Details:
省略

Data Structures

struct scsi_host_template

LLD単位で一つの"struct scsi_host_template"があります**。それはドライバのヘッダファイルで静的なファイルスコープとして初期化されます。その方法では明示的に初期化されないメンバーは0またはNULLが設定されます。関心のあるメンバーは以下です:
name - ドライバ名(スペースは含んでもよいが、80文字までにしてください)
proc_name - "/proc/scsi//"のと "drivers"ディレクトリの一つにあるsysfsによって使われます。更に、"proc_name"はUnixファイル名で受け付けられる文字のみ含むべきです。
(
queuecommand)() - mid levelがSCSIコマンドをLLDに注入するために利用する主要なコールバックです。
scsi_host_template構造体はinclude/scsi/scsi_host.hで定義とコメントをされています。

*** 極端な状況では、一つのドライバがいくつかの異なるハードウェアのクラスを制御するなら、一つのドライバが複数のインスタンスを持つかもしれません。(e.g. LLDがISAカードとPCIカード両方を扱い、それぞれのクラスに対して分離されたscsi_host_template構造体を持ちます)。

struct Scsi_Host

LLDが制御するhost(HBA)につき一つのScsi_Host構造体があります。Scsi_Host構造体は"struct scsi_host_template"と共通してたくさんのメンバーを持ちます。新しいScsi_Host構造体のインスタンスが作成されると(hosts.cにあるscsi_host_alloc()によって作成される)、それらの共通のメンバーはドライバのscsi_host_template構造体のインスタンスから初期化されます。関心のあるメンバーは以下です:

host_no      - このホストを特定するためのシステム全体で唯一の番号です。0から昇順に値が振られます。
can_queue    - 0より大きい値でなければなりません。can_queueの値より多くのコマンドをアダプタへ送ってはいけません。
this_id      - SCSIホスト(イニシエータ)のIDを表します。不明なら-1を設定します。
sg_tablesize - ホストが許容する最大のスキャッターギャザーの要素数です。0ならそのホストはスキャッターギャザーをサポートしていないことを意図します。
max_sectors  - 一つのSCSIコマンドで許容される最大のセクター数です(通常は512byteです)。初期値0は、現在は1024を設定するSCSI_DEFAULT_MAX_SECTORS(scsi_host.hで定義されています)の設定を導きます。これまでのところ、max_sectorsが定義されていないときディスクの最大転送長は512KBです。このサイズはディスクファームウェアのアップロードには十分ではないかもしれないことに注意してください。
cmd_per_lun  - ホストによって制御されるデバイスにキューイングできる最大のコマンド数です。LLDがscsi_adjust_queue_depth()を呼ぶことで上書きします。
unchecked_isa_dma -1 =>のときは RAMの下位16MBのみ使います(ISAのDMAアドレッシングの制限)、0 >= のときは32bit以上のDMAアドレス空間をフルに使えます。
use_clustering - 1=>mid levelのSCSIコマンドはマージ可能です,
                 0=>SCSIコマンドのマージは不許可です
hostt        - 生成されたScsi_Host構造体のインスタンスからドライバのscsi_host_template構造体へのポインタです。
hostt->proc_name  -LLDの名前です。これはsysfsが使うドライバの名前です。 
transportt   - scsi_transport_templateインスタンスへのポインタです(もしあれば)。FCとSPIのtransportは現在サポートされています。
sh_list      - すべてのScsi_Hostインスタンスへの双方向リンクドリストです。(現在やhost_noの昇順にならんでいます)。
my_devices   - このホストに属するscsi_device構造体への双方向リンクドリストです。    hostdata[0]  - Scsi_Host構造体の最後にあるLLD用の予約領域です。サイズはscsi_host_alloc()またはscsi_register()の第2引数によって設定されます。
vendor_id    - Scsi_Hostのためにベンダーが提供しているLLDを特定するユニークな値です。大半はベンダー特湯のメッセージ要求を検証するのに使います。値は識別子の型とベンダー特有の値で構成されます。有効なフォーマットの記述はscsi_netlink.hを見てください。

scsi_host構造体はinclude/scsi/scsi_host.hで定義されています。

struct scsi_device

一般的に、1つのホスト上の各SCSI LU毎にこの構造体のインスタンスが1つあります。ホストへ接続されたSCSIデバイスはチャネル番号、ターゲットID、logical unit number(lun)によって一意に特定されます。この構造体はinclude/scsi/scsi_device.hで定義されています。

struct scsi_cmnd

この構造体のインスタンスはSCSIコマンドをLLDへ伝え、応答をmid levelへ返します。SCSI mid levelはscsi_adjust_queue_depth()(またはScsi_Host構造体のcmd_per_lun)によって意図された値以上のSCSIコマンドをLLDに対してキューイングはしません。それぞれのSCSIデバイスに対して少なくとも1つのscsi_cmnd構造体のインスタンスが利用可能でしょう。関心のあるメンバーは以下です:

cmnd         - SCSI commandを保持する配列
cmnd_len     - SCSI commandの配列長(in bytes)
sc_data_direction - データフェーズでのデータ転送方向。include/linux/dma-mapping.hの"enum dma_data_direction" を見てください
request_bufflen - 転送するデータバイトの数(0は転送データなし)
use_sg       - ==0 -> スキャッターリストを使用しない。すなわち、request buffer へ/からの転送データ                  - >0 ->  use_sg要素を用いたrequest_buffferのスキャッターギャザーリスト(実際には配列)    request_buffer - use_sgの設定に依存したデータバッファーかスキャッターギャザーリストのどちらか。スキャッターギャザーの要素はinclude/asm/scatterlist.hのstruct scatterlistによって定義されている。
done         - SCSIコマンドが完了(成功またはその他)したときにLLDによって起動されるべき関数ポインタ。LLDがコマンドを受け入れた(i.e queuecommand()が0を返したまたは返す)なら、LLDによってのみ呼ばれるべきです。LLDはqueuecommand()が終了する前にdoneを起動するかもしれません。
result       - doneを呼ぶより前にLLDがによって設定されるべきです。値0はコマンドが成功したことを意図します。(そしてすべてのデータがSCSIターゲットデバイスへ/から転送されました。)resultは4つの関連するバイトとして閲覧される32bitのunsigned integerです。SCSIステータスの値はLSBです。Include/scsi/scsi.h のstatus_byte(), msg_byte(), host_byte(), driver_byte()マクロと関係する定数を見てください。
sense_buffer - SCSIステータス(resultのLSB)がCHECK CONDITION(2)に設定されたとき書かれるべき配列(最大長:SCSI_SENSE_BUFFERSIZEバイト)。CHECK CONDITIONが設定されたとき、sense_buffer[0]の上位4bitが値7なら、mid levelはsense_bufferの配列が有効なSCSIセンスバッファーを含んでいると仮定します。そうでなければ、mid levelはREQUESET SENSEのSCSIコマンドをセンスバッファーを獲得するために発行するでしょう。後者の戦略はコマンドのキューイングの存在においてエラーしがちである。そのためLLDはいつもauto-senseであるべきだ。
device       - このコマンドに関連付けられたscsi_deviceオブジェクトへのポインタである
resid        - LLDはこの符号付き整数に要求した転送長(i.e request_buffer)から実際に転送したバイト数を引いたものを設定すべきである。residは予め0が設定されており、もしunderrun(overrunはまれ)を検知できなかったらLLDはそれを無視できます。もし可能ならLLDはdoneの起動前にresidを設定すべきです。もっとも興味深いケースはunderrunするSCSIターゲットデバイス(e.g READs)からのデータ転送です。
underflow    - LLDはこの値より実際に転送されたバイト数が少ないなら、resultの中に(DID_ERROR_ << 16)を配置すべきです。このチェックを実装し、DID_ERRORをレポートするよりログへエラーメッセージを吐き出すLLDはあまり多くありません。LLDにとってより良いのはresidを実装することです。

LLDはSCSIターゲットデバイスからのデータ転送にはresidを設定することをおすすめします(例. READs)。MEDIUM_ERRORとHARDWARE ERROR(可能ならRECOVERD ERROR)のセンスキーをもつようなデータ転送のときは、residを設定することは特に重要です。もしLLDがデータをどれくらい受け入れるかわからずに、それから最も安全なアプローチとしてデータを受け入れないようにするこれらの場合。例えば:有効なデータが受信されていないことを示すために、LLDはこれらのヘルパー関数を使うかもしれません:

scsi_set_resid(SCpnt, scsi_bufflen(SCpnt));

SCpntはscsi_cmndオブジェクトへのポインタです。512バイトのブロックを3つだけ受信したことを示すために、residはこのように設定されます。

scsi_set_resid(SCpnt, scsi_bufflen(SCpnt) - (3 * 512));

scsi_cmnd構造体はinclude/scsi/scsi_cmnd.hで定義されています。

Locks

Scsi_Host構造体のインスタンスはscsi_host_alloc()で初期化されるScsi_Host::default_lockと呼ばれるspin_lockを持ちます。同じ関数内で、Scsi_Host::host_lockポインタはdefault_lockを指すように初期化されます。その後、lockとunlockの操作はScsi_Host::host_lockポインタを使用するmid levelによって行われます。以前のドライバはhost_lockを上書きできましたが、これはもう許可されていません。

Autosense

Autosense(or auto-sense)はSAM-2ドキュメントで、CHECK_CONDITIONの状態が発生したときに、アプリケーションクライアントへのセンスデータの自動的な応答がSCSIコマンドの完了と一致することと定義されています。LLDsはautosenseを演じるべきです。これはLLDがそれぞれによってCHECK CONDITIONを検知したときになされるべきです:
a) そのような応答においてさらなるデータを演じるためにSCSIプロトコルの命令をします。
b) または、LLDはREQUEST SENSEコマンドを自身に発行します

どちらの方法にせよ、CHECK CONDITIONが認識されたとき、mid levelはstruct scsi_cmnd::sense_buffer[0]をチェックして、LLDがautosenseを演じるかどうかを決定します。
もしこのバイトが7の上位4bit(0b0111の意味? or 0xf)を持っているなら、それからautosenseが起こったと想定されます。もし別の値を持っているなら(そしてこのバイトはそれぞれのコマンドの前に0に初期化されます)、mid levelはREQUESET SENSEコマンドを発行します。

キューイングされたコマンドが存在する場合、次のREQUESET SENSEまで失敗するコマンドからセンスバッファーデータを維持するnexusは同期がとれないかもしれません。これはLLDがautosenseを演じる最もよい理由です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む