- 目錄
- 概述
- 硬件 — CPU指令集:機器指令、匯編指令
- 操作系統(tǒng)
- 內(nèi)核Kernel及系統(tǒng)調(diào)用埂材、實現(xiàn)說明
- 殼層Shell
- 目前的操作系統(tǒng)舉例
- ABI和API
- 標準庫
- 高級語言編程
一塑顺、概述
首先看一下軟硬件間的關(guān)系,如圖所示:
二俏险、硬件 — CPU指令集
一般指令集專利持有者在設(shè)計指令集的時候茬暇,往往提供指令集對應(yīng)的機器語言規(guī)范首昔。 而為了方便,一般也會提供匯編語言規(guī)范糙俗。
(注意:CPU微架構(gòu)設(shè)計廠商可能會對指令集進行微調(diào)勒奇。關(guān)于指令集設(shè)計者、微架構(gòu)設(shè)計者兩者的關(guān)系可以參考指令集巧骚、微架構(gòu)赊颠、手機芯片(Soc)及ARM的介紹(偏硬件科普))
匯編語言(assembly language)是一種用于電子計算機、微處理器劈彪、微控制器或其他可編程器件的低級語言竣蹦,亦稱為符號語言。在匯編語言中沧奴,用
助記符
代替機器指令的操作碼痘括,用地址符號或標號
代替指令或操作數(shù)的地址。匯編語言又被稱為第二代計算機語言滔吠。
匯編語言產(chǎn)生的原因:
對于絕大多數(shù)人來說纲菌,二進制程序是不可讀的,當然有能人可以讀疮绷,比如第一代程序員翰舌,但這類人快滅絕了,直接看二進制不容易看出來究竟做了什么事情冬骚,比如最簡單的加法指令二進制表示為 00000011椅贱,如果它混在一大串01字符串中就很難把它找出來,所以匯編語言主要就是為了解決二進制編碼的可讀性及編寫效率問題(CPU指令集機器碼形式記不住呀...)只冻。
即指令集有兩種表示形式:機器碼形式庇麦、匯編語言形式。比如 00000011 加法指令喜德,對應(yīng)的匯編指令是 ADD山橄,在調(diào)用匯編器時就會把 ADD 翻譯成 00000011。
常見的指令集以及匯編語言規(guī)范:
-
x86(IA-32)住诸、x86-64指令集(常見于PC端)驾胆,對應(yīng)有2家公司發(fā)布的不同匯編語言規(guī)范:
intel公司發(fā)布的匯編語言規(guī)范,稱
intel 匯編
:Windows派系(Microsoft)贱呐,比較著名的匯編器有微軟的masm和開源的nasm丧诺。AT&T公司發(fā)布的匯編語言規(guī)范,稱
AT&T 匯編
:Unix派系(或者說GNU)奄薇,比如g++編譯器等驳阎。
-
ARM指令集(常見于嵌入式、移動端設(shè)備,粗略統(tǒng)計覆蓋95%左右的手段):ARM公司發(fā)布的匯編語言規(guī)范呵晚,稱ARM 匯編(目前常見的是ARM 64匯編)蜘腌,見官網(wǎng)文檔
匯編語言規(guī)范,是給匯編程序開發(fā)者看的饵隙,也是給編譯器(主要是匯編器)看的撮珠,目的只有一個:保證匯編程序能通過匯編器轉(zhuǎn)換成CPU兼容執(zhí)行、實現(xiàn)邏輯功能的指令(二進制碼)序列金矛。如果你能編寫自己的匯編器芯急,完全可以定義自己的匯編語言規(guī)范
所以,匯編語言和機器語言是一一對應(yīng)的嗎驶俊?
在同一匯編規(guī)范下娶耍,它們是一一對應(yīng)的。如果考慮到不同的匯編語言規(guī)范饼酿,它們就不是一一對應(yīng)的了榕酒。在多數(shù)場合,籠統(tǒng)地說故俐,匯編語言和機器語言是一一對應(yīng)的(比如 00000011 就是 ADD)想鹰。所以匯編語言就和機器語言一樣,很受硬件限制购披,移植性很差杖挣。
高級語言在執(zhí)行時肩榕,需要 → 編譯器翻譯為匯編語言 → 匯編器翻譯為機器語言刚陡,此時,才能夠被CPU識別并執(zhí)行株汉。指令集直接操作硬件筐乳。
通常是先指定使用的指令集,然后去設(shè)計微架構(gòu)(處理器硬件結(jié)構(gòu))乔妈。
三蝙云、操作系統(tǒng)
操作系統(tǒng)分為內(nèi)核(Kernel)、殼層(Shell)
3.1 內(nèi)核(Kernel)與系統(tǒng)調(diào)用
3.1.1 內(nèi)核概述
內(nèi)核(Kernel路召,又稱核心)在計算機科學(xué)中是一個用來管理軟件發(fā)出的數(shù)據(jù)I/O(輸入與輸出)要求的電腦程序勃刨,是現(xiàn)代操作系統(tǒng)中最基本的部分。
它是為眾多應(yīng)用程序提供對計算機硬件的安全訪問的一部分軟件股淡,這種訪問是有限的身隐,并由內(nèi)核決定一個程序在什么時候?qū)δ巢糠钟布僮鞫嚅L時間。直接對硬件操作是非常復(fù)雜的唯灵。所以內(nèi)核通常提供一種硬件抽象的方法贾铝,來完成這些操作。
總結(jié):內(nèi)核對硬件的操作進行封裝,向上層(應(yīng)用垢揩、Shell)提供接口玖绿,使上層使用硬件更方便,會對上層的硬件資源申請統(tǒng)一管理叁巨,當然也就意味上層的使用會受到一些限制斑匪。
嚴格地說,內(nèi)核并不是計算機系統(tǒng)中必要的組成部分锋勺。有些程序可以直接地被調(diào)入計算機中執(zhí)行秤标;這樣的設(shè)計,說明了設(shè)計者不希望提供任何硬件抽象和操作系統(tǒng)的支持宙刘;它常見于早期計算機系統(tǒng)的設(shè)計中苍姜。
但隨著電腦技術(shù)的發(fā)展,最終悬包,一些輔助性程序衙猪,例如程序加載器和調(diào)試器,被設(shè)計到機器內(nèi)核當中布近,或者寫入在只讀記憶體里垫释。這些變化發(fā)生時,操作系統(tǒng)內(nèi)核的概念就漸漸明晰起來了撑瞧。
3.1.2 系統(tǒng)調(diào)用
內(nèi)核提供的接口稱為系統(tǒng)調(diào)用棵譬,指運行在用戶空間的程序訪問操作系統(tǒng)內(nèi)核所提供服務(wù)的接口(內(nèi)核服務(wù)意味著更高的權(quán)限)。系統(tǒng)調(diào)用是代碼方式使用预伺,也就表示了用戶(非編程人員)不能直接與內(nèi)核進行交互订咸。
系統(tǒng)調(diào)用是基于CPU指令集的,進行封裝酬诀、擴展(比如擴展中斷向量脏嚷,實現(xiàn)一系列的系統(tǒng)調(diào)用)。類似于語言庫中的API對系統(tǒng)調(diào)用的封裝瞒御。
調(diào)用系統(tǒng)調(diào)用的兩種方式:以Linux為例(其他系統(tǒng)也是類似)父叙,在 Linux 平臺下有兩種方式來使用系統(tǒng)調(diào)用:
利用封裝后的 C 庫(libc)。Linux肴裙、Windows趾唱、iOS等許多系統(tǒng)內(nèi)核大部分都是C語言編寫的,其中也有少部分的C++蜻懦、C#等語言甜癞,所以大部分的系統(tǒng)調(diào)用通常是C、C++語言格式的
通過匯編直接調(diào)用阻肩。通過匯編語言來直接調(diào)用系統(tǒng)調(diào)用带欢,是最高效地使用 Linux 內(nèi)核服務(wù)的方法运授,因為最終生成的程序不需要與任何庫進行鏈接,而是直接和內(nèi)核通信乔煞。
第一種方式是對第二種方式的封裝吁朦。如DOS、Linux 的系統(tǒng)調(diào)用都是通過中斷匯編指令(int 0x80)來實現(xiàn)的渡贾。(見下面—系統(tǒng)調(diào)用的實現(xiàn))
3.1.3 系統(tǒng)調(diào)用的實現(xiàn)
系統(tǒng)調(diào)用接口在實現(xiàn)中往往以軟件中斷(Software interrupt)逗宜,簡稱INT(軟中斷)的方式提供,比如:
Linux 使用
INT 0x80
(INT是匯編指令空骚,0x80為參數(shù)纺讲,表示80號中斷)作為系統(tǒng)調(diào)用接口。Windows使用 0x2E 號中斷作為系統(tǒng)調(diào)用接口(從Windows XP Sp2開始囤屹,Windows開始采用一種新的系統(tǒng)調(diào)用方式)熬甚。
以Linux為例,在進行系統(tǒng)調(diào)用的時候:
發(fā)起int 0x80中斷肋坚,并傳入系統(tǒng)調(diào)用號乡括。系統(tǒng)調(diào)用號:有一個
sys_call_table
是一個全局函數(shù)數(shù)組,存儲所有系統(tǒng)調(diào)用的地址(可以查看include/linux/sys.h
文件)智厌,系統(tǒng)調(diào)用號是系統(tǒng)調(diào)用在該數(shù)組中的下標诲泌。調(diào)用
set_system_gate
函數(shù)處理中斷,首先從當前的用戶態(tài)切換到內(nèi)核態(tài)铣鹏,然后找到中斷向量表敷扫,根據(jù)中斷編號(0x80),找到對應(yīng)的中斷處理程序找到中斷處理程序
system_call
(在linux/kernel/system_call.s
中诚卸,是匯編程序)葵第,根據(jù)系統(tǒng)調(diào)用號在sys_call_table
表中找到相應(yīng)系統(tǒng)調(diào)用對應(yīng)的函數(shù)入口。調(diào)用
call匯編指令
惨险,輸入函數(shù)地址羹幸。
3.1.4 關(guān)于中斷向量的一些補充
計算機啟動時脊髓,引導(dǎo)扇區(qū)辫愉、系統(tǒng)內(nèi)核會陸續(xù)的將一些中斷填入中斷向量表,并將中斷向量指向自己編寫的中斷服務(wù)程序将硝。
如操作系統(tǒng)啟動過程的最后恭朗,由
head.s
進入main.c
中,會進行一系列的初始化:內(nèi)存依疼、設(shè)備痰腮、時鐘、中斷等律罢。其中:trap_init()
函數(shù)膀值,會初始化一些中斷向量sched_init()
函數(shù)中會調(diào)用set_system_gate(0x80,&system_call)
棍丐,設(shè)置中斷向量號0x80的中斷描述符
系統(tǒng)也會為用戶保留一些中斷向量,用戶可以將自己的中斷服務(wù)程序?qū)懭脒@些中斷向量中沧踏。不僅如此歌逢,用戶還可以自己更改和完善系統(tǒng)已有的中斷向量。相當于給INT指令增加參數(shù)(中斷向量號)選項翘狱,且指定對應(yīng)的調(diào)用程序秘案。
引導(dǎo)扇區(qū)(實模式) → bootloader(實模式 → 保護模式,R0層權(quán)限) → 操作系統(tǒng)(保護模式潦匈,R0層權(quán)限) → 操作系統(tǒng)先加載的是內(nèi)核阱高,內(nèi)核處于R0,shell已經(jīng)是在用戶態(tài)了
3.2 殼層(Shell)
操作系統(tǒng)存在的目的是方便用戶的使用茬缩,而內(nèi)核提供的代碼形式的系統(tǒng)調(diào)用赤惊,是面向編程開發(fā)者的,所以需要一個殼層:在計算機科學(xué)中指“為用戶提供用戶界面”的軟件凰锡,或者說是指操作系統(tǒng)中提供訪問內(nèi)核所提供之服務(wù)的程序
Shell又分為了CLI Shell(命令行)荐捻、GUI Shell(圖形化)兩種形式,用戶都可以用來使用操作系統(tǒng)寡夹。區(qū)別:
- 樣式區(qū)別:前者的頁面是一行行的文字命令处面,后者是圖形狀的
- 程序(APP、命令行工具)的使用:前者需要用戶記住每個程序的路徑菩掏,然后輸入特定的命令來調(diào)用對應(yīng)的功能魂角。后者會將所有的APP以圖標的形式顯示在桌面上,通過點擊就可使用
- 本質(zhì):本質(zhì)上沒區(qū)別智绸,都是對系統(tǒng)調(diào)用的封裝野揪,功能一定程度上都是共通的
3.3 目前的操作系統(tǒng)為例
對上面的知識點進行總結(jié),以目前的操作系統(tǒng)為例:
針對不同的角色瞧栗,操作系統(tǒng)提供了不同的用戶界面(不同的操作系統(tǒng)提供的用戶界面也不同)
- 對普通用戶和管理員用戶提供命令控制界面(也就是
Shell
):是一組不同操作命令組成的集合斯稳,每個命令實現(xiàn)用戶所要求的不同功能,為用戶提供相應(yīng)的服務(wù)迹恐。- 包括GUI圖形化界面挣惰、CLI命令行界面
- 用戶利用這些操作命令來組織和控制作業(yè)的執(zhí)行。
- 對編程人員提供編程界面:是一組系統(tǒng)調(diào)用的集合殴边,這些系統(tǒng)調(diào)用允許編程人員請求操作系統(tǒng)內(nèi)核提供的服務(wù)憎茂,開發(fā)能夠滿足用戶服務(wù)需求的新的控制命令。
- 命令控制界面是基于編程界面锤岸,也就是系統(tǒng)調(diào)用之上開發(fā)完成的竖幔。
3.4 ABI與API
操作系統(tǒng)相關(guān)的有兩類接口(意思并不是說只有OS才有ABI、API):
- API應(yīng)用程序接口:源代碼層次的接口是偷,即操作系統(tǒng)提供了哪些系統(tǒng)調(diào)用(一般是C/C++編寫)拳氢,正確調(diào)用OS提供的API募逞,才能被成功編譯
- ABI應(yīng)用二進制接口:機器碼層次的接口,按照系統(tǒng)ABI編寫(或轉(zhuǎn)換成)的機器碼文件(目標文件馋评、可執(zhí)行文件)凡辱,才能成功與OS提供的庫(也是目標文件,機器碼)鏈接栗恩,然后成功被OS裝載運行透乾。
3.4.1 API
API(Application Programming Interface)
應(yīng)用程序接口:定義了源代碼和庫之間的接口(函數(shù)名、參數(shù)磕秤、返回值乳乌、數(shù)據(jù)類型定義等),即規(guī)定源代碼可以怎么使用庫的功能市咆,使得一套 源代碼
可以在支持這個API的任何系統(tǒng)中 編譯
汉操。
3.4.2 ABI
ABI(Application Binary Interface)
應(yīng)用二進制接口:是指兩程序模塊間的接口,通常其中一個程序模塊會是庫或操作系統(tǒng)所提供的服務(wù)蒙兰,而另一邊的模塊則是用戶所運行的程序(前面已經(jīng)知道程序主模塊磷瘤、動態(tài)鏈接庫都叫程序模塊)。
規(guī)定了機器代碼的書寫格式(二進制應(yīng)用程序應(yīng)該怎么調(diào)用CPU指令集中的指令)搜变,使得一套編譯好的二進制代碼(目標文件采缚、可執(zhí)行文件)可以在兼容ABI的系統(tǒng)中,無需任何修改直接運行挠他。
- 決定要不要采取既定的ABI(不論是否由官方提供)扳抽,通常由編譯器,操作系統(tǒng)或庫的開發(fā)者來決定
ABI涵蓋了各種細節(jié)殖侵,如:
- 數(shù)據(jù)類型的大小贸呢、布局和對齊;
- 調(diào)用約定(控制著函數(shù)的參數(shù)如何傳送以及如何接受返回值),例如:
- 是所有的參數(shù)都通過棧傳遞拢军,還是部分參數(shù)通過寄存器傳遞楞陷;
- 哪個寄存器用于哪個函數(shù)參數(shù);
- 通過棧傳遞的第一個函數(shù)參數(shù)是最先push到棧上還是最后茉唉;
- 系統(tǒng)調(diào)用的編碼和一個應(yīng)用如何向操作系統(tǒng)進行系統(tǒng)調(diào)用固蛾;
- 以及在一個完整的操作系統(tǒng)ABI中,目標文件的二進制格式赌渣、程序庫等等魏铅。
3.4.3 兩者對比
- 定義層級
- API在源代碼定義這些,則較為高端坚芜,并不直接相依于硬件,通常會是人類可閱讀的代碼斜姥。
- ABI在二進制代碼(目標文件鸿竖、可執(zhí)行文件)層次定義了機器代碼怎么寫沧竟,此處所定義的界面相當?shù)投瞬⑶蚁嘁烙谟布?/li>
- 標準化方面
- POSIX 標準、C99 標準缚忧,都是對 API 的規(guī)定悟泵。
- 有一些努力嘗試標準化ABI,以減少銷售商將程序移植到其他系統(tǒng)時所需的工作闪水。然而糕非,直到現(xiàn)在還沒有很成功的例子,雖然Linux標準化工作組正在為Linux做這方面的努力球榆。
四朽肥、語言標準庫
編程語言的標準庫是該語言的每種實現(xiàn)中都按例提供的庫。在某些情況下持钉,編程語言規(guī)格說明中會直接提及該庫衡招;另一些情況下,標準庫的內(nèi)容由編程社區(qū)中的非正式慣例決定每强。
根據(jù)宿主語言構(gòu)成要素的不同始腾,標準庫可包含如下要素:
- 子程序
- 宏定義
- 全局變量
- 類別定義
- 模板
大多數(shù)標準庫都至少含有如下常用組件的定義:
- 算法(例如排序算法)
- 數(shù)據(jù)結(jié)構(gòu)(例如 表、 樹空执、哈希表)
- 與宿主平臺的交互浪箭,包括輸入輸出和操作系統(tǒng)調(diào)用
比如我們經(jīng)典的C語言版“hello world”程序,使用C語言標準庫的“printf”函數(shù)來輸出一個字符串辨绊,“printf”函數(shù)對字符串進行一些必要的處理以后山林,最后會調(diào)用操作系統(tǒng)提供的系統(tǒng)調(diào)用。
各個操作系統(tǒng)邢羔,往終端輸出字符串的API都不一樣驼抹,在Linux下,它是一個 “write”
的系統(tǒng)調(diào)用拜鹤,而在Windows下則是 “WriteConsole”
系統(tǒng)API框冀。此外,還帶有很多一些常用的函數(shù)敏簿。
Q1:如Java明也、Object-C語言庫可不可以不調(diào)用C、C++語言格式的系統(tǒng)調(diào)用(API)惯裕?
當然可以温数,上文講過,操作系統(tǒng)一般都提供了兩種方式來使用系統(tǒng)調(diào)用:C語言庫蜻势、匯編語言撑刺。系統(tǒng)調(diào)用對Linux、DOS來說只是int 0x80(中斷)而已握玛,所以不管語言庫是什么語言編寫的够傍,(在Linux上運行的軟件代碼)只要能通過編譯器翻譯成對應(yīng)Linux下的int 0x80的匯編語言就行了甫菠。
但是注意:封裝高級語言格式的系統(tǒng)調(diào)用、設(shè)置0x80中斷及對應(yīng)的中斷處理程序?qū)崿F(xiàn)系統(tǒng)調(diào)用冕屯,都是操作系統(tǒng)提供的功能寂诱。
而且操作系統(tǒng)的系統(tǒng)調(diào)用實現(xiàn)是一定的,兩種方式(其實是一種安聘,C庫本質(zhì)上也是封裝的匯編中斷指令)最后找到的系統(tǒng)調(diào)用函數(shù)的實現(xiàn)(函數(shù)入口地址)都是一樣的...
其實痰洒,編程人員開發(fā)時,用什么語言來調(diào)用系統(tǒng)調(diào)用是無所謂的浴韭,因為運行的時候丘喻,都需要編譯、匯編成機器代碼囱桨。從硬件角度來看(即最后的可執(zhí)行文件)仓犬,區(qū)別不大的(高級語言調(diào)用、匯編語言調(diào)用最后生成的機器碼還是有些區(qū)別的)舍肠。
Q2:既然可以直接寫匯編語言搀继,而匯編語言又是直接對應(yīng)CPU指令,即可以直接操作硬件資源翠语,那為什么不能越過操作系統(tǒng)直接使用硬件叽躯?
不能。主語不要弄錯肌括,并不是匯編語言為什么不能操控硬件点骑,是用戶程序不能越過操作系統(tǒng)操控硬件。
核心問題:用戶程序和系統(tǒng)調(diào)用的權(quán)限問題谍夭。
系統(tǒng)調(diào)用是操作系統(tǒng)內(nèi)核的代碼黑滴,擁有最高的權(quán)限。以x86架構(gòu)為例紧索,x86指令運行的權(quán)限是從Ring0到Ring3的袁辈,操作系統(tǒng)內(nèi)核運行在R0,用戶程序運行在R3珠漂。
有一些指令只能在R0執(zhí)行晚缩,比如修改CR寄存器(mov cr0, eax),這種操作在用戶態(tài)直接會報錯媳危。因為CPU會保存當前運行的代碼的CPL(當前特權(quán)級別)和IOPL(IO權(quán)限)荞彼,如果CPL/IOPL不符,CPU會拋出異常待笑,丟給內(nèi)核R0的代碼去處理鸣皂。這種錯誤,是執(zhí)行到某條特定指令才會遇到的,不是加載的時候遇到的签夭,是CPU行為齐邦,不是操作系統(tǒng)行為椎侠。
操作系統(tǒng)之所以不會被限制第租,是因為操作系統(tǒng)內(nèi)核運行在R0上,對CPU有完整的控制權(quán)我纪。
計算機剛啟動時慎宾,屬于實模式,從實模式切換到保護模式的過程中浅悉,默認是進入到R0里的趟据,所以操作系統(tǒng)在啟動的過程中,是自動獲得了R0的權(quán)限的术健。而用戶代碼都是被操作系統(tǒng)啟動的(裝載汹碱、鏈接),此時操作系統(tǒng)能控制用戶代碼運行在什么級別上荞估。
那么為什么操作系統(tǒng)的代碼就可以切換特權(quán)級而用戶的代碼卻不可以切換特權(quán)級咳促?
實際上可以的。Windows可以加載用戶驅(qū)動到內(nèi)核(但受到一些限制)勘伺,這就是把用戶代碼放到R0里執(zhí)行的過程跪腹,Linux也有類似的東西存在。
五飞醉、編程開發(fā)的應(yīng)用程序
內(nèi)核 → 系統(tǒng)調(diào)用 → Shell
內(nèi)核 → 系統(tǒng)調(diào)用 → 編程語言標準庫 → 編程人員開發(fā)的應(yīng)用程序
以我們?nèi)粘J褂玫氖謾C系統(tǒng)為例冲茸,應(yīng)用程序與GUI Shell的關(guān)系其實并沒有誰基于誰的關(guān)系,Shell本質(zhì)上也是一個應(yīng)用程序缅帘。都是依賴于內(nèi)核提供的系統(tǒng)調(diào)用開發(fā)轴术、運行的。只不過功能不同而已:
- 應(yīng)用程序是編程人員為了實現(xiàn)某些功能(比如看書钦无、聽音樂等)開發(fā)的
- GUI Shell逗栽,它的功能是以圖形化的方式管理所有的應(yīng)用入口(并內(nèi)置了一些應(yīng)用程序比如文件管理、設(shè)置之類的)铃诬,并負責將用戶的一些交互行為轉(zhuǎn)換為控制命令祭陷。
- 比如用戶點擊APP圖標,打開APP趣席,本質(zhì)上是GUI Shell幫忙調(diào)用了系統(tǒng)調(diào)用:
fork()
創(chuàng)建進程 →execve()
執(zhí)行指定的可執(zhí)行文件等
- 比如用戶點擊APP圖標,打開APP趣席,本質(zhì)上是GUI Shell幫忙調(diào)用了系統(tǒng)調(diào)用: