根軌跡描いてみた
ただ、単純に根軌跡描いてみただけ。
制御工学を学ぶ際、よく教科書で以下の表記を見かけるけど、テスト勉強でしかやったことなくて今いち実感がつかめなかったので。
根軌跡の描かれる理由
フィードバック制御は,外乱の影響を抑えるために,ま た目標値との誤差を制御に利用できるという点で優れた 制御方法だが,下手なフィードバックをすると振動が生 じたり不安定になってしまう.
その原因としてよくある のが「過剰な制御」,「フィードバックのかけ過ぎ」,す なわち,制御のゲインを高くしすぎることである.
コントローラのゲインを高くすると振動が生じるのか, どの程度まで制御ゲインを高くしても安定性を保つことが できるのかなどの解析をしたいとき,制御ゲインとフィー ドバック系の極との関係を図で表したものがあると便利 である
今回は、以下の図の制御系においてC(s)=k/sとし、Kを0→∞と変化させたときの根軌跡を描いた。
MATLABでは以下のコマンドで根軌跡が確認できる。
ここで開ループ伝達関数はG_OP(s)=C(s)*P(s)。
と打ち込むとsを用いて以下のように伝達関数ベースで書いていけるので比較的楽
ゲインを動かした際のステップ応答を見る為に、
とコマンドウィンドウに打ち込み、rltoolを起動させる。
システムデータに補償器と制御対象のデータを、また入力や閉ループなどの情報を解析プロットにて入力した出力結果を以下に示す。
閉ループ極(根軌跡上のピンク点)の移動に伴い、ループゲインKが変化し、ステップ応答が収束or発散しているのが確認できた。
2リンクマニピュレータの位置制御
以下のマニピュレータについて、次の二通りの制御方式を用いたシミュレーションを行った。
制御方式
- 逆ヤコビ行列を用いた方法
- 1に外乱オブザーバーを付加する方法
PD制御を用いており、システムのブロック図は以下のようになる。 v
まず、理論を述べ、その後シミュレーション結果を載せる。
先端の目標軌道
- ステップ入力
- 円軌道
2リンクマニピュレータの動力学
動力学はマニピュレータの関節駆動力・関節初期位置から各リンクの位置・速度・加速度を求める順動力学問題と、各リンクの位置・速度・加速度及び作用力から各関節のモータが発生すべき関節駆動力を求める逆動力学問題がある.
順動力学はロボットの動作を表現するものとしてシミュレーションで必要となる.
逆動力学はロボット実機への制御入力を決定するために必要となる.一般のマニピュレータにおいて運動方程式は非常に複雑であるため、リアルタイム性が要求される実機の駆動においては影響の少ない項あるいは仕様に不必要な項を省略し計算を行っている.外乱オブザーバはこれらの省略項の影響を補償するものである.
動力学方程式(運動方程式)の導出方法としてはラグランジュ方程式による方法、ニュートンオイラー方程式による方法等があるが、ここではラグランジュ法によって導く.
ラグランジュ方程式の一般形
Robot Motionにおいてはニュートンオイラー法から運動方程式を求めているがここではラグランジュ方程式を用いて求める.
ラグランジュ関数Lは運動エネルギーKと保存エネルギーPとの差で定義される.
運動方程式はラグランジュ方程式で表される.
qiは各エネルギーを表現する一般化座標で、Fiはqiに依存した力あるいはトルクである.
ラグランジュ関数の導出
リンク1の運動エネルギーは次式で表される.
保存エネルギー(位置エネルギー)は次式のように求められる.
第2リンクの先端の座標を( x,y )とすると、θ1,θ2との関係は次のようになる.
以上の情報から、先端の速度が得られる。またリンク2の運動エネルギーK2、保存エネルギーP2が計算できる。
よってラグランジュ関数L = k-Pより導出が可能である。
動力学方程式の導出
動力学方程式導出のため、ラグランジュ方程式の各項を計算する.Lをθ1の微分で編微分したもの、それをtで微分したもの、またLのθ1の微分したものの3つの関係式から関節1におけるモータのトルクが以下のように計算できる。(計算が結構めんどくさいので省略)
同様に関節2のモータのトルクを求める.
これらの式をまとめると以下のように表現できる
但し
ここで、慣性行列MにおいてMiiは自己慣性係数、Mijは相互慣性係数として知られる.非線形項Hの中で、Hijj(θj(の微分))^2項は関節jの速度により関節iに働く求心トルクである.またHiij(θi(の微分))^2(θj(の微分))^2はジョイントjの速度によりジョイントiに働くコリオリ項である.Gは重力項を表している.
運動学
運動学はマニピュレータの先端の位置と、各関節の角度、角速度、角加速度とを結び付けるものである.運動学には順運動学と逆運動学がある.順運動学は各関節角から先端位置を一意に決定するものである.一方、逆運動学は先端位置より各関節角を定めるものであるが、その解は一意に存在するとは限らない.また、逆運動学は軌道計画の際に重要であり、目標軌道(通常直交座標空間におけるハンドあるいはマニピュレータ先端位置の軌道が計画される)を関節空間に変換する役割を持っている.2リンクマニピュレータの順運動学
2×2の変換行列はヤコビ行列(Jacobian)と呼ばれる.速度と角速度の関係式は以下のように表すことができ、これは多自由度マニピュレータにも適用できる.ただし多自由度の場合はヤコビ行列は正則行列にならない.上式を微分することにより、関節角速度と先端加速度を結び付ける関係式が得られる.
2リンクマニピュレータの逆運動学
余弦定理を用いることにより次式が得られる
先述したように、逆運動学問題では解が一意に求まらない.ここではθ1に2つの解が存在し、それぞれ肘を上げた、あるいは下げた姿勢に相当する.次に直交座標の速度から関節角速度を導出する.
加速度は逆ヤコビ行列より次のようになる.(速度も同様)
通常加速度における逆運動学は(37)~の第二項が第一項に比べ無視できることから次式のように近似して用いる.
逆ヤコビ行列を用いた制御方法
マニピュレータの制御は目標軌道と実際の軌道との偏差を直交座標空間上で計算し、逆運動学(逆ヤコビ行列)によって関節空間に変換して制御入力値とする.
このシステムの場合、下図に示すようにプラント(マニピュレータ)への入力は加速度であり、加速度制御系を構成している. 図中の添え字〇_nはノミナル化したパラメータを示す.ノミナル化とは行列の対角成分のみを抽出することである.ここではトルク定数を不変とし、Ktn = Ktとみなして計算を行う.実際のシステムと比較した場合、このシステムのモデルにはモデル化誤差・パラメータ変動等が存在する.
関節モータも含めた全体の動力学
マニュピュレータ全体の運動を制御するためには第1章で考察したモータから関節にかかる力と第2章で考察して来たマニュピュレータの運動により関節に生じる力とを合成する必要がある. モータにおける外乱Tdisを考慮しないとき、関節モータの動力学を含めた全体の動力学は次のように表される.
ここで、Gr,Jm,Dは対角成分のみ0でない値を持つ定数行列である. また、上図におけるMnは初期姿勢のMとする.
シミュレーション結果
ステップ軌道
オブザーバー適用前
オブザーバー適用後
円軌道
オブザーバー適用前
オブザーバー適用後
オブザーバー適用前と適用後で、重力,コリオリ力などの外乱が補償できている事が分かる。
マルチスレッドを利用したアプリケーション作ってみた
タイトルはかっこいいけど、アプリケーションとはただのスライドショーアプリ。
こんな感じで、自動で動く。下の選択バーと画像はもちろん手動で対応させる事もできる。
目的
複数スレッドを利用したアプリケーションを作って「マルチスレッド」気分を味わいたかった
予備知識
「タスク,プロセス」
OSがCPUに割り当てて実行させるプログラムの単位。
CPUから見たプログラムの実行単位であり,メモリ空間(プロセス空間)を割り当てる単位でもある。複数のプログラムが同時に動作することを「マルチタスク」あるいは「マルチプロセス」とも呼ぶ
「スレッド」
スレッドは,1つのプロセスをさらに小さな単位に分けたもの。
複数のスレッドが同じメモリ空間を共有するため,高速に切り替えて実行できるという特徴がある。
1つのプロセスの中にある複数のスレッドを同時に実行することを「マルチスレッド」と呼ぶ。
荒っぽく言えば,Windows上で「Word」と「Excel」を同時に実行することがマルチタスク(マルチプロセス)であり,Wordの文書ファイルを印刷中に,文書の編集を行うことがマルチスレッドである
タスク,プロセス,スレッドのいずれであれ,1つのCPUを使ってなぜ複数のプログラムを同時に実行させられるのかというとプログラムは実行される際に常にCPUを必要としているわけではないから
プログラムは「入力」,「演算」,「出力」という3つの基本的な処理から成るが,CPUを使うのは演算だけで,入力と出力はI/Oを使う。このため,プログラムがI/Oを使っている間はCPUが空き状態になり,この時間を使って別のプログラムを実行できる
参考 OSの基本機能——マルチタスクや仮想記憶で,ハード資源を有効活用
スレッドの利用箇所
ひとつはタイマー処理で画像を表示させる
もうひとつはボタンやスピナー(選択リスト)の処理
スレッドを作らない場合、時間を計測している間、他の操作が全くできなくなり画面がフリーズしているように見える
プログラム部分をRunnableインターフェースで作成しHandlerクラスを使って呼び出す。
スライドショーのクラスに画像を切り替えるプログラムrunnableChangeImageをRunnableインターフェースで宣言し、プログラムの呼び出しにHandlerクラスのオブジェクトhandlerを生成する。
流れとしてはクラスAのコンストラクタで画像パスをスピナーにセット。
リスナーを登録しておき、スピナーにおいて選択した要素を取り出し表示。
タイマー処理については、スレッドを作成し内部のメソッドでwhile文でsleepさせながらハンドラーから画像を変更するプログラム(インタフェース)を呼び出している。
インターフェース内では要素の位置を加算し、最後尾になったら最初の要素に戻すようにしている。
簡易Twitterクライアント作ってみた
目的
twitterに絡んだ、「今風?」なアプリの構築手順を理解したかった(OAuthとか)
こういったアプリを作っておけるのも今だけ。。
学部4年からは雑魚ながら、がっつり研究室に貢献したい。
まずはパブリックライムラインのツイート取得。
タイムラインの取得やAPIの使用等はTwitterのAPIドキュメントを参照してもらうとして
以下のURLでXMLデータを取得する事が可能。
http://api.twitter.com/1/statuses/public_timeline.xml
コードは省略。とりあえずXMLのtextタグのみを抽出したらこんな感じ
次はリストビューをカスタマイズする。
ユーザー名とツイート内容、アイコン画像を表示させる。
ビューはこんな感じに設定。
Before
After
んでもって、コーティング部分ではツイート内容、ユーザー名、アイコン画像のURLをまとめて扱う為クラスを定義し、そのオブジェクトをアダプターに追加している。
この時、アダプターをString型を扱うArrayAdapter
こんな感じ
OAuth認証を組み込む
まずはここでアプリケーションの登録を申請。
ここでConsumer keyとConsumer secret、OAuth認証を行う際に必要なurlが3つ発行される。
OAuth認証自体のプログラムはライブラリを普通使う。仕組みの概念とかはググってちょ。
それを組み込んだ上で、レイアウトの配置とコーディング。それは省略。
OAuth認証手順としては
- 1: 「Consumer key」と「Consumer secret」と,3つのURLからtwittetの認証ページへのURLを生成
- 2: 生成されたURLを開くと登録申請hしたアプリケーションの認証ページが表示される
- 3: 認証ページにある「許可」ボタンをクリックするとアプリケーションを使用する為の暗証番号が表示される
- 4: 生成されたURLと暗証番号から認証用の「Token key」と「Token secret」のペアを生成
- 5: 「Token key」と「Token secret」を使ってOAuth認証を行い、ホームタイムラインの表示
んで、実行させた。
デザインのインタフェースも自分で作成する。こんな感じ。
実行画面。まず、URLが生成され用意しておいたWebViewにTwitterの認証ページが表示される
暗証番号が発行されるので、それを今回は入力ボックスにセット。
これでOAuth認証の準備完了
後は、OAuth認証の部分をちょこっと実装、具体的には先ほどまで使っていたボタン等の部品を隠したり,URLから直接インプットストリームで渡していた部分をコネクションから取り出したりなど。
実行すると、確認ダイアログが出てくる。
すると暗証番号が出るので、これを打つと自分のタイムラインが取得できる。
ただ、現状だとアプリケーションを起動する度に認証が行われるのでToken keyとToken secretを保存し2回目以降は認証をパスするようにした。
具体的にはテキストファイルに情報を保存。その情報を読み書きするメソッドの用意、内部ではストリーム操作と読み込みの確認フラグの操作。
NetBSDのソースを読んでいく getchar編
テーマ「ファイル入出力に使われるfreadなどの内部で使われる低レベルシステムコールreadとバッファ管理されてファイルの入出力を極力させないgetcharの違いを調べる。」
まずgetcharを見ていく。んがmanを見る限りgetcharはgetcにstdinを指定したものと同じであり、getcはfgetcと同じ機能でマクロで書いてある。
なのでまずはfgetcを読む。
#include <sys/cdefs.h>
#if defined(LIBC_SCCS) && !defined(lint)
#if 0
static char sccsid[] = "@(#)fgetc.c 8.1 (Berkeley) 6/4/93";
#else
__RCSID("$NetBSD: fgetc.c,v 1.9 1999/09/20 04:39:26 lukem Exp $");
#endif
#endif /* LIBC_SCCS and not lint */
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include "reentrant.h"
int
fgetc(fp)
FILE *fp;
{
int r;
_DIAGASSERT(fp != NULL);
FLOCKFILE(fp);
r = __sgetc(fp);
FUNLOCKFILE(fp);
return r;
}
FLOCKFILEはgrepなどで探したりしても、NetBSDではthread対応のためのコードであり将来のサポートを見越しての記述らしいので無視。
大事なのは本質を追うこと。全部解読しようとするなんてナンセンス。
__sgetcを見ていく。
$ locate __sgetc.c
見当たらない。manでも見てみる。
$ man __sgetc.c
No manual entry for __sgetc.c
見当たらない。includeの方を探してみる。
$ grep __sgetc /usr/src/include/*
/usr/src/include/stdio.h:#define __sgetc(p) (--(p)->_r < 0 ? __srget(p) : (int)(*(p)->_p++))
/usr/src/include/stdio.h:#define getc(fp) __sgetc(fp)
/usr/src/include/stdio.h:#define getc_unlocked(fp) __sgetc(fp)
stdioでマクロで定義されてある。この定義で"--"と->の強弱の確認方法は
$ man operator
よってここでは以下のように整形できる。
--(p->_r);
if ((p->_r) < 0) {
return __srget(p);
} else {
(p->_p)++;
return (int)(*(p->_p));
}
ここで"p"はFILE構造体へのポインタ。_rは現時点では分からない。
/usr/src/lib/libc/stdio/getchar.c にはstdio.hをincludeするよう書いてあり、stdio.hの内部にはFILE構造体の記述がある。
getchar()
{
FILE *fp = stdin;
int r;
......
stdio.hに_rが書いてある
typedef struct __sFILE {
unsigned char *_p; /* current position in (some) buffer */
int _r; /* read space left for getc() */
int _w; /* write space left for putc() */
short _flags; /* flags, below; this FILE is free if 0 */
short _file; /* fileno, if Unix descriptor, else -1 */
struct __sbuf _bf; /* the buffer (at least 1 byte, if !NULL) */
int _lbfsize; /* 0 or -_bf._size, for inline putc */
/* operations */
void *_cookie; /* cookie passed to io functions */
int (*_close) __P((void *));
int (*_read) __P((void *, char *, int));
fpos_t (*_seek) __P((void *, fpos_t, int));
int (*_write) __P((void *, const char *, int));
/* file extension */
struct __sbuf _ext;
/* separate buffer for long sequences of ungetc() */
unsigned char *_up; /* saved _p when _p is doing ungetc data */
int _ur; /* saved _r when _r is counting ungetc data */
/* tricks to meet minimum requirements even when malloc() fails */
unsigned char _ubuf[3]; /* guarantee an ungetc() buffer */
unsigned char _nbuf[1]; /* guarantee a getc() buffer */
/* separate buffer for fgetln() when line crosses buffer boundary */
struct __sbuf _lb; /* buffer for fgetln() */
/* Unix stdio files get aligned to block boundaries on fseek() */
int _blksize; /* stat.st_blksize (may be != _bf._size) */
fpos_t _offset; /* current lseek offset */
} FILE;
__BEGIN_DECLS
extern FILE __sF[];
__END_DECLS
つまり、_pは現在の文字、_rは読み出し用に既に読み込んである文字総数。_fileが開いているファイルへの記述子。_bf._baseがバッファへのポインタ。_pは通常この中身を指す。
よってfgetcの本質である__sgetc()は_rが一以上、つまりバッファに読み込んである文字がまだあれば__srget()の返り値を返す。この"__s"は上記から分かるようにstdioの下位関数なのでrgetを見てみる。
#include <sys/cdefs.h>
#if defined(LIBC_SCCS) && !defined(lint)
#if 0
static char sccsid[] = "@(#)rget.c 8.1 (Berkeley) 6/4/93";
#else
__RCSID("$NetBSD: rget.c,v 1.10 2001/12/07 11:47:43 yamt Exp $");
#endif
#endif /* LIBC_SCCS and not lint */
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include "local.h"
/*
* Handle getc() when the buffer ran out:
* Refill, then return the first character
* in the newly-filled buffer.
*/
int
__srget(fp)
FILE *fp;
{
_DIAGASSERT(fp != NULL);
_SET_ORIENTATION(fp, -1);
if (__srefill(fp) == 0) {
fp->_r--;
return (*fp->_p++);
}
return (EOF);
}
ソースを見る限り__srefillで文字を読み込んで、バッファを減らし、その中から一文字返しているっぽい。
__srefillを見てみる。
$ locate refill
/usr/share/emacs/22.1/lisp/textmodes/refill.el.gz
/usr/share/emacs/22.1/lisp/textmodes/refill.elc
/usr/src/lib/libc/stdio/refill.c
まず、全部のっける。
__srefill(fp)
FILE *fp;
{
_DIAGASSERT(fp != NULL);
/* make sure stdio is set up */
if (!__sdidinit)
__sinit();
fp->_r = 0; /* largely a convenience for callers */
/* SysV does not make this test; take it out for compatibility */
if (fp->_flags & __SEOF)
return (EOF);
/* if not already reading, have to be reading and writing */
if ((fp->_flags & __SRD) == 0) {
if ((fp->_flags & __SRW) == 0) {
errno = EBADF;
return (EOF);
}
/* switch to reading */
if (fp->_flags & __SWR) {
if (__sflush(fp))
return (EOF);
fp->_flags &= ~__SWR;
fp->_w = 0;
fp->_lbfsize = 0;
}
fp->_flags |= __SRD;
} else {
/*
* We were reading. If there is an ungetc buffer,
* we must have been reading from that. Drop it,
* restoring the previous buffer (if any). If there
* is anything in that buffer, return.
*/
if (HASUB(fp)) {
FREEUB(fp);
if ((fp->_r = fp->_ur) != 0) {
fp->_p = fp->_up;
return (0);
}
}
}
if (fp->_bf._base == NULL)
__smakebuf(fp);
/*
* Before reading from a line buffered or unbuffered file,
* flush all line buffered output files, per the ANSI C
* standard.
*/
if (fp->_flags & (__SLBF|__SNBF)) {
rwlock_rdlock(&__sfp_lock);
(void) _fwalk(lflush);
rwlock_unlock(&__sfp_lock);
}
fp->_p = fp->_bf._base;
fp->_r = (*fp->_read)(fp->_cookie, (char *)fp->_p, fp->_bf._size);
fp->_flags &= ~__SMOD; /* buffer contents are again pristine */
if (fp->_r <= 0) {
if (fp->_r == 0)
fp->_flags |= __SEOF;
else {
fp->_r = 0;
fp->_flags |= __SERR;
}
return (EOF);
}
return (0);
}
この真ん中あたりでまずバッファを確保してる。
if (fp->_bf._base == NULL)
__smakebuf(fp);
ほんでもってここが難しい。
fp->_p = fp->_bf._base;
fp->_r = (*fp->_read)(fp->_cookie, (char *)fp->_p, fp->_bf._size);
fp->_flags &= ~__SMOD; /* buffer contents are again pristine */
if (fp->_r <= 0) {
if (fp->_r == 0)
fp->_flags |= __SEOF;
else {
fp->_r = 0;
fp->_flags |= __SERR;
}
return (EOF);
}
return (0);
}
ここ
fp->_r = (*fp->_read)(fp->_cookie, (char *)fp->_p, fp->_bf._size);これはポインタによる関数呼び出し。関数名とは実は関数本体へのポインタで"*"をつけると関数そのものになる。例えば↓
hogehoge()と(*hogehoge)()は同じ意味。
よって、ここでやっている事は_read()の返す値を_rに代入している。
_read()の返り値はfread()のmanを見るとSEE ALSOの部分にread(2)とあったのでシステムコールreadのmanを見てみる。
つまり何バイト読み込んだかを_rに代入している。これがバッファ処理の仕組み。
あらかじめバッファにファイルからデータをある程度読み込んでおき、データがバッファにあるうちはそこから文字を取る。空になればまたファイルから読み込む。
これでファイルへのアクセス回数を減らしている。
NetBSDのソースを読んでいく passwd編
とある研究室の見学に行った時に、linuxのソースが迷宮過ぎて、どうやって読んでいったらいいか分からないと言われた時にすすめられた本「デーモン君のソース探検」の読書メモついでに自分の調べた事も載せていく。
テーマ「UNIXにログインする際にパスワードが表示されないようにする方法」
まずはloginコマンドを調べる
$ locate login.c
/Applications/MAMP/htdocs/cake_1_3/app/views/users/login.ctp
/Applications/MAMP/htdocs/gpr/app/views/users/login.ctp
/usr/src/crypto/dist/heimdal/appl/login/login.c
/usr/src/crypto/dist/heimdal/appl/login/utmp_login.c
......
......
/usr/src/usr.bin/login/login.c
......
$ less /usr/src/usr.bin/login/login.c
vimでPasswordを検索語句に調べていくと、412行目あたりにhit↓
pwprompt = "Password:";
p = getpass(pwprompt);
getpassを調べる
$ man getpass
マニュアルを見る限り、パスワードを読み込む関数でパスワードは表示しないと言ってる。
getpassをみていく。
manのLibralyにはStandard C Libraryと書いてあったのでソースは /usr/src/lib/libcにある。
$ cd /usr/src/lib/libc
$ grep getpass *
$ grep getpass */*
hit! gen/getpass.c
gen/Makefile.inc: getpass.c getprogname.c getpwent.c getsubopt.c getttyent.c \
gen/Makefile.inc: getmntinfo.3 getnetgrent.3 getpagesize.3 getpass.3 \
gen/getpass.3:.\" $NetBSD: getpass.3,v 1.9 2002/02/07 07:00:13 ross Exp $
gen/getpass.3:.\" @(#)getpass.3 8.1 (Berkeley) 6/4/93
gen/getpass.3:.Nm getpass
gen/getpass.3:.Fn getpass "const char *prompt"
gen/getpass.3:.Fn getpass
gen/getpass.c:/* $NetBSD: getpass.c,v 1.14 2000/01/22 22:19:11 mycroft Exp $ */
gen/getpass.c:static char sccsid[] = "@(#)getpass.c 8.1 (Berkeley) 6/4/93";
gen/getpass.c:__RCSID("$NetBSD: getpass.c,v 1.14 2000/01/22 22:19:11 mycroft Exp $");
gen/getpass.c:__weak_alias(getpass,_getpass)
gen/getpass.c:getpass(prompt)
include/namespace.h:#define getpass _getpass
getpass.cを見ていくと、ECHOがある辺りがちょっと臭う。
if ((echo = (term.c_lflag & ECHO)) != 0) {
term.c_lflag &= ~ECHO;
(void)tcsetattr(fileno(fp), TCSAFLUSH|TCSASOFT, &term);
}
tcsetattrを調べる
$ locate tcsetattr.c
/usr/src/lib/libc/termios/tcsetattr.c
中を見てみる
{
struct termios localterm;
_DIAGASSERT(fd != -1);
_DIAGASSERT(t != NULL);
if (opt & TCSASOFT) {
localterm = *t;
localterm.c_cflag |= CIGNORE;
t = &localterm;
}
switch (opt & ~TCSASOFT) {
case TCSANOW:
return (ioctl(fd, TIOCSETA, t));
case TCSADRAIN:
return (ioctl(fd, TIOCSETAW, t));
case TCSAFLUSH:
return (ioctl(fd, TIOCSETAF, t));
default:
errno = EINVAL;
return (-1);
}
}
ここでioctlはmanで調べれば分かるが2番のシステムコール。
よってUNIX上で動作するプログラム、つまりlogin実行ファイルはカーネルへの標準出力への書き込み要求の発行をioctl、デバイス操作によってキャンセルしていることが分かった。
参考 割り込みメソッドを使用したシステム・コールの簡易フロー
NetBSDのソースを読んでいく rm編
テーマ「あるファイル-exampleのように"-"で始まるファイルをrm ./-example のようにファイル名の前にパスを書かずに削除するには?」
$ which rm
/bin/rm
普通にmanに "--" を記述すればファイルオプションが終了するって書いてあるけど天才中学生デーモン君は気づかなかったらしいw
とりあえず、rmのソースを見てみる。
main(int argc, char *argv[])
{
int ch, rflag;
setprogname(argv[0]);
(void)setlocale(LC_ALL, "");
Pflag = rflag = 0;
while ((ch = getopt(argc, argv, "dfiPRrW")) != -1)
switch (ch) {
case 'd':
dflag = 1;
break;
case 'f':
fflag = 1;
iflag = 0;
break;
case 'i':
fflag = 0;
iflag = 1;
break;
case 'P':
Pflag = 1;
break;
case 'R':
case 'r': /* Compatibility. */
rflag = 1;
break;
case 'W':
Wflag = 1;
break;
case '?':
default:
usage();
}
argc -= optind;
argv += optind;
if (argc < 1)
usage();
checkdot(argv);
if (*argv) {
stdin_ok = isatty(STDIN_FILENO);
if (rflag)
rm_tree(argv);
else
rm_file(argv);
}
exit(eval);
/* NOTREACHED */
}
getoptをまずはmanから見てみる。
getoptはコマンドもある。関数の説明を見たいので今回はライブラリの方のmanを見る
$ man 3 getopt
ここに全部書いてあるけど、getoptは第3引数にオプション名の書式を渡す。
例えば"-f"じゃswitch文の中でfになる。またcc -o ls ls.cのようにオプションがパラメータを持つ場合も"o:"のように":"をつける。
またオプションとして用意された文字列とマッチしなければ"?"を返すのでdefaultに捕まる。
"--"の場合は-1を返り値として返すので、whileの継続条件から外れる。
またargc -= optind,argv += optindを解析する。
上記は解析したオプションを飛ばす為に必要。
例えば2つオプションがあった場合、コマンド名もいれると3つ、よって引数argcを3つ減らし、文字列のポインタの配列で、3つポインタを飛ばす、これでオプションとしてではなく引数として扱うべき所を処理できる。
以下、それを見ていく
optindはソース内のどこにも記述されていない。
manをよく見ればわかるが#include
locateで調べると/usr/include/unistd.h にあるらしい。
optindはextern intと宣言されているのでgetoptを実装しているファイルで定義されているはず。
$ locate getopt.c | grep libc
/usr/src/lib/libc/stdlib/getopt.c
getoptのソースを見てみる
全部載せると長いので要点だけ。
getopt(nargc, nargv, ostr)
int nargc;
char * const nargv[];
const char *ostr;
{
static char *place = EMSG; /* option letter processing */
char *oli; /* option letter list index */
......
......
まず、関数内にstaticな変数placeがある、これは関数の内外のどちらに位置するかで意味が変わる事に注意。
両方、staticを付けると他ファイルからアスセスできない事からprivateと同義だが、関数内の場合、普通は関数が呼び出される度に毎回新しく変数は生成される一方、プログラムが動いている間ずっと存在する、つまり前の呼び出しで代入された値を利用可能。
また、これだと毎回呼び出し時にplaceにEMSGが代入されると思いきや、関数にstaticな変数はコンパイル時に初期化される、そして呼び出しの時はこの行は無い事と同じとみなされる。
またplaceは下記のplace = nargv[optind]と記述されているようにオプションを指す
if (optreset || !*place) { /* update scanning pointer */
optreset = 0;
if (optind >= nargc || *(place = nargv[optind]) != '-') {
place = EMSG;
return (-1);
}
また、 if (optreset || !*place) { の!*place は*place == "¥0"と同義で文字列の終わりだったらという意味、
ここで文字列の最後かどうか確かめる意味は、例えばrm -r -f dir と使われるだけならいいがrm -rf dirとされた場合、rの次のオプション、fの場所を覚えていないといけない、よってplaceは関数にstaticな変数でないといけないし-r -fの場合rの次は"\o"なので上記のif文を通ってplaceが-fを指すようになる。
また、ここを通ってオプションの先頭文字が"-"でなければ-1を返スコとでgetopt()の呼び出し側ではオプションの解析が終了し、残りは引数になる。
そして以下の場所で次の文字を調べて"-"の場合、オプション全体では"--"となるのでここで-1を返し終了している。
if (place[1] && *++place == '-' /* found "--" */
&& place[1] == '\0') {
++optind;
place = EMSG;
return (-1);
}
そしてここでoptoptに実際のオプション文字が代入され、オプションの書式ostr中にこのオプションがあるかどうかstrchrで探している
if ((optopt = (int)*place++) == (int)':' ||
!(oli = strchr(ostr, optopt))) {
/*
* if the user didn't specify '-' as an option,
* assume it means -1.
*/
if (optopt == (int)'-')
return (-1);
if (!*place)
++optind;
if (opterr && *ostr != ':')
(void)fprintf(stderr,
"%s: unknown option -- %c\n", getprogname(),
optopt);
次にBADCH(最初の方で"?"と定義されてある)を返している、これは書式の中にオプション文字があってその次の文字が":"でないならそのオプションはパラメータを取らない
return (BADCH);
よってこの部分でoptindを1進め、最後にoptoptつまりオプション文字を返している
if (!*place)
++optind;
}
else { /* need an argument */
if (*place) /* no white space */
optarg = place;
else if (nargc <= ++optind) { /* no arg */
place = EMSG;
if (*ostr == ':')
return (BADARG);
if (opterr)
(void)fprintf(stderr,
"%s: option requires an argument -- %c\n",
getprogname(), optopt);
return (BADCH);
}
else /* white space */
optarg = nargv[optind];
place = EMSG;
++optind;
}
return (optopt);
やっぱgetoptのソースはっとこ。
#include <sys/cdefs.h>
#if defined(LIBC_SCCS) && !defined(lint)
#if 0
static char sccsid[] = "@(#)getopt.c 8.3 (Berkeley) 4/27/95";
#else
__RCSID("$NetBSD: getopt.c,v 1.21 2001/04/24 09:07:43 joda Exp $");
#endif
#endif /* LIBC_SCCS and not lint */
#include "namespace.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef __weak_alias
__weak_alias(getopt,_getopt)
#endif
int opterr = 1, /* if error message should be printed */
optind = 1, /* index into parent argv vector */
optopt, /* character checked for validity */
optreset; /* reset getopt */
char *optarg; /* argument associated with option */
#define BADCH (int)'?'
#define BADARG (int)':'
#define EMSG ""
/*
* getopt --
* Parse argc/argv argument vector.
*/
int
getopt(nargc, nargv, ostr)
int nargc;
char * const nargv[];
const char *ostr;
{
static char *place = EMSG; /* option letter processing */
char *oli; /* option letter list index */
_DIAGASSERT(nargv != NULL);
_DIAGASSERT(ostr != NULL);
if (optreset || !*place) { /* update scanning pointer */
optreset = 0;
if (optind >= nargc || *(place = nargv[optind]) != '-') {
place = EMSG;
return (-1);
}
if (place[1] && *++place == '-' /* found "--" */
&& place[1] == '¥0') {
++optind;
place = EMSG;
return (-1);
}
} /* option letter okay? */
if ((optopt = (int)*place++) == (int)':' ||
!(oli = strchr(ostr, optopt))) {
/*
* if the user didn't specify '-' as an option,
* assume it means -1.
*/
if (optopt == (int)'-')
return (-1);
if (!*place)
++optind;
if (opterr && *ostr != ':')
(void)fprintf(stderr,
"%s: unknown option -- %c¥n", getprogname(),
optopt);
return (BADCH);
}
if (*++oli != ':') { /* don't need argument */
optarg = NULL;
if (!*place)
++optind;
}
else { /* need an argument */
if (*place) /* no white space */
optarg = place;
else if (nargc <= ++optind) { /* no arg */
place = EMSG;
if (*ostr == ':')
return (BADARG);
if (opterr)
(void)fprintf(stderr,
"%s: option requires an argument -- %c¥n",
getprogname(), optopt);
return (BADCH);
}
else /* white space */
optarg = nargv[optind];
place = EMSG;
++optind;
}
return (optopt); /* dump back option letter */
}