進捗ダメです

進捗ダメなしがないエンジニアのブログ

selectの掟 を守らないとどうなるか 〜ループでのfd_set未初期化〜

Linuxシステムコールの「select」について、学んだことをメモ Δ1 2017/04/16 タイトル変更

selectシステムコールとは

selectは、Linuxシステムコールの一つ。4.2BSD(なんと1983年リリース!)にて初登場。 同じくLinuxシステムコールの「open」で作成されたファイルディスクリプタ(以下:fd)を複数まとめて監視し、該当のfdに対応したファイルに書き込みが行われたときに処理を開始することができる。 fdを使用するので、名前付きパイプやsocketを取り扱う際に効果を発揮する。 主にマルチスレッド・マルチプロセスプログラミングに於いて、同期処理やデータ転送に使用される。

詳細は、右ページを参照 ⇛ Man page of SELECT

「selectの掟」

このselect、非常に癖がある。 上記ページにも記載があるが、selectには「掟」と呼ばれるルールが存在する。(Man page of SELECT_TUT) これ守らないと、「理解しにくい挙動に出くわ」す、とされ、「select() を使う利点は簡単に失われてしまう」らしい。 (なんとも面倒くさい化石のようなシステムコールである)

selectの掟の殆どは、なんのことはない、普通にfdを使用していれば守れるわけだが、いくつかの項目でハマりそう(と言うか私がハマってしまった)ので、以下にサンプルを書いて検証した。

ループ処理内でのselect

イベントドリブンなプログラムモデルを考えると、必然的にselectは、メインスレッドとは別にスレッドを立ててそこでwhile(1)でループすることになるだろう。 その場合に注意するのが、掟の11項目

select() はファイルディスクリプター集合を変更するので、 select() がループの中で使用されている場合には、呼び出しを行う前に毎回 ディスクリプター集合を初期化し直さなければならない。

ううむ、なんとも面倒くさい。 実際に確認してみた。

実行環境は以下

言語:C++ 
コンパイラ:gcc version 6.2.1 

