在UNIX的世界中,我們在shell下執行一個新的程式時,shell基本上會先fork(),然後再用exec*()來替換child process以執行指定的程式。但是既然fork()之後往往都要喚起exec*(),為什麼UNIX要將這兩個動作分開呢?
原因很簡單:因為要讓使用者為child process設定好運行環境,而這個運行環境作業系統事先並不知道。
舉例來說,如果要實現I/O redirection(Ex:cat < input.txt ),我們會有類似如下程式(從MIT的6.828課程文件擷取):
而如果要實現pipe(Ex:echo "hello world" | wc),則會有類似如下程序:
從這兩個例子我們可以看到,一樣是fork()/exec*()的組合運用,但在exec*()之前透過不一樣的環境設定,就可以讓在child process中要運行的程式接收到不同來源的資料。
我們可以發現:
那麼,在命令列下的程式組合將會變得異常複雜,所需的system call也會以m*n*...的方式急速增加。多了幾年的經驗後,再次溫習UNIX基本概念,對於UNIX的設計者識別出關鍵的正交概念,並具體實現出來,真的感到萬分佩服。
UNIX的優雅與精隨,透過其設計者Dennis Richie的話完美呈現:
Unix is simple. It just takes a genius to understand its simplicity.
原因很簡單:因為要讓使用者為child process設定好運行環境,而這個運行環境作業系統事先並不知道。
舉例來說,如果要實現I/O redirection(Ex:cat < input.txt ),我們會有類似如下程式(從MIT的6.828課程文件擷取):
- char *argv[2];
- argv[0] = "cat";
- argv[1] = 0;
- if(fork() == 0) {
- close(0);
- open("input.txt", O_RDONLY);
- exec("cat", argv);
- }
而如果要實現pipe(Ex:echo "hello world" | wc),則會有類似如下程序:
- int p[2];
- char *argv[2];
- argv[0] = "wc";
- argv[1] = 0;
- pipe(p);
- if(fork() == 0) {
- close(0);
- dup(p[0]);
- close(p[0]);
- close(p[1]);
- exec("/bin/wc", argv);
- } else {
- write(p[1], "hello world\n", 12);
- close(p[0]);
- close(p[1]);
- }
從這兩個例子我們可以看到,一樣是fork()/exec*()的組合運用,但在exec*()之前透過不一樣的環境設定,就可以讓在child process中要運行的程式接收到不同來源的資料。
我們可以發現:
- 如果一開始UNIX的設計沒有將fork()/exec*()分開
- 如果一開始UNIX沒有將Everything is file的概念引進
- 如果一開始UNIX沒有對file的使用採用通用的file descriptor(此處使用int)
那麼,在命令列下的程式組合將會變得異常複雜,所需的system call也會以m*n*...的方式急速增加。多了幾年的經驗後,再次溫習UNIX基本概念,對於UNIX的設計者識別出關鍵的正交概念,並具體實現出來,真的感到萬分佩服。
UNIX的優雅與精隨,透過其設計者Dennis Richie的話完美呈現:
Unix is simple. It just takes a genius to understand its simplicity.
留言
張貼留言