嵌入式為什么沒有軟件架構(gòu)師?
我從事嵌入式軟件開發(fā)有6、7個(gè)年頭,bsp,驅(qū)動(dòng),應(yīng)用軟件,android hall,framework等都有涉獵。平時(shí)除了關(guān)注嵌入式行業(yè)的發(fā)展,也多少對(duì)Web,后臺(tái)服務(wù)端,分布式等方向的技術(shù)有一些關(guān)注。
近期有萌生換個(gè)行業(yè)方向的想法,想做做后臺(tái)服務(wù)器相關(guān)的開發(fā),由于之前工作中并沒有這方面的實(shí)際需求,只是自己平時(shí)關(guān)注,了解了些知識(shí),比如:
NIO
,epoll
,ngnix
,zeromq
,libevent
,libuv
,高并發(fā),分布式,redis
,python
,tornado
,django
,涉獵比較雜,都了解個(gè)皮毛,不精。意外的是屢屢被互聯(lián)網(wǎng)行業(yè)鄙視,面試機(jī)會(huì)都寥寥無幾。此時(shí)我想到底是什么問題呢,難道嵌入式出身的已經(jīng)這么不受待見了嗎?想當(dāng)初,嵌入式,驅(qū)動(dòng)開發(fā),可是趨之若鶩的行業(yè)(有點(diǎn)夸張,不過8,9年前嵌入式可是聽著比做java web的要牛逼些哦)問題總是有原因的,我說下自己的理解:為什么沒有嵌入式軟件架構(gòu)師?
打開各種招聘網(wǎng)站,搜索架構(gòu)師,會(huì)出現(xiàn)各種系統(tǒng)架構(gòu)師,web架構(gòu)師,后臺(tái)服務(wù)端架構(gòu)師等等,但是唯獨(dú)很難看到嵌入式軟件架構(gòu)師。嵌入式軟件不需要架構(gòu)嗎,驅(qū)動(dòng)不需要架構(gòu)嗎?答案是當(dāng)然需要,不過為什么沒有這方面的職位?我的看法:**目前國(guó)內(nèi)的嵌入式開發(fā)主要分為嵌入式底層開發(fā)和嵌入式應(yīng)用開發(fā),嵌入式的底層開發(fā)一般叫做驅(qū)動(dòng)開發(fā),或者bsp開發(fā),有時(shí)也有稱之為linux內(nèi)核開發(fā),**名字聽著都很高大上的感覺。這么高大上的名字為什么沒有架構(gòu)師呢:linux kernel的架構(gòu)師是linus等一眾linux kernel開發(fā)維護(hù)者,因?yàn)楸旧韑inux kernel或者操作系統(tǒng)就是一個(gè)通用的平臺(tái),解決通用的問題,linux開源屆的大牛都已經(jīng)制定好了架構(gòu)規(guī)則,留給可發(fā)揮的地方并不多,大部分工作只需要按照規(guī)則框架填充就可以了。而且以目前國(guó)內(nèi)大部分公司的業(yè)務(wù)需求,只是在做外圍設(shè)備的集成,嵌入式平臺(tái)的porting,搭建裁剪,業(yè)務(wù)需求完全不會(huì)超過kernel里提供的功能范圍。導(dǎo)致沒有什么新的架構(gòu)需要開發(fā)人員去設(shè)計(jì),實(shí)現(xiàn)。那嵌入式bsp開發(fā)人員都在做什么:除了調(diào)試多種多樣的外設(shè),替硬件擦屁股,就是解些穩(wěn)定性的bug了(這里對(duì)具體工作不詳細(xì)描述了,調(diào)試外設(shè)只會(huì)增加一些經(jīng)驗(yàn),增加廣度,對(duì)提高深度貢獻(xiàn)不大,只是按不會(huì)調(diào)試->會(huì)調(diào)試->調(diào)試的快這個(gè)路線發(fā)展,而解穩(wěn)定性問題確實(shí)是需要一些積累經(jīng)驗(yàn))而嵌入式上的應(yīng)用開發(fā),一般業(yè)務(wù)邏輯比較簡(jiǎn)單,被很多人忽略,所以招聘方也會(huì)感覺沒有什么必要找架構(gòu)師級(jí)別的了。至此感覺嵌入式行業(yè)的確不需要架構(gòu)師,被互聯(lián)網(wǎng)行業(yè)的鄙視也沒什么大驚小怪的。但的確是這樣子的嗎?對(duì)于嵌入式底層的開發(fā),有能力對(duì)kernel,驅(qū)動(dòng)架構(gòu)提出架構(gòu)層優(yōu)化的,國(guó)內(nèi)的開發(fā)人員應(yīng)該不多,所以對(duì)于大部分普通人,還是不要“妄想”做Linux kernel的架構(gòu)師了(當(dāng)然我相信國(guó)人中一定存在有這個(gè)能力的大牛),發(fā)現(xiàn),解決一些bug,到更靠譜些。
我們真的不需要架構(gòu)嗎?
以自己的實(shí)際經(jīng)歷講述下曾經(jīng)對(duì)一個(gè)嵌入式設(shè)備應(yīng)用軟件的架構(gòu)設(shè)計(jì)和優(yōu)化:我曾經(jīng)接手過一個(gè)項(xiàng)目,項(xiàng)目采用單進(jìn)程多線程的模型,項(xiàng)目中包括幾個(gè)模塊,以a, b, c, d,e代表。這個(gè)項(xiàng)目的業(yè)務(wù)邏輯決定這幾個(gè)模塊有不少關(guān)聯(lián)。例如:最初的設(shè)計(jì)中a模塊是一個(gè)狀態(tài)監(jiān)測(cè)模塊,它會(huì)基于監(jiān)測(cè)到的狀態(tài)調(diào)用b,c模塊的接口實(shí)現(xiàn)一些功能(多線程的好處就是直接調(diào)用很方便,所以開發(fā)人員大多這么干,簡(jiǎn)單粗暴),但是需求總是千變?nèi)f化,加入一個(gè)f模塊,f模塊也需要對(duì)a模塊監(jiān)測(cè)的狀態(tài)進(jìn)行一個(gè)處理,按照之前的套路,完成這個(gè)功能分兩步:1,在f模塊提供個(gè)接口;2,在a模塊中調(diào)用該接口。至此新需求已經(jīng)“完美”的解決了。前面提到需求總是千變?nèi)f化的,新的需求又來了,客戶提出定制需求,需要加入另一個(gè)g模塊,同樣處理a模塊監(jiān)測(cè)的狀態(tài),但是該定制需求不需要?jiǎng)倓偧尤氲膄模塊,此時(shí)最簡(jiǎn)單粗暴的方式是,定義一個(gè)宏,區(qū)分該定制需求和之前的通用需求,build兩個(gè)程序版本。這樣的做法看似簡(jiǎn)單,但后面如果定制需求逐漸增多,維護(hù)這么多定制版本程序就是個(gè)噩夢(mèng),代碼管理和通用性也會(huì)是很大的問題,同時(shí)代碼中充斥著對(duì)不同宏定義的差異化處理,#ifdef?xxx
do_something;
#endif
比較好的做法是加入設(shè)備型號(hào)版本的動(dòng)態(tài)監(jiān)測(cè),用一個(gè)build程序版本動(dòng)態(tài)支持所有的定制需求,這樣減少了對(duì)不同build程序的維護(hù)。但是這種做法只解決build程序的版本維護(hù)工作,沒有解決宏定義差異化處理的問題,只是會(huì)將之前的宏判斷,改為動(dòng)態(tài)設(shè)備版本號(hào)判斷,如果這些差異化的判斷只集中在一處進(jìn)行,也不會(huì)引起大的復(fù)雜化的問題,但顯然這個(gè)是不好保證,有可能這些差異化的處理會(huì)蔓延到整個(gè)項(xiàng)目的各個(gè)角落,這樣項(xiàng)目維護(hù)起來就會(huì)變成一場(chǎng)噩夢(mèng)。不需要什么高深的軟件思想,大部人都會(huì)想到把差異化的部分提取出來,放在一個(gè)統(tǒng)一的地方集中管理,對(duì)差異化的修改只集中在這個(gè)統(tǒng)一管理的地方。通用做法就是采用callback設(shè)置鉤子,然后在callback中定制差異化的需求,對(duì)callback的處理做差異化的配置。對(duì)應(yīng)到上面例子,就是在a模塊添加一個(gè)鉤子,然后在系統(tǒng)初始化時(shí),根據(jù)設(shè)備版本號(hào)的不同,差異化定制callback處理函數(shù),同時(shí)要將這些定制callback處理函數(shù)放在同一地方處理,否則仍然分散在各個(gè)角落里就沒有意義(前一種方式不放置鉤子是無法將這些差異化配置放在一起的)。這樣處理帶來的另外一個(gè)好處是,我們對(duì)功能性需求的改變,不會(huì)影響到a模塊的處理,也就是我們添加功能,不需要修改a模塊的代碼了(前一種方式要修改a模塊的調(diào)用流程),這樣也就實(shí)現(xiàn)了一個(gè)模塊的分離。至此第二種的方案的架構(gòu)(其實(shí)也談不上架構(gòu)了)相比第一種方案已經(jīng)有了不少提升,至少讓開發(fā)人員稍微輕松了些,對(duì)于其他定制需求,開發(fā)人員之需要修改這個(gè)callback處理,關(guān)注差異化部分就可以了。軟件是需要不斷進(jìn)化的,第二種方案是最優(yōu)解嗎,當(dāng)然不是,還有優(yōu)化空間嗎?下面先跑個(gè)題,談?wù)劧嗑€程/多進(jìn)程模型的優(yōu)缺點(diǎn),主要談多進(jìn)程的優(yōu)點(diǎn)了:教科書上的解釋就不提了,首先我對(duì)大的項(xiàng)目是推崇多進(jìn)程模型,無關(guān)性能,主要原因有:模塊的解耦
很多開發(fā)人員維護(hù)開發(fā)的多線程模型項(xiàng)目應(yīng)該都多少會(huì)存在下面的問題:跨模塊間的直接調(diào)用,如果不相信,好,你的項(xiàng)目一定是分模塊的吧,現(xiàn)在隨機(jī)的刪掉一個(gè)模塊,build下看能build通過嗎(只需要build不需要運(yùn)行),我相信大部分情況下一定會(huì)遇到某個(gè)函數(shù)調(diào)用,某個(gè)全局變量找不到的情況,這種情況說明你的模塊間存在強(qiáng)耦合了。由于多線程天然的優(yōu)勢(shì),地址空間的相互可見,導(dǎo)致直接調(diào)用十分容易,很多經(jīng)驗(yàn)尚淺的工程師,很容易就寫出直接調(diào)用的簡(jiǎn)單粗暴的接口,如果遇到個(gè)static接口的函數(shù),圖方便也會(huì)把static去掉,直接拿過來用了。這樣整個(gè)工程隨著功能不斷的添加,模塊間的交叉越來越多,耦合越高。而我之所以推崇多進(jìn)程的原因就是,多進(jìn)程能從物理上隔絕了這種“方便”的通訊方式,導(dǎo)致在想實(shí)現(xiàn)一個(gè)模塊交互時(shí),會(huì)多思考下這個(gè)交互是必要的嗎,如果是必要的,則會(huì)進(jìn)一步思考接口定義是否簡(jiǎn)單明了(因?yàn)檫M(jìn)程間的通訊相對(duì)會(huì)麻煩些,開發(fā)人員會(huì)本著能減少交互,明確接口的想法去仔細(xì)考慮接口,協(xié)議的定義,否則折騰的是自己了),這如同人生,如果一直順風(fēng)順?biāo)藗兛赡懿粫?huì)想太多,思考太多,而如果道路上有些坎坷,則會(huì)有另一種感悟吧。所以我的想法是多進(jìn)程的模型會(huì)逼迫你去更多的思考想程序的設(shè)計(jì),物理上減少模塊的耦合。抽象通用組件,分離通用功能和業(yè)務(wù)邏輯功能:當(dāng)把一個(gè)多線程模型修改為多進(jìn)程模型的過程中,經(jīng)常會(huì)發(fā)現(xiàn)有些接口代碼重復(fù)的出現(xiàn)在多個(gè)進(jìn)程模塊中,因?yàn)橹敖涌诤瘮?shù)是在一個(gè)進(jìn)程空間,大家都可以直接調(diào)用的,比如接口A被模塊a;b調(diào)用,模塊a,b分離為兩個(gè)獨(dú)立的進(jìn)程后,接口A需要在a,b中分別實(shí)現(xiàn)了,無需解釋,重復(fù)代碼這個(gè)在軟件工程中是大忌,必須消除。做法也很簡(jiǎn)單,將這些被多個(gè)模塊調(diào)用的接口分離處理做成lib,供其他模塊調(diào)用,當(dāng)你完成這部分工作后,你發(fā)現(xiàn)了什么,是不是剝離的接口,可以作為整個(gè)項(xiàng)目的通用組件存在了,完美的情況下,lib下的代碼是通用基礎(chǔ)組件,各個(gè)模塊中是獨(dú)立的業(yè)務(wù)處理模塊。方便定位問題
多線程模型中當(dāng)又一個(gè)線程異常退出,會(huì)導(dǎo)致整個(gè)進(jìn)程退出,當(dāng)然通過一些crash信息,可以定位是那個(gè)線程死掉,但如果這些線程模塊是由多個(gè)小組,人員維護(hù),當(dāng)整個(gè)進(jìn)程崩潰掉后,如何判斷由那個(gè)小組解決,會(huì)是一個(gè)大的問題,而且有時(shí)還會(huì)出現(xiàn)的現(xiàn)象是掛在一個(gè)線程,但其實(shí)是另外一個(gè)線程模塊引起的(耦合的禍端),遇到這種情況,難免出現(xiàn)小組間的扯皮,推諉。(自信的工程師都認(rèn)為我的代碼沒有問題)而如果采用多進(jìn)程的模型,好吧,你的服務(wù)進(jìn)程掛了,你自己找原因吧,沒什么可爭(zhēng)辯的了。方便性能測(cè)試
多線程種單個(gè)線程的資源占用不是很好查看(至少有些嵌入式系統(tǒng)沒有完善的命令),當(dāng)整個(gè)進(jìn)程資源消耗很高時(shí),如何判斷定位時(shí)那個(gè)模塊線程的問題,同3一樣難以抉擇,而如果是多進(jìn)程的模型,誰的進(jìn)程占了好多資源,誰就去查下吧,其實(shí)這個(gè)還是個(gè)顆粒度的問題,同樣的系統(tǒng),劃分成多個(gè)進(jìn)程,單個(gè)進(jìn)程的復(fù)雜度一定比只有一個(gè)進(jìn)程的復(fù)雜度低的多,復(fù)雜度降低,也就更容易定位查找各種問題。分布式部署
互聯(lián)網(wǎng)行業(yè)一直強(qiáng)調(diào)的分布式,云啊什么的,嵌入式行業(yè)就很苦逼了,貌似不需要什么分布式吧,其實(shí)也對(duì),大部分情況下,嵌入式采用單芯片,獨(dú)立運(yùn)行,分布式遇到的很少。但如果萬一那天你在一個(gè)設(shè)備中,將本來一個(gè)芯片完成的功能分散到兩個(gè)芯片中處理呢,多進(jìn)程的擴(kuò)展就容易的多了。這只是舉個(gè)特殊的例子,其實(shí)嵌入式設(shè)備就是個(gè)分布式的行業(yè),只是一開始就已經(jīng)實(shí)現(xiàn)分離了,而不是從集中到分布式的路線發(fā)展起來的。方便公司的代碼權(quán)限隔離:其實(shí)我鄙視這種做法,公司要相信自己的員工,但鑒于誠(chéng)信在中國(guó)已經(jīng)。。。。,做些隔離也無可厚非了。多線程模型下,前面講到如果去除一個(gè)模塊,你可能都不能build了,那么是要把所有代碼暴露給所有的工程師嗎,顯然不能,所以各個(gè)模塊只能提供庫(kù)的形式了,不過我覺得將通用功能接口組織成通用庫(kù)是正常的做法,而如果把和業(yè)務(wù)相關(guān)的模塊也提供成庫(kù),就有點(diǎn)。。。。至此在補(bǔ)充一下,以上所有的優(yōu)點(diǎn),其實(shí)都不是很關(guān)鍵的點(diǎn),都不能夠讓多進(jìn)程有絕對(duì)的優(yōu)勢(shì)壓倒多線程模型,只是從個(gè)人的角度覺得,多進(jìn)程模型更能強(qiáng)迫工程師思考解決一些問題。(而這些問題有經(jīng)驗(yàn)的工程師無論什么模型都會(huì)思考的)上面說了這么多,該考慮下把之前項(xiàng)目的例子改成多進(jìn)程模型,否則就只是紙上談兵了,下面開始:首當(dāng)其沖的問題就是:選擇多進(jìn)程的通訊方式,多線程間的直接調(diào)用是不能用了,那么如何選擇多進(jìn)程的通訊方式呢?linux下提供很多ipc方式,此處不一一列舉,對(duì)于非大數(shù)據(jù)量的控制,通訊消息的傳遞,比較好的方式是采用socket,本機(jī)上更多采用unix socket方式,(這種方式有什么好處?當(dāng)你有需要把單一系統(tǒng)做成分布式系統(tǒng)時(shí),優(yōu)勢(shì)就明顯了)但是僅僅采用socket來實(shí)現(xiàn)前面例子的功能,同樣會(huì)存在一些問題:還是前面的例子,首先說明前面我們優(yōu)化后的第二種方案在多進(jìn)程模型已經(jīng)不能在繼續(xù)使用了,原因比較簡(jiǎn)單,應(yīng)該不需要解釋……簡(jiǎn)單的做法即基于方案一,把直接調(diào)用改為socket通信(定義好通信協(xié)議即可),但是熟悉socket開發(fā)的工程師都清楚,開始socket通信要先進(jìn)行一些前期的工作(主要就是連接,將兩個(gè)模塊關(guān)聯(lián)起來),所以前面的例子會(huì)變成這個(gè)樣子,模塊a要和模塊b,c建立連接,如果加入f模塊,模塊a還要和f模塊建立連接。這樣情況在心里畫一張連接圖就會(huì)發(fā)現(xiàn)好像我們織了一張蜘蛛網(wǎng),節(jié)點(diǎn)間的關(guān)系錯(cuò)綜復(fù)雜,而且和方案一一樣,我們添加一個(gè)和a關(guān)聯(lián)的模塊,就要修改模塊a的代碼,而且這種情況比多線程模型還有繁瑣復(fù)雜的多了。這種做法絕對(duì)是個(gè)噩夢(mèng)。好吧如何解決,我想很多人一定想到了采用總線分發(fā)的方式。了解android系統(tǒng)開發(fā)的會(huì)想到binder,了解openwrt的會(huì)想到ubus,了解桌面會(huì)想到dbus,互聯(lián)網(wǎng)行業(yè)的開發(fā)者一定也知道redis里提供的sub/pub模塊。上面的binder,ubus等原理很簡(jiǎn)單,就是建立一個(gè)消息中心,構(gòu)建一個(gè)轉(zhuǎn)發(fā)路由模型,所有其他模塊之間不直接交互,而是采用消息中心轉(zhuǎn)發(fā),路由,而如何決定路由規(guī)則,則采用訂閱/發(fā)布的觀察者模式來進(jìn)行規(guī)則的定義。嵌入式開發(fā)或者c語言開發(fā)者,經(jīng)常會(huì)誤以為設(shè)計(jì)模式是和面向?qū)ο笳Z言關(guān)聯(lián)的,是面向?qū)ο笳Z言獨(dú)有,雖然有很多大牛做了這方面的普及,但鑒于有些開發(fā)者的信息渠道比較閉塞,導(dǎo)致這種想法仍然十分盛行基于這個(gè)模型,我們上面例子的需求就很好解決了,加入一個(gè)消息中心模塊,所有需要通信的模塊只同該消息中心模塊連接,然后訂閱自己感興趣的事件,當(dāng)事件發(fā)生時(shí),只需要進(jìn)行相應(yīng)的處理就可以了。這樣上面的模塊b,c訂閱模塊a的事件,當(dāng)模塊a檢測(cè)到某事件時(shí),發(fā)布該事件,該事件先到達(dá)消息中心,在由消息中心轉(zhuǎn)發(fā)給模塊b,c,而對(duì)于新加入的模塊f,也只需要訂閱該模塊,而不需要在修改到模塊a的代碼,使功能的擴(kuò)展十分方便。同時(shí)對(duì)于前面提到的定制化開發(fā)同樣得到了簡(jiǎn)化,如果定制化版本需要加入模塊g,這樣只需要定制化版本中將模塊g作為一個(gè)獨(dú)立進(jìn)程啟動(dòng),然后訂閱模塊a的事件即可,而定制版本和通用版的區(qū)別就在于是否啟動(dòng)模塊g的進(jìn)程,從而實(shí)現(xiàn)了軟件工程的一個(gè)目標(biāo):功能的添加如同搭積木一樣,只需要把一個(gè)模塊插入(啟動(dòng))或拔出(不啟動(dòng))即可,功能的改變只局限在一個(gè)或某幾個(gè)模塊間,對(duì)主體框架不會(huì)有任何影響。以上大概描述了對(duì)一個(gè)項(xiàng)目需求逐步優(yōu)化的過程,例子看似是基于嵌入式項(xiàng)目,但貌似對(duì)軟件工程同樣適用。
來到互聯(lián)網(wǎng)行業(yè)
查看下各大網(wǎng)站架構(gòu)師對(duì)本網(wǎng)站技術(shù)架構(gòu)變革分享的文章,首先提到的一般都是,基于業(yè)務(wù)將之前的一個(gè)應(yīng)用服務(wù)器功能拆分,更加細(xì)化(比如電商對(duì)登錄,注冊(cè),交易,商品,賣家等業(yè)務(wù)服務(wù)的拆分),然后將拆分出來的服務(wù)部署在多臺(tái)服務(wù)器上,來提供并發(fā)。這里是否有些耳熟,和前面講到的多線程到多進(jìn)程的劃分是否有相似呢。拆分后同樣遇到通信的問題,此時(shí)很多消息中間件應(yīng)運(yùn)而生,比如阿里的duboo,簡(jiǎn)單了解下這些中間件的原理,無外乎訂閱發(fā)布,RPC等機(jī)制,可以說大同小異,而難點(diǎn)在于協(xié)議的制定和性能處理的提升。在對(duì)照下互聯(lián)網(wǎng)行業(yè)的負(fù)載均衡方案,仿佛那個(gè)負(fù)載均衡的前端也像一個(gè)消息中心了。上面說了這么多,只是想說明一個(gè)問題,軟件的設(shè)計(jì)是相通的,基于的思想是相同的,雖然嵌入式行業(yè)的業(yè)務(wù)邏輯相對(duì)比較簡(jiǎn)單,但其實(shí)在仔細(xì)思考后,仍然會(huì)有很多架構(gòu)上的改進(jìn),設(shè)計(jì)。但是讓我感到悲哀的是,有些嵌入式開發(fā)者,鑒于業(yè)務(wù)邏輯的簡(jiǎn)單,感覺采用一些不那么好的處理方式也能解決問題,不去思考如何去優(yōu)化,改進(jìn)。**比如上面例子的方案一,如果在定制需求不多的情況下,維護(hù)起來也沒太大問題,即使定制需求多了,再招些初級(jí)程序員也能維護(hù)的過來,一個(gè)人一套代碼負(fù)責(zé)一個(gè)項(xiàng)目的公司也不是不存在。同樣互聯(lián)網(wǎng)行業(yè)和嵌入式行業(yè)也不應(yīng)該存在一個(gè)不可以逾越的高墻,我們更應(yīng)該關(guān)注的是通用的軟件工程思想。聲明:本文素材來源網(wǎng)絡(luò),版權(quán)歸原作者所有。如涉及作品版權(quán)問題,請(qǐng)與我聯(lián)系刪除。—— The End?——
推薦好文??點(diǎn)擊圖片即可跳轉(zhuǎn)
嵌入式入門必看,看看老鳥如何華麗蛻變?。ǜ韶浄窒硖?/p>
C語言如何使用斷言避免踩坑
6步!教你寫一個(gè)mqtt調(diào)試助手
提高單片機(jī)設(shè)計(jì)的秘訣,不容忽視的10個(gè)細(xì)節(jié)
分享???點(diǎn)贊???在看????以“三連”行動(dòng)支持優(yōu)質(zhì)內(nèi)容!