寫(xiě)程序離不開(kāi)運(yùn)行時(shí),但是有很多伙伴卻并沒(méi)有搞清楚運(yùn)行時(shí)到底是什么。運(yùn)行時(shí)的概念之所以容易被混淆,是因?yàn)檫\(yùn)行時(shí)有兩層不同的含義:run time 和 runtime,先賢們?cè)诜g的時(shí)候可能忽略了中間的空格,導(dǎo)致運(yùn)行時(shí)一詞代表了兩種含義:運(yùn)行時(shí)期和運(yùn)行環(huán)境/系統(tǒng)。
運(yùn)行時(shí)期(Run time)在計(jì)算機(jī)科學(xué)中代表一個(gè)程序從開(kāi)始執(zhí)行到終止執(zhí)行的運(yùn)作時(shí)期,與之相對(duì)的其他時(shí)期包括:設(shè)計(jì)時(shí)期(design time)、編譯時(shí)期(compile time)、鏈接時(shí)期(link time)與載入時(shí)期(load time)。
runtime 是一個(gè)通用抽象的術(shù)語(yǔ),指的是計(jì)算機(jī)程序運(yùn)行的時(shí)候所需要的一切代碼庫(kù),框架,平臺(tái)等。它包括了程序的執(zhí)行環(huán)境和執(zhí)行狀態(tài),以及程序在運(yùn)行時(shí)所產(chǎn)生的各種數(shù)據(jù)和結(jié)果。runtime主要是為了實(shí)現(xiàn)程序設(shè)計(jì)語(yǔ)言的執(zhí)行模型,在每種語(yǔ)言有著不同的實(shí)現(xiàn)。runtime的概念在編程中非常重要,它關(guān)系到程序的正確性、穩(wěn)定性和性能等方面。
run time 和 runtime 就有區(qū)別又有聯(lián)系,都是軟件設(shè)計(jì)的基礎(chǔ)。run time 是從程序自身的視角來(lái)看的,而runtime 則是從程序的執(zhí)行環(huán)境和依賴的視角來(lái)看的。因此,在我們討論運(yùn)行時(shí)的時(shí)候,一定要明確上下文,才能理解運(yùn)行時(shí)的真正含義,并通過(guò)運(yùn)行時(shí)的相關(guān)技術(shù)讓我們的軟件系統(tǒng)更加靈活可靠。
運(yùn)行時(shí):run time——運(yùn)行時(shí)期
運(yùn)行時(shí)期(run time)是程序生命周期的一個(gè)階段,是代碼在執(zhí)行時(shí)的行為。編譯時(shí)期(compile time)是指程序設(shè)計(jì)中,編譯器在編譯源代碼時(shí)的行為,包括語(yǔ)法分析、語(yǔ)義分析、類型檢查、模板實(shí)例化、代碼生成等。編程語(yǔ)言通常會(huì)指出源程序必須滿足的編譯時(shí)期要求。有些編程語(yǔ)言在鏈接時(shí)期或運(yùn)行時(shí)期才執(zhí)行一部分編譯,例如即時(shí)編譯(Just-in-time compilation)。
程序執(zhí)行中的某些問(wèn)題只能在運(yùn)行時(shí)期才能進(jìn)行檢測(cè),例如邏輯錯(cuò)誤或數(shù)組邊界檢查等。因此,用戶也會(huì)遇到諸如運(yùn)行時(shí)錯(cuò)誤之類的信息。
運(yùn)行時(shí)期的驗(yàn)證
運(yùn)行時(shí)驗(yàn)證是一種輕量級(jí)的驗(yàn)證技術(shù)。它是傳統(tǒng)驗(yàn)證技術(shù),是模型檢測(cè)和測(cè)試的一個(gè)有效補(bǔ)充。它最重要的一個(gè)特征是其驗(yàn)證對(duì)象為被監(jiān)控系統(tǒng)的實(shí)際運(yùn)行,從而使得檢測(cè)到錯(cuò)誤時(shí)能及時(shí)采取相應(yīng)的調(diào)整行為,以達(dá)到避免軟件失效發(fā)生或阻止軟件失效進(jìn)一步傳播的目的。
運(yùn)行時(shí)驗(yàn)證關(guān)注于檢測(cè)程序的運(yùn)行軌跡是否滿足或違背監(jiān)控的性質(zhì)。當(dāng)程序的運(yùn)行滿足或違背監(jiān)控性質(zhì)時(shí),運(yùn)行時(shí)驗(yàn)證技術(shù)一般不會(huì)對(duì)被監(jiān)控的系統(tǒng)進(jìn)行調(diào)整。也就是說(shuō),運(yùn)行時(shí)驗(yàn)證技術(shù)是關(guān)于程序運(yùn)行軌跡是否滿足監(jiān)控性質(zhì)的判別技術(shù)?;贏OP的監(jiān)控器具有高效(不需要進(jìn)行環(huán)境切換)、容易部署(不涉及到對(duì)虛擬機(jī)和操作系統(tǒng)的修改)和準(zhǔn)確(基于AOP的監(jiān)控器能夠獲取被監(jiān)控程序更多的狀態(tài)信息)的特點(diǎn)。
程序在編譯時(shí)期和運(yùn)行時(shí)間的不同體現(xiàn)
這里以多態(tài)為例來(lái)描述一下程序特性在編譯時(shí)期和運(yùn)行時(shí)間的不同體現(xiàn)。
先簡(jiǎn)要澄清一下多態(tài)。多態(tài)指同一個(gè)實(shí)體同時(shí)具有多種形式,是面向?qū)ο蟪绦蛟O(shè)計(jì)(OOP)的一個(gè)重要特征。也就是說(shuō),同一操作作用于不同的對(duì)象,可以有不同的解釋,產(chǎn)生不同的執(zhí)行結(jié)果。在運(yùn)行時(shí),可以通過(guò)指向基類的指針,來(lái)調(diào)用實(shí)現(xiàn)派生類中的方法。如果一個(gè)語(yǔ)言只支持類而不支持多態(tài),只能說(shuō)明它是基于對(duì)象的,而不面向?qū)ο蟮摹?/p>
多態(tài)分為兩種情況:編譯時(shí)多態(tài)與運(yùn)行時(shí)多態(tài)。編譯時(shí)多態(tài)即在編譯時(shí)就能夠確定調(diào)用哪個(gè)方法。而運(yùn)行時(shí)多態(tài)則相反,只有在運(yùn)行時(shí)才能確定調(diào)用哪個(gè)方法。在方法重載時(shí),都是編譯時(shí)多態(tài)。在編譯期可根據(jù)參數(shù)的數(shù)據(jù)類型、個(gè)數(shù)以及次序來(lái)確定調(diào)用方法。當(dāng)子類對(duì)象引用自身類實(shí)例方法時(shí),也為編譯時(shí)多態(tài)。但是當(dāng)父類對(duì)象引用子類實(shí)例方法時(shí),是運(yùn)行時(shí)多態(tài),因?yàn)榇藭r(shí)只有在運(yùn)行時(shí)才可以去匹配到對(duì)應(yīng)方法進(jìn)行調(diào)用。
運(yùn)行時(shí)期的軟件配置
運(yùn)行時(shí)期的軟件配置是軟件配置的一種形式,增加了軟件系統(tǒng)的靈活性和適應(yīng)性。
軟件配置可以分為運(yùn)行時(shí)期配置(run time configuration) 和編譯時(shí)配置 (compile time configuration). 運(yùn)行時(shí)配置是軟件的重要接口, 可以定制不同的功能、管理資源的分配、適應(yīng)環(huán)境的變化以及滿足不同用戶的需求, 主要面向軟件管理員和用戶, 無(wú)需重新編譯部署即可實(shí)現(xiàn)軟件調(diào)整. 而編譯時(shí)配置也稱軟件生產(chǎn)線配置(software product line configuration, SPL configuration),主要用于軟件構(gòu)建和部署時(shí)期決定特定功能模塊是否加入可執(zhí)行程序中, 以此決定軟件的最終形態(tài), 主要面向軟件開(kāi)發(fā)和部署人員, 必須重新編譯才能使用。
運(yùn)行時(shí)期的編譯優(yōu)化
一般地,軟件的優(yōu)化技術(shù)分成兩類:靜態(tài)優(yōu)化和動(dòng)態(tài)優(yōu)化。其中,動(dòng)態(tài)優(yōu)化目前主要集中在對(duì)動(dòng)態(tài)語(yǔ)言程序如 Java、Ruby、Python 等的優(yōu)化。運(yùn)行時(shí)期的軟件優(yōu)化大多通過(guò)動(dòng)態(tài)編譯技術(shù)來(lái)實(shí)現(xiàn)。
動(dòng)態(tài)編譯技術(shù)又可以分為兩大類:①選擇性編譯:主要關(guān)注何時(shí)選擇程序哪一部分代碼進(jìn)行動(dòng)態(tài)編譯和優(yōu)化;②基于反饋的優(yōu)化:利用剖析信息來(lái)指導(dǎo)動(dòng)態(tài)編譯器來(lái)做何種級(jí)別的編譯優(yōu)化。動(dòng)態(tài)編譯器通常支持兩種執(zhí)行方式:一種是解釋執(zhí)行或無(wú)優(yōu)化的編譯器編譯執(zhí)行;一種是優(yōu)化編譯,對(duì)熱點(diǎn)路徑進(jìn)行重點(diǎn)優(yōu)化。動(dòng)態(tài)編譯器中的編譯優(yōu)化工作是在程序運(yùn)行時(shí)期中進(jìn)行,編譯開(kāi)銷包含在程序的運(yùn)行開(kāi)銷中。
動(dòng)態(tài)編譯器不僅可以選擇對(duì)哪些代碼做編譯或者優(yōu)化編譯,而且還可以在程序的運(yùn)行中采集程序的運(yùn)行信息,主要包括程序的循環(huán)深度、程序輸入以及運(yùn)行環(huán)境(操作系統(tǒng)、體系結(jié)構(gòu))等。利用這些運(yùn)行時(shí)信息,動(dòng)態(tài)編譯器可以更好地決定采用何種編譯優(yōu)化策略對(duì)程序進(jìn)行編譯優(yōu)化。動(dòng)態(tài)剖析技術(shù)使得多級(jí)優(yōu)化編譯成為可能,每一級(jí)采用特定的優(yōu)化策略,熱點(diǎn)的性
能更容易得到提升,這就是基于反饋的動(dòng)態(tài)優(yōu)化技術(shù))。
基于反饋的動(dòng)態(tài)優(yōu)化技術(shù)主要通過(guò)對(duì)程序插樁,利用插入的代碼收集需要的程序信息,然后將它們按照一定的格式組織起來(lái)進(jìn)行分析, 從而指導(dǎo)優(yōu)化器進(jìn)行重編譯。
運(yùn)行時(shí):runtime——運(yùn)行時(shí)環(huán)境/運(yùn)行時(shí)系統(tǒng)
runtime 或者 run-time是指運(yùn)行環(huán)境,又稱為“運(yùn)行時(shí)系統(tǒng)”,簡(jiǎn)稱“運(yùn)行時(shí)”,是執(zhí)行碼在目標(biāo)機(jī)器上運(yùn)行的環(huán)境。它有可能是由操作系統(tǒng)提供,或由執(zhí)行此程序的父程序提供。通常由操作系統(tǒng)負(fù)責(zé)處理程序的載入:利用加載器(loader)讀入程序執(zhí)行碼,進(jìn)行基本的內(nèi)存配置,并視需要鏈接此程序指定的所有動(dòng)態(tài)鏈接庫(kù)。有些編程語(yǔ)言也會(huì)由此語(yǔ)言提供的運(yùn)行環(huán)境處理上述工作。
運(yùn)行環(huán)境可以解決許多問(wèn)題,包括應(yīng)用程序內(nèi)存的管理、程序如何訪問(wèn)變量、程序之間傳遞參數(shù)的機(jī)制、與操作系統(tǒng)的接口等問(wèn)題。編譯器根據(jù)具體的運(yùn)行時(shí)系統(tǒng)做出假設(shè),以生成正確的代碼。通常情況下,運(yùn)行時(shí)系統(tǒng)將承擔(dān)一些設(shè)置和管理堆棧的責(zé)任,并可能包括諸如垃圾回收、線程或其他內(nèi)置于語(yǔ)言中的動(dòng)態(tài)功能。
對(duì)于很多傳統(tǒng)高級(jí)語(yǔ)言尤其是動(dòng)態(tài)語(yǔ)言而言,?運(yùn)行時(shí)系統(tǒng)基本上就是程序和硬件的橋梁。一般地,運(yùn)行時(shí)系統(tǒng)實(shí)際上提供了一個(gè)動(dòng)態(tài)語(yǔ)言得以執(zhí)行的統(tǒng)一的虛擬計(jì)算機(jī),這樣一個(gè)虛擬機(jī)完全屏蔽了硬件的具體特征?從而極大的提高了軟件的開(kāi)發(fā)效率。但是正因?yàn)檫@個(gè)虛擬機(jī)完全屏蔽了程序員接觸硬件特性的可能,虛擬機(jī)本身必須承擔(dān)起充分利用硬件特性來(lái)高效執(zhí)行動(dòng)態(tài)語(yǔ)言程序的責(zé)任。
運(yùn)行時(shí)模型與運(yùn)行時(shí)體系結(jié)構(gòu)
每種語(yǔ)言都有一個(gè)特定的執(zhí)行模型(Execution Model),而這個(gè)執(zhí)行模型就需要運(yùn)行時(shí)系統(tǒng)的支撐。運(yùn)行時(shí)系統(tǒng)除了提供程序運(yùn)行機(jī)制之外,一般還提供內(nèi)存管理機(jī)制和并發(fā)機(jī)制。
運(yùn)行時(shí)模型是關(guān)聯(lián)系統(tǒng)的因果關(guān)系自述,從問(wèn)題空間的角度強(qiáng)調(diào)系統(tǒng)的結(jié)構(gòu)、行為或目標(biāo),運(yùn)行時(shí)模型提供了運(yùn)行時(shí)現(xiàn)象的抽象,利用運(yùn)行時(shí)模型能夠修復(fù)設(shè)計(jì)錯(cuò)誤或?qū)⑿碌脑O(shè)計(jì)決策折疊到正在運(yùn)行的系統(tǒng)中, 以支持受控的在線設(shè)計(jì)。同時(shí),運(yùn)行時(shí)模型作為軟件演化和適應(yīng)行為的基礎(chǔ) ,也是解決復(fù)雜系統(tǒng)運(yùn)行時(shí)管理問(wèn)題的關(guān)鍵 。
運(yùn)行時(shí)模型分為運(yùn)行時(shí)結(jié)構(gòu)模型和運(yùn)行時(shí)行為模型。其中, 運(yùn)行時(shí)結(jié)構(gòu)模型側(cè)重于描述系統(tǒng)的組成及配置, 即強(qiáng)調(diào)軟件的構(gòu)建方式,如面向?qū)ο蠖?,結(jié)構(gòu)模型描述的是繼承關(guān)系和調(diào)用途徑、組件及其連接 等;運(yùn)行時(shí)行為模型側(cè)重于描述組件間動(dòng)態(tài)交互信息,強(qiáng)調(diào)根據(jù)事件或跟蹤流程的系統(tǒng)執(zhí)行,或者事件的發(fā)生及其執(zhí)行途徑,如行為模型描述事件的到達(dá)、排隊(duì)、選擇、調(diào)度等信息。
運(yùn)行時(shí)軟件體系結(jié)構(gòu)是控制運(yùn)行時(shí)系統(tǒng)復(fù)雜性、輔助系統(tǒng)運(yùn)行時(shí)管理的有效途徑.作為系統(tǒng)運(yùn)行時(shí)刻的動(dòng)態(tài)描述,運(yùn)行時(shí)體系結(jié)構(gòu)刻畫(huà)了系統(tǒng)當(dāng)前時(shí)刻的構(gòu)成元素、各元素內(nèi)部的屬性以及元素之間的關(guān)系。運(yùn)行時(shí)體系結(jié)構(gòu)與目標(biāo)系統(tǒng)之間具有“因果關(guān)聯(lián)”,即系統(tǒng)發(fā)生變化時(shí),體系結(jié)構(gòu)隨之改變;而體系結(jié)構(gòu)被修改后,系統(tǒng)也將隨之改變,這種動(dòng)態(tài)的因果關(guān)聯(lián)保證了系統(tǒng)管理者可以通過(guò)讀寫(xiě)體系結(jié)構(gòu)中的元素、屬性以及連接關(guān)系,實(shí)現(xiàn)對(duì)目標(biāo)系統(tǒng)的監(jiān)測(cè)和調(diào)整。運(yùn)行時(shí)體系結(jié)構(gòu)抽象了系統(tǒng)的運(yùn)行時(shí)數(shù)據(jù),并以符合管理視角的方式對(duì)這些數(shù)據(jù)進(jìn)行組織,同時(shí)對(duì)外提供簡(jiǎn)單、一致的操作方式.
運(yùn)行時(shí)體系結(jié)構(gòu)與設(shè)計(jì)階段體系結(jié)構(gòu)最大的不同在于:運(yùn)行時(shí)體系結(jié)構(gòu)與運(yùn)行時(shí)系統(tǒng)間存在因果聯(lián)系,而設(shè)計(jì)階段體系結(jié)構(gòu)不具備這一屬性。運(yùn)行時(shí)體系結(jié)構(gòu)模型側(cè)重如何通過(guò)模型來(lái)描述運(yùn)行時(shí)系統(tǒng)當(dāng)前時(shí)刻的結(jié)構(gòu)和配置 。
運(yùn)行時(shí)的實(shí)現(xiàn)與分類
在編程語(yǔ)言中,runtime通常是由編譯器和運(yùn)行時(shí)庫(kù)共同實(shí)現(xiàn)的。編譯器負(fù)責(zé)將源代碼編譯成可執(zhí)行代碼,而運(yùn)行時(shí)庫(kù)則負(fù)責(zé)在程序運(yùn)行時(shí)提供各種運(yùn)行時(shí)支持和服務(wù)。運(yùn)行時(shí)庫(kù)通常包括了各種系統(tǒng)庫(kù)和標(biāo)準(zhǔn)庫(kù),以及一些特定于編程語(yǔ)言的庫(kù)和框架。
在C語(yǔ)言中,runtime的實(shí)現(xiàn)通常是由C庫(kù)和操作系統(tǒng)共同提供的。C庫(kù)提供了各種常用的函數(shù)和數(shù)據(jù)類型,而操作系統(tǒng)則提供了一些底層的系統(tǒng)調(diào)用和服務(wù)。在Java語(yǔ)言中,runtime的實(shí)現(xiàn)則是由Java虛擬機(jī)(JVM)和Java類庫(kù)共同提供的。JVM負(fù)責(zé)將Java字節(jié)碼編譯成可執(zhí)行代碼,并提供各種運(yùn)行時(shí)支持和服務(wù),而Java類庫(kù)則提供了各種常用的類和函數(shù)。
根據(jù)運(yùn)行時(shí)系統(tǒng)實(shí)現(xiàn)的不同,可以將運(yùn)行時(shí)系統(tǒng)初步分成三類:
第一種是是原生運(yùn)行時(shí),例如C/C++/Rust,這些語(yǔ)言的運(yùn)行時(shí)系統(tǒng)是依賴操作系統(tǒng)的,操作系統(tǒng)也可以認(rèn)為是一種“運(yùn)行時(shí)環(huán)境”。
第二種是輕運(yùn)行時(shí),例如Golang,它的運(yùn)行時(shí)是和代碼打包到一起的,相對(duì)輕量一些。
第三種是重運(yùn)行時(shí),例如Java(JVM),Python(CPython),還有 C#(.NET Runtime)。它們的運(yùn)行時(shí)環(huán)境是需要單獨(dú)安裝的,而且一般還不小,幾十兆上百兆都是正常的。
其中,在C/C++里你可以不引用任何庫(kù),Rust里有一個(gè)專門的特性叫 no_std,脫離標(biāo)準(zhǔn)庫(kù)提供了一個(gè)很牛的能力,就是直接和硬件交互。也就是說(shuō),C/C++/Rust是可以用來(lái)編寫(xiě)操作系統(tǒng)內(nèi)核的。
運(yùn)行時(shí)庫(kù)
運(yùn)行時(shí)庫(kù)(runtime library),在計(jì)算機(jī)程序設(shè)計(jì)領(lǐng)域中,是指編程語(yǔ)言程序運(yùn)行時(shí)(執(zhí)行)所需要的一種特殊的計(jì)算機(jī)程序庫(kù),編譯器會(huì)調(diào)用運(yùn)行時(shí)庫(kù)至已編譯的可執(zhí)行二進(jìn)制代碼中。這種庫(kù)一般包括基本的輸入輸出或是內(nèi)存管理等,一般是一群支持正在執(zhí)行程序的函數(shù),與操作系統(tǒng)合作提供諸如數(shù)學(xué)運(yùn)算、輸入輸出等功能,讓開(kāi)發(fā)者不需要“重新發(fā)明輪子”,并使用操作系統(tǒng)提供的功能。
運(yùn)行時(shí)庫(kù)提供了程序執(zhí)行時(shí)的最基本需要。比如Visual Basic需要復(fù)雜的運(yùn)行時(shí)庫(kù)支持而C的運(yùn)行時(shí)庫(kù)則相對(duì)簡(jiǎn)單。運(yùn)行時(shí)庫(kù)中的函數(shù)可能對(duì)程序員透明,也可能不透明,這是由編譯器廠商根據(jù)語(yǔ)言執(zhí)行環(huán)境的需求而決定的。
許多近代語(yǔ)言設(shè)計(jì)了更強(qiáng)大的運(yùn)行時(shí)環(huán)境并添加了更多功能,很多面向?qū)ο笳Z(yǔ)言也包含了分派器與類別讀取器,Java虛擬機(jī)(JVM)便是此類的典型執(zhí)行環(huán)境,而.NET架構(gòu)也是另外一個(gè)運(yùn)行時(shí)庫(kù)的實(shí)例。異常處理(Exception handling)是專門處理執(zhí)行期錯(cuò)誤的語(yǔ)言機(jī)制,使開(kāi)發(fā)者可以完全捕捉非預(yù)期錯(cuò)誤,或沒(méi)有適當(dāng)處理的錯(cuò)誤結(jié)果。
動(dòng)態(tài)鏈接庫(kù)或靜態(tài)鏈接庫(kù)與運(yùn)行時(shí)庫(kù)的分類角度不同,運(yùn)行時(shí)庫(kù)就是程序運(yùn)行的時(shí)候所需要依賴的庫(kù)文件.
C和C++運(yùn)行時(shí)庫(kù)
為了提高C語(yǔ)言的開(kāi)發(fā)效率,C標(biāo)準(zhǔn)定義了一系列常用的函數(shù),稱為C庫(kù)函數(shù)。C標(biāo)準(zhǔn)僅僅定義了函數(shù)原型,并沒(méi)有提供實(shí)現(xiàn)。因此這個(gè)任務(wù)留給了各個(gè)支持C語(yǔ)言標(biāo)準(zhǔn)的編譯器。每個(gè)編譯器通常實(shí)現(xiàn)了標(biāo)準(zhǔn)C的超集,稱為C運(yùn)行時(shí)庫(kù)(C Run Time Library),簡(jiǎn)稱CRT。C標(biāo)準(zhǔn)庫(kù)就是任何平臺(tái)都可以使用的基本C語(yǔ)言庫(kù)。而CRT除了將C標(biāo)準(zhǔn)庫(kù)加入所屬范圍外,還擴(kuò)展了與平臺(tái)相關(guān)的接口庫(kù),這些接口實(shí)現(xiàn)根據(jù)不同平臺(tái)調(diào)用不同平臺(tái)的操作系統(tǒng)API。
與C語(yǔ)言類似,C++也定義了自己的標(biāo)準(zhǔn),同時(shí)提供相關(guān)支持庫(kù),稱為C++運(yùn)行時(shí)庫(kù)或C++標(biāo)準(zhǔn)庫(kù)。由于C++對(duì)C的兼容性,C++標(biāo)準(zhǔn)庫(kù)包括了C標(biāo)準(zhǔn)庫(kù),除此之外還包括了IO流和標(biāo)準(zhǔn)模板庫(kù)STL。
圖片
VC+按照C和C++標(biāo)準(zhǔn)定義的函數(shù)原型實(shí)現(xiàn)了上述運(yùn)行時(shí)庫(kù)。為了方便有不同需求的客戶使用,VC++分別實(shí)現(xiàn)了動(dòng)態(tài)鏈接庫(kù)DLL版本和靜態(tài)鏈接庫(kù)LIB版本。同時(shí)為了支持程序調(diào)試且不影響程序的性能,又分別提供了對(duì)應(yīng)的調(diào)試版本。使用DLL版的C和C++運(yùn)行庫(kù),程序在運(yùn)行時(shí)動(dòng)態(tài)的加載對(duì)應(yīng)的DLL。程序體積變小,但一個(gè)很大的問(wèn)題就是一旦找不到對(duì)應(yīng)DLL,程序?qū)o(wú)法運(yùn)行。
在windows上開(kāi)發(fā)多線程程序時(shí),需要選擇MT、MTd、MD、MDd其中的一個(gè)。對(duì)于MT/MTd,由于連接運(yùn)行時(shí)庫(kù)是LIBCMT.lib/LIBCMTD.lib,這兩個(gè)庫(kù)是靜態(tài)庫(kù),所以此種方式編譯的程序,移到另一臺(tái)機(jī)器上面也可以正常運(yùn)行,所以這種方式,不會(huì)產(chǎn)生缺少動(dòng)態(tài)庫(kù)的報(bào)錯(cuò)。但是對(duì)于MD/MDd,連接的是動(dòng)態(tài)庫(kù),所以如果另一臺(tái)機(jī)器上沒(méi)有MSVCRT.dll/MSVCRTD.dll時(shí),就提示缺少動(dòng)態(tài)庫(kù)這樣的錯(cuò)誤。對(duì)用戶自己寫(xiě)的庫(kù)或其他第三方庫(kù),其連接方式取決于代碼(顯示連接動(dòng)態(tài)庫(kù)Loadlibrary)或所提供的lib文件,移動(dòng)程序到別的機(jī)器上時(shí),還是要帶上所需要的動(dòng)態(tài)庫(kù)的。在多工程開(kāi)發(fā)時(shí),最好所有的工程使用同一種運(yùn)行時(shí)庫(kù)。
運(yùn)行時(shí)安全
近年來(lái)出現(xiàn)的運(yùn)行時(shí)應(yīng)用程序自我保護(hù)技術(shù)(Runtime Application Self-Protection,簡(jiǎn)稱RASP),是一種解決應(yīng)用程序漏洞問(wèn)題并為IT基礎(chǔ)架構(gòu)增加額外安全層的方法。根據(jù)Gartner的說(shuō)法,運(yùn)行時(shí)應(yīng)用程序自我保護(hù)是“建立在或鏈接到應(yīng)用程序運(yùn)行時(shí)環(huán)境的安全技術(shù),它能夠控制應(yīng)用程序的執(zhí)行,并且檢測(cè)和阻止實(shí)時(shí)攻擊?!?/p>
運(yùn)行時(shí)應(yīng)用程序自我保護(hù)是預(yù)防性的,它可以采取多種措施來(lái)阻止黑客破壞系統(tǒng),在出現(xiàn)問(wèn)題時(shí)控制應(yīng)用程序。除了檢測(cè)惡意代碼,RASP還可以:
- 突然結(jié)束用戶會(huì)話
- 關(guān)閉應(yīng)用程序或系統(tǒng)
- 標(biāo)記異常事件的管理員和安全人員
- 向系統(tǒng)用戶發(fā)送警告
RASP一般與應(yīng)用程序集成,并通過(guò)監(jiān)視和分析流量以及用戶行為來(lái)防止程序運(yùn)行時(shí)受到攻擊,這使它們可以在應(yīng)用程序中看到功能級(jí)別的代碼。這種可見(jiàn)性使其能夠更準(zhǔn)確地識(shí)別攻擊,減少誤報(bào)并報(bào)告或阻止構(gòu)成合法威脅的那些動(dòng)作。
常見(jiàn)的運(yùn)行時(shí)環(huán)境
這里,我們來(lái)了解不同編程語(yǔ)言的運(yùn)行時(shí)環(huán)境,包括C語(yǔ)言的操作系統(tǒng)運(yùn)行機(jī)制、Apache Portable Runtime、Common Language Runtime、Java Runtime Environment、Android Runtime、iOS Runtime、JavaScript引擎與運(yùn)行時(shí)環(huán)境以及小程序的運(yùn)行時(shí)環(huán)境。進(jìn)而,思考各個(gè)運(yùn)行時(shí)環(huán)境的特點(diǎn)和優(yōu)劣,以及它們?cè)诓煌脚_(tái)上的表現(xiàn)。
C/C++運(yùn)行時(shí)
C 語(yǔ)言最主要的運(yùn)行時(shí),實(shí)際上就是操作系統(tǒng)。C 語(yǔ)言和現(xiàn)代的各種操作系統(tǒng)可以說(shuō)是伴生關(guān)系,就像 Java 和 JVM 是伴生關(guān)系一樣。
在程序執(zhí)行機(jī)制方面,C 語(yǔ)言編譯完畢的程序是完全按照操作系統(tǒng)的運(yùn)行機(jī)制來(lái)執(zhí)行的。在內(nèi)存管理方面,C 語(yǔ)言使用了操作系統(tǒng)提供的線程棧,操作系統(tǒng)能夠自動(dòng)幫助程序管理內(nèi)存。程序也可以從堆里申請(qǐng)內(nèi)存,但必須自己負(fù)責(zé)釋放,沒(méi)有自動(dòng)內(nèi)存管理機(jī)制。在并發(fā)機(jī)制方面,當(dāng)然也是直接用操作系統(tǒng)提供的線程機(jī)制。如果操作系統(tǒng)沒(méi)有提供協(xié)程和 Actor 機(jī)制,所以 C 語(yǔ)言也沒(méi)有提供這種并發(fā)機(jī)制。
APR
Apache可移植運(yùn)行時(shí)(Apache Portable Runtime,簡(jiǎn)稱APR)是Apache HTTP服務(wù)器的支持庫(kù),提供了一組映射到下層操作系統(tǒng)的API。如果操作系統(tǒng)不支持某個(gè)特定的功能,APR將提供一個(gè)模擬實(shí)現(xiàn)。這樣程序員使用APR編寫(xiě)真正可在不同平臺(tái)上移植的程序。
APR使得平臺(tái)細(xì)節(jié)的處理進(jìn)行下移。對(duì)于應(yīng)用程序而言,它們根本就不需要考慮具體的平臺(tái),不管是Unix、Linux還是Window,應(yīng)用程序執(zhí)行的接口基本都是統(tǒng)一一致的。因此對(duì)于APR而言,可移植性和統(tǒng)一的上層接口是其考慮的一個(gè)重點(diǎn)。而最初,APR是作為Apache HTTP服務(wù)器的一部分而存在的,但是Apache軟件基金會(huì)將其延伸成一個(gè)單獨(dú)的項(xiàng)目,其他的應(yīng)用程序可以使用APR來(lái)實(shí)現(xiàn)平臺(tái)無(wú)關(guān)性。APR的目標(biāo)則是希望安全合并所有的能夠合并的代碼而不需要犧牲性能。
CLR
通用語(yǔ)言執(zhí)行平臺(tái)(Common Language Runtime,簡(jiǎn)稱CLR)是微軟為他們的.NET的虛擬機(jī)所選用的名稱。它是微軟對(duì)通用語(yǔ)言架構(gòu)(CLI)的實(shí)例,它定義了一個(gè)程式碼執(zhí)行的環(huán)境。CLR執(zhí)行一種稱為通用中間語(yǔ)言的字節(jié)碼,運(yùn)行在Windows操作系統(tǒng)上。
.NET 運(yùn)行時(shí)安裝在計(jì)算機(jī)上,供依賴該框架的應(yīng)用程序使用。該運(yùn)行時(shí)具有一個(gè)廣泛的標(biāo)準(zhǔn)類庫(kù)集,稱為運(yùn)行時(shí)框架庫(kù)或基類庫(kù) (BCL)。此外,還有運(yùn)行時(shí)庫(kù)的擴(kuò)展,這些庫(kù)為許多常見(jiàn)和特定于與應(yīng)用的類型、算法和實(shí)用程序功能提供實(shí)現(xiàn)。
JRE
JRE 是 Java 程序的運(yùn)行環(huán)境,其中包含一個(gè) JAVA 虛擬機(jī)以及一些標(biāo)準(zhǔn)的函數(shù)類庫(kù)。也就是說(shuō),JRE(Java Runtime Environment)一種能夠執(zhí)行Java字節(jié)碼的虛擬機(jī),以堆棧結(jié)構(gòu)來(lái)實(shí)現(xiàn)的。JVM有自己完善的硬體架構(gòu),如處理器、堆棧、寄存器等,還具有相應(yīng)的指令系統(tǒng)。JRE屏蔽了與具體操作系統(tǒng)平臺(tái)相關(guān)的信息,使得Java程序只需生成在Java虛擬機(jī)上運(yùn)行的字節(jié)碼,就可以在多種平臺(tái)上不加修改地運(yùn)行。
作為一種編程語(yǔ)言的虛擬機(jī),JVM實(shí)際上不只是專用于Java語(yǔ)言,只要生成的編譯文件符合JVM對(duì)載入編譯文件格式要求,任何語(yǔ)言都可以在JRE上運(yùn)行。
ART
Android Runtime(縮寫(xiě)為ART),是一種在Android操作系統(tǒng)上的運(yùn)行環(huán)境,由Google公司研發(fā),并在2013年作為Android 4.4系統(tǒng)中的一項(xiàng)測(cè)試功能正式對(duì)外發(fā)布,在Android 5.0及后續(xù)Android版本中作為正式的運(yùn)行時(shí)庫(kù)取代了以往的Dalvik虛擬機(jī)。ART能夠把應(yīng)用程序的字節(jié)碼轉(zhuǎn)換為機(jī)器碼,是Android所使用的一種新的虛擬機(jī)。
圖片
與Dalvik虛擬機(jī)不同的是,ART引入了AOT這種預(yù)編譯技術(shù),在應(yīng)用程序安裝的過(guò)程中,ART就已經(jīng)將所有的字節(jié)碼重新編譯成了機(jī)器碼。應(yīng)用程序運(yùn)行過(guò)程中無(wú)需進(jìn)行實(shí)時(shí)的編譯工作,只需要進(jìn)行直接調(diào)用。因此,ART極大地提高了應(yīng)用程序的運(yùn)行效率,同時(shí)也減少了手機(jī)的電量消耗,提高了移動(dòng)設(shè)備的續(xù)航能力,在垃圾回收等機(jī)制上也有了較大的提升。為了保證向下兼容,ART使用了相同的Dalvik字節(jié)碼文件(dex),即在應(yīng)用程序目錄下保留了dex文件供舊程序調(diào)用,然而.odex文件則替換成了可執(zhí)行與可鏈接格式(ELF)可執(zhí)行文件。一旦一個(gè)程序被ART的dex2oat命令編譯,那么這個(gè)程序?qū)?huì)只通過(guò)ELF可執(zhí)行文件來(lái)運(yùn)行。因此,相對(duì)于Dalvik虛擬機(jī)模式,ART模式下Android應(yīng)用程序的安裝需要消耗更多的時(shí)間,同時(shí)也會(huì)占用更大的內(nèi)部?jī)?chǔ)存空間,用于儲(chǔ)存編譯后的代碼,但節(jié)省了很多Dalvik虛擬機(jī)用于實(shí)時(shí)編譯的時(shí)間。
iOS Runtime
在iOS中,通常將 Runtime 理解成實(shí)現(xiàn)Obj-C語(yǔ)言動(dòng)態(tài)的 API。Obj-C的動(dòng)態(tài)性就是運(yùn)行時(shí)機(jī)制,其中最主要的是消息機(jī)制。
在iOS Runtime的支持下,動(dòng)態(tài)類型和動(dòng)態(tài)綁定使得選擇那個(gè)接收者以及調(diào)用哪個(gè)方法都可以在運(yùn)行時(shí)決定;應(yīng)用可以根據(jù)需要加載可執(zhí)行代碼以及資源,而不是在啟動(dòng)時(shí)就加載所有資源;iOS在編譯的時(shí)候會(huì)根據(jù)方法的名字(包括參數(shù)序列),生成一個(gè)用來(lái)區(qū)分這個(gè)方法的唯一的ID,這個(gè)ID是SEL類型的,只要方法的名字(包括參數(shù)序列)相同,那么它們的ID都是相同的。通過(guò) dlopen 把動(dòng)態(tài)庫(kù)文件載入運(yùn)行的 App 里,接下來(lái) dlsym 會(huì)得到動(dòng)態(tài)庫(kù)的符號(hào)地址,然后就可以處理類的替換工作。整個(gè)過(guò)程無(wú)需重新編譯和重載 App,使用動(dòng)態(tài)庫(kù)方式極速調(diào)試的目的就達(dá)成了。
JS引擎與運(yùn)行時(shí)
JavaScript 引擎是一個(gè)解釋 JavaScript 代碼的程序,該引擎負(fù)責(zé)執(zhí)行代碼。任何 JavaScript 引擎通常都包含一個(gè)調(diào)用棧和一個(gè)堆。調(diào)用棧是代碼執(zhí)行的地方,堆是一個(gè)非結(jié)構(gòu)化的內(nèi)存池,用于存儲(chǔ)應(yīng)用程序所需的所有對(duì)象。當(dāng)代碼片段進(jìn)入運(yùn)行時(shí)環(huán)境時(shí),代碼首先被讀取,隨后被解析為叫作抽象語(yǔ)法樹(shù)(AST)的數(shù)據(jù)結(jié)構(gòu)。生成的樹(shù)被用來(lái)來(lái)創(chuàng)建機(jī)器代碼。
JavaScript 運(yùn)行時(shí)環(huán)境包含了運(yùn)行 JavaScript 所需的所有組件,包括 JavaScript 引擎、Web API 和回調(diào)隊(duì)列。
圖片
瀏覽器運(yùn)行時(shí)和 Node.js 是JS運(yùn)行時(shí)環(huán)境的具體例子。當(dāng) JavaScript 在 Web 瀏覽器中執(zhí)行時(shí),它是在瀏覽器的運(yùn)行時(shí)環(huán)境中運(yùn)行的。瀏覽器運(yùn)行時(shí)環(huán)境提供對(duì) DOM 的訪問(wèn),從而實(shí)現(xiàn)了與網(wǎng)頁(yè)元素的交互,處理事件,以及對(duì)頁(yè)面結(jié)構(gòu)的操作。Node.js 提供了一個(gè)服務(wù)器端運(yùn)行時(shí)環(huán)境,它在瀏覽器外部執(zhí)行 JavaScript,所以它不能訪問(wèn) Web API。Node.js 運(yùn)行時(shí)環(huán)境將其替換為叫作 C++ 綁定和線程池的東西。每個(gè)主流的瀏覽器都有一個(gè)可以執(zhí)行 JavaScript 代碼的 JavaScript 引擎。最流行的是谷歌瀏覽器 Chrome 的 V8 引擎,同時(shí)為 Chrome 和 Node.js 提供了支持。
小程序的運(yùn)行時(shí)
小程序的運(yùn)行時(shí)環(huán)境可以看作JS運(yùn)行時(shí)環(huán)境的特例。不同運(yùn)行平臺(tái)上,小程序的運(yùn)行時(shí)環(huán)境有所不同,性能表現(xiàn)也存在差異:
- 在 iOS、iPadOS 和 Mac OS 上,小程序的 JavaScript 代碼運(yùn)行在 JavaScriptCore 中,;
- 在 Android 上,小程序的 JavaScript 代碼運(yùn)行在 V8 中;
- 在 Windows 上,小程序JavaScript 使用 Chromium 內(nèi)核;
- 在 開(kāi)發(fā)工具上,小程序 JavaScript 代碼一般運(yùn)行在 NW.js 中。
其中,JavaScriptCore 無(wú)法開(kāi)啟 JIT 編譯,同等條件下的運(yùn)行性能要明顯低于其他平臺(tái)。
盡管各運(yùn)行環(huán)境是十分相似的,但是還是有些許區(qū)別,那就是JavaScript 語(yǔ)法和 API 支持存在不一致,開(kāi)發(fā)者可以通過(guò)開(kāi)啟 ES6 轉(zhuǎn) ES5 的功能來(lái)規(guī)避。
運(yùn)行時(shí)的云應(yīng)用
容器運(yùn)行時(shí)負(fù)責(zé)運(yùn)行容器并設(shè)置命名空間和控制組,如Docker、containerd、rkt、Kata Container、CRI-O等;FaaS的運(yùn)行時(shí)支持多種語(yǔ)言,每個(gè)主要編程語(yǔ)言版本都有單獨(dú)的運(yùn)行時(shí),可以使用Lambda提供的運(yùn)行時(shí)或構(gòu)建自己的運(yùn)行時(shí),也可以自定義運(yùn)行時(shí)為Shell腳本或可在Linux可執(zhí)行的二進(jìn)制文件。
容器運(yùn)行時(shí)
容器運(yùn)行時(shí)更側(cè)重于運(yùn)行容器,為容器設(shè)置命名空間和控制組(cgroup),也被稱為底層容器運(yùn)行時(shí)。高層的容器運(yùn)行時(shí)或容器引擎專注于格式、解包、管理和鏡像共享。它們還為開(kāi)發(fā)者提供 API,這種情況下的容器運(yùn)行時(shí)相當(dāng)于一個(gè)可獨(dú)立運(yùn)行的模塊,可以將它視為功能性的 Native 類庫(kù)使用。
Docker是目前最廣泛的容器引擎技術(shù)。當(dāng)然,隨著容器生態(tài)圈的日益繁榮,業(yè)界慢慢也出現(xiàn)了其他各種運(yùn)行時(shí)工具,如containerd、rkt、Kata Container、CRI-O等。這些工具提供的功能不盡相同,有些只有容器運(yùn)行的功能,有些除運(yùn)行容器外還提供了容器鏡像的管理功能。
圖片
低層運(yùn)行時(shí)主要負(fù)責(zé)與宿主機(jī)操作系統(tǒng)打交道,根據(jù)指定的容器鏡像在宿主機(jī)上運(yùn)行容器的進(jìn)程,并對(duì)容器的整個(gè)生命周期進(jìn)行管理。常見(jiàn)的低層運(yùn)行時(shí)系統(tǒng)的種類有:
- runc:傳統(tǒng)的運(yùn)行時(shí)環(huán)境,基于Linux Namespace和Cgroups技術(shù)實(shí)現(xiàn),代表實(shí)現(xiàn)Docker
- runv:基于虛擬機(jī)管理程序的運(yùn)行時(shí)環(huán)境,通過(guò)虛擬化 guest kernel,將容器和主機(jī)隔離開(kāi)來(lái),使得其邊界更加清晰,代表實(shí)現(xiàn)是Kata Container和Firecracker
- runsc:runc + safety ,通過(guò)攔截應(yīng)用程序的所有系統(tǒng)調(diào)用,提供安全隔離的輕量級(jí)容器運(yùn)行時(shí)沙箱,代表實(shí)現(xiàn)是谷歌的gVisor
Kubernetes利用Docker作為容器運(yùn)行時(shí)管理工具,后來(lái)又添加了對(duì)rkt的支持。但隨著容器技術(shù)的蓬勃發(fā)展,越來(lái)越多的運(yùn)行時(shí)工具出現(xiàn),提供對(duì)所有運(yùn)行時(shí)工具的支持,顯然是一項(xiàng)龐大的工程。如果直接將運(yùn)行時(shí)的集成內(nèi)置于Kubernetes,對(duì)Kubernetes代碼本身也是一種負(fù)擔(dān),每次更新,都需要考慮對(duì)所有容器運(yùn)行時(shí)的兼容適配。因此,Kubernetes將對(duì)容器的操作抽象為一個(gè)接口,將此接口作為kubelet與運(yùn)行時(shí)工具之間的橋梁,kubelet通過(guò)發(fā)送接口請(qǐng)求對(duì)容器進(jìn)行啟動(dòng)和管理,各個(gè)容器工具通過(guò)實(shí)現(xiàn)這個(gè)接口即可接入Kubernetes。這個(gè)統(tǒng)一的容器操作接口,就是容器運(yùn)行時(shí)接口(Container Runtime Interface, CRI)。
FaaS的運(yùn)行時(shí)
Lambda 通過(guò)使用運(yùn)行時(shí)支持多種語(yǔ)言。運(yùn)行時(shí)系統(tǒng)提供特定于語(yǔ)言的環(huán)境,用于在 Lambda 與函數(shù)之間中繼調(diào)用事件、上下文信息和響應(yīng)。我們可以使用 Lambda 提供的運(yùn)行時(shí),或構(gòu)建您自己的運(yùn)行時(shí)。
每個(gè)主要編程語(yǔ)言版本都有單獨(dú)的運(yùn)行時(shí),并具有唯一的運(yùn)行時(shí)標(biāo)識(shí)符,例如 python3.10 或 nodejs18.x。要將某個(gè)云端的函數(shù)計(jì)算配置為使用新的主要語(yǔ)言版本,需要更改運(yùn)行時(shí)系統(tǒng)標(biāo)識(shí)符。對(duì)于定義為容器映像的函數(shù),可以在創(chuàng)建容器映像時(shí)選擇運(yùn)行時(shí)系統(tǒng)和 Linux 發(fā)行版。要更改運(yùn)行時(shí),需要?jiǎng)?chuàng)建一個(gè)新的容器映像。
在將 .zip 文件存檔作為部署程序包的時(shí)候,需要在創(chuàng)建函數(shù)時(shí)選擇運(yùn)行時(shí)。要更改運(yùn)行時(shí),需要更新云端函數(shù)計(jì)算的配置。云服務(wù)的底層執(zhí)行環(huán)境提供了可通過(guò)函數(shù)代碼訪問(wèn)的額外的庫(kù)和環(huán)境變量。
自定義的運(yùn)行時(shí)可以是Shell腳本,也可以是可在linux可執(zhí)行的二進(jìn)制文件。
小結(jié)
本文首先澄清了運(yùn)行時(shí)的核心概念,然后介紹了不同類型的運(yùn)行時(shí)環(huán)境,包括Android的ART和Dalvik虛擬機(jī)、iOS的Runtime、JavaScript引擎和運(yùn)行時(shí)環(huán)境、小程序的運(yùn)行時(shí)環(huán)境、以及云應(yīng)用的容器運(yùn)行時(shí)和FaaS的運(yùn)行時(shí)。對(duì)于容器運(yùn)行時(shí),了解了常見(jiàn)的低層運(yùn)行時(shí)種類以及Kubernetes如何通過(guò)容器運(yùn)行時(shí)接口(CRI)實(shí)現(xiàn)對(duì)所有容器運(yùn)行時(shí)的支持。對(duì)于FaaS的運(yùn)行時(shí),介紹了Lambda如何通過(guò)使用運(yùn)行時(shí)支持多種語(yǔ)言,并提供特定于語(yǔ)言的環(huán)境,用于在Lambda與函數(shù)之間中繼調(diào)用事件、上下文信息和響應(yīng)。