0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

VS Code源碼深入淺出--依賴注入設(shè)計

jf_8lIj6kO1 ? 來源:SegmentFault思否 ? 作者:Duang ? 2022-12-14 10:37 ? 次閱讀

在閱讀 VS Code 代碼的過程中,我們會發(fā)現(xiàn)每一個模塊中都有大量裝飾器的使用,用來裝飾模塊以及其中依賴的模塊變量。這樣做的目的是什么呢?在這一篇中我們來詳細(xì)分析一下。

依賴注入介紹


如果有這樣一個模塊 A,它的實現(xiàn)依賴另一個模塊 B 的能力,那么應(yīng)該如何設(shè)計呢?很簡單,我們可以在 A 模塊的構(gòu)造函數(shù)中實例化模塊 B,這樣就可以在模塊 A 內(nèi)部使用模塊 B 的能力了。

classA{
constructor(){
this.b=newB();
}
}

classB{}

consta=newA();

但是這樣做有兩個問題,一是模塊 A 的實例化過程中,需要手動實例化模塊 B,而且如果模塊 B 的依賴關(guān)系發(fā)生變化,那么也需要修改模塊 A 的構(gòu)造函數(shù),導(dǎo)致代碼耦合。

二是在復(fù)雜項目中,我們在實例化模塊 A 時,難以判斷模塊 B 是否被其他模塊依賴而已經(jīng)實例化過了,從而可能將模塊 B 多次實例化。若模塊 B 較重或者需要為單例設(shè)計,這將帶來性能問題。

因此,更好的方式是,將所有模塊的實例化交給外層框架,由框架統(tǒng)一管理模塊的實例化過程,這樣就可以解決上述兩個問題。

classA{
constructor(privateb:B){
this.b=b;
}
}

classB{}

classC{
constructor(privatea:A,privateb:B){
this.b=b;
}
}

constb=newB();
consta=newA(b);
constc=newC(a,b);

這種將依賴對象通過外部注入,避免在模塊內(nèi)部實例化依賴的方式,稱為依賴注入 (Dependencies Inject, 簡稱 DI)。這在軟件工程中是一種常見的設(shè)計模式,我們在 Java 的 Spring,JS 的 Angular,Node 的 NestJS 等框架中都可以看到這種設(shè)計模式的應(yīng)用。

當(dāng)然,在實際應(yīng)用中,由于模塊眾多,依賴復(fù)雜,我們很難像上面的例子一樣,規(guī)劃出來每個模塊的實例化時機,從而編寫模塊實例化順序。并且,許多模塊可能并不需要第一時間被創(chuàng)建,需要按需實例化,因此,粗暴的統(tǒng)一實例化是不可取的。

因此我們需要一個統(tǒng)一的框架來分析并管理所有模塊的實例化過程,這就是依賴注入框架的作用。

借助于 TypeScript 的裝飾器能力,VSCode 實現(xiàn)了一個極為輕量化的依賴注入框架。我們可以先來簡單實現(xiàn)一下,解開這個巧妙設(shè)計的神秘面紗。

最簡依賴注入框架設(shè)計


實現(xiàn)一個依賴注入框架只需要兩步,一個是將模塊聲明并注冊到框架中進行管理,另一個是在模塊構(gòu)造函數(shù)中,聲明所需要依賴的模塊有哪些。

我們先來看模塊的注冊過程,這需要 TypeScript 的類裝飾器能力。我們在注入時,只需要判斷模塊是否已經(jīng)注冊,如果沒有注冊,將模塊的 id(這里簡化為模塊 Class 名稱)與類型傳入即可完成單個模塊的注冊。

exportfunctionInjectable():ClassDecorator{
return(Target:Class):any=>{
if(!collection.providers.has(Target.name)){
collection.providers.set(Target.name,target);
}
returntarget;
};
}

之后我們再來看看模塊是如何聲明依賴的,這需要 TypeScript 的屬性裝飾器能力。我們在注入時,先判斷依賴的模塊是否已經(jīng)被實例化,如果沒有,則將依賴模塊進行實例化,并存入框架中管理。最終返回已經(jīng)被實例化完成的模塊實例。

exportfunctionInject():PropertyDecorator{
return(target:Property,propertyKey:string)=>{

constinstance=collection.dependencies.get(propertyKey);
if(!instance){
constDependencyProvider:Class=collection.providers.get(propertyKey);
collection.dependencies.set(propertyKey,newDependencyProvider());
}

target[propertyKey]=collection.dependencies.get(propertyKey);
};
}

最后只需要保證框架本身在項目運行前完成實例化即可。(在例子中表示為 injector)

exportclassServiceCollection{
readonlyproviders=newMap();
readonlydependencies=newMap();
}

constcollection=newServiceCollection();
exportdefaultcollection;

這樣,一個最簡化的依賴注入框架就完成了。由于保存了模塊的類型與實例,它實現(xiàn)了模塊的按需實例化,無需在項目啟動時就初始化所有模塊。

我們可以嘗試調(diào)用它,以上面舉出的例子為例:

@injectable()
classA{
constructor(@inject()privateb:B){
this.b=b;
}
}

@injectable()
classB{}

classC{
constructor(@inject()privatea:A,@inject()privateb:B){
this.b=b;
}
}

constc=newC();

無需知曉模塊 A,B 的實例化時機,直接初始化任何一個模塊,框架會自動幫你找到并實例化好所有依賴的模塊。

VSCode 的依賴收集實現(xiàn)


上面介紹了一個依賴注入框架的最簡實現(xiàn)。但當(dāng)我們真正閱讀 VSCode 的源碼時,我們發(fā)現(xiàn) VSCode 中的依賴注入框架貌似并不是這樣消費的。

