←検索窓の楽しみ方
  ショッピングモール  掲示板ランキング


【掲示板ご利用上の注意】

 ※題名は具体的に!
 ※学校の課題の丸投げ禁止!
 ※ソースの添付は「HTML変換ツール」で字下げ!
 ※返信の引用は最小限に!
 ※環境(OSとコンパイラ)や症状は具体的に詳しく!
 ※マルチポスト(多重投稿)は慎んで!

 詳しくはこちら



 本当はこんなに大きく書きたくはないのですが、なかなか守っていただけなくて…。
 守ってくださいね。お願いします。(by管理人)

C言語ソース⇒HTML形式ツール   掲示板1こちら


管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    記事検索    ログ    タグ一覧

No.3409

select関数のバッファリングについての疑問
投稿者---chikato(2005/02/07 01:23:03)


度々スイマセン。

select関数についての振舞いについて調べています。
(Win2k+Cygwin)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/types.h>
#include <curses.h>
#include <signal.h>
#include <unistd.h>
//===================
void session_loop(){
 fd_set mask;
 FD_ZERO(&mask);
 FD_SET(0,&mask);
 fd_set readOK;
 int width=1;
 char c;
 printf("う\n");
 while(1){
 printf("あ\n");
 readOK=mask;
        printf("こwidth=%d\n",width);
 select(width,(fd_set *)&readOK,NULL,NULL,NULL);
        printf("け\n");
 if ( FD_ISSET(0, &readOK ) ){
 printf("い\n");
 c=getchar();  //getcharはバッファリングあり関数
 printf("c=%c\n",c);
        printf("さ\n");
 }
 }
}
//=================
int main(void){
 session_loop();
 return 0;
}


を実行すると
$ ./test  ←[Enter]キー
う
あ
こwidth=1   ←ここで止まるので[k]キーを押してみる
け
い
k ←ここで止まるので[Enter]を押してみる
c=k
さ
あ
こwidth=1 ←ここで止まるので[k]キーを押してみる
け
い
c=

さ
あ
こwidth=1
け
い
k ←ここで止まる

という風な振舞いをするのですが何故「c= 」という風になるのか分かりません。
select関数はバッファリングする(バッファが満杯になるか改行文字が入るまでは開
放されない)関数なのですよね?
何故2ループ目は「c=k」とならないのでしょうか?


 k,\n,kと入力されているから、
c=\nになっているからではとも思ったのですがそれなら

 :
 さ
 あ
 こwidth=1 ←ここで止まるので[k]キーを押してみる
 
 
 ここで止まるのは何故なんですかね?
(ここの時点では既に改行文字が入っているだろうから止まらない筈では???)
 
 select関数は改行文字が押されるとフラッシュされるんですよね?
 



この投稿にコメントする

削除パスワード

発言に関する情報 題名 投稿番号 投稿者名 投稿日時
<子記事> Re:select関数のバッファリングについての疑問 3411 かずま 2005/02/07 14:51:54


No.3411

Re:select関数のバッファリングについての疑問
投稿者---かずま(2005/02/07 14:51:54)


> という風な振舞いをするのですが何故「c= 」という風になるのか分かりません。
> select関数はバッファリングする(バッファが満杯になるか改行文字が入るまでは開
> 放されない)関数なのですよね?
> 何故2ループ目は「c=k」とならないのでしょうか?

動作を説明します。

select は、入力デバイス(この場合はキーボード)の状態を見ます。
stdin のバッファの状態は見ません。
キーボードドライバの論理ドライバである端末ドライバは独自の行バッ
ファを持っています。このバッファが空なので、select は待ちます。

[k]キーを押すと、'k' がバッファに入るので、select の待ちは終了し、プロ
グラムは次に進んで、"け\nこ\n" を出力します。端末ドライバは 'k' を
エコーバックし、それが表示されます。

次に、getchar で、stdin に入力を要求します。
stdin のバッファは空なので、入力デバイスに read の要求を出します。
ところが、端末ドライバが行バッファリングをしているので、バッファ内の
'k' は返ってきません。read は待ちに入ります。

[Enter] を入力すると、'\r' のコードが入力されるのですが、端末ドライバは
それを '\n' に変換し、自分のバッファに入れます。行バッファリングをして
いるので、"k\n" の 2文字を stdin に送ります。

stdin は、getchar 要求に対して 'k' だけを返します。
プログラムは進んで、"c=k\nさ\nあ\nこwidth=1\n" を表示します。
次の select は、端末ドライバのバッファが空なので待ちます。

[k]キーを押すと、'k' がバッファに入るので、select の待ちは終了し、プロ
グラムは次に進んで、"け\nこ\n" を出力します。
次の getchar に対して、stdin は、自分のバッファに残っている '\n' を
返します。
プログラムにより、"け\nい\nc=\n\nさ\nあ\nこwidth=1\n" が表示されます。

次の select では、端末ドライバのバッファに 'k' が入っているので、
select は待ちに入りません。

プログラムは "け\nい\n" を表示し、端末ドライバは 'k' をエコーバック
します。

その次の getchar で stdin に入力を要求しますが、stdin のバッファは空
なので、stdin は 端末ドライバに read 要求を出します。

端末ドライバは行バッファリングしているので、自分の持っている 'k' を
返しません。そこで待ちに入ります。

これでご理解いただけますか?


この投稿にコメントする

削除パスワード

No.3412

Re:select関数のバッファリングについての疑問
投稿者---かずま(2005/02/07 21:07:47)


訂正。

> [k]キーを押すと、'k' がバッファに入るので、select の待ちは終了し、プロ
> グラムは次に進んで、"け\nこ\n" を出力します。

"け\nい\n" を出力します。



> [Enter] を入力すると、'\r' のコードが入力されるのですが、端末ドライバは
> それを '\n' に変換し、自分のバッファに入れます。行バッファリングをして
> いるので、"k\n" の 2文字を stdin に送ります。

"k\n" を stdin に返す前に、'\n' のエコーバックがあります。



> [k]キーを押すと、'k' がバッファに入るので、select の待ちは終了し、プロ
> グラムは次に進んで、"け\nこ\n" を出力します。
> 次の getchar に対して、stdin は、自分のバッファに残っている '\n' を
> 返します。
> プログラムにより、"け\nい\nc=\n\nさ\nあ\nこwidth=1\n" が表示されます。

"け\nい\n" を出力してから、'\n' を getchar するので、そのあとは、
"c=\n\nさ\nあ\nこwidth=1\n" が表示されます。


この投稿にコメントする

削除パスワード

No.3426

Re:select関数のバッファリングについての疑問
投稿者---chikato(2005/02/13 02:48:49)


ご回答大変有難うございます。

> キーボードドライバの論理ドライバである端末ドライバは独自の行バッ
> ファを持っています。このバッファが空なので、select は待ちます。

> 次に、getchar で、stdin に入力を要求します。
> stdin のバッファは空なので、入力デバイスに read の要求を出します。
> ところが、端末ドライバが行バッファリングをしているので、バッファ内の
> 'k' は返ってきません。read は待ちに入ります。
> これでご理解いただけますか?
端末ドライバなるものがあるとは知りませんでした。
えーと、何度も読返しまして自分なりの言葉で解釈しようと試みました。

端末ドライバ
…[Enter]キーが押されるまでバッファリングし続ける。押キー毎に標準出力にエコーバック処理を行う。[Enter]キーが押されるとその間バッファリングされて来た文字(\n文字も含まれる)が一斉にstdinバッファと呼ばれるメモリ領域に排出される(端末ドライバ内は空バッファとなる)。

(0番ビットに1が立っている)select関数
…コールされた時点で端末ドライバが空バッファの時だけブロック処理(入力待ち状態)を行う。

