彭民德:《電子計算60年》(18)UNIX原始碼的一些運用

彭民德發表於2016-08-17

手頭有了UNIX原始碼,就有運用的天地。曾經看到中科院計算(軟體)所、成都電訊工程學院、合肥工大等單位有過應用報導。我們也在閱讀分析的同時,首先把它用於教學。UNIX中的程式可以作為作業系統、資料結構、C語言、軟體工程等課程的用例。在北京大學第二分校劉日昇、孫玉芳1982年3月編寫的《UNIX作業系統分析報告》的附錄中,就實際地選擇羅列了UNIX原始碼中的16個C函式,提供學習C語言使用。我們也從UNIX原始碼中選擇陣列、結構、指標等資料型別和諸如線性表、連結串列操作程式作為相應的教學用例。

對於UNIX原始碼的又一項運用是,1987年我們開發了一個基於UNIX環境的用於作業系統輔助教學的OSCAI系統(以caios命名)。鑑於作業系統核心工作從計算機外面看不見摸不著,很難理解,而閱讀原始碼雖然有較大收穫,但面對一本靜態的程式程式碼,要理解作業系統的動態性還是很難的。我們想通過跟蹤應用程式的執行過程,儘可能展示作業系統核心,在加工該應用程式某些特定語句時的實際狀況。如果能夠做到這一點,那麼第一,應用程式和作業系統都是實際的,應用程式可以獲得預期結果。第二,看不見的核心加工應用程式的過程,可以適當展示出來,提供外界對計算機內部工作過程,一種較直觀的瞭解。要實現上述功能,顯而易見的難度是核心程式碼和資料區無法訪問,核心程式碼在管理態執行,神聖不可侵犯。

我們的做法是,在應用程式和作業系統之間,增加一層OSCAI軟體,它截獲應用程式請求作業系統所做的系統呼叫,模擬核心對該系統呼叫進行加工,讀取通過系統呼叫可以得到的,核心與執行該應用程式相關的資料結構,並受控地顯示給應用者。這個OSCAI就是模擬UNIX的一個在使用者態執行的作業系統,因為借鑑了UNIX V6原始碼和第七版的技術,這種模擬可以接近真實。

OSCAI模擬核心設定了一套同名的資料結構,又用與核心相同的演算法編寫了在其上的一套操作程式。這些程式凡是能夠引用UNIX系統呼叫實現自己功能的,都用系統呼叫實現,讓實際UNIX去加工,以保持真實性。從UNIX來看,OSCAI只是一個實用程式,但從使用者來看,它是一個看得見摸得著的作業系統。

這樣做的代價是,記憶體中除了已有的UNIX,還有一套相似的OSCAI,增加了程式碼的冗餘,減慢了使用者程式的執行速度。但使用者可以選擇是否呼叫OSCAI,如果不用,那就保持原先的作業系統環境,不會增加程式碼冗餘。

本系統在UNIX環境下工作。使用本系統與使用UNIX的基本命令和其它實用程式的方法完全一樣。下面用例子加以說明。假定有一個C語言程式,檔名為prog.c,其中包含若干條系統呼叫。一方面可以用通常的方法編譯執行,顯示結果,也可以呼叫我們的caios,只要執行命令“caios prog.c”即可。
caios 是一個shell程式,包括下列依序的4步動作:
addcai $1
cc –o caios$$ caiosinit.c $1 –lcai
caios$$
rmcai $1
其中:
1. 命令“addcai $1”對prog.c進行加工,將程式中各條系統呼叫的名字,悄悄地在前面加上cai。比如,將
fd=open(“name”,0);
改成
fd=caiopen(“name”,0);
以便程式執行時不是直接由UNIX截獲,而是先經由我們的OSCAI系統加工。同時將使用者程式中的主函式main改為mmain,使它變成一個一般函式名,以便能夠與我們的caiosinit.c一起編譯成一個目標程式。
2. 命令“cc –o caios$$ caiosinit.c $1 –lcai ”的功能是將$1所代表的prog.c與caiosinit.c一起編譯成目標碼檔案caios$$。檔名中包含當前程式號$$,可以保證目標檔名的唯一性。後面的任選項告訴編譯程式cc,編譯時引用我們寫的OSCAI庫函式。命令列中的caiosinit.c對使用者程式做些預處理,執行經改造了的使用者程式mmain,最後做些善後處理。其程式碼為:
main(){
caiinit();
mmain();
endinit();
}
3. 命令“caios$$”是執行目標檔案。
4. 執行命令“rmcai $1”,還原上面被加工了的使用者程式。