ソースは以下

  1 #include <cstdio>                                                
  2 #include <string>                                                
  3 #include <algorithm>                                             
  4 #include <sys/types.h>                                           
  5 #include <sys/stat.h>                                            
  6 #include <fcntl.h>                                               
  7 #include <sys/select.h>                                          
  8 #include <unistd.h>                                              
  9                                                                  
 10 // In this program, all fd will not close.                       
 11                                                                  
 12 int main() {                                                     
 13                                                                  
 14   std::string pipe_name_1 = "/tmp/pipe_test_1";                  
 15   std::string pipe_name_2 = "/tmp/pipe_test_2";                  
 16   std::string pipe_name_3 = "/tmp/pipe_test_3";                  
 17                                                                  
 18   if (mkfifo(pipe_name_1.c_str(), 0666) < 0) {                   
 19     perror("mkfifo");                                            
 20   }                                                              
 21   if (mkfifo(pipe_name_2.c_str(), 0666) < 0) {                   
 22     perror("mkfifo");                                            
 23   }                                                              
 24   if (mkfifo(pipe_name_3.c_str(), 0666) < 0) {                   
 25     perror("mkfifo");                                            
 26   }                                                              
 27                                                                  
 28   int fd1r = open(pipe_name_1.c_str(), O_RDONLY | O_NONBLOCK);   
 29   if (fd1r < 0) {                                                
 30     printf("fd1r create err.\n");                                
 31     perror("open");                                              
 32   }                                                              
 33   int fd1w = open(pipe_name_1.c_str(), O_WRONLY | O_NONBLOCK);   
 34   if (fd1w < 0) {                                                
 35     printf("fd1w create err.\n");                                
 36     perror("open");     
 36     perror("open");                                              
 37   }                                                              
 38                                                                  
 39   int fd2r = open(pipe_name_2.c_str(), O_RDONLY | O_NONBLOCK);   
 40   if (fd2r < 0) {                                                
 41     printf("fd2r create err.\n");                                
 42     perror("open");                                              
 43   }                                                              
 44   int fd2w = open(pipe_name_2.c_str(), O_WRONLY | O_NONBLOCK);   
 45   if (fd2w < 0) {                                                
 46     printf("fd2w create err.\n");                                
 47     perror("open");                                              
 48   }                                                              
 49                                                                  
 50   int fd3r = open(pipe_name_3.c_str(), O_RDONLY | O_NONBLOCK);   
 51   if (fd3r < 0) {                                                
 52     printf("fd3r create err.\n");                                
 53     perror("open");                                              
 54   }                                                              
 55   int fd3w = open(pipe_name_3.c_str(), O_WRONLY | O_NONBLOCK);   
 56   if (fd3w < 0) {                                                
 57     printf("fd3w create err.\n");                                
 58     perror("open");                                              
 59   }                                                              
 60                                                                  
 61                                                                  
 62   fd_set fds;                                                    
 63   int fd_max;                                                    
 64                                                                  
 65   FD_ZERO(&fds);                                                 
 66   FD_SET(fd1r, &fds);                                            
 67   FD_SET(fd2r, &fds);                                            
 68                                                                  
 69   fd_max = std::max(fd1r, fd2r);                                 
 70                                    
 71   char w_buf[2]{'1'};                                            
 72   if (write(fd1w, w_buf, sizeof(w_buf)) < 0) {                   
 73     printf("1w wr err.\n");                                      
 74     return 0;                                                    
 75   }                                                              
 76                                                                  
 77   if (select(fd_max + 1, &fds, nullptr, nullptr, nullptr) < 0) { 
 78     perror("select()");                                          
 79     return 0;                                                    
 80   }                                                              
 81                                                                  
 82   if (FD_ISSET(fd1r, &fds)) {                                    
 83     char buf[2]{};                                               
 84     if (read(fd1r, buf, sizeof(buf)) < 0) {                      
 85       printf("read 1r err.\n");                                  
 86       return 0;                                                  
 87     } else {                                                     
 88       printf("read complete:[%c]\n", buf[0]);                    
 89     }                                                            
 90   }                                                              
 91   if (FD_ISSET(fd2r, &fds)) {                                    
 92     char buf[2]{};                                               
 93     if (read(fd2r, buf, sizeof(buf)) < 0) {                      
 94       printf("read 2r err.\n");                                  
 95       return 0;                                                  
 96     } else {                                                     
 97       printf("read complete:[%c]\n", buf[0]);                    
 98     }                                                            
 99   }                                                              
100                                                                  
101   // I ignore select rule 11.                                    
102   // FD_ZERO(&fds);                                              
103   // FD_SET(fd1r, &fds);                                         
104   // FD_SET(fd2r, &fds);                                         
105                                                                  
106   // fd_max = std::max({fd1r, fd2r});                            
107                                                                  
108   char w_buf_2[2]{'1'};                                          
109   if (write(fd2w, w_buf_2, sizeof(w_buf_2)) < 0) {               
110     printf("2w wr err.\n");                                      
111     return 0;                                                    
112   }                                                              
113                                                                  
114   if (select(fd_max + 1, &fds, nullptr, nullptr, nullptr) < 0) { 
115     perror("select()");                                          
116     return 0;                                                    
117   }                                                              
118                                                                  
119   if (FD_ISSET(fd1r, &fds)) {                                    
120     char buf[2]{};                                               
121     if (read(fd1r, buf, sizeof(buf)) < 0) {                      
122       printf("read 1r err.\n");                                  
123       return 0;                                                  
124     } else {                                                     
125       printf("fd1r read complete:[%c]\n", buf[0]);               
126     }                                                            
127   }                                                              
128   if (FD_ISSET(fd2r, &fds)) {  
129     char buf[2]{};                                               
130     if (read(fd2r, buf, sizeof(buf)) < 0) {                      
131       printf("read 2r err.\n");                                  
132       return 0;                                                  
133     } else {                                                     
134       printf("fd2r read complete:[%c]\n", buf[0]);               
135     }                                                            
136   } 
137   return 0;
138 }                                                                                                                      

コンソールでの標準出力結果は以下

$ ./a.out 
read complete:[1]

ソース72行目のfd1へのwriteでの結果は82行目のFD_ISSET後に標準出力で表示されている。 しかし、109行目のfd2へのwriteは、128行目のFD_ISSETで検知できておらず、したがって134行目の標準出力が表示されない。

結論

selectを呼ぶ前には、毎回必ずファイルディスクリプタ郡を初期化し、ファイルディスクリプタを再セットしよう! (骨董品は丁寧に扱おう!)