進(jìn)程和內(nèi)存
進(jìn)程有自己的內(nèi)存空間(指令 數(shù)據(jù) 和棧)若贮,翻譯這段話真別扭。操作系統(tǒng)可以在一組需要執(zhí)行的進(jìn)程之間進(jìn)行切換痒留,當(dāng)一個(gè)進(jìn)程沒(méi)有執(zhí)行時(shí)谴麦,系統(tǒng)會(huì)保存進(jìn)程的寄存器狀態(tài),在下一次執(zhí)行的時(shí)候恢復(fù)狀態(tài)伸头。是不是很簡(jiǎn)單匾效,簡(jiǎn)單,你寫(xiě)一個(gè)試試恤磷。內(nèi)核會(huì)給每個(gè)進(jìn)程分配一個(gè)進(jìn)程標(biāo)識(shí)面哼,就是經(jīng)常看到的pid扫步。
利用fork可以創(chuàng)建一個(gè)新的進(jìn)程魔策,這個(gè)是什么意思?
看看fork
int pid = fork();
if(pid > 0){
printf("parent: child=%d\n", pid);
pid = wait();
printf("child %d is done\n", pid);}
else if(pid == 0){
printf("child: exiting\n");
exit();
} else {
printf("fork error\n");
}
首先父進(jìn)程調(diào)用fork,產(chǎn)生子進(jìn)程河胎,子進(jìn)程擁有和父親相同內(nèi)容的內(nèi)存空間闯袒。最奇怪的就是fork調(diào)用會(huì)返回兩次,所以后面就是兩個(gè)程序了游岳,就像影分身搁吓。在父進(jìn)程里面返回子進(jìn)程的進(jìn)程id,在子進(jìn)程里面返回0(為啥喜歡這樣干吭历,迷惑吧)堕仔。
exit調(diào)用會(huì)終止進(jìn)程,且釋放內(nèi)存和文件資源晌区。wait會(huì)等待子進(jìn)程退出摩骨。
程序的輸出
parent: child=1234// p
child: exiting //c
parent: child 1234 is done
p和c的順序不是一定的通贞,看誰(shuí)首先獲得調(diào)度,但是最后一句肯定在后面∧瘴澹現(xiàn)在兩個(gè)程序有不同的寄存器和內(nèi)存昌罩,改變一個(gè)變量不會(huì)影響另外一個(gè)。
exec這個(gè)系統(tǒng)調(diào)用灾馒,會(huì)從文件系統(tǒng)中加載一個(gè)可執(zhí)行文件茎用,替換掉當(dāng)前的進(jìn)程內(nèi)存空間。這個(gè)文件需要符合一定的格式睬罗,包含指令轨功,啟動(dòng)地址,和數(shù)據(jù)容达,調(diào)用以后直接執(zhí)行啟動(dòng)命令古涧。
char *argv[3];
argv[0] = "echo";
argv[1] = "hello";
argv[2] = 0;
exec("/bin/echo", argv);
printf("exec error\n");
這兩個(gè)的區(qū)別是一個(gè)復(fù)制父進(jìn)程,一個(gè)替換花盐,是不是有點(diǎn)難以理解羡滑,為啥不新建一個(gè)(如果你也思考了這個(gè)問(wèn)題,那后面就會(huì)有回答)算芯,再做處理柒昏,這可能是進(jìn)程資源創(chuàng)建的問(wèn)題。exec會(huì)分配足夠的內(nèi)存熙揍,如果不夠用昙楚,再進(jìn)行系統(tǒng)調(diào)用分配內(nèi)存。
是不是感覺(jué)獲得了一甲子功力?孩子別傻诈嘿,現(xiàn)在還早堪旧,到現(xiàn)在一切還流于表面,后續(xù)的路還多著奖亚。請(qǐng)不要著急淳梦,耐心一點(diǎn)。
io和文件描述符
文件描述符是一個(gè)小整數(shù)昔字,代表進(jìn)程要讀寫(xiě)的對(duì)象爆袍,這沒(méi)有什么奇怪的,匯編里面也支持讀寫(xiě)文件作郭,在上一節(jié)中陨囊,可以在寄存器里填上值,代表不同的操作夹攒。
這里的文件描述符蜘醋,代表一種抽象的文件,可以是文件咏尝,設(shè)備压语,管道啸罢,都當(dāng)做一種字節(jié)流來(lái)處理。
每個(gè)進(jìn)程都保存這一個(gè)文件資源表格胎食,默認(rèn)從0開(kāi)始扰才,代表輸入,1代表輸出厕怜,2代表錯(cuò)誤衩匣。shell為了實(shí)現(xiàn)重定向(> <)和管道(|),要確保這幾個(gè)總是打開(kāi)的狀態(tài)粥航。
read會(huì)讀取幾個(gè)字節(jié)到buf里面琅捏,返回字節(jié)數(shù),如果沒(méi)有了就返回0躁锡。這里初學(xué)者比較難以理解的就是為啥要buf午绳,一下把文件讀入內(nèi)存不就得了置侍,得到我要的字符串就好了映之。這肯定沒(méi)有考慮到大文件的情況,內(nèi)存放不下的時(shí)候蜡坊,一般測(cè)試者這里只是一個(gè)有幾字節(jié)的小文件而已杠输。剛開(kāi)始嘛,想一下子解決問(wèn)題秕衙,誰(shuí)會(huì)想到用這些奇怪的技巧蠢甲,但是起碼得漲下經(jīng)驗(yàn)吧。write類(lèi)似就不說(shuō)了据忘。
基本的cat實(shí)現(xiàn)(單純用c語(yǔ)言的getchar putchar 也可以實(shí)現(xiàn))
char buf[512];
int n;
for(;;){
n = read(0, buf, sizeof buf);
if(n == 0)break;
if(n < 0){
fprintf(2, "read error\n");
exit();
}
if(write(1, buf, n) != n){
fprintf(2, "write error\n");
exit();
}
}
注意這里的cat基本不知道從哪里讀鹦牛,往哪里寫(xiě),是文件還是管道勇吊。
還有一點(diǎn)沒(méi)有提的就是曼追,fork和exec調(diào)用都會(huì)保存父進(jìn)程的文件表格,所以借用這些就可以實(shí)現(xiàn)重定向汉规。
cat <input.txt
char *argv[2];
argv[0] = "cat";
argv[1] = 0;
if(fork() == 0) {
close(0);
open("input.txt", O_RDONLY);
exec("cat", argv);
}
因?yàn)樵趀xec->cat后礼殊,進(jìn)程的0文件還是input.txt里面的內(nèi)容,當(dāng)然這次1就成了控制臺(tái)了。
其他
(真是個(gè)好的分類(lèi)针史,什么都可以扔這里晶伦,因?yàn)榈竭@里已經(jīng)寫(xiě)的很累了,讀者肯定也累了啄枕,也不要期望婚陪,一下子理解,因?yàn)槲矣X(jué)得不可能频祝。我會(huì)分出一篇接口二近忙。)
管道利用了一些類(lèi)似的技巧竭业,可以得到期望的文件描述符。
文件系統(tǒng)很值得學(xué)習(xí)的就是路徑的表示方法及舍,是樹(shù)這種數(shù)據(jù)結(jié)構(gòu)一種神來(lái)之筆未辆,樹(shù)真的是一種強(qiáng)大的數(shù)據(jù)結(jié)構(gòu),無(wú)處不在锯玛「拦瘢互聯(lián)網(wǎng)也有這種路徑的表示方法的意思,包括最近rest接口定義攘残。
文件實(shí)現(xiàn)相關(guān)的肯定需要了解磁盤(pán)的結(jié)構(gòu)了拙友,后面再說(shuō)吧,cpu本身就有文件的讀寫(xiě)指令歼郭。這種將各種設(shè)備資源抽象成文件的方法遗契,受到人們的追捧,我也不知道好不好病曾。
總之里面shell展示了系統(tǒng)調(diào)用(如wait close等)的靈活組裝方式牍蜂,真可以感嘆一些這些精巧是如何想到的,起碼對(duì)于我來(lái)說(shuō)很不自然泰涂。
總結(jié)
一些目前學(xué)到的東西鲫竞,以便更好的前行。首先是一些超凡脫俗的名詞逼蒙,看文章首的接口圖片从绘,還有一些沒(méi)有講到的自己查閱。大師是命名高手是牢,一個(gè)詞匯代表了太多的東西僵井,絕逼是抽象派的。
良好的接口定義驳棱,可以讓系統(tǒng)容易實(shí)現(xiàn)批什,且擁有靈活的組合方式,畢竟接口定義了這個(gè)操作系統(tǒng)蹈胡,你說(shuō)牛不牛渊季。Java有jnr實(shí)現(xiàn)了一系列底層的接口,js都可以加載操作系統(tǒng)了罚渐。如今docker和openstack如日中天却汉,你說(shuō)不學(xué)點(diǎn)操作系統(tǒng)對(duì)得起誰(shuí)?接口說(shuō)完了,該說(shuō)一下實(shí)現(xiàn)了荷并,對(duì)不起這篇文章已經(jīng)太長(zhǎng)了合砂,期待下一篇吧,下次要火力全開(kāi)了源织。
這系列教程翩伪,是在閱讀一些書(shū)籍和代碼過(guò)程的記錄微猖,基本是xv6的翻譯了(暈吧) 。如果你讀到更好的文章推薦給我缘屹,我不寫(xiě)了凛剥。歡迎閱讀我的其它文章。