pipe_wait問題_轉

轉自:調用Process.waitfor致使的進程掛起 html

最近遇到pipe_wait問題,父進程調用子進程時,子進程阻塞,cat /proc/$child/wchan輸出pipe_wait,進程阻塞在pipe_wait不執行,轉載文章對此問題分析很透徹。java

問題背景

若是要在Java中調用shell腳本時,可使用Runtime.exec或ProcessBuilder.start。它們都會返回一個Process對象,經過這個Process能夠對獲取腳本執行的輸出,而後在Java中進行相應處理。例如,下面的代碼:node

         一般,安全編碼規範中都會指出:使用Process.waitfor的時候,可能致使進程阻塞,甚至死鎖。 那麼這句應該怎麼理解呢?用個實際的例子說明下。linux

問題描述

         使用Java代碼調用shell腳本,執行後會發現Java進程和Shell進程都會掛起,沒法結束。shell

Java代碼 processtest.javac#

[java] view plain copy
  1. try   
  2. {  
  3.     Process process = Runtime.getRuntime().exec(cmd);  
  4.     System.out.println("start run cmd=" + cmd);  
  5.       
  6.     process.waitFor();  
  7.     System.out.println("finish run cmd=" + cmd);  
  8. }   
  9. catch (Exception e)   
  10. {             
  11.     e.printStackTrace();  
  12. }  

 

被調用的Shell腳本doecho.sh安全

[plain] view plain copy
  1. #!/bin/bash  
  2.   
  3. for((i=0; ;i++))  
  4. do      
  5.     echo -n "0123456789"  
  6.     echo $i >> count.log  
  7. done  

掛起緣由

  1.  主進程中調用Runtime.exec會建立一個子進程,用於執行shell腳本。子進程建立後會和主進程分別獨立運行。
  2.  由於主進程須要等待腳本執行完成,而後對腳本返回值或輸出進行處理,因此這裏主進程調用Process.waitfor等待子進程完成。
  3.  經過shell腳本能夠看出:子進程執行過程就是不斷的打印信息。主進程中能夠經過Process.getInputStream和Process.getErrorStream獲取並處理。
  4.  這時候子進程不斷向主進程發生數據,而主進程調用Process.waitfor後已掛起。當前子進程和主進程之間的緩衝區塞滿後,子進程不能繼續寫數據,而後也會掛起。
  5. 這樣子進程等待主進程讀取數據,主進程等待子進程結束,兩個進程相互等待,最終致使死鎖。

解決方法

         基於上述分析,只要主進程在waitfor以前,能不斷處理緩衝區中的數據就能夠。由於,咱們能夠再waitfor以前,單獨啓兩個額外的線程,分別用於處理InputStream和ErrorStream就能夠。實例代碼以下:bash

JDK上的說明

By default, the created subprocess does not have its own terminal or console. All its standard I/O (i.e. stdin, stdout, stderr) operations will be redirected to the parent process, where they can be accessed via the streams obtained using the methods getOutputStream(), getInputStream(), and getErrorStream(). The parent process uses these streams to feed input to and get output from the subprocess. Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, or even deadlock.electron

 從JDK的說明中能夠看出兩點:async

  • 若是系統中標準輸入輸出流使用的bufffer大小有限,全部讀寫時可能會出現阻塞或死鎖。------這點上面已分析
  • 子進程的標準I/O已經被重定向到了父進程。父進程能夠經過對應的接口獲取到子進程的I/O。------I/O是如何重定向的?

背後的故事

要回答上面的問題能夠從系統的層面嘗試分析。

首先經過ps命令能夠看到,在Linux上多出了兩個進程:一個Java進程、一個shell進程,且shell是java的子進程。


         而後,能夠看到shell進程的狀態顯示爲pipe_w。我剛開始覺得pipe_w表示pipe_write。進一步查看/proc/pid/wchan 發現pipe_w其實表示爲pipe_wait。一般/proc/pid/wchan表示一個內存地址或進程正在執行的方法名稱。所以,這彷佛代表該進程 在操做pipe時發生了等待,從而被掛起。咱們知道pipe是IPC的一種,一般用於父子進程之間通訊。這樣咱們能夠猜想:多是父子進程之間經過 pipe通訊的時候出現了阻塞。

         另外,觀察父子進程的fd信息,即/proc/pid/fd。能夠看到子進程的0/1/2(即:stdin/stdout/stderr)分別被重定向到了三個pipe文件;父親進程中對應的也有對着三個pipe文件的引用。


         綜上所述,這個過程應該是這樣的:子進程不斷向pipe中寫數據,而父進程一直不讀取pipe中的數據,致使pipe被塞滿,子進程沒法繼續寫入,因此出現pipe_wait的狀態。那麼pipe到底有多大呢?

測試pipe的大小

由於我已經在doecho.sh的腳步中記錄了打印了字符數,查看count.log就能夠知道子進程最終發送了多少數據。在子進程掛起 了,count.log的數據一致保持在6543不變。故,當前子進程向pipe中寫入6543*10=65430bytes時,出現進程掛起。 65536-65430=106byte即距離64K差了106bytes。


         換另外的測試方式,每次寫入1k,記錄總共能夠寫入多少。進程代碼如test_pipe_size.sh所示。測試結果爲64K。兩次結果相差了106byte,那個這個pipe到底多大?

