日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

美是有目共睹的。Unix之美,稍微體會(huì),便能得到。

1969年,Unix初始,沒(méi)有fork,沒(méi)有exec,沒(méi)有pipe,沒(méi)有 “一切皆文件” ,但是那時(shí)它已經(jīng)是Unix了。它簡(jiǎn)單,可塑。

Melvin Conway在1963年的論文中敘述fork思想時(shí)就解釋說(shuō)并行路徑要用結(jié)果來(lái)交互,也就是在匯合的join點(diǎn)來(lái)同步結(jié)果。這個(gè)同步點(diǎn)所得到的,就是一個(gè)并行進(jìn)程的 輸出 。

在此之外,Unix還有另一個(gè)原則,就是 組合小程序!

Unix把一系列功能單一的小程序組合成一個(gè)復(fù)雜的邏輯,這個(gè)原則有以下優(yōu)勢(shì):

  • 每一個(gè)小程序都很容易編寫。
  • 每一個(gè)小程序可以分別完成。
  • 每一個(gè)小程序可以分別迭代修復(fù)。
  • 多個(gè)小程序可以自由組合。

這是典型的模塊化思想,小到統(tǒng)籌佐餐燒飯,大到組成生命的嘌呤嘧啶,都不自覺(jué)地和這種模塊化思想相契機(jī),原來(lái)這就是真理。 程序盡量小,只做一件事并且做好它。

Unix程序在自身的邏輯之外對(duì)外暴露的只有輸入和輸出。那么 用輸出連接另一個(gè)程序輸入 就是一種好的方法。所謂Conway的join點(diǎn)對(duì)于Unix進(jìn)程指的就是輸出。

對(duì)外暴露的越少,程序越內(nèi)聚。這是一種范式,類似RISC處理器也是抽象出僅有的load和store來(lái)和內(nèi)存交互。

簡(jiǎn)單來(lái)講,Unix程序通過(guò)輸入和輸出來(lái)彼此連接。下面是一幅來(lái)自Wiki的圖示:

60行C代碼實(shí)現(xiàn)一個(gè)shell

 

詳見(jiàn)Pipeline (Unix):

https://en.wikipedia.org/wiki/Pipeline_(Unix)

Unix的另一個(gè)原則,即著名的 “一切皆文件!” 連接輸出和輸入的那個(gè)管道在Unix中被實(shí)現(xiàn)為Pipe,顯然,它也是文件,一個(gè)FIFO文件。

說(shuō)實(shí)話,協(xié)作幾個(gè)小程序形成一個(gè)大邏輯的思想還是來(lái)自于Convey,在Convey的論文里,他稱為 協(xié)程, Pile可以說(shuō)是直接實(shí)現(xiàn)了 Convey協(xié)程 之間的交互。有關(guān)這段歷史,請(qǐng)看:

http://www.softpanorama.org/Scripting/Piporama/history.shtml

用Pipe連接作為輸出和輸入連接Unix進(jìn)程可以做成什么事情呢?讓我們?nèi)ジ惺芤粋€(gè)再熟悉不過(guò)的實(shí)例,即數(shù)學(xué)式子:

 

60行C代碼實(shí)現(xiàn)一個(gè)shell

 

 

我們把運(yùn)算符加號(hào),乘號(hào),除號(hào)(暫不考慮括號(hào),稍后解釋為什么)這些看作是程序(事實(shí)上它們也真的是),那么類似數(shù)字3,5,7,6就是這些程序的輸入了,這個(gè)式子最終需要一個(gè)輸出,獲得這個(gè)輸出的過(guò)程如下:

  1. 數(shù)字3,5是加號(hào)程序的輸入,3+5執(zhí)行,它獲得輸出8.
  2. 第1步中的輸出8連同數(shù)字7作為乘號(hào)程序的輸入,8 × 7執(zhí)行,獲得輸出56.
  3. 第2步中的輸出56連同數(shù)字6作為除號(hào)的輸入,…

這個(gè)數(shù)學(xué)式子的求值過(guò)程和pipe連接的Unix程序組合獲得最終結(jié)果的過(guò)程完全一致。

如果你相信數(shù)學(xué)可以描述整個(gè)世界,那么Pipe連同Unix程序同樣是描述這個(gè)世界的語(yǔ)言 。

在數(shù)學(xué)領(lǐng)域,程序 就是所有的運(yùn)算符,加號(hào),減號(hào),乘號(hào),除號(hào),乘方,開(kāi)方,求和,積分,求導(dǎo)…它們無(wú)一例外, 只做一件事。

在Unix看來(lái)也同樣。它做的事情和下面的應(yīng)該差不多,而且更多:

60行C代碼實(shí)現(xiàn)一個(gè)shell

 

寫出上面的式子中每一個(gè)數(shù)學(xué)運(yùn)算符的程序并不困難,比如加號(hào)程序:

// plus.c
#include <stdio.h>
int main(int argc, char **argv)
{
	int a, b;
	a = atoi(argv[1]);
	b = atoi(argv[2]);
	a = a + b;
	printf("%dn", a);
}

同樣,我們可以寫出除法,直到偏導(dǎo)的程序。然后我們通過(guò)pipe就能將它們組合成任意的數(shù)學(xué)式子。

現(xiàn)在談?wù)刄nix組合程序的具體寫法,如果我們要化簡(jiǎn)薛定諤方程,我們應(yīng)該如何用Unix命令寫出與上述式子等價(jià)的組合程序命令行呢?我們無(wú)法像數(shù)學(xué)家手寫那樣隨意使用括號(hào),顯然,計(jì)算機(jī)并不認(rèn)識(shí)它。我們能夠使用的只有兩個(gè)符號(hào):

  1. 代表具體Unix小程序的命令。
  2. Pipe符號(hào)"|"。

換句話說(shuō),我們需要寫出一個(gè) 鏈?zhǔn)浇M合表達(dá)式。 這時(shí)就要用到前綴表達(dá)式了。

數(shù)學(xué)式子里的括號(hào),其實(shí)它無(wú)關(guān)緊要,括號(hào)只是給人看的,它規(guī)定一些運(yùn)算的優(yōu)先級(jí)順序,這叫 中綴表達(dá)式 ,一個(gè)中綴表達(dá)式可以輕松被轉(zhuǎn)換為 前綴表達(dá)式,后綴表達(dá)式 ,從而消除括號(hào)。事實(shí)上,Unix的Pipe最初也面臨過(guò)這樣的問(wèn)題,到底是中綴好呢,還是前/后綴好呢?

我們現(xiàn)在使用的Unix/linux命令,以cp舉例:

cp $in $out

這是一個(gè)典型的前綴表達(dá)式,但是當(dāng)pipe的發(fā)明者M(jìn)cIlroy最初引入pipe試圖組合各個(gè)程序時(shí),最初上面的命令行被建議成:

$in cp $out

就像我們的(3 + 5) × 8 一樣。但是這非常不適合計(jì)算機(jī)處理的風(fēng)格,計(jì)算機(jī)不得不首先掃描解析這個(gè)式子,試圖:

  1. 理解 “括號(hào)括起來(lái)的要優(yōu)先處理” 這句復(fù)雜的話;
  2. 區(qū)分哪些是輸入,哪些是操作符…

對(duì)于式子(3 + 5) × 8 的求值,計(jì)算機(jī)更適合用一種在簡(jiǎn)單規(guī)則下非常直接的方式去 順序執(zhí)行求解,這就是前綴表達(dá)式的優(yōu)勢(shì)。

× 8 + 35就是(3 + 5) × 8 的前綴表達(dá)式,可以看到,沒(méi)有了括號(hào)。對(duì)于pipe組合程序而言,同樣適用于這個(gè)原則。于是前綴命令成了pipe組合命令的首選,現(xiàn)如今,我們可以用:

pro1 $stdin|pro2|pro3|pro4|...|proX $stdout

輕松組合成任意復(fù)雜的邏輯。

Pipe協(xié)同組合程序的Unix原則是一個(gè)創(chuàng)舉,程序就是一個(gè)加工過(guò)濾器,它把一系列的輸入經(jīng)過(guò)自己的程序邏輯生成了一系列的輸出,該輸出又可以作為其它程序的輸入。

在Unix/Linux中,各種shell本身就實(shí)現(xiàn)了這樣的功能,但是為了徹底理解這種處理方式的本質(zhì),只能自己寫一個(gè)才行。來(lái)寫一個(gè)微小的shell吧。

再次看上面提到的Unix Pipe的處理序列:

pro1 $stdin|pro2|pro3|pro4|...|proX $stdout

如果讓一個(gè)shell處理以上組合命令,要想代碼量少,典型方案就是遞歸,然后用Pipe把這些遞歸調(diào)用過(guò)程給串起來(lái),基本邏輯如下:

int exec_cmd(CMD *cmd, PIPE pipe)
{
 // 持續(xù)解析命令行,以pipe符號(hào)|分割每一個(gè)命令
 while (cmd->next) {
 PIPE pp = pipe_create();
 if (fork() > 0) {
 // 父進(jìn)程遞歸解析下一個(gè)
 exec_cmd(cmd->next, pp);
 return 0;
 }
 // 子進(jìn)程執(zhí)行
 dup_in_out(pp);
 exec(cmd->cmdline);
 }
 if (fork() > 0) {
 wait_all_child();
 return 0;
 } else {
 dup_in_out(pp);
 exec(cmd->cmdline);
 }
}

按照上面的思路實(shí)現(xiàn)出來(lái),大概60行左右代碼就可以:

// tinysh.c
// gcc tinysh.c -o tinysh
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define CMD_BUF_LEN	512
char cmd[CMD_BUF_LEN] = {0};
void fork_and_exec(char *cmd, int pin, int pout)
{
 if (fork() == 0) {
 if (pin != -1) {
 dup2 (pin, 0);
 close(pin);
 }
 if (pout != -1) {
 dup2 (pout, 1);
 close(pout);
 }
 system(cmd);
 exit(0);
 }
	if (pin != -1)
		close(pin);
	if (pout != -1)
		close(pout);
}
int execute_cmd(char *cmd, int in)
{
	int status;
	char *p = cmd;
	int pipefd[2];
	while (*p) {
		switch (*p) {
		case '|':
			*p++ = 0;
			pipe(pipefd);
			fork_and_exec(cmd, in, pipefd[1]);
			execute_cmd(p, pipefd[0]);
			return 0;
		default:
			p++;
		}
	}
	fork_and_exec(cmd, in, -1);
	while(waitpid(-1, &status, WNOHANG) != -1);
	return 0;
}
int main(int argc, char **argv)
{
	while (1) {
		printf("tiny sh>>");
		gets(cmd);
		if (!strcmp(cmd, "q")) {
			exit(0);
		} else {
			execute_cmd(cmd, -1);
		}
	}
	return 0;
}

下面是執(zhí)行tinysh的結(jié)果:

[root@10 test]# ls -l
總用量 28
-rw-r--r-- 1 root root 0 9月 1 05:39 a
-rwxr-xr-x 1 root root 9000 9月 1 05:38 a.out
-rw-r--r-- 1 root root 0 9月 1 05:39 b
-rw-r--r-- 1 root root 0 9月 1 05:39 c
-rw-r--r-- 1 root root 0 9月 1 05:39 d
-rw-r--r-- 1 root root 0 9月 1 05:39 e
-rwxr-xr-x 1 root root 9000 9月 1 05:38 tinysh
-rw-r--r-- 1 root root 1167 9月 1 05:38 tinysh.c
[root@10 test]# ./tinysh
tiny sh>>ls -l |wc -l
9
tiny sh>>cat /etc/inittab |grep init
# inittab is no longer used when using systemd.
tiny sh>>cat /etc/inittab |grep init|wc -l
1
tiny sh>>q
[root@10 test]#

遞歸解析的過(guò)程中fork/exec,一氣呵成,這就是一個(gè)最簡(jiǎn)單shell實(shí)現(xiàn)。它可完成組合程序的執(zhí)行并給出結(jié)果。

這個(gè)tiny shell命令解析器的邏輯可以表示如下:

60行C代碼實(shí)現(xiàn)一個(gè)shell

 

現(xiàn)在,讓我們用上面的tiny shell來(lái)實(shí)現(xiàn)式子

 

60行C代碼實(shí)現(xiàn)一個(gè)shell

 

 

的計(jì)算,我需要寫表示四則混合運(yùn)算符的Unix程序,首先看加號(hào)運(yùn)算符程序,將上文中plus.c改成從標(biāo)準(zhǔn)輸入讀取加數(shù)即可:

// plus.c
// gcc plus.c -o plus
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
	float a, b;
	a = atof(argv[1]);
	scanf("%f", &b);
	b = b + a;
	printf("%fn", b);
}

再看減法運(yùn)算符程序代碼:

// sub.c
// gcc sub.c -o sub
#include <stdio.h>
#include <stdio.h>
int main(int argc, char **argv)
{
	float a, b;
	a = atof(argv[1]);
	scanf("%f", &b);
	b = b - a;
	printf("%fn", b);
}

接下來(lái)是乘法和除法的代碼:

// times.c
// gcc times.c -o times
#include <stdio.h>
#include <stdio.h>
int main(int argc, char **argv)
{
	float a, b;
	a = atof(argv[1]);
	scanf("%f", &b);
	b = b*a;
	printf("%fn", b);
}
// div.c
// gcc div.c -o div
#include <stdio.h>
#include <stdio.h>
int main(int argc, char **argv)
{
	int a, b;
	a = atof(argv[1]);
	scanf("%d", &b);
	b = b/a;
	printf("%dn", b);
}

可以看到,這些都是非常簡(jiǎn)單的程序,但是任意組合它們便可以實(shí)現(xiàn)任意四則運(yùn)算,我們看看

60行C代碼實(shí)現(xiàn)一個(gè)shell

 

這個(gè)如何組合。

首先在標(biāo)準(zhǔn)的Linux bash中我們?cè)囈幌拢?/p>

[root@10 test]# ./plus 5|./times 7|./sub 20|./div 6
3
6.000000
[root@10 test]#

計(jì)算結(jié)果顯然是正確的。現(xiàn)在我在自己實(shí)現(xiàn)的tinysh中去做類似的事情:

[root@10 test]# ./tinysh
tiny sh>>./plus 5|./times 7|./sub 20|./div 6
3
6.000000
tiny sh>>q
[root@10 test]#

可以看到,tinysh的行為和標(biāo)準(zhǔn)Linux bash的行為是一致的。

簡(jiǎn)單吧,簡(jiǎn)單!無(wú)聊吧,無(wú)聊!Pipe連接了若干小程序,每一個(gè)小程序只做一件事。

如果我們的系統(tǒng)中沒(méi)有任何shell程序,比如我們沒(méi)有bash,我們只有tinysh,加上以上這4個(gè)程序,一共5個(gè)程序,就可以完成任意算式的四則混合運(yùn)算。

現(xiàn)在我們用以上的組合Unix程序的方法試試計(jì)算下面的式子:

 

60行C代碼實(shí)現(xiàn)一個(gè)shell

 

 

根號(hào)怎么辦?

按照非Unix的編程風(fēng)格,就要在程序里寫函數(shù)計(jì)算開(kāi)根號(hào),但是用Unix的風(fēng)格,則只需要再加個(gè)開(kāi)根號(hào)的程序即可:

// sqrt.c
// gcc sqrt.c -lm -o sqrt
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main(int argc, char *argv[])
{
	float b;
	scanf("%f", &b);
	b = sqrt(b);
	printf("%fn", b);
}

有了這個(gè)開(kāi)根號(hào)的程序,結(jié)合已經(jīng)有的四則運(yùn)算程序,讓我們的tinysh用pipe將它們串起來(lái),就成了。好了,現(xiàn)在讓我們計(jì)算上面的式子:

./tinysh
tiny sh>>./sqrt |./plus 3|./div 2
9
3.000000
tiny sh>>q

本文該結(jié)束了,后面要寫的應(yīng)該就是關(guān)于經(jīng)典Unix IPC的內(nèi)容了,是的,自從Pipe之后,Unix便開(kāi)啟了IPC,System V開(kāi)始稱為標(biāo)準(zhǔn)并持續(xù)引領(lǐng)著未來(lái),但這是另一篇文章的話題了。

最后,來(lái)自Unix初創(chuàng)者之一Dennis M. Ritchie關(guān)于Unix的滿滿回憶,非常感人:

60行C代碼實(shí)現(xiàn)一個(gè)shell

 

原文來(lái)自 The Evolution of the Unix Time-sharing System :http://www.read.seas.harvard.edu/~kohler/class/aosref/ritchie84evolution.pdf

分享到:
標(biāo)簽:shell
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定