getchar関数
…コールされた時点でstdinバッファに文字があれば順次、1文字ずつ読み込むでいく。

以上の事を踏まえて、処理を追っていくと

$ ./test ←[Enter]キー


は"\n"のエコー処理により、改行されているのですね。


$ ./test ←[Enter]キー


こwidth=1 ←ここで止まるので[k]キーを押してみる


k ←ここで止まる





$ ./test ←[Enter]キー


こwidth=1k ←ここで止まるので[k]キーを押してみる

い ←ここで止まる


とならないのはエコーバックの遅延が起きているのですね。

色々と試してみまして、
\n→k→j→k→\n→\n
の順に押キーしていくと、次のようになっていきました(「_」はカーソルを表す)。

$ ./test0_





$ ./test0


こwidth=1
_





$ ./test0


こwidth=1


k_





$ ./test0


こwidth=1


kj_





$ ./test0


こwidth=1


kjk_





$ ./test0


こwidth=1


kjk
c=k


こwidth=1
_





$ ./test0


こwidth=1


kjk
c=k


こwidth=1


c=j


こwidth=1


c=k


こwidth=1


c=



こwidth=1



c=



こwidth=1
_


という風に一気に処理が進んでしまいました。途中までは理解できるのですが最後の一気にズラっと処理が進むのがイマイチ解りません。
[Enter]を押しているので端末ドライバは空バッファとなっていて、
3回目の「こwidth=1」で一旦止まるだろうと予想していたのですが止まりませんでした。
これはどうしてなんでしょうか???
select関数についての解釈が間違っていますでしょうか?


この投稿にコメントする

削除パスワード

No.3427

Re:select関数のバッファリングについての疑問
投稿者---かずま(2005/02/13 16:05:12)


> 端末ドライバ
> …[Enter]キーが押されるまでバッファリングし続ける。押キー毎に標準出力
> にエコーバック処理を行う。[Enter]キーが押されるとその間バッファリング
> されて来た文字(\n文字も含まれる)が一斉にstdinバッファと呼ばれるメモリ
> 領域に排出される(端末ドライバ内は空バッファとなる)。

端末ドライバは、いろいろな設定が可能ですが、通常は、キー入力を常にバッ
ファリングします。バッファの内容をアプリケーションプログラムに渡すのは、
アプリケーションプログラムから read 要求がある場合です。read 要求がな
い場合は、'\n' をバッファに入れても、そこまでの文字をアプリケーション
プログラムに渡すことはありません。

端末ドライバのエコーバックは、標準出力ではなく、端末ドライバの出力側
(画面出力)に対して直接行われます。


> getchar関数
> …コールされた時点でstdinバッファに文字があれば順次、1文字ずつ読み込むでいく。

getchar関数は、呼び出されたとき、
stdinバッファに文字があれば、バッファの先頭の 1文字を返します。
stdinバッファに文字がなければ、0番のデバイス(今は端末ドライバ)に、
stdinバッファのサイズ分の文字の read 要求を出します。



さて、k j k \n \n というキー入力ですが、
最初の k で、端末ドライバのバッファに文字が入るので、select のブロック
が終了します。
次の getchar は、stdinバッファが空なので、端末ドライバに read 要求を
出します。
j k \n のキー入力で、端末ドライバは、k j k \n を stdinバッファに返します。
最初の getchar は、'k' を呼び出し元のプログラムに返します。この時点で、
stdinバッファには j k \n が入っており、端末ドライバのバッファは空です。

次の select はブロックします。

次の \n の入力で、端末ドライバのバッファに文字が入ったので、
次の select はブロックを終了します。
この時点で、read 要求はありませんから、'\n' は端末ドライバのバッファに
残ったままです。

次の getchar は、'j' を返します。
次の select は、ブロックしません。
次の getchar は、'k' を返します。
次の select は、ブロックしません。
次の getchar は、'\n' を返します。
次の select は、ブロックしません。
次の getchar は、stdin バッファが空なので、read 要求を出します。
端末ドライバは、read 要求に対して、'\n' を返します。
read 要求を出した getchar は、'\n' を呼び出し元のプログラムに返します。
この時点で、stdinバッファも端末ドライババッファの空になりました。

次の select は、ブロックします。

この説明で納得できますか?


この投稿にコメントする

削除パスワード

No.3428

Re:select関数のバッファリングについての疑問
投稿者---chikato(2005/02/14 02:32:33)


ご回答大変有難うございます。恐縮してます。m(_ _)m

> 端末ドライバは、いろいろな設定が可能ですが、通常は、キー入力を常にバッ
> ファリングします。バッファの内容をアプリケーションプログラムに渡すのは、
> アプリケーションプログラムから read 要求がある場合です。read 要求がな
> い場合は、'\n' をバッファに入れても、そこまでの文字をアプリケーション
> プログラムに渡すことはありません。
Windowsボタンをクリックすると画面左下にスタートメニューが現われるのは常にWindowsがread要求を出しているからなのですね。

> 端末ドライバのエコーバックは、標準出力ではなく、端末ドライバの出力側
> (画面出力)に対して直接行われます。
イマイチ、標準出力の意味がわかっていなかったようです。
つまり、
標準出力とはstdoutメモリ領域の格納されている文字が関数処理によってモニタに表示される事で、エコーバックは押キーされるときーボードから信号がCPUに伝わり(stdoutメモリ領域を経由せずに)直接モニタに表示される
という違いがあるのですね。

>> getchar関数
>> …コールされた時点でstdinバッファに文字があれば順次、1文字ずつ読み込むでい
> getchar関数は、呼び出されたとき、
> stdinバッファに文字があれば、バッファの先頭の 1文字を返します。
> stdinバッファに文字がなければ、0番のデバイス(今は端末ドライバ)に、
> stdinバッファのサイズ分の文字の read 要求を出します。
端末ドライバ自体が空バッファになっていればブロック(入力待ち状態)するのですね。
ところで、stdinバッファのサイズ分とは何バイトなんでしょうか?
(調べる方法がありますでしょうか?)

> さて、k j k \n \n というキー入力ですが、
> 最初の k で、端末ドライバのバッファに文字が入るので、select のブロック
> が終了します。
遅延で"k"がエコーバックされてますね。

> 次の getchar は、stdinバッファが空なので、
kjkまではそうですね。端末ドライバはフラッシュされませんものね。

> 端末ドライバに read 要求を
> 出します。
このread要求中がブロック状態な訳ですね。

> j k \n のキー入力で、端末ドライバは、k j k \n を stdinバッファに返します。
> 最初の getchar は、'k' を呼び出し元のプログラムに返します。この時点で、
> stdinバッファには j k \n が入っており、端末ドライバのバッファは空です。
> 次の select はブロックします。
ここらへんは納得です。

> 次の \n の入力で、端末ドライバのバッファに文字が入ったので、
> 次の select はブロックを終了します。
これも納得です。

> この時点で、read 要求はありませんから、'\n' は端末ドライバのバッファに
> 残ったままです。
ここが問題の箇所ですね。
'\n'が押キーされたので、直ちに端末バッファフラッシュされて、'\n'がstdinバッファに排出されるのかと勘違いしてました。
端末ドライバは'\n'が押キーされてもgetchar関数からのread要求がなければフラッシュしないのですね。
それで立て続けに




c=j


こwidth=1


c=k


こwidth=1


c=



こwidth=1 ←ここでもselect関数は通過。

   ←ここは'\n'のエコーバック
c= ←ここでread要求か出されて、端末ドライバに残っていた'\n'がstdinへの'\n'のフラッシュ(排出)。
←ここは変数cに格納された'\n'が標準出力された為。


こwidth=1
_