Linux上pipe分析

最直接的方式就是看源碼。Pipe的實現代碼主要在linux/fs/pipe.c中,咱們主要看pipe_wait方法。

     pipe_read(struct kiocb *iocb, struct iov_iter *to)  
    230 {  
    231         size_t total_len = iov_iter_count(to);  
    232         struct file *filp = iocb->ki_filp;  
    233         struct pipe_inode_info *pipe = filp->private_data;  
    234         int do_wakeup;  
    235         ssize_t ret;  
    236   
    237         /* Null read succeeds. */  
    238         if (unlikely(total_len == 0))  
    239                 return 0;  
    240   
    241         do_wakeup = 0;  
    242         ret = 0;  
    243         __pipe_lock(pipe);  
    244         for (;;) {  
    245                 int bufs = pipe->nrbufs;  
    246                 if (bufs) {  
    247                         int curbuf = pipe->curbuf;  
    248                         struct pipe_buffer *buf = pipe->bufs + curbuf;  
    249                         const struct pipe_buf_operations *ops = buf->ops;  
    250                         size_t chars = buf->len;  
    251                         size_t written;  
    252                         int error;  
    253   
    254                         if (chars > total_len)  
    255                                 chars = total_len;  
    256   
    257                         error = ops->confirm(pipe, buf);  
    258                         if (error) {  
    259                                 if (!ret)  
    260                                         ret = error;  
    261                                 break;  
    262                         }  
    263   
    264                         written = copy_page_to_iter(buf->page, buf->offset, chars, to);  
    265                         if (unlikely(written < chars)) {  
    266                                 if (!ret)  
    267                                         ret = -EFAULT;  
    268                                 break;  
    269                         }  
    270                         ret += chars;  
    271                         buf->offset += chars;  
    272                         buf->len -= chars;  
    273   
    274                         /* Was it a packet buffer? Clean up and exit */  
    275                         if (buf->flags & PIPE_BUF_FLAG_PACKET) {  
    276                                 total_len = chars;  
    277                                 buf->len = 0;  
    278                         }  
    279   
    280                         if (!buf->len) {  
    281                                 buf->ops = NULL;  
    282                                 ops->release(pipe, buf);  
    283                                 curbuf = (curbuf + 1) & (pipe->buffers - 1);  
    284                                 pipe->curbuf = curbuf;  
    285                                 pipe->nrbufs = --bufs;  
    286                                 do_wakeup = 1;  
    287                         }  
    288                         total_len -= chars;  
    289                         if (!total_len)  
    290                                 break;  /* common path: read succeeded */  
    291                 }  
    292                 if (bufs)       /* More to do? */  
    293                         continue;  
    294                 if (!pipe->writers)  
    295                         break;  
    296                 if (!pipe->waiting_writers) {  
    297                         /* syscall merging: Usually we must not sleep 
    298                          * if O_NONBLOCK is set, or if we got some data. 
    299                          * But if a writer sleeps in kernel space, then 
    300                          * we can wait for that data without violating POSIX. 
    301                          */  
    302                         if (ret)  
    303                                 break;  
    304                         if (filp->f_flags & O_NONBLOCK) {  
    305                                 ret = -EAGAIN;  
    306                                 break;  
    307                         }  
    308                 }  
    309                 if (signal_pending(current)) {  
    310                         if (!ret)  
    311                                 ret = -ERESTARTSYS;  
    312                         break;  
    313                 }  
    314                 if (do_wakeup) {  
    315                         wake_up_interruptible_sync_poll(&pipe->wait, POLLOUT | POLLWRNORM);  
    316                         kill_fasync(&pipe->fasync_writers, SIGIO, POLL_OUT);  
    317                 }  
    318                 pipe_wait(pipe);  
    319         }  
    320         __pipe_unlock(pipe);  
    321   
    322         /* Signal writers asynchronously that there is more room. */  
    323         if (do_wakeup) {  
    324                 wake_up_interruptible_sync_poll(&pipe->wait, POLLOUT | POLLWRNORM);  
    325                 kill_fasync(&pipe->fasync_writers, SIGIO, POLL_OUT);  
    326         }  
    327         if (ret > 0)  
    328                 file_accessed(filp);  
    329         return ret;  
    330 }  

能夠看到Pipe被組織成環狀結構,即一個循環鏈表。鏈表中的元素爲struct pipe_buffer的結構,每一個pipe_buffer對於一個page。鏈表中共有16個元素,即pipe buffer的總大小爲16*page。若是page大小爲4K,那麼pipe buffer的總大小應該爲16*4K=64K。



參考資料

Java 中的進程與線程

https://www.ibm.com/developerworks/cn/java/j-lo-processthread/

 When Runtime.exec() won't

http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=3

 Linux進程間通訊之管道(pipe)、命名管道(FIFO)與信號(Signal)

http://www.cnblogs.com/biyeymyhjob/archive/2012/11/03/2751593.html

 buffering in standard streams

http://www.pixelbeat.org/programming/stdio_buffering/

 Todd.log - a place to keep my thoughts onprogramming

http://www.cnblogs.com/weidagang2046/p/io-redirection.html

 linux cross reference

http://lxr.free-electrons.com/source/fs/pipe.c#L103

 How big is the pipe buffer

http://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer

相關文章
相關標籤/搜索