例如在下面這段鑒權(quán)服務(wù)中,我們發(fā)現(xiàn)該類并沒有@injectable()作為類的依賴收集,并且依賴服務(wù)也直接用其類名作為修飾器,而不是@inject()。

//srcvsworkbenchservicesauthenticationrowserauthenticationService.ts
exportclassAuthenticationServiceextendsDisposableimplementsIAuthenticationService{
constructor(
@IActivityServiceprivatereadonlyactivityService:IActivityService,
@IExtensionServiceprivatereadonlyextensionService:IExtensionService,
@IStorageServiceprivatereadonlystorageService:IStorageService,
@IRemoteAgentServiceprivatereadonlyremoteAgentService:IRemoteAgentService,
@IDialogServiceprivatereadonlydialogService:IDialogService,
@IQuickInputServiceprivatereadonlyquickInputService:IQuickInputService
){}
}

其實這里的修飾符并不是真正指向類名,而是一個同名的資源描述符 id(VSCode 中稱之為 ServiceIdentifier),通常使用字符串或 Symbol 標(biāo)識。

通過 ServiceIdentifier 作為 id,而不是簡單粗暴地通過類名稱作為 id 注冊 Service,有利于處理項目中一個 interface 可能存在多態(tài)實現(xiàn),需要同時多個同名類實例的問題。

此外,在構(gòu)造 ServiceIdentifier 時,我們便可以將該類聲明注入框架,而無需@injectable()顯示調(diào)用了。

那么,這樣一個 ServiceIdentifier 該如何構(gòu)造呢?

//srcvsplatforminstantiationcommoninstantiation.ts
/**
*The*only*validwaytocreatea{{ServiceIdentifier}}.
*/
exportfunctioncreateDecorator(serviceId:string):ServiceIdentifier{

if(_util.serviceIds.has(serviceId)){
return_util.serviceIds.get(serviceId)!;
}

constid=function(target:Function,key:string,index:number):any{
if(arguments.length!==3){
thrownewError('@IServiceName-decoratorcanonlybeusedtodecorateaparameter');
}
storeServiceDependency(id,target,index);
};

id.toString=()=>serviceId;

_util.serviceIds.set(serviceId,id);
returnid;
}

//被 ServiceIdentifier 裝飾的類在運行時,將收集該類的依賴,注入到框架中。
functionstoreServiceDependency(id:Function,target:Function,index:number):void{
if((targetasany)[_util.DI_TARGET]===target){
(targetasany)[_util.DI_DEPENDENCIES].push({id,index});
}else{
(targetasany)[_util.DI_DEPENDENCIES]=[{id,index}];
(targetasany)[_util.DI_TARGET]=target;
}
}

我們僅需通過createDecorator方法為類創(chuàng)建一個唯一的ServiceIdentifier,并將其作為修飾符即可。

以上面的 AuthenticationService 為例,若所依賴的 ActivityService 需要變更多態(tài)實現(xiàn),僅需修改 ServiceIdentifier 修飾符確定實現(xiàn)方式即可,無需更改業(yè)務(wù)的調(diào)用代碼。

exportconstIActivityServicePlanA=createDecorator("IActivityServicePlanA");
exportconstIActivityServicePlanB=createDecorator("IActivityServicePlanB");
exportinterfaceIActivityService{...}

exportclassAuthenticationService{
constructor(
@IActivityServicePlanAprivatereadonlyactivityService:IActivityService,
){}
}

循環(huán)依賴問題


模塊之間的依賴關(guān)系是有可能存在循環(huán)依賴的,比如 A 依賴 B,B 依賴 A。這種情況下進行兩個模塊的實例化會造成死循環(huán),因此我們需要在框架中加入循環(huán)依賴檢測機制來進行規(guī)避。

本質(zhì)上,一個健康的模塊依賴關(guān)系就是一個有向無環(huán)圖(DAG),我們之前介紹過有向無環(huán)圖在 excel 表格函數(shù)中的應(yīng)用,放在依賴注入框架的設(shè)計中也同樣適用。

我們可以通過深度優(yōu)先搜索(DFS)來檢測模塊之間的依賴關(guān)系,如果發(fā)現(xiàn)存在循環(huán)依賴,則拋出異常。

//src/vs/platform/instantiation/common/instantiationService.ts
while(true){
letroots=graph.roots();

//ifthereisnomorerootsbutstill
//nodesinthegraphwehaveacycle
if(roots.length===0){
if(graph.length!==0){
throwCycleError();
}
break;
}

for(letrootofroots){
//createinstanceandoverwritetheservicecollections
constinstance=this._createInstance(root.data.desc,[]);
this._services.set(root.data.id,instance);
graph.removeNode(root.data);
}
}

該方法通過獲取圖節(jié)點的出度,將該類的全部依賴提取出來作為roots,然后逐個實例化,并從途中剝離該依賴節(jié)點。由于依賴樹的構(gòu)建是逐層依賴的,因此按順序?qū)嵗纯?。?dāng)發(fā)現(xiàn)該類的所有依賴都被實例化后,圖中仍存在節(jié)點,則認(rèn)為存在循環(huán)依賴,拋出異常。

總結(jié)


本篇文章簡要介紹并實現(xiàn)了一個依賴注入框架,并解析了VSCode在實際問題上做出的一些改進。

實際上 VSCode 的依賴注入能力還有很多細(xì)節(jié)需要處理。例如異步實例化能力支持,通過封裝 Deferred 類取得Promise執(zhí)行狀態(tài),等等,在此就不一一展開了。感興趣的同學(xué)可以參考 VSCode 源碼:src/vs/platform/instantiation/common/instantiationService.ts,https://segmentfault.com/a/src/vs/platform/instantiation/common/instantiationService.ts做更進一步的學(xué)習(xí)。


審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 源碼
    +關(guān)注

    關(guān)注

    8

    文章

    651

    瀏覽量

    29339
  • 變量
    +關(guān)注

    關(guān)注

    0

    文章

    613

    瀏覽量

    28434
  • vscode
    +關(guān)注

    關(guān)注

    1

    文章

    157

    瀏覽量

    7766

原文標(biāo)題:VS Code 源碼深入淺出 -- 依賴注入設(shè)計

文章出處:【微信號:玩轉(zhuǎn)VS Code,微信公眾號:玩轉(zhuǎn)VS Code】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    深居AutoCAD二次開發(fā)

    深居AutoCAD二次開發(fā),net版
    發(fā)表于 01-06 14:12 ?0次下載

    使用MCUXpresso for VS Code插件開發(fā)Zephyr的hello world

    本期來到Zephyr實戰(zhàn)經(jīng)驗演練,小編帶著大家一起使用MCUXpresso for VS Code插件來開發(fā)一個屬于Zephyr的hello world。
    的頭像 發(fā)表于 01-03 09:21 ?568次閱讀
    使用MCUXpresso for <b class='flag-5'>VS</b> <b class='flag-5'>Code</b>插件開發(fā)Zephyr的hello world

    Zephyr領(lǐng)進門系列:MCUXPresso for VS Code插件安裝

    在上一期-Zephyr的構(gòu)建工具,我們?yōu)榇蠹医榻B了一位新朋友,Zephyr OS。相信通過上一篇的介紹,大家已經(jīng)對這一OS有了一些簡單的了解。那么本期小編將帶著大家一起從0開始結(jié)合VS Code搭建
    的頭像 發(fā)表于 12-19 09:53 ?1146次閱讀
    Zephyr領(lǐng)進門系列:MCUXPresso for <b class='flag-5'>VS</b> <b class='flag-5'>Code</b>插件安裝

    ?IAR C-SPY為VS Code社區(qū)樹立調(diào)試新標(biāo)準(zhǔn)

    全球領(lǐng)先的嵌入式系統(tǒng)開發(fā)軟件解決方案供應(yīng)商IAR宣布,對VS Code中的調(diào)試擴展IAR C-SPY調(diào)試器進行了重大升級。此次升級引入了IAR的Listwindow技術(shù),進一步提升了調(diào)試能力,使IAR C-SPY調(diào)試器在VS
    的頭像 發(fā)表于 12-06 10:27 ?238次閱讀

    一文掌握基礎(chǔ)電路,嵌入式開發(fā)深入淺出

    1. 電路知識 1.1.?驅(qū)動能力 IC是數(shù)字邏輯芯片,其輸出的是邏輯電平。邏輯電平0表示輸出電壓低于閾值電壓,邏輯1表示輸出電壓高于閾值電壓。負(fù)載則是被驅(qū)動的電路或元件,負(fù)載大小則指負(fù)載的電阻大小。 驅(qū)動能力主要表現(xiàn)在幾個方面: 負(fù)載能力:負(fù)載過大表現(xiàn)為外部負(fù)載的阻值過小,在電壓不變的情況下,過小的阻值會導(dǎo)致電流過大,可能會燒壞器件。負(fù)載過小,表現(xiàn)為外部負(fù)載的阻值達(dá)大,在電壓不變的情況下,過大的阻值會導(dǎo)致電流過小,信
    的頭像 發(fā)表于 12-03 10:36 ?375次閱讀
    一文掌握基礎(chǔ)電路,嵌入式開發(fā)<b class='flag-5'>深入淺出</b>

    深入淺出RISC-V調(diào)試

    一、JTAG簡介 目前RISC-V官方支持的調(diào)試方式是JTAG(Joint Test Action Group),而ARM支持的調(diào)試方式有JTAG和SWD(Serial Wire Debug)這兩種。 JTAG是一種國際標(biāo)準(zhǔn)的調(diào)試方式(IEEE1149.1),而SWD是ARM開發(fā)的。標(biāo)準(zhǔn)JTAG采用四線方式,分別是TCK、TMS、TDI和TDO,有一個可選的TRST引腳。 ● TCK:測試時鐘輸入。 ● TMS:測試模式選擇。 ● TDI:測試數(shù)據(jù)輸入。 ● TDO:測試數(shù)據(jù)輸出。 在調(diào)試時需要用到一個工具,比如JLink或者CMSIS-DAP,對于這個工具,在這里稱為JTAG主機(JTAG host),而嵌入在芯片內(nèi)部的JTAG稱為JTAG從機(JTAG slave),需要注意的是上面這些信號的輸入輸出方向是對于JTAG從機來說的。下文中如無特別說明,JTAG都是指JTAG從機。 一個JTAG主機可以同時對多個JTAG從機進行調(diào)試,這通過JTAG掃描鏈(JTAG Scan Chain)完成,如圖1所示。 圖1 一個JTAG主機連接多個JTAG從機 JTAG內(nèi)部有一個TAP(Test Access Port)控制器(或者說狀態(tài)機),通過TCK和TMS信號來改變狀態(tài)機的狀態(tài)。這個狀態(tài)機的核心是兩路SCAN,分別是IR SCAN和DR SCAN,TAP狀態(tài)機如圖2所示。 圖2 TAP狀態(tài)機 箭頭上的0或1表示的是TMS信號的電平。JTAG在每一個TCK信號的上升沿采樣TMS信號和TDI信號,決定狀態(tài)機的狀態(tài)是否發(fā)生變化,在每一個TCK信號的下降沿輸出TDO信號??梢钥吹?,無論TAP目前處于哪一個狀態(tài),只要TMS保持高電平并持續(xù)5個TCK時鐘,則TAP一定會回到Test-Logic-Reset狀態(tài)。 JTAG內(nèi)部有一個IR(instruction register)寄存器和多個DR(data register)寄存器,IR寄存器決定要訪問的是哪一個DR寄存器。DR寄存器有IDCODE、BYPASS等。在Test-Logic-Reset狀態(tài)下IR寄存器默認(rèn)選擇的是IDCODE這個DR寄存器。 JTAG主機通過IR SCAN設(shè)置IR寄存器的值,然后通過DR SCAN來讀、寫相應(yīng)的DR寄存器。 二、RISC-V調(diào)試Spec 調(diào)試模塊在CPU芯片設(shè)計里是最為不起眼的,但又是最為復(fù)雜的模塊之一,大部分開源的處理器IP都沒有調(diào)試模塊。 下面的內(nèi)容基于RISC-V debug spec 0.13版本。 目前RISC-V的官方調(diào)試上位機是openocd,調(diào)試工具可以是JLink或者CMSIS-DAP,RISC-V調(diào)試系統(tǒng)框架如圖3所示。 圖3 RISC-V調(diào)試系統(tǒng)框架 可以看到主要分為3個部分,分別是Debug Host,可以理解為PC;Debug Hardware,可以理解為JLink或者CMSIS-DAP這樣的調(diào)試工具;第三部分就是嵌入在芯片內(nèi)部的調(diào)試模塊。在調(diào)試模塊內(nèi)部,與調(diào)試工具直接交互的是DTM模塊,DTM模塊通過DMI接口與DM模塊交互。 1>DTM模塊 在DTM模塊里實現(xiàn)了一個TAP控制器(狀態(tài)機),其中IR寄存器的長度最少為5位,當(dāng)TAP控制器復(fù)位時,IR的值默認(rèn)為5\'b00001,即選擇的是IDCODE寄存器。DTM模塊的寄存器(DR寄存器)定義如圖4所示。 圖4 DTM寄存器 其中紅色框起來的寄存器是必須要實現(xiàn)的。下面簡單介紹一下這幾個寄存器。 ① IDCODE寄存器(0x01) 當(dāng)TAP狀態(tài)機復(fù)位時,IR寄存器的值默認(rèn)為0x01,即選擇的是IDCODE寄存器。IDCODE寄存器的每一位含義如圖5所示。IDCODE是只讀寄存器。 圖5 IDCODE寄存器 ● Version:只讀,版本號,可為任意值。 ● PartNumber:只讀,可為任意值。 ● Manufld:只讀,廠商號,遵循JEP106標(biāo)準(zhǔn)分配,實際中可為任意值,只要不與已分配的廠商號沖突即可。 ② DTM控制和狀態(tài)寄存器(dtmcs,0x10) dtmcs寄存器的每一位含義如圖6所示。 圖6 dtmcs寄存器 ● dmihardreset:DTM模塊硬復(fù)位,寫1有效。 ● dmireset:清除出錯,寫1有效。 ● idle:只讀,JTAG 主機在Run-Test-Idle狀態(tài)停留的時鐘周期數(shù),0表示不需要進入Run-Test-Idle狀態(tài),1表示進入Run-Test-Idle狀態(tài)后可以馬上進入下一個狀態(tài),以此類推。 ● dmistat:只讀,上一次操作的狀態(tài)。0表示無出錯,1或者2表示操作出錯,3表示操作還未完成。 ● abits:只讀,dmi寄存器中address域的大小(位數(shù))。 ● version:只讀,實現(xiàn)所對應(yīng)的spec版本,0表示0.11版本,1表示0.13版本。 ③ DM模塊接口訪問寄存器(dmi,0x11) dmi寄存器的每一位含義如圖7所示。 圖7 dmi寄存器 ● address:可讀可寫,DM寄存器的長度(位數(shù))。 ● data:可讀可寫,往DM寄存器讀、寫的數(shù)據(jù),固定為32位。 ● op:可讀可寫,讀或者寫這個域時有不同的含義。當(dāng)寫這個域時,寫0表示忽略address和data的值,相當(dāng)于nop操作;寫1表示從address指定的寄存器讀數(shù)據(jù);寫2表示把data的數(shù)據(jù)寫到address指定的寄存器。寫3為保留值。當(dāng)讀這個域時,0表示上一個操作正確完成;1為保留值;2表示上一個操作失敗,這個狀態(tài)是會被記住的,因此需要往dtmcs寄存器的dmireset域?qū)?才能清除這個狀態(tài)。3表示上一個操作還未完成。 在Update-DR狀態(tài)時,DTM開始執(zhí)行op指定的操作。在Capture-DR狀態(tài)時,DTM更新data域。 ④ BYPASS寄存器(0x1f) 只讀,長度為1,值固定為0。 2>DM模塊 從圖3可知,DM模塊訪問RISC-V Core有兩種方式,一種是通過abstract command,另一種是通過system bus。abstract command方式是必須要實現(xiàn)的,system bus的方式是可選的。 DM模塊的寄存器都為32位,定義如圖8所示。 圖8 DM寄存器 下面介紹一下紅色框起來這幾個重要的寄存器。 ① data寄存器(data0-data11,0x04-0x0f) 這12個寄存器是用于abstract command的數(shù)據(jù)寄存器,長度為32位,可讀可寫。 ② DM控制寄存器(dmcontrol,0x10) dmcontrol寄存器的每一位含義如圖9所示。 圖9 dmcontrol寄存器 ● haltreq:只寫,寫1表示halt(暫停)當(dāng)前hart(hart表示CPU核,存在多核的情況)。 ● resumereq:只能寫1,寫1表示resume(恢復(fù))當(dāng)前hart,即go。 ● hartreset:可讀可寫,寫1表示復(fù)位DM模塊,寫0表示撤銷復(fù)位,這是一個可選的位。 ● ackhavereset:只能寫1,寫1表示清除當(dāng)前hart的havereset狀態(tài)。 ● hasel:可讀可寫,0表示當(dāng)前只有一個已經(jīng)被選擇了的hart,1表示當(dāng)前可能有多個已經(jīng)被選擇了的hart。 ● hartsello:可讀可寫,當(dāng)前選擇的hart的低10位。1位表示一個hart。 ● hartselhi:可讀可寫,當(dāng)前選擇的hart的高10位。1位表示一個hart。如果只有一個hart,那么hasel的值為0,hartsello的值為1,hartselhi的值為0。 ● setresethaltreq:只能寫1,寫1表示當(dāng)前選擇的hart復(fù)位后處于harted狀態(tài)。 ● clrresethaltreq:只能寫1,寫1表示清除setresethaltreq的值。 ● ndmreset:可讀可寫,寫1表示復(fù)位整個系統(tǒng),寫0表示撤銷復(fù)位。 ● dmactive:可讀可寫,寫0表示復(fù)位DM模塊,寫1表示讓DM模塊正常工作。正常調(diào)試時,此位必須為1。 ③ DM狀態(tài)寄存器(dmstatus,0x11) dmstatus寄存器是一個只讀寄存器,每一位含義如圖10所示。 圖10 dmstatus寄存器 ● impebreak:1表示執(zhí)行完progbuf的指令后自動插入一條ebreak指令,這樣就可以節(jié)省一個progbuf。當(dāng)progbufsize的值為1時,此值必須為1。 ● allhavereset:1表示當(dāng)前選擇的hart已經(jīng)復(fù)位。 ● anyhavereset:1表示當(dāng)前選擇的hart至少有一個已經(jīng)復(fù)位。 ● allresumeack:1表示當(dāng)前選擇的所有hart已經(jīng)應(yīng)答上一次的resume請求。 ● anyresumeack:1表示當(dāng)前選擇的hart至少有一個已經(jīng)應(yīng)答上一次的resume請求。 ● allnonexistent:1表示當(dāng)前選擇的hart不存在于當(dāng)前平臺。 ● anynonexistent:1表示至少有一個選擇了的hart不存在于當(dāng)前平臺。 ● allunavail:1表示當(dāng)前選擇的hart都不可用。 ● anyunavail:1表示至少有一個選擇了的hart不可用。 ● allrunning:1表示當(dāng)前選擇的hart都處于running狀態(tài)。 ● anyrunning:1表示至少有一個選擇了的hart處于running狀態(tài)。 ● allhalted:1表示當(dāng)前選擇的hart都處于halted狀態(tài)。 ● anyhalted:1表示至少有一個選擇了的hart處于halted狀態(tài)。 ● authenticated:0表示使用DM模塊之前需要進行認(rèn)證,1表示已經(jīng)通過認(rèn)證。 ● authbusy:0表示可以進行正常的認(rèn)證,1表示認(rèn)證處于忙狀態(tài)。 ● hasresethaltreq:1表示DM模塊支持復(fù)位后處于halted狀態(tài),0表示不支持。 ● confstrptrvalid:1表示confstrptr0~3寄存器保存了配置字符串的地址。 ● version:0表示DM模塊不存在,1表示DM模塊的版本為0.11,2表示DM模塊的版本為0.13。 ④ abstract控制和狀態(tài)寄存器(abstractcs,0x16) abstractcs寄存器定義如圖11所示。 圖11 abstractcs寄存器 ● progbufsize:只讀,program buffer的個數(shù),取值范圍為0~16,每一個的大小為32位。 ● busy:只讀,1表示abstract命令正在執(zhí)行,當(dāng)寫command寄存器后該位應(yīng)該馬上被置位直到命令執(zhí)行完成。 ● cmderr:可讀、只能寫1,cmderr的值僅當(dāng)busy位為0時有效。0表示無錯誤,1表示正在操作command、abstractcs、data或者progbuf寄存器,2表示不支持當(dāng)前命令,3表示執(zhí)行命令時出現(xiàn)異常,4表示由于當(dāng)前hart不可用,或者不是處于halted/running狀態(tài)而不能被執(zhí)行,5表示由于總線出錯(對齊、訪問大小、超時)導(dǎo)致的錯誤,7表示其他錯誤。寫1清零cmderr。 ● datacount:只讀,所實現(xiàn)的data寄存器的個數(shù)。 ⑤ abstract命令寄存器(command,0x17) 當(dāng)寫這個寄存器時,相應(yīng)的操作就會被執(zhí)行。command寄存器只能寫,定義如圖12所示。 圖12 command寄存器 ● cmdtype:只寫,命令類型,0為表示訪問寄存器,1表示快速訪問,2表示訪問內(nèi)存。 ● control:只寫,不同的命令類型有不同的含義,說明如下。 當(dāng)cmdtype為0時,control定義如圖13所示。 圖13 訪問寄存器 ● cmdtype:值為0。 ● aarsize:2表示訪問寄存器的最低32位,3表示訪問寄存器的最低64位,4表示訪問寄存器的最低128位。如果大于實際寄存器的大小則此次訪問是失敗的。 ● aarpostincrement:1表示成功訪問寄存器后自動增加regno的值。 ● postexec:1表示執(zhí)行progbuf里的內(nèi)容(指令)。 ● transfer:0表示不執(zhí)行write指定的操作,1表示執(zhí)行write指定的操作。 ● write:0表示從指定的寄存器拷貝數(shù)據(jù)到arg0指定的data寄存器。1表示從arg0指定的data寄存器拷貝數(shù)據(jù)到指定的寄存器。 ● regno:要訪問的寄存器。 綜上,可知: Ⅰ. 當(dāng)write=0,transfer=1時,從regno指定的寄存器拷貝數(shù)據(jù)到arg0對應(yīng)的data寄存器。 Ⅱ. 當(dāng)write=1,transfer=1時,從arg0對應(yīng)的data寄存器拷貝數(shù)據(jù)到regno指定的寄存器。 Ⅲ. 當(dāng)aarpostincrement=1時,將regno的值加1。 Ⅳ. 當(dāng)postexec=1時,執(zhí)行progbuf寄存器里的指令。 arg對應(yīng)的data寄存器如圖14所示。 圖14 arg對應(yīng)的data寄存器 即當(dāng)訪問的寄存器位數(shù)為32位時,arg0對應(yīng)data0寄存器,arg1對應(yīng)data1寄存器,arg2對應(yīng)data2寄存器。 當(dāng)cmdtype為1時,control定義如圖15所示。 圖15 快速訪問 ● cmdtyte:值為1。 此命令會執(zhí)行以下操作: 1)halt住當(dāng)前hart。 2)執(zhí)行progbuf寄存器里的指令。 3)resume當(dāng)前hart。 當(dāng)cmdtype為2時,control定義如圖16所示。 圖16 訪問內(nèi)存 ● cmdtype:值為2。 ● aamvirtual:0表示訪問的是物理地址,1表示訪問的是虛擬地址。 ● aamsize:0表示訪問內(nèi)存的低8位,1表示訪問內(nèi)存的低16位,2表示訪問內(nèi)存的低32位,3表示訪問內(nèi)存的低64位,4表示訪問內(nèi)存的低128位。 ● aampostincrement:1表示訪問成功后,將arg1對應(yīng)的data寄存器的值加上aamsize對應(yīng)的字節(jié)數(shù)。 ● write:0表示從arg1指定的地址拷貝數(shù)據(jù)到arg0指定的data寄存器,1表示從arg0指定的data寄存器拷貝數(shù)據(jù)到arg1指定的地址。 ● target-specific:保留。 綜上,可知: Ⅰ. 當(dāng)write=0時,從arg1指定的地址拷貝數(shù)據(jù)到arg0指定的data寄存器。 Ⅱ. 當(dāng)write=1時,從arg0指定的data寄存器拷貝數(shù)據(jù)到arg1指定的地址。 Ⅲ. 當(dāng)aampostincrement=1時,增加arg1對應(yīng)的data寄存器的值。 ⑥ 系統(tǒng)總線訪問控制和狀態(tài)寄存器(sbcs,0x38) sbcs寄存器定義如圖17所示。 圖17 sbcs寄存器 ● sbversion:只讀,0表示system bus是2018.1.1之前的版本,1表示當(dāng)前debug spec的版本,即0.13版本。 ● sbbusyerror:只讀,寫1清零,當(dāng)debugger要進行system bus訪問操作時,如果上一次的system bus訪問還在進行中,此時會置位該位。 ● sbbusy:只讀,1表示system bus正在忙。在進行system bus訪問前必須確保該位為0。 ● sbreadonaddr:可讀可寫,1表示每次往sbaddress0寄存器寫數(shù)據(jù)時,將會自動觸發(fā)system bus從新的地址讀取數(shù)據(jù)。 ● sbaccess:可讀可寫,訪問的數(shù)據(jù)寬度,0表示8位,1表示16位,2表示32位,3表示64位,4表示128位。 ● sbautoincrement:可讀可寫,1表示每次system bus訪問后自動將sbaddress的值加上sbaccess的大小(字節(jié))。 ● sbreadondata:可讀可寫,1表示每次從sbdata0寄存器讀數(shù)據(jù)后將自動觸發(fā)system bus從新的地址讀取數(shù)據(jù)。 ● sberror:可讀,寫1清零,0表示無錯誤,1表示超時,2表示訪問地址錯誤,3表示地址對齊錯誤,4表示訪問大小錯誤,7表示其他錯誤。 ● sbasize:只讀,system bus地址寬度(位數(shù)),0表示不支持system bus訪問。 ● sbaccess128:只讀,1表示system bus支持128位訪問。 ● sbaccess64:只讀,1表示system bus支持64位訪問。 ● sbaccess32:只讀,1表示system bus支持32位訪問。 ● sbaccess16:只讀,1表示system bus支持16位訪問。 ● sbaccess8:只讀,1表示system bus支持8位訪問。 ⑦ 系統(tǒng)總線地址0寄存器(sbaddress0,0x39) 可讀可寫,如果sbcs寄存器中的sbasize的值為0,那么此寄存器可以不用實現(xiàn)。 當(dāng)寫該寄存器時,會執(zhí)行以下流程: Ⅰ. 設(shè)置sbcs.sbbusy的值為1。 Ⅱ. 從新的sbaddress地址讀取數(shù)據(jù)。 Ⅲ. 如果讀取成功并且sbcs.sbautoincrement的值為1,則增加sbaddress的值。 Ⅳ. 設(shè)置sbcs.sbbusy的值為0。 ⑧ 系統(tǒng)總線數(shù)據(jù)0寄存器(sbdata0,0x3c) 可讀可寫,如果sbcs寄存器中的所有sbaccessxx的值都為0,那么此寄存器可以不用實現(xiàn)。 當(dāng)寫該寄存器時,會執(zhí)行以下流程: Ⅰ. 設(shè)置sbcs.sbbusy的值為1。 Ⅱ. 將sbdata的值寫到sbaddress指定的地址。 Ⅲ. 如果寫成功并且sbcs.sbautoincrement的值為1,則增加sbaddress的值。 Ⅳ. 設(shè)置sbcs.sbbusy的值為0。 當(dāng)讀該寄存器時,會執(zhí)行以下流程: Ⅰ. 準(zhǔn)備返回讀取的數(shù)據(jù)。 Ⅱ. 設(shè)置sbcs.sbbusy的值為1。 Ⅲ. 如果sbcs.sbautoincrement的值為1,則增加sbaddress的值。 Ⅳ. 如果sbcs.sbreadondata的值為1,則開始下一次讀操作。 Ⅴ. 設(shè)置sbcs.sbbusy的值為0。 三、RISC-V調(diào)試上位機分析 RISC-V官方支持的調(diào)試器上位機是openocd。openocd是地表最強大(沒有之一)的開源調(diào)試上位機,支持各種target(ARM(M、A系列)、FPGA、RISC-V等),支持各種調(diào)試器(Jlink、CMSIS-DAP、FTDI等),支持JTAG和SWD接口。 這里不打算詳細(xì)分析整個openocd的實現(xiàn),只是重點關(guān)注針對RISC-V平臺的初始化、讀寫寄存器和讀寫內(nèi)存這幾個流程。 1>openocd啟動過程 openocd啟動時需要通過-f參數(shù)制定一個cfg文件,比如: openocd.exe -f riscv.cfg riscv.cfg文件的內(nèi)容如下: adapter_khz1000 reset_config srst_only adapter_nsrst_assert_width 100 interface cmsis-dap transport select jtag set _CHIPNAME riscv jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x1e200a6d set _TARGETNAME $_CHIPNAME.cpu target create $_TARGETNAME riscv -chain-position $_TARGETNAME ■ 第一行設(shè)置TCK的時鐘為1000KHz。 ■ 第二行表示不支持通過TRST引腳復(fù)位,只支持TMS為高電平并持續(xù)5個TCK時鐘這種方式的復(fù)位。 ■ 第三行是復(fù)位持續(xù)的延時。 ■ 第四行指定調(diào)試器為CMSIS-DAP。 ■ 第五行指定調(diào)試接口為JTAG。 ■ 第六行指定調(diào)試的target類型為riscv。 ■ 第七行指定生成一個IR寄存器長度為5位、IDCODE為0x1e200a6d的JTAG TAP。 ■ 第八、九行指定生成一個riscv target。 openocd啟動時的主要流程如圖18所示。 圖18 openocd啟動流程 下面重點關(guān)注一下examine target這個流程。 這里的target是指riscv,對于riscv,首先會讀取dtmcontrol這個寄存器,因為openocd支持0.11和0.13版本的DTM,通過這個寄存器可以知道當(dāng)前調(diào)試的DTM是哪一個版本。這里選擇0.13版本來分析。通過讀取dtmcontrol,還可以知道idle、abits這些參數(shù)。接下來會將dmcontrol這個寄存器的dmactive域?qū)?后再寫1來復(fù)位DM模塊。接下來再讀取dmstatus,判斷version域是否為2。接下來還會讀取sbcs和abstractcs寄存器,最后就是初始化每一個hart的寄存器。 2>read register過程 讀寄存器時,先構(gòu)建command寄存器的內(nèi)容,首先將cmdtype的值設(shè)為0,aarsize的值設(shè)為2(寄存器的寬度為32位),transfer的值設(shè)為1,regno的值設(shè)為要讀的寄存器的number,其他值設(shè)為0,然后寫到command寄存器里。然后一直讀取abstractcs寄存器,直到abstractcs寄存器的busy位為0或者超時。然后再判斷abstractcs寄存器的cmderr的值是否為0,如果不為0則表示此次讀取寄存器失敗,如果為0則繼續(xù)讀取data0寄存器,這樣就可以得到想要讀的寄存器的值。 3>write register過程 寫寄存器時,先將需要寫的值寫到data0寄存器,然后構(gòu)建command寄存器的內(nèi)容,首先將cmdtype的值設(shè)為0,aarsize的值設(shè)為2(寄存器的寬度為32位),transfer的值設(shè)為1,write的值設(shè)為1,regno的值設(shè)為要寫的寄存器的number,其他值設(shè)為0,然后寫到command寄存器里。然后一直讀取abstractcs寄存器,直到abstractcs寄存器的busy位為0或者超時。然后再判斷abstractcs寄存器的cmderr的值是否為0,如果不為0則表示此次寫寄存器失敗,如果為0則表示寫寄存器成功。 4>read memory過程 如果progbufsize的值大于等于2,則會優(yōu)先使用通過執(zhí)行指令的方式來讀取內(nèi)存。這里不分析這種方式,而是分析使用system bus的方式。通過前面的分析可知,system bus有兩個版本V0和V1,這里以V1版本來說明。 先將sbcs寄存器的sbreadonaddr的值設(shè)為1,sbaccess的值設(shè)為2(32位),然后將要讀內(nèi)存的地址寫入sbaddress0寄存器。接著讀sbdata0寄存器,最后讀sbcs寄存器,如果其中的sbbusy、sberror和sbbusyerror都為0,則從sbdata0讀取到的內(nèi)容就是要讀的內(nèi)存的值。 5>write memory過程 和read memory類似,同樣以V1版本來說明。 先將要寫的內(nèi)存地址寫到sbaddress0寄存器,然后將要寫的數(shù)據(jù)寫到data0寄存器,最后讀sbcs寄存器,如果其中的sbbusy、sberror和sbbusyerror都為0,則此次寫內(nèi)存成功。 四、RISC-V JTAG的實現(xiàn) 通過在STM32F103C8T6上實現(xiàn)(模擬)RISC-V調(diào)試標(biāo)準(zhǔn),進一步加深對RISC-V JTAG調(diào)試的理解。 使用STM32的四個GPIO作為JTAG信號的四根線,其中TCK所在的引腳設(shè)為外部中斷,即上升沿和下降沿觸發(fā)方式,實現(xiàn)了可以通過openocd以RISC-V的調(diào)試標(biāo)準(zhǔn)來訪問STM32的寄存器和內(nèi)存。程序流程如圖19所示。 圖19 JTAG實現(xiàn)的程序流程 五、參考資料 1、在STM32上模擬RISC-V JTAG的實現(xiàn):stm32_riscv_jtag_slave 2、一個從零開始寫的易懂的RISC-V處理器核:tinyriscv
    發(fā)表于 11-28 22:00

    Microchip發(fā)布面向VS Code的MPLAB擴展早期體驗版本

    為充分利用Microsoft Visual Studio Code (VS Code) 的多功能性,Microchip Technology(微芯科技公司)發(fā)布面向VS
    的頭像 發(fā)表于 08-28 10:01 ?657次閱讀

    深入淺出系列之代碼可讀性

    原創(chuàng)聲明:該文章是個人在項目中親歷后的經(jīng)驗總結(jié)和分享,如有搬運需求請注明出處。 這是“深入淺出系列”文章的第一篇,主要記錄和分享程序設(shè)計的一些思想和方法論,如果讀者覺得所有受用,還請“一鍵三連
    的頭像 發(fā)表于 08-09 16:00 ?284次閱讀

    深入淺出談TDR阻抗測試

    Chrent為什么要測阻抗?計算機、通信系統(tǒng)、視頻系統(tǒng)和網(wǎng)絡(luò)系統(tǒng)等領(lǐng)域的數(shù)字系統(tǒng)開發(fā)人員正面臨著越來越快的時鐘頻率和數(shù)據(jù)速率,隨之,信號完整性變得越來越重要。在當(dāng)前的高工作速率下,影響信號上升時間、脈寬、時序、抖動或噪聲內(nèi)容的任何事物都會影響整個系統(tǒng)的性能和可靠性。為保證信號完整性,必須了解和控制信號經(jīng)過的傳輸環(huán)境的阻抗。阻抗不匹配和不連續(xù)會導(dǎo)致反射,增加系
    的頭像 發(fā)表于 06-06 08:28 ?6327次閱讀
    <b class='flag-5'>深入淺出</b>談TDR阻抗測試

    深入淺出帶你搞懂-MOSFET柵極電阻

    一、MOSFET簡介MOSFET是金屬(metal)—氧化物(oxide)—半導(dǎo)體(semiconductor)場效應(yīng)晶體管,屬于電壓控制電流型元件,是開關(guān)電路中的基本元件,其柵極(G極)內(nèi)阻極高。以N溝道增強型為例,其結(jié)構(gòu)為在一塊濃度較低的P型硅上擴散兩個濃度較高的N型區(qū)作為漏極和源極,半導(dǎo)體表面覆蓋二氧化硅絕緣層并引出一個電極作為柵極。由于mos管本身的
    的頭像 發(fā)表于 05-09 08:10 ?2.3w次閱讀
    <b class='flag-5'>深入淺出</b>帶你搞懂-MOSFET柵極電阻

    深入淺出TVS二極管的工作原理和特性

    ESD保護二極管設(shè)計在信號線與GND之間,保護DUP器件免受浪涌電壓的沖擊。在正常工作模式下,ESD保護二極管幾乎沒有電流經(jīng)過,只會有少量電流使反向擊穿電壓高于信號線電壓。
    發(fā)表于 04-25 10:53 ?803次閱讀
    <b class='flag-5'>深入淺出</b>TVS二極管的工作原理和特性

    怎么理解負(fù)頻率呢?射頻人眼中的負(fù)頻率

    說實話,我對負(fù)頻率這個概念,也是有點凌亂。不過,最近不是正在看“深入淺出通信原理”嘛,看了一些相關(guān)概念。
    的頭像 發(fā)表于 03-05 16:10 ?3433次閱讀
    怎么理解負(fù)頻率呢?射頻人眼中的負(fù)頻率

    VS CodeVS Codium之間的區(qū)別有哪些?你選哪個?

    VS Codium 是一個 VS Code 的克隆版本,百分之百免費且開源。
    的頭像 發(fā)表于 02-23 15:28 ?1976次閱讀
    <b class='flag-5'>VS</b> <b class='flag-5'>Code</b>和<b class='flag-5'>VS</b> Codium之間的區(qū)別有哪些?你選哪個?

    深入淺出理解三極管

    原文來自原創(chuàng)書籍《硬件設(shè)計指南 從器件認(rèn)知到手機基帶設(shè)計》: 本小節(jié)介紹下三極管的特性,清晰易懂,使用通俗的水流模型加強對三極管的原理記憶,一定比課堂上講的要形象的多,各位同學(xué)要學(xué)會類比的方法來加深記憶(比如在介紹相對論中引力扭曲時空的概念時,國外科學(xué)家們就用生活中的漩渦,或者在彈性膜中間的重球,來類比星體引力對時空的影響,這樣會大大簡化我們學(xué)習(xí)、理解和記憶的過程,這種學(xué)習(xí)方法被稱為類比學(xué)習(xí)法)。 我們
    的頭像 發(fā)表于 02-23 08:41 ?734次閱讀
    <b class='flag-5'>深入淺出</b>理解三極管

    Simplicity Studio 5擴增功能支持以VS Code開發(fā)

    隨著SimplicityStudio 5 (SSv5) 5.6.0.0版本的發(fā)布,SiliconLabs(亦稱“芯科科技”)已經(jīng)引入了針對Visual Studio CodeVS Code)作為
    的頭像 發(fā)表于 01-29 10:34 ?1055次閱讀
    Simplicity Studio 5擴增功能支持以<b class='flag-5'>VS</b> <b class='flag-5'>Code</b>開發(fā)