> 次の getchar は、'j' を返します。
> 次の select は、ブロックしません。
> 次の getchar は、'k' を返します。
> 次の select は、ブロックしません。
> 次の getchar は、'\n' を返します。
> 次の select は、ブロックしません。
> 次の getchar は、stdin バッファが空なので、read 要求を出します。
> 端末ドライバは、read 要求に対して、'\n' を返します。
> read 要求を出した getchar は、'\n' を呼び出し元のプログラムに返します。
> この時点で、stdinバッファも端末ドライババッファの空になりました。
> 次の select は、ブロックします。
> この説明で納得できますか?
はい、納得です。

「端末ドライバ
…[Enter]キーが押されるまでバッファリングし続ける。押キー毎に標準出力にエコーバック処理を行う。[Enter]キーが押されるとその間バッファリングされて来た文字(\n文字も含まれる)が一斉にstdinバッファと呼ばれるメモリ領域に排出される(端末ドライバ内は空バッファとなる)。」



「端末ドライバ
…押キー毎に標準出力にエコーバック処理を行う。read要求が出されるまでその間バッファリングし続け、read要求をキャッチするか端末ドライバが限界バッファに達すると一斉にstdinバッファと呼ばれるメモリ領域に排出される(端末ドライバ内は空バッファとなる)。」

という風に書き直せばいいですかね。
端末ドライバは[Enter]キーが押されたからフラッシュする訳ではないという事だったのですね。


この投稿にコメントする

削除パスワード

No.3429

Re:select関数のバッファリングについての疑問
投稿者---あかま(2005/02/14 03:15:08)


>ところで、stdinバッファのサイズ分とは何バイトなんでしょうか?
マクロのBUFSIZにデフォルトのバッファサイズが定義されています。
#include <stdin.h>
int main(){
    printf("%d",BUFSIZ);
}

自分でバッファをセットできるsetbuf()とsetvbuf()という関数もあるようです。



この投稿にコメントする

削除パスワード

No.3434

Re:select関数のバッファリングについての疑問
投稿者---chikato(2005/02/15 00:06:06)


ご紹介有難うございます。


> >ところで、stdinバッファのサイズ分とは何バイトなんでしょうか?
> マクロのBUFSIZにデフォルトのバッファサイズが定義されています。
> #include <stdin.h>
> int main(){
> printf("%d",BUFSIZ);
> }
これで1024と表示されました。
私の環境でのデフォルドバッファ限界値は1024バイトという事なのですね。

> 自分でバッファをセットできるsetbuf()とsetvbuf()という関数もあるようです。
載ってました。
参考になります。


この投稿にコメントする

削除パスワード

No.3435

Re:select関数のバッファリングについての疑問
投稿者---かずま(2005/02/15 02:54:18)


> Windowsボタンをクリックすると画面左下にスタートメニューが現われるの
> は常にWindowsがread要求を出しているからなのですね。

ちがいます。Windows は全く異なる仕組みで動いています。


> 標準出力とはstdoutメモリ領域の格納されている文字が関数処理によって
> モニタに表示される事で、

ちがいます。標準出力とは、アプリケーションプログラムが動くとき、既に
オープンされている出力のことです。stdout を使うとは限りません。
write(1, "hoge", 4); で標準出力に直接 4バイトの "hoge" が出力されます。
fputs("hoge", stdout); だと、stdout のバッファを経由して、ファイルデス
クリプタ 1 の標準出力へ 4バイトの "hoge" が出力されます。いや、stdout
が行バッファリングを行っているので、この 4バイトはバッファに入るだけと
いったほうがよいかもしれません。


> エコーバックは押キーされるときーボードから信号がCPUに伝わり(stdoutメ
> モリ領域を経由せずに)直接モニタに表示されるという違いがあるのですね。

エコーバックは、キーボードドライバから端末ドライバの入力側にに渡された
キー入力文字が端末ドライバの出力側に置かれ、画面出力ドライバに送られ
ることと考えたほうがよいでしょう。実際は、ウインドウシステムとの関係で
もっと複雑になっています。


> 「端末ドライバ
> …押キー毎に標準出力にエコーバック処理を行う。read要求が出されるまで
> その間バッファリングし続け、read要求をキャッチするか端末ドライバが限
> 界バッファに達すると一斉にstdinバッファと呼ばれるメモリ領域に排出さ
> れる(端末ドライバ内は空バッファとなる)。」

エコーバックは標準出力に行われるわけではありません。プログラムを起動す
るとき、シェルのリダイレクト機能を使って、標準出力をファイルに切り換え
たとしても、キーボード入力のエコーバックは画面に対して行われます。

端末ドライバのバッファが一杯になった場合、新たなキー入力文字がバッファ
に入らず、捨てられるだけです。初期の UNIX のドライバには、バッファ全体
をクリアしてしまうものもありました。端末ドライバは stdinバッファの存在
を知りません。read システムコールの引数になっているバッファに文字を送る
だけです。したがって、read要求が来ないとどこへも文字を送りません。


この投稿にコメントする

削除パスワード

No.3436

Re:select関数のバッファリングについての疑問
投稿者---chikato(2005/02/15 17:15:47)


ありがとうございます。大変参考になります。

>> Windowsボタンをクリックすると画面左下にスタートメニューが現われるの
>> は常にWindowsがread要求を出しているからなのですね。
> ちがいます。Windows は全く異なる仕組みで動いています。
そうでしたか。甘かったです。

>> 標準出力とはstdoutメモリ領域の格納されている文字が関数処理によって
>> モニタに表示される事で、
> ちがいます。標準出力とは、アプリケーションプログラムが動くとき、既に
> オープンされている出力のことです。stdout を使うとは限りません。
stoutバッファ領域を経由するとは限らないという事ですね。憶えときます。

> write(1, "hoge", 4); で標準出力に直接 4バイトの "hoge" が出力されます。
えーと、標準出力とはモニタ出力(環境によっては1番ディスクリプタをモニタ出力にしてないものもあるかもしれませんが)の事なのですかね。で、
標準出力する際に
直接的にモニタ出力させる処理(エコーバック等)もあれば、
stdin・stdoutを経由してモニタ出力させる処理(getchar関数、printf関数等)もあるという事ですね。

> fputs("hoge", stdout); だと、stdout のバッファを経由して、ファイルデス
> クリプタ 1 の標準出力へ 4バイトの "hoge" が出力されます。いや、stdout
> が行バッファリングを行っているので、この 4バイトはバッファに入るだけと
> いったほうがよいかもしれません。
つまり、
fputs("hoge", stdout);
はstdoutバッファリングされるだけでフラッシュはされない。
fputs("hoge\n", stdout);
ならフラッシュされ、モニタ出力まで処理が行われるという事ですね。

>> エコーバックは押キーされるときーボードから信号がCPUに伝わり(stdoutメ
>> モリ領域を経由せずに)直接モニタに表示されるという違いがあるのですね。
> エコーバックは、キーボードドライバから端末ドライバの入力側にに渡された
> キー入力文字が端末ドライバの出力側に置かれ、画面出力ドライバに送られ
> ることと考えたほうがよいでしょう。実際は、ウインドウシステムとの関係で
> もっと複雑になっています。
エコーバックはあまり(stdoutバッファ等を)経由せずに出力されるという訳ですね。
(ショートカット?)

>> 「端末ドライバ
>> …押キー毎に標準出力にエコーバック処理を行う。read要求が出されるまで
>> その間バッファリングし続け、read要求をキャッチするか端末ドライバが限
>> 界バッファに達すると一斉にstdinバッファと呼ばれるメモリ領域に排出さ
>> れる(端末ドライバ内は空バッファとなる)。」
> エコーバックは標準出力に行われるわけではありません。
ん? リダイレクトされうる事もあるという事ですか?