考慮核心資料結構太多,對於OSCAI追蹤系統呼叫執行過程中,要顯示的系統資料進行了精選。僅顯示核心3類重要資料結構,即有關程式管理方面的proc結構、user結構;有關儲存管理方面的u.u_uisa和u.u_uisd頁表結構;有關檔案管理方面的filesys、inode、 u.u_file和file結構。這批資料讓使用者對於核心的瞬時情況有了基本瞭解。這些資訊無法從核心獲得的,來自我們的OSCAI,凡是能夠經系統呼叫從核心取得的,便直接取自核心,以儘量保持資料的真實性。

系統以醒目的表格形式顯示各個資料結構。要顯示的資料結構,有的往往又含有指向其它資料結構的指標成員,在這種情況下,就像對一顆樹進行搜尋一樣,不用退出顯示程式,就可以逐枝地任意搜尋整棵樹。顯示模組還利用了VDU-140圖形終端具有4屏顯示緩衝的特點,可以對4屏內容同時、輪流查閱。

下面引用一個例項。現有一個C語言的父子程式通訊程式:
char buf[]={“A:How do you do?\n”};
main()
{
int fpd[2];
char buf[20] ; pipe(fdp);
if(fork()==0) {
close(fdp[0]);
write(fdp[1],buf,20);
close(fdp[1]);
exit(0);
}
read(fdp[0],buf,20);
printf(“%s\n”,buf);
}

上面程式的功能是父程式用系統呼叫pipe() 建立一個通訊管道,並用系統調fork()建立一個子程式,子程式經管道向父程式發信“A:How do you do?”,父程式接收後把信件顯示到螢幕上。該程式被OSCAI按照上述辦法做第一步處理後,將被改造成下述樣子:
char buf[]={“A:How do you do?\n”};
mmain()
{
int fpd[2];
char buf[20];
caipipe(fdp);
if(caifork()==0) { caiclose(fdp[0]) ; write(fdp[1],buf,20);
caiclose(fdp[1]);
caiexit(0);
}
read(fdp[0],buf,20);
printf(“%s\n”,buf);
}

其中有幾條系統呼叫pipe、fork、close、exit被修改成由我們控制的caipipe、caifork、caiclose、caiexit,而系統呼叫read、write當時尚未把它們寫到OSCAI中,那就直接交給UNIX處理。程式執行中可以顯示一些資料結構的變化,以反映作業系統核心加工該程式的情況。

作為第一階段工作,我們在OSCAI子程式庫中,包含了25條以cai打頭的系統呼叫。

系統開發完成後,我們寫了一篇文章,參加了1988年10月在上海召開的亞太你地區國際計算機教育會議。同一組4篇論文中,另有清華大學、復旦大學各一篇,還有一篇是美國人的。

在掌握了UNIX和C語言後,又一項運用是,對於我的同事們正在進行的“UNIX漢化”專案進行支援。當時執行最新的UNIX第七版的Dual 68000的多使用者微機,是純英文環境。馬友申老師等正在做漢化工作,將國標的常用漢字以點陣字型檔的方式存到硬碟上,提供輸入/輸出介面,必要時能夠從硬碟上取出漢字點陣,送到顯示緩衝器把漢字顯示到螢幕上,或者送到印表機列印。當時還沒有帶硬字型檔的漢字印表機,用這種方式列印速度比較慢,但是解決了從無到有的問題也是一個飛躍。我參與用C語言編寫了電話號碼查詢程式。基本實現是將一組常用電話,用“單位或姓名 電話號碼”記錄形式,存於正文檔案中,查詢程式每次從檔案讀入一塊512位元組資訊到記憶體快取。提供查詢介面接收查詢輸入,用C語言提供的字串操作函式,逐條記錄進行匹配,將匹配的記錄顯示到螢幕上。在專案的鑑定會上,專家們隨意用他單位或者本人的中文名,都可以執行程式phone查出所要的電話號碼,或者反過來,也可以用電話號碼查出名稱。程式經編譯讓phone獲得執行權後,就可以查詢了。例如執行
$ phone 湖南大學
將報告
湖南大學 82711
執行
$ phone 26356
將報告
國防科大 26356
當時還是用5位電話號碼。由此表明我們得到了最初的UNIX系統的漢化成果,時間是1984年。

用C語言寫的電話號碼查詢程式不到50行,很小巧。我們還用UNIX的shell語言寫了同樣功能的程式,那就更簡單了。比如先把電話號碼存到當前目錄下檔案phone_num中,再用下列方法生成一個執行grep查詢功能的程式檔案phone。
$ echo ‘grep $1 ./ phone_num’ > phone
這個shell程式phone中只有一個語句,比C語言程式簡單多了。用 $chomd +x phone
賦予它執行權,就可以像上述C程式一樣進行查詢了。

(與本文相關的更多內容,可參看 彭民德《電子計算60年》第4章 三代計算併發共享 電子工業出版社)

相關文章