> プログラムを起動するとき、シェルのリダイレクト機能を使って、標準出力をファイルに切り換え
> たとしても、キーボード入力のエコーバックは画面に対して行われます。
ん? 画面表示を標準出力というのではないんですかね。

「エコーバックは標準出力されるとは限らないがリダイレクトとかしようがしまいが常に画面に表示される。」
という事は標準出力とは画面出力とは意味が違うという事ですかね。

"標準出力"の定義がイマイチわかっていないようです。

エコーバックは
キーボードドライバ→端末ドライバ入力側→端末バッファ→端末ドライバ出力側→ビデオカードドライバ→モニタ出力

といった具合の動作を行い、

fput("hoge\n",stdout)関数の出力は
stdoutバッファ→アプリケーション→ビデオカードドライバ→モニタ出力

という違いですかね(自信無しですが)。
ビデオカードドライバへの出力が標準出力と思っています(今現在)。

ところで、この端末ドライバってカーネルみたいな存在ですかね。

> 端末ドライバのバッファが一杯になった場合、新たなキー入力文字がバッファ
> に入らず、捨てられるだけです。初期の UNIX のドライバには、バッファ全体
> をクリアしてしまうものもありました。端末ドライバは stdinバッファの存在
> を知りません。read システムコールの引数になっているバッファに文字を送る
> だけです。したがって、read要求が来ないとどこへも文字を送りません。
つまり、バッファとはメモリ領域の事を表し、
端末バッファとstdin・stdoutバッファとは異なるメモリ領域なのですかね。


この投稿にコメントする

削除パスワード

No.3437

Re:select関数のバッファリングについての疑問
投稿者---かずま(2005/02/16 02:45:48)


> "標準出力"の定義がイマイチわかっていないようです。
#include <stdio.h>

int main(void)
{
    FILE *fp = fopen("con", "w");
    fprintf(stdout, "fileno(stdout) = %d\n", fileno(stdout));
    fprintf(fp, "fileno(fp) = %d\n", fileno(fp));
    fclose(fp);
    return 0;
}

このプログラムを a.c とします。
cygwin の gcc でコンパイルすると a.exe ができます。

コマンドラインで、a または ./a を入力して実行すると画面に 2行表示され
ます。a >file と入力して実行すると、画面には 1行しか表示されません。
標準出力ストリームである stdout に出力されたものは file に入ります。

標準出力とは、プログラム実行開始直前に出力先が決まるものです。

それは画面出力とは限りません。ファイルだったり、プリンタだったり、パイ
プを使って他のプログラムの入力となったり、ネットワークを中継して他の
マシン上のプログラムの入力になることもあります。



この投稿にコメントする

削除パスワード

No.3450

Re:select関数のバッファリングについての疑問
投稿者---chikato(2005/02/17 17:52:43)


大変、お世話様です。m(_ _)m


> fprintf(stdout, "fileno(stdout) = %d\n", fileno(stdout));
fileno関数でディスクリプタが何番かを見れるのですね。

> コマンドラインで、a または ./a を入力して実行すると画面に 2行表示され
> ます。a >file と入力して実行すると、画面には 1行しか表示されません。
> 標準出力ストリームである stdout に出力されたものは file に入ります。
仰るとおりになりました。
$ ./a.exe
fileno(stdout) = 1
fileno(fp) = 3

$ ./a.exe > file
fileno(fp) = 3

$ cat file
fileno(stdout) = 1


> 標準出力とは、プログラム実行開始直前に出力先が決まるものです。
つまり、
$ ./a.exe > file
を実行した時に決まるという事ですね(この場合は出力先がコンソールではなくファイルへ)。

> それは画面出力とは限りません。ファイルだったり、プリンタだったり、パイ
> プを使って他のプログラムの入力となったり、ネットワークを中継して他の
> マシン上のプログラムの入力になることもあります。
"標準"という言葉に惑わされていました。
標準出力とは飽くまでディスクリプタ1番に対する出力の事で、特にリダイレクトしないと画面(コンソール)上に出力されるというだけのことですね。

標準出力とは「ディスクリプタ1番の出力経路先」の事なのですね。
この経路先は通常はコンソールだがリダイレクトで色々な所が経路先となりうるのですね。

因みに標準エラーは2番ディスクリプタでリダイレクトされないという性質を持つのですね。

再度気を取り直して、、、

端末ドライバは
キー入力を端末ドライババッファというメモリ領域に(通常は)常にバッファリングし、入力された内容をビデオカードドライバに(リアルタイムで)出力する。
アプリケーションプログラムからread要求があり且つ'\n'がバッファリングされ(てい)た時に
バッファリングしていた全文字類をstdinバッファと呼ばれるメモリ領域に一斉に排出する。read要求がない場合には幾ら'\n'がキー入力されようがバッファの限界値までえんえんとバッファリングしていく。

getcha関数は
コールされた時点でstdinバッファに文字があれば、そのバッファの先頭の 1文字を順次返す。
stdinバッファに文字がなければ、0番のデバイス(ここでは端末ドライバ)に、stdinバッファのサイズ分の文字の read 要求を出す。端末ドライババッファに'\n'がバッファリングされてれば直ちにstdinバッファサイズ分の文字類を一定にstdinバッファに取込みそのバッファの先頭の 1文字を順次返していく。'\n'が全くバッファリングされてなければ端末ドライババッファが限界値に達するか'\n'がバッファされるまでブロックし続ける。

select関数は端末ドライババッファが空ならブロックし続ける。空でないなら開放される(その際、ビットが立っていた個数を返す)。

ですかね。

> ちがいます。標準出力とは、アプリケーションプログラムが動くとき、既に
> オープンされている出力のことです。stdout を使うとは限りません。
「オープン」とはブロック等されずに常に垂れ流し(出力要求があれば直ちに出力する)出力という事ですかね。

「標準」とは常にオープンされているという意味ですね。
「標準入力」、「標準出力」、「標準エラー」は常にオープンされていて、使用するディスクリプタが夫々、0番、1番、2番という事なのですね。


大分解ってきました。



この投稿にコメントする

削除パスワード

No.3451

Re:select関数のバッファリングについての疑問
投稿者---Ban(2005/02/17 23:59:31)


> 因みに標準エラーは2番ディスクリプタでリダイレクトされないという
> 性質を持つのですね。

環境によりますが、単に標準出力とは個別にリダイレクトできるだけで
「リダイレクトされない」とは限りません。

2> output
&> output

例えばこういう指定ができるシェルもあります。(bash on Linux とか)






この投稿にコメントする

削除パスワード

No.3453

Re:select関数のバッファリングについての疑問
投稿者---かずま(2005/02/18 19:37:05)


> 因みに標準エラーは2番ディスクリプタでリダイレクトされないという
> 性質を持つのですね。

ファイルディスクリプタ 2 の標準エラー出力もリダイレクトできます。
#include <stdio.h>

int main(void)
{
    fprintf(stdout, "stdout\n");
    fprintf(stderr, "stdout\n");
}

$ ./a 2>file2
stdout
$ cat file2
stderr

> 端末ドライバは
> キー入力を端末ドライババッファというメモリ領域に(通常は)常にバッファリ
> ングし、入力された内容をビデオカードドライバに(リアルタイムで)出力する。
> アプリケーションプログラムからread要求があり且つ'\n'がバッファリング
> され(てい)た時に
> バッファリングしていた全文字類をstdinバッファと呼ばれるメモリ領域に
> 一斉に排出する。read要求がない場合には幾ら'\n'がキー入力されようが
> バッファの限界値までえんえんとバッファリングしていく。

端末ドライバは、stdinバッファの存在を知りません。readシステムコールの
引数で指定されたバッファに、最初の '\n' までの文字列を返すだけです。



> getcha関数は
> コールされた時点でstdinバッファに文字があれば、そのバッファの先頭の
> 1文字を順次返す。
> stdinバッファに文字がなければ、0番のデバイス(ここでは端末ドライバ)に、
> stdinバッファのサイズ分の文字の read 要求を出す。端末ドライババッファ
> に'\n'がバッファリングされてれば直ちにstdinバッファサイズ分の文字類を
> 一定にstdinバッファに取込みそのバッファの先頭の 1文字を順次返していく。
> '\n'が全くバッファリングされてなければ端末ドライババッファが限界値に
> 達するか'\n'がバッファされるまでブロックし続ける。

getchar関数(マクロかもしれませんが)は、stdinバッファに文字がなければ、
stdinバッファを引数にした readシステムコールを呼びます。

readシステムコールは、端末ドライバのバッファに 1行分のデータがそろうま
で待って、それを返します。

端末ドライバのバッファに 1行分のデータがそろうまでは、read はブロックし、
getchar もブロックしているように見えます。


>> ちがいます。標準出力とは、アプリケーションプログラムが動くとき、既に
>> オープンされている出力のことです。stdout を使うとは限りません。
>「オープン」とはブロック等されずに常に垂れ流し(出力要求があれば直ちに
> 出力する)出力という事ですかね。

オープンとは、openシステムコールのことです。
fopen は、その内部で open を呼び、Cygwin だったら open の内部で Win32
API の CreateFile を呼び出しているはずです。
getchar -> read -> ReadFile や、fclose -> close -> CloseHandle も同様。

ファイルディスクリプタ 1 をプログラムで open せずに write できるのは、
そのプログラムを起動するプログラム(シェル)が open しているのです。


この投稿にコメントする

削除パスワード

No.3454

Re:select関数のバッファリングについての疑問
投稿者---かずま(2005/02/18 22:07:43)


> readシステムコールは、端末ドライバのバッファに 1行分のデータがそろうま
> で待って、それを返します。
>
> 端末ドライバのバッファに 1行分のデータがそろうまでは、read はブロックし、
> getchar もブロックしているように見えます。

ちょっと補足しますが、'\n' の代わりに コントロールD を入力してやると、
そのコントロールD はバッファに入れられることなく、既にバッファに入って
いるデータが read が指定したバッファに転送されます。

バッファにデータがない場合、read の返却値は 0 です。このとき、getchar
の返却値は EOF です。



この投稿にコメントする

削除パスワード

No.3463

Re:select関数のバッファリングについての疑問
投稿者---chikato(2005/02/20 16:08:38)


皆様、ご回答有難うございます。

> 環境によりますが、単に標準出力とは個別にリダイレクトできるだけで
> 「リダイレクトされない」とは限りません。
> 2> output
> &> output
> 例えばこういう指定ができるシェルもあります。(bash on Linux とか)
有難うございます。参考になります。

> #include <stdio.h>
> int main(void)
> {
> fprintf(stdout, "stdout\n");
> fprintf(stderr, "stdout\n");
> }
> $ ./a 2>file2
> stdout
> $ cat file2
> stderr
「fprintf(stderr, "stdout\n");」

「fprintf(stderr, "stderr\n");」
ですね。仰る通りになりました。
書式が「2>」となるのですね。2番ディスクリプタへリダイレクトという意味ですね。

>> バッファリングしていた全文字類をstdinバッファと呼ばれるメモリ領域に
>> 一斉に排出する。read要求がない場合には幾ら'\n'がキー入力されようが
>> バッファの限界値までえんえんとバッファリングしていく。
> 端末ドライバは、stdinバッファの存在を知りません。readシステムコールの
> 引数で指定されたバッファに、最初の '\n' までの文字列を返すだけです。
「一斉に排出する」という言い方だと端末ドライバが能動的に動作しているように感じますね。他プログラムからのread要求に応えている丈ですよね。

> readシステムコールは、端末ドライバのバッファに 1行分のデータがそろうま
> で待って、それを返します。
「1行分…」つまり、'\n'が少なくとも1つがバッファリングされるまでという意味ですね。

所で、readシステムコールって
ssize_t read(int fd,void *buf,size_t const);
ですよね。端末ドライババッファにread要求を出す場合にはここの第1引数は何と指定するのでしょうか?
(勿論、stdinではないですよね(端末ドライババッファとstdinバッファは全く別物なので))

> 端末ドライバのバッファに 1行分のデータがそろうまでは、read はブロックし、
> getchar もブロックしているように見えます。
つまり、readから返値が何も返ってこないのでgetchar関数は先に進みようがないのですね。

>>> ちがいます。標準出力とは、アプリケーションプログラムが動くとき、既に
>>> オープンされている出力のことです。stdout を使うとは限りません。
>>「オープン」とはブロック等されずに常に垂れ流し(出力要求があれば直ちに
>> 出力する)出力という事ですかね。
> オープンとは、openシステムコールのことです。
載ってました。
int fd=open("filename",char *path,int flags);
ですね。シェルが初めて起動する時に
opne("stdout",stdout_no_path,O_WRONLY);
みたいな関数が実行されて1番ディスクリプタが使用中になる訳ですね。

> fopen は、その内部で open を呼び、Cygwin だったら open の内部で Win32
> API の CreateFile を呼び出しているはずです。
> getchar -> read -> ReadFile や、fclose -> close -> CloseHandle も同様。
つまり、エーと、自分なりの言葉で書くと
"標準"とはアブリケーションプログラムが動く時に既にopneシステムコールが実行されているという事ですね。
もっと言えば、"標準"とは「アプリケーションのプログラム自信がopenシステムコールを呼ぶ必要が無い(シェルが呼んでくれるので)」という意味なのですね。

0,1,2以外のディスクリプタを使う場合には非標準(シェルによって自動的に呼ばれない)なのでwrite、readする時にはopenシステムコールを呼ばねばならないのですね。

> ファイルディスクリプタ 1 をプログラムで open せずに write できるのは、
> そのプログラムを起動するプログラム(シェル)が open しているのです。
opne("stdout",stdout_no_path,O_WRONLEY);
という関数が実行されているのですね。

> ちょっと補足しますが、'\n' の代わりに コントロールD を入力してやると、
> そのコントロールD はバッファに入れられることなく、既にバッファに入って
> いるデータが read が指定したバッファに転送されます。
端末ドライババッファに'a'、'b'が入っている場合には(readはgecharによって呼ばれているので指定されたバッファ)stdinバッファに
'a'、'b'が転送されるという訳ですね。

#include <stdio.h>
int main(void)
{
char c;
while(c=getchar()!=EOF){
printf("c=%c",c);
}
}

を実行してみましたが('\n'→'a'→'b'→Ctrl+D)

$ ./test.exe
ab

$

となりました。転送されるので(エコーバック以外にstdinに転送された分もprintf出力されて)

$ ./test.exe
abc=ac=b
$

となると予想したのですが、、、
勘違いしてますでしょうか?

> バッファにデータがない場合、read の返却値は 0 です。このとき、getchar
> の返却値は EOF です。
端末ドライババッファが空っぽの場合にはCtrl+D入力があるとこのような処理手順でwhileを抜出てたのですね。


この投稿にコメントする

削除パスワード

No.3468

Re:select関数のバッファリングについての疑問
投稿者---かずま(2005/02/20 23:49:09)


> 「fprintf(stderr, "stderr\n");」
> ですね。仰る通りになりました。

これは失礼しました。その通りです。


> 書式が「2>」となるのですね。2番ディスクリプタへリダイレクトという意味ですね。

デフォルトで端末出力となっているファイルディスクリプタ2をファイルに
リダイレクトしているという表現したほうがよいように思います。


> 所で、readシステムコールって
> ssize_t read(int fd,void *buf,size_t const);
> ですよね。端末ドライババッファにread要求を出す場合にはここの第1引数
> は何と指定するのでしょうか?

fd という名前からも分かるようにファイルディスクリプタです。
標準入力の場合、0 です。
それから、第3引数の const というのは count の間違いですね。
const は C のキーワードですから。


> 載ってました。
> int fd=open("filename",char *path,int flags);
> ですね。シェルが初めて起動する時に
> opne("stdout",stdout_no_path,O_WRONLY);
> みたいな関数が実行されて1番ディスクリプタが使用中になる訳ですね。

何に載っていましたか? そんな形式ではないはずです。
Web だと、http://www.linux.or.jp/JM/index.html で、open を検索すると
    int open(const char *pathname, int flags);
    int open(const char *pathname, int flags, mode_t mode);
と出てきます。シェルが標準出力をファイル(pathname)にリダイレクトするときは、
2番目の形式を使い、
    fd = open(pathname, O_WRONLY | O_CREAT | O_TRUNC, 0666);
でオープンしたファイルディスクリプタを、新しく起動(exec)するプログラムのための
プロセスのファイルディスクリプタ 1 に fd を複写(dup など)しているのだと
思います。


> char c;
> while(c=getchar()!=EOF){
> printf("c=%c",c);
> }

プログラムが間違っています。インデントもついていません。
    int c;
    while ((c = getchar()) != EOF)
        printf("[%c]", c);
で試してみてください。


この投稿にコメントする

削除パスワード

No.3506

Re:select関数のバッファリングについての疑問
投稿者---chikato(2005/02/27 08:55:14)


ご回答有難うございます。遅くリまして申し訳有りません。

>> 書式が「2>」となるのですね。2番ディスクリプタへリダイレクトという意味です
> ね。
> デフォルトで端末出力となっているファイルディスクリプタ2をファイルに
> リダイレクトしているという表現したほうがよいように思います。
「>」はデフォルトでは1番ファイルディスクリプタに出力設定されている処理を途中で
拾って(これがリダイレクトの意味だと思いますが)、(HDDかメディア上の)ファイル
に出力。
「2>」はデフォルトで1番ファイルディスクリプタに出力設定されている処理を途
中で拾って、2番ファイルディスクリプタ(2番も結局は端末出力だが)に出力。
という事ですね。

/*test1.c*/
#include<stdio.h)
int main(void){
printf("HelloWorld");
}

$ ./test1.exe > hoge.txt

とするのと

/*test2.c*/
#include<stdio.h)
int main(void){
FILE *fp=fopen("hoge.txt","w");
fprintf(fp,"HelloWorld");
}

$ ./test2.exe

とするのでは処理が異なりますよね(前者は0,1,2以外のファイルディスクリプタを静的に使用している点。後者は動的に使用している点)

>> 所で、readシステムコールって
>> ssize_t read(int fd,void *buf,size_t const);
>> ですよね。端末ドライババッファにread要求を出す場合にはここの第1引数
>> は何と指定するのでしょうか?
> fd という名前からも分かるようにファイルディスクリプタです。
> 標準入力の場合、0 です。
第1引数はファイルディスクリプタですよね。0番はstdinバッファであって端末ドライババッファではないですよね。
すると、getchar関数によって下される端末ドライバ宛のreadシステムコール(read関
数)もread(0,取込んだ文字(列)・定数をいれる変数のアドレス,取込みたいサイズ);
となるとはどういう事ですか??

gethchar関数は端末ドライババッファにread要求を出すんですよね(stdinではないで
すよね)。
なのに0番ディスクリプタを指定するんですか???
(stdinバッファと端末ドライババッファは別物だったのでは!?)


今迄、端末ドライババッファとstdinバッファとをごっちゃしてた私に対し、
これらは別物だと再三ご指摘戴いてますよね。

> それから、第3引数の const というのは count の間違いですね。
スイマセン。失礼致しました。

> const は C のキーワードですから。
そうでした。

>> 載ってました。
>> int fd=open("filename",char *path,int flags);
>> ですね。シェルが初めて起動する時に
>> opne("stdout",stdout_no_path,O_WRONLY);
>> みたいな関数が実行されて1番ディスクリプタが使用中になる訳ですね。
> 何に載っていましたか? そんな形式ではないはずです。
スイマセン。見まちがってました。大変失礼致しました。m(_ _)m

> Web だと、http://www.linux.or.jp/JM/index.html で、open を検索すると
> int open(const char *pathname, int flags);
> int open(const char *pathname, int flags, mode_t mode);
> と出てきます。
仰るとおりになってました。

> シェルが標準出力をファイル(pathname)にリダイレクトするときは、
> 2番目の形式を使い、
> fd = open(pathname, O_WRONLY | O_CREAT | O_TRUNC, 0666);
> でオープンしたファイルディスクリプタを、
O_WRONLY…書込みとして開く。
O_CREAT…pathnameというファイルが存在しない場合には(飽くまでパーミッション許可があれば)作成して開く。
O_TRUNC…pathnameというファイルが存在する場合にはファイルサイズを(飽くまでパーミッション許可があれば)0にして開
く(飽くまでパーミッション許可があれば)。
0666…パーミッション(-rw-rw-rw)ですね。

これは、fd = creat(pathname, 0666);と等価なんですね。
(pathnameというファイルが未存ならどこかにそのファイルが(飽くまでパーミッション許可があれば)0666で生成され、カレ
ントディレクトリにリンクが張られる。既存なら、引数0666は無視され実行ユーザに
対し、(その実行ユーザにパーミッション的許可があれば)ファイルサイズを0にして
書込用として開かれる。既存でその実行ユーザにパーミッション的許可が無ければエ
ラー値「-1」を返す。成功した場合にはファイルディスクリプタを返す)

ですね。


この投稿にコメントする

削除パスワード

No.3508

Re:select関数のバッファリングについての疑問
投稿者---整理を推奨(2005/02/27 11:32:38)


******************************************************
本でも買って標準入出力と低水準入出力などを体系的に
整理しなおすことをお勧めします。
******************************************************

> 「>」はデフォルトでは1番ファイルディスクリプタに出力
> 設定されている処理を途中で拾って(これがリダイレクトの意味だと
> 思いますが)、(HDDかメディア上の)ファイルに出力。
> 「2>」はデフォルトで1番ファイルディスクリプタに出力
> 設定されている処理を途中で拾って、2番ファイルディスクリプタ
> (2番も結局は端末出力だが)に出力。という事ですね。

リダイレクトしても 1番ディスクリプタは 1番ディスクリプタのまま、
2番ディスクリプタも 2番ディスクリプタのままです。
そのディスクリプタがオープンする実際の対象自体が端末ドライバから
ファイルに変わるのであって、リダイレクトされれば端末ドライバは
そもそも開かれませんし、途中で拾ったりもしません。
(main 関数を呼び出す前の処理を見ると、open しているさまが分かります)

混同されているようですが、ディスクリプタ=識別子はプログラムが
便宜上つける名前であって、対象(端末ドライバやファイルなど)を直接
示すものではありません。(「番号札」のようなものです>識別子)

排他制御がない限り、同一の対象を複数回オープンすることも可能。
ファイルディスクリプタの番号はプロセス内で一意なので、同一
プロセスから開けば番号は変わるが、対象の実体は同一。
別プロセスからのオープンであれば同一番号となる(可能性も高い)。
※標準のディスクリプタ以外は番号の保証がない。

> とするのでは処理が異なりますよね(前者は0,1,2以外のファイル
> ディスクリプタを静的に使用している点。後者は動的に使用している点)

前者の方は 0 番のディスクリプタを使用しています。
その対象を動的に変更しているだけで、あくまで 0番です。
後者は、動的にそれ以外の番号のディスクリプタを使用しています。

> 0番はstdinバッファであって端末ドライババッファではないですよね。

いいえ。ファイルディスクリプタの 0番は(リダイレクトがない限り)
端末ドライバのバッファです(リダイレクトがあればその変更先)。
stdin の制御をするには、(本来) FILE構造体を使います。

stdin の宣言は、FILE* 型でしょう? 0番のディスクリプタを使うという
ことは、stdin の内部情報を (stdin に無断で)直接使用するということ
です。

 --------------------------------
多少説明用の嘘がありますが、

ハードからの入力はその制御用ドライバのバッファにためられる。
それが端末からの入力として、端末用のドライバのバッファにためられる。
これは、例えば Unix であれば、tty とかの単位に存在する。

個別のプロセスは、低水準入出力(≒システムコール)を通じてこの
ドライバに要求を出してデータを貰う。低水準入出力を直接呼べば、
標準入出力のバッファを通らずにドライバのバッファを直接制御できる。
識別はファイルディスクリプタで行う。

標準入出力関数は、ライブラリの中で独自に低水準入出力を使い、
プロセスの中に更にバッファを構築する。標準関数を使うとプロセス内の
このバッファが制御でき、ドライバのバッファは必要に応じて標準関数が
間接的に低水準入出力を使い制御する。識別は FILE 構造体(または
C++ のストリーム郡)で行う(これらはいずれも内部に独自のファイル
ディスクリプタを持つ)。
# C++ のストリームは、stdin/stdout とはまた別のバッファを
# 独自に持つ(std::cin, std::cout)、ここではひとまずおいておく。

C/C++ 言語では、起動時に標準的な FILE 構造体がオープンされた状態で
main が呼ばれる。stdin, stdout, stderr 等である。
FILE 構造体が内部にディスクリプタを持つことは前述の通りであるが、
その番号は通常それぞれ 0, 1, 2 となる。
# 一応 fileno を使って FILE 構造体から取得することを推奨。

但し、これらはあくまで標準的な入出力対象を示すものであり、
デフォルトではいずれも端末ドライバが対象となるが、リダイレクトに
よりそのオープン先を外部から変更することが可能である。

リダイレクトの有無を問わず端末を直接制御したい場合、tty などの
端末を直接扱う必要があるが、この方法は完全に環境依存であり、
C/C++ 言語標準の範囲からは逸脱する。
# 実は、ディスクリプタの制御自体もシステムコールへの環境依存が
# ありますが、ドライバ制御と比較すれば標準的です。


この投稿にコメントする

削除パスワード

No.3509

Re:select関数のバッファリングについての疑問
投稿者---整理を推奨(2005/02/27 11:39:33)


>前者の方は 0 番のディスクリプタを使用しています。
>その対象を動的に変更しているだけで、あくまで 0番です。
>後者は、動的にそれ以外の番号のディスクリプタを使用しています。

申し訳ない。上記0番は1番の間違いです。


この投稿にコメントする

削除パスワード

No.3543

Re:select関数のバッファリングについての疑問
投稿者---chikato(2005/03/08 16:55:17)


ご回答有難うございます。
遅くなりまして申し訳有りません。

> リダイレクトしても 1番ディスクリプタは 1番ディスクリプタのまま、
> 2番ディスクリプタも 2番ディスクリプタのままです。
> そのディスクリプタがオープンする実際の対象自体が端末ドライバから
> ファイルに変わるのであって、リダイレクトされれば端末ドライバは
> そもそも開かれませんし、途中で拾ったりもしません。
> (main 関数を呼び出す前の処理を見ると、open しているさまが分かります)
fprintf(stderr,"stderr\n");
でのリダイレクト「2>」はfprintf関数がstderr(2番ファイルディスクプタに"stderr\n"を出力しようとする。
↓
「2> file」により2番ファイルディスクリプタがHDDのfileを引数にしてopenシステ
ムコールする(※リダイレクトされない場合にはopenシステムコールは呼ばれず、デ
フォルトのモニターに出力される)。
↓
fileに出力される。

という具合ですね。

> 混同されているようですが、ディスクリプタ=識別子はプログラムが
> 便宜上つける名前であって、対象(端末ドライバやファイルなど)を直接
> 示すものではありません。(「番号札」のようなものです>識別子)
分かります。
出力の終点(モニタやHDD上のファイル)はハードウェアですものね。
ファイルディスクリプタはソフトウェア(プログラム)であり、別物ですね。
厭くまで、「>2 file」とした時にはfileを引数にしてopenシステムコールが呼ば
れ、
何もリダイレクトしない場合にはその環境々々により、デフォルトが決められている
のですね(例えば私の環境(cygwin)ではデフォルトではモニタに指定されいる)。

> 排他制御がない限り、同一の対象を複数回オープンすることも可能。
> ファイルディスクリプタの番号はプロセス内で一意なので、同一
> プロセスから開けば番号は変わるが、対象の実体は同一。
$ cat test6.c
#include<stdio.h>
int main(void){
 FILE *fp1,*fp2;
 fp1=fopen("file.txt","w");
 fp2=fopen("file.txt","a");
 printf("fp1->fd=%d\n",fileno(fp1));
 printf("fp2->fd=%d\n",fileno(fp2));
 write(fileno(fp1),"first\n",6);
 write(fileno(fp2),"second\n",7);
 fclose(fp1);
 return 0;
}

$ gcc -o test6 test6.c

$ test6est6.ctxtest6.c
fp1->fd=3
fp2->fd=4

$ gcc -o test6 test6.c

$ test6
fp1->fd=3
fp2->fd=4

$ cat ./file.txt
first
second

となり、ファイルディスクリプタは異なっていますが対象物は同じという事が確認できました。

>> とするのでは処理が異なりますよね(前者は0,1,2以外のファイル
>> ディスクリプタを静的に使用している点。後者は動的に使用している点)
> 前者の方は 1番のディスクリプタを使用しています。
> その対象を動的に変更しているだけで、あくまで 1番です。
> 後者は、動的にそれ以外の番号のディスクリプタを使用しています。
そうでしたね。
1番ディスクリプタがhoge.txtを引数としてopenシステムコールするんでしたね。
"動的"…プログラム実行中のみに確立される性質。
でした。全然"静的"なんかじゃありませんね。

>> 0番はstdinバッファであって端末ドライババッファではないですよね。
> いいえ。ファイルディスクリプタの 0番は(リダイレクトがない限り)
> 端末ドライバのバッファです(リダイレクトがあればその変更先)。
> stdin の制御をするには、(本来) FILE構造体を使います。
> stdin の宣言は、FILE* 型でしょう?
そうでした。勘違いしてました。

> 0番のディスクリプタを使うという
> ことは、stdin の内部情報を (stdin に無断で)直接使用するということ
> です。
納得です。
0番ファイルディスクリプタは端末ドライババッファでデフォルトでキーボードが始点ですね。
stdinはFILE*型(FILE構造体へのポインタ)であって、そのメンバstdin->fdの評価値がデフォルトでは0番ファイルディスクリプタをなのですね。

> ハードからの入力はその制御用ドライバのバッファにためられる。
> それが端末からの入力として、端末用のドライバのバッファにためられる。
このようにデータが移動されるのですね。

> これは、例えば Unix であれば、tty とかの単位に存在する。
/dev/ttyはHDD上のファイルではなく、メモリ領域だったのですね。

> 個別のプロセスは、低水準入出力(≒システムコール)を通じてこの
> ドライバに要求を出してデータを貰う。低水準入出力を直接呼べば、
> 標準入出力のバッファを通らずにドライバのバッファを直接制御できる。
> 識別はファイルディスクリプタで行う。
これは重要ですね。。。
例として、getchar関数経由ではなく直接、open、read、writeシステムコールを直接呼べばstdin、stdoutバッファを使用しないで、端末ドライババッファを直接制御できる訳ですね。

> 標準入出力関数は、ライブラリの中で独自に低水準入出力を使い、
> プロセスの中に更にバッファを構築する。
「更にバッファ」とは例えばstdin、stdoutが持つバッファの事ですね。

> 標準関数を使うとプロセス内の
> このバッファが制御でき、ドライバのバッファは必要に応じて標準関数が
> 間接的に低水準入出力を使い制御する。識別は FILE 構造体(または
> C++ のストリーム郡)で行う(これらはいずれも内部に独自のファイル
> ディスクリプタを持つ)。
これは凄く分かりやすいです。
stdinなら独自のファイルディスクリプタとはstdin->fdの事ですね。

> C/C++ 言語では、起動時に標準的な FILE 構造体がオープンされた状態で
> main が呼ばれる。stdin, stdout, stderr 等である。
> FILE 構造体が内部にディスクリプタを持つことは前述の通りであるが、
> その番号は通常それぞれ 0, 1, 2 となる。
> # 一応 fileno を使って FILE 構造体から取得することを推奨。
この関数は勉強になります。
シェルは起動時にopenシステムコールで0、1、2番ディスクリプタを生成(オープン)するように、main関数はコールされた時にstdin、stdout、sterrというファイルポインタ(FILE構造体へのポインタ)を生成(オープン)するのですね。

> 但し、これらはあくまで標準的な入出力対象を示すものであり、
> デフォルトではいずれも端末ドライバが対象となるが、リダイレクトに
> よりそのオープン先を外部から変更することが可能である。
納得です。

> リダイレクトの有無を問わず端末を直接制御したい場合、tty などの
> 端末を直接扱う必要があるが、この方法は完全に環境依存であり、
> C/C++ 言語標準の範囲からは逸脱する。
これはアセンブリレベルなんですね。

> # 実は、ディスクリプタの制御自体もシステムコールへの環境依存が
> # ありますが、ドライバ制御と比較すれば標準的です。
納得です。了解致しました。



この投稿にコメントする

削除パスワード

No.3568

Re:select関数のバッファリングについての疑問
投稿者---chikato(2005/03/18 17:15:39)


ご回答有難うございます。
dup関数について返事が出来てませんでした。

>     int open(const char *pathname, int flags);
>     int open(const char *pathname, int flags, mode_t mode);
> と出てきます。シェルが標準出力をファイル(pathname)にリダイレクトするときは、
> 2番目の形式を使い、
> 
>     fd = open(pathname, O_WRONLY | O_CREAT | O_TRUNC, 0666);
> でオープンしたファイルディスクリプタを、新しく起動(exec)するプログラムのため
> の
> プロセスのファイルディスクリプタ 1 に fd を複写(dup など)しているのだと
> 思います。
えーと、
#include <stdio.h>
int main(void)
{
    fprintf(stdout, "stdout\n");
    fprintf(stderr, "sterr\n");
}
$ ./a 2>file2
stdout
$ cat file2
stderr

では

fd=open(file2, O_WRONLY | O_CREAT | O_TRUNC,0444);
tmp_fd=dup(2); 
//↑「tmp_fd⇔端末ドライババッファ」という経路の増設
close(2);
dup(fd);
//↑「2>file2」の為に「2番⇔file2」という経路の増設。リダイレクト開始
close(2);
dup(tmp_fd);
//↑元の状態に戻すべく
//↑「2番⇔端末ドライババッファ」という経路の復帰
close(tmp_fd);
//↑「tmp_fd⇔端末ドライババッファ」という経路の破棄

というような手順が踏まれて、
リダイレクトが行われているのですね。



この投稿にコメントする

削除パスワード

No.3569

Re:select関数のバッファリングについての疑問
投稿者---かずま(2005/03/18 20:01:40)


$ cat a.c
#include <stdio.h>

int main(void)
{
    fprintf(stdout, "stdout\n");
    fprintf(stderr, "stderr\n");
    return 0;
}
$ cat b.c
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

void exec_a(const char *cmd, int fd, const char *file)
{
    int pid = fork();
    if (pid == 0) {
        int fd2 = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
        if (fd2 < 0) exit(1);
        close(fd); dup(fd2); close(fd2);
        execlp(cmd, cmd, 0);
    } else
        wait(0);
}

int main(void)
{
    char buf[1024];  int n;

    for (;;) {
        write(1, "====$ ", 6);
        n = read(0, buf, sizeof buf);
        if (n < 0) break;
        buf[n] = '\0';
        if (memcmp(buf, "./a 2>file2\n", 12) == 0)
            exec_a("./a", 2, "file2");
        else if (memcmp(buf, "exit", 4) == 0)
            break;
        else 
            system(buf);
    }
    return 0;
}
$ gcc a.c
$ gcc -o b b.c
$ ./b
====$ ./a 2>file2
stdout
====$ cat file2
stderr
====$ exit
$



この投稿にコメントする

削除パスワード

No.3571

Re:select関数のバッファリングについての疑問
投稿者---chikato(2005/03/21 22:15:19)


リストのご紹介誠に有難うございます。
大変参考になりました。

> $ cat a.c
> #include <stdio.h>
:
> stderr
> ====$ exit
> $

$ cat a.c
#include <stdio.h>

int main(void)
{
    fprintf(stdout, "stdout\n");
    fprintf(stderr, "stderr\n");
    return 0;
}

$ cat b.c
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

void exec_a(const char *cmd, int fd, const char *file)
{
    int pid = fork();
    if (pid == 0) {  //親プロセスの場合の処理
        int fd2 = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
        if (fd2 < 0) exit(1);
        close(fd); dup(fd2); close(fd2);
        //「2番⇔端末ドライババッファ」が閉じられ、「2番⇔file」が生成される
        execlp(cmd, cmd, 0);  //execlp("./a","./a",0);が実行される
                              //file2に./aの「fprintf(stderr, "stderr\n");」結果が出力される
    } else
        wait(0);  //子プロセスの場合の処理。waitは子プロセスの終了を待つ関数。
                  //statusは子プロセスの終了状態を保存する領域へのint型へのポインタ
                  //0(NULL)を設定するとその情報は保存されない
}

int main(void)
{
    char buf[1024];  int n;

    for (;;) {
        write(1, "====$ ", 6);
        n = read(0, buf, sizeof buf);  // sizeof bufはbuf配列全体の大きさ
        if (n < 0) break;  //読込エラーの場合には
        buf[n] = '\0';
        if (memcmp(buf, "./a 2>file2\n", 12) == 0)  //文字列の比較
            exec_a("./a", 2, "file2");
        else if (memcmp(buf, "exit", 4) == 0)
            break;
        else 
            system(buf);  //bufで受け取った文字列を実行する
    }
    return 0;
}

という感じですかね。

> $ gcc a.c
> $ gcc -o b b.c
> $ ./b
> ====$ ./a 2>file2
これにより
「execlp(cmd, cmd, 0); 」にて「execlp("./a","./a",0);」が実行される

> stdout
「fprintf(stdout, "stdout\n");」により出力

> ====$ cat file2
> stderr
「close(fd); dup(fd2); close(fd2);」により、2番への出力がfile2に出力された

> ====$ exit
> $
「system(buf); 」によりこのようになった。



この投稿にコメントする

削除パスワード

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    記事検索    ログ    タグ一覧




掲示板提供:Real Integrity