讀懂操作系統之緩存原理(cache)(三)

前言

本節內容計劃是講解TLB與高速緩存的關係,但是在涉及高速緩的前提是我們必須要了解操作系統緩存原理,所以提前先詳細了解下緩存原理,我們依然是採取循序漸進的方式來解答緩存原理,若有敘述不當之處,還請批評指正。

緩存原理

高速緩存被劃分為多個塊,其大小可能不同,緩存中的塊數通常為2的冪。如下為一個具有八個塊的高速緩存,每個塊包含一個字節。

通過本節對緩存原理的學習我們能夠學習到四點:

【1】當我們將數據塊從主存儲器複製到緩存,我們到底應該放在哪裡?

【2】如何判斷一個字是否已經在緩存中,或者它是否必須首先從主存儲器中獲取?

【3】較小的緩存最終會填滿, 需要至從主存加載新塊,我們必須替換緩存中現有的哪個塊?

【4】存儲系統如何處理寫操作?

數據放在緩存哪裡?

緩存最簡單的數據結構是直接映射: 其中每個存儲器地址僅僅對應到緩存中的一個位置。例如如下,16個字節的主存和4個字節的緩存(每個塊一個字節),內存地址為0、4、8、12分別映射到緩存中為0的塊,而地址1、5、9、13被映射到塊1

等等,我們是不是講解的太快了,上述地址怎麼就劃分到比如塊0或塊1了呢?要找出緩存所在塊採取取模法:(塊地址)mod (緩存中的塊數),如果緩存包含2k塊,則內存地址i處的數據將進入緩存塊索引為i mod 2k。還是不懂?我們來舉個例子,如下緩存有4個塊,那麼地址為14將映射到塊2即(14 mod 4 = 2)。

為便於大家理解如上為10進製表示內存地址,將內存地址映射到緩存塊中實際等效的方式是將內存地址中的最低有效k位(二進制)進行映射。正如下面我們所看到的,內存地址14(1110,二進制)將最低有效位10作為塊中的索引

怎樣找到緩存中數據?

到目前為止我們知道了將地址利用直接映射的結構映射到緩存中,那麼我們找到數據是否在緩存中呢?如果要讀取內存地址i,則可以使用mod技巧來確定哪個緩存塊將包含i,如上所述,若其他地址也可能映射到相同的緩存塊,那麼我們如何區分它們呢?例如如下內存地址2、6、10、14都在緩存塊2中

為了解決這個問題,我們需要向高速緩存中添加標記(tag),通過內存地址的高位來提供標記位,以使我們能夠區分映射到同一高速緩存塊的不同存儲位置。例如如下。內存地址6即(0110,二進制),將低位10作為索引(index),高位01作為標記(tag)。

我們通過將高速緩存塊標記(tag)與塊索引(index)組合起來,可以準確地知道主存儲器的哪些地址存儲在高速緩存中。

當程序加載到內存中時,緩存為空,不包含有效數據,我們應該通過為每個緩存塊添加一個有效位來解決這個問題,系統初始化時,所有有效位均設置為0,當數據加載到特定的緩存塊中時,相應的有效位設置為1。

當CPU嘗試從內存中讀取數據時,該地址將被發送到緩存控制器,地址的最低k位將在緩存中索引一個塊,如果該塊有效且標籤與m位地址的高(m-k)位匹配,則該數據將被發送到CPU,如下為一個32位內存地址和210字節高速緩存的圖。

到這裏我們會發現一個問題,將每一個字節對應一字節緩存塊並沒有很好的利用空間局部性,要是訪問一個地址后將訪問附近的地址,我們又該怎麼辦?我們要做的是將緩存塊的大小要大於1個字節。如下,我們使用兩個字節的塊,因此我們可以用兩個來加載緩存一次讀取一個字節,如果我們從內存地址12讀取數據,則地址中的數據12和13都將被複制到緩存塊2。

現在,我們又該如何確定數據應放在緩存中的位置?現在演變成塊地址,如果緩存塊大小為2n字節,我們也可以在概念上將主內存也劃分成2n字節塊,要確定字節地址i的塊地址,可以進行整數除法(i / 2n),如下示例中有2個字節的緩存塊,因此我們可以將16個字節的主存儲器視為8塊主存儲器,例如,存儲器地址12和13都對應於塊地址6,因為12 / 2 = 6和13 / 2 = 6。 

現在我們知道了塊地址,就可以像上述一樣將其映射到緩存:找到塊地址除以緩存塊數后的餘數。在如下示例中,內存塊6屬於緩存塊2,因為6 mod 4 =2,這對應於將來自存儲器字節地址12和13的數據都放入高速緩存塊2中。

當我們訪問內存中的一個字節數據時,我們會將其整個塊複製到緩存中以達到充分利用空間局部性。在我們的示例中,如果程序從字節地址12讀取,我們會將所有存儲塊6(地址12和13)都加載到緩存塊2中(注意:字節地址13對應於相同的存儲塊地址)因此,對地址13的讀取也會導致將存儲塊6(地址12和13)加載到高速緩存塊2中。為了簡化起見,存儲塊的字節i始終存儲在相應高速緩存塊的字節i中。

假設我們有一個包含2k塊的緩存,每個塊包含2n個字節,我們可以通過查看其在主內存中的地址來確定該緩存中一個字節的數據位置,地址的k位將選擇2k個高速緩存塊之一,最低的n位現在是一個塊偏移量,它決定了高速緩存塊中的2n個字節中的哪個將存儲數據。

我們來舉個例子加深理解,如下示例使用22塊高速緩存,每個塊佔21字節,因此,存儲器地址13(1101)將存儲在高速緩存塊2的字節1中。

到這裏為止,我們才算分析清楚了緩存中有效位、標記位、索引、偏移它們的由來以及實際作用。同時對於緩存採用的直接映射(direct mapped)結構:索引和偏移量可以使用位運算符或簡單的算術運算,因為每個內存地址都恰好屬於一個塊。實際上我們可以將一個塊放置到緩存中的任何一個位置,這種機制稱為全相聯(fully associative)。全相聯的高速緩存允許將數據存儲在任何高速緩存塊中,而不是將每個內存地址強制映射到一個特定的塊中,從內存中獲取數據時,可以將其放置在高速緩存的任何未使用塊中。 這樣,我們將永遠不會在映射到單個緩存塊的兩個或多個內存地址之間發生衝突,在上述示例中,我們可能將內存地址2放在緩存塊2中,並將地址6放在塊3中。然後對2和6的後續重複訪問將全部命中而不是未命中,如果所有塊都已被使用,則使用LRU算法進行替換。但是在全相聯緩存中要查找一個指定的塊,由於該塊存放在緩存中的任何位置,因此需要檢索緩存中的所有項,為了是檢索更加有效,它是由一個與緩存中每個項都相關的比較器并行完成的,這些比較器加大了硬件開銷,因而,全相聯只適合塊數較少的緩存。介於直接映射和全相聯之間的設計是組相聯(set associative)。在組相聯緩存中,每個塊可被放置的位置數固定,每個塊有n個位置可放的緩存被稱作n路組相聯,一個n路組相聯緩存由很多組組成,每個組有n個塊。通過上述對直接映射的講解,最終我們得出指定內存地址所在存儲的塊號為:(塊號) mod (緩存中的塊數),而組相聯對於存儲塊號是:(塊號) mod (緩存中的組數)。如下為8塊高速緩存的組織

組相聯實際上就是將塊進行分組,比如如上第一個圖則是直接映射(我們大可將其看做是每一個塊就是一個組,所以是1路8組相聯),而第二張圖則是每2個塊作為一組,所以是2路4組相聯,同理第三張圖是4路2組相聯。換句話說,若每組有2n塊,那麼就是2n路相聯。通過對組相聯的講解,我們再敘內存地址在組相聯緩存中的位置。如果我們有2s組並且每塊有2n字節,那麼內存地址映射在緩存中的位置則是如下這般

現在我們運算則是計算緩存中的組索引而非再是塊,上述Block offset(在組中塊偏移)= 內存地址 mod 2n,塊地址 = 內存地址 / 2n,set index(組索引) = 塊地址 mod 2s。我們還是通過圖解來進行敘述,假設有一個8塊的高速緩存,然後每個塊是16個字節,那麼內存地址為6195的數據存儲在緩存哪裡呢?首先我們將6195轉換為二進制  = 110000 011 0011,因每個塊是16字節即24,所以塊偏移量為4位即0011,若採用1路8組相聯(直接映射)那麼其組索引就是(6195 mod 8) = 3,取6195轉換而二進制去除偏移量4位,所以為011,同理(根據上述給定計算公式)對於2路4組相聯其組索引為(11),4路2組相聯其組索引為(1),如下:

到這裏我們知道將數據進行緩存我們可以採取直接映射、組相聯、全相聯的機制,通過增加相聯度通常可以降低緩存缺失率,但是增加相聯度也就增加了每組中的塊數,也就是并行查找時同時比較的次數,相聯度每增加兩倍就會使得每組中的塊數加倍而使得組數減半,所以增大了訪問時間的開銷。如何找到一個塊,當然也就依賴於所使用的將塊放置的機制(直接映射、組相聯、全相聯),相較於全相聯而言,它使用複雜的替換策略而降低缺失率且很容易被索引,而不需要額外的硬件,也不需要進行查找。因此虛擬存儲系統通常使用全相聯映射,而組相聯映射通常應用於緩存和TLB。

緩存缺失和寫操作

緩存缺失被分為以下三類(3C模型,three Cs model),因其三類名稱以字母c開頭而得名

強制缺失(compulsory miss):對從沒有在緩存中出現的塊第一次進行訪問引起的缺失,也稱為冷啟動缺失(cold-start miss)

容量缺失:(capacity miss):由於緩存容納不了一個程序執行所需要的所有塊而引起的緩存缺失,當某些塊被替換出去,隨後再被調入時,將發生容量缺失

衝突缺失(conflict miss):在組相聯或者直接映射的緩存中,多個競爭同一個組時而引起的緩存缺失。衝突缺失在直接映射或組相聯緩存中存在,而在同樣大小的全相聯緩存中不存在,這種緩存缺失也稱為碰撞缺失(collision miss)

改變緩存設計的某一方面就能直接影響這些缺失的原因。衝突缺失是因為爭用同一個緩存塊而引起的,因此提高相聯度可以減少衝突缺失,然後提高相聯度會延長訪問時間,導致整個性能的降低,容量缺失可以簡單地通過增大緩存容量來減少,當然緩存容量增大的同時必然導致訪問時間的增加,也將導致整體性能的降低。

在相聯的緩存中發生缺失時,我們必須決定替換哪一塊,如若是全相聯,那麼所有的塊都是被替換的候選者,如若是組相聯,我們必須在某一組的塊中進行選擇,當然,直接映射的緩存替換很簡單,因為只有一個可以替換的候選者。因此在全相聯或組相聯緩存中 ,有兩種主要的替換策略

隨機法:隨機選擇候選塊,可能使用一些硬件來協助實現,例如TLB缺失、MIPS支持隨機替換

LRU(最近最少使用算法):被替換的塊是最久沒有被使用過的塊 (在大多虛擬存儲器中,對於LRU都是通過提供引用位來近似實現(比如TLB))

指令緩存缺失(數據缺失也類似如此)處理步驟如下:

【1】將程序計數器(PC)的原始值送到寄存器

【2】通知主存執行一次讀操作,並等待主存訪問完成

【3】寫緩存項,將從主存取回的數據寫入緩存中存放數據的部分,並將高位(從ALU中得到)寫入標記域,設置有效位

【4】重啟指令執行第一步,重新取指,這次該指令發生在緩存中

數據訪問是對緩存的控制基本相同:發生缺失時,處理器發生阻塞,直到從存儲器中取回數據后才響應。在執行寫操作時,如果有一個存儲指令,我們只將數據寫入緩存而不改變主存中的內容,那麼在寫入緩存后將導致緩存和主存被認為不一致,保持主存和緩存一致性最簡單的方法是將數據同時寫入主存和緩存中,這種方法稱為【寫直達】法。但是這種方法無法提供良好的性能,因為每次寫操作都要把數據寫入主存中,這些寫操作將花費大量的時間,可能至少花費100個處理時鐘周期,並且大大降低了機器速度,解決這個問題的方案之一是採用【寫緩衝:一個保存等待寫入主存數據的緩衝隊列】,當一個數據在等待寫入緩存時,先將其寫入緩衝中,當數據寫入緩存和緩衝后,處理器可以繼續執行,當寫主存操作完成后,寫緩衝里的數據項也得到有效釋放。如果寫緩衝已經滿了,那麼當處理器執行到一個寫操作時就必須停下來直到寫緩衝中有一個空位置,當然,如果存儲器完成寫操作的速度比處理器產生寫操作的速度慢,那麼再多的緩衝器也無用,因為產生寫操作比存儲系統接收它們更快。

 

除了寫直達方法外,另外一種可選擇的方法是【寫回】,在寫回機制中,當發生寫操作時,新值僅僅被寫入到緩存塊中,只有當修改過的塊被替換時才需要寫到磁盤上,寫回機制可提高系統性能,尤其是當處理器寫操作的速度和主存處理寫操作速度一樣快甚至更快時,但是,寫回機制的實現比寫直達要複雜得多。大部分寫回機制的緩衝也是使用寫緩衝,當在發生缺失替換一個被修改的塊時,寫緩衝可以起到降低缺失代價的作用。在這種情況下,被修改的數據塊移入與緩存相聯的寫回緩衝器,同時從主存中讀出所需要的數據塊。隨後,寫回緩衝器再將數據寫入主存,如果下一次缺失沒有立刻發生,當臟數據塊必須被替換時,這種方法可以減少一半的缺失代價。

總結

一個緩存塊可以放在何處:一個位置(直接映射),一些位置(組相聯),任何位置(全相聯)。

如何找到一個塊:索引(直接映射的緩存中),有限的檢索(組相聯的緩存中),全部檢索(全相聯的緩存中)、專用查找頁表。

緩存缺失時替換哪一塊:隨機選取、LRU

寫操作如何處理:寫直達或寫回策略

本文我們非常詳細的講解了緩存的基本原理,當然對於如何處理緩存一致性並未涉及(大多採用監聽協議),希望通過我對緩存原理的理解能給閱讀的您能有力所能及的幫助,謝謝。 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

搭上末班車去了京東,終於可以做東哥兄弟…

介紹下自己

開篇先簡單介紹一下自己,雙非本科,大三在讀,通信學院物聯網工程專業。這個專業的發展方向大致分為軟、硬件兩種,大二的時候感覺自己更喜歡軟件方面,也就開始學習比較常用的 Java。

到了今年三月,開始投簡歷,投的都是 Java 開發工程師崗,參与春招實習生招聘,想體驗一下筆試、面試的過程,最好能拿到實習offer。

搭上了末班車

春招基本結束了,算是搭上了春招的末班車,被春招支配的恐懼也可以告一段落了,藉此機會梳理一下我的春招歷程。

被恐懼支配

剛開始心態很差,每過一面就會感覺很放鬆,可能會開心一下,這種開心的時候可能就不是很用心的在學習。

然而每掛一面就會非常沮喪,心情很低落,連續幾天都會整理不好心情繼續沉浸在學習中。還會懷疑自己是不是不行,是不是能力不夠,是不是….

心情起起落落,甚至在每面完一場,都會隔幾分鐘就刷新官網,看下流程情況,看下自己是不是掛掉了。焦灼的心,顫抖的手,浮躁的狀態。

完完全全被焦慮和恐懼給支配,沒心思學習,感覺自己整個思路、情緒、狀態都到了一個極點,而且是自己沒辦法突破的極點。

被恐懼支配比恐懼本身恐懼多了,而且後勁十足

我苦苦在尋找一個突破口,但一直沒能找到如何突破。如果找不到這個突破口,接下來的春招之路感覺很難走下去。

我遇到了龍叔

一次偶然在朋友圈看到同學轉發一篇文章,是說面試介紹的 當你面試“自我介紹”還在我是XXX時,看到這篇文章的同學們已經拿到了offer… ,看是關於面試的文章,就點進去看了。

看這篇文章確實補充了我很多盲點,於是我就點進去看了下其他的文章,比如這篇 學會龍叔這套面試秘訣,一套大招帶走面試官 ,發現裏面可以加交流群 和 加作者微信

還看到龍叔給粉絲輔導簡歷,於是我就鼓起勇氣加了龍叔微信,把自己的情況描述了下。抱着試試的態度,沒想到龍叔非常認真的回答了我,而且還在粉絲交流群里給大家說了。

非常幸運的遇到了龍叔,他告訴我,

面試完就不要總是等待結果,面試就像期末考,考完我們都不會再去翻書了。面完了,結果就不在我們考慮範圍之內了,要為下一場準備,也不能寄希望於一個公司,應該把心思放在複習和準備上。時刻保持自己的面試狀態,充滿鬥志、不要灰心。因為 offer 是需要流程的,不是面完就發,多準備,多面,之後就是收割 offer 的事情。天天憂心忡忡的,實在無濟於事,完全是浪費自己的時間。

我聽完犹如醍醐灌頂,受益匪淺,麻溜兒地寫在便簽紙上,貼在眼前,提醒自己。

人總有失意和遇到困難想不通的時候,而這時候能讓我的思想從短路變為通路,非常感謝龍叔。

成功的路總是不平坦的

之後四月份,身邊同學陸陸續續有收到 offer 的,去牛客網每次刷新的時候,也都是喜提校招或者實習 offer的記錄帖,羡慕、恰檸檬之餘。

我為自己還沒有理想的offer 感到發愁,又到了浮躁的一個新階段。這個階段雖說是浮躁,但是比起剛開始那一堵牆,已經好很多了。

會和朋友相互鼓勵,相互吐槽失敗的面試,心情 down 的時候聽聽大張偉的《陽光彩虹小白馬》 “你就是最強噠最棒噠最亮噠最發光噠”,努力讓自己平和、快樂,強行相信自己。很快就能調整好自己的狀態,繼續投入到戰鬥中。

心態太重要了,只有心態好了,複習才能更加有效率

這兩個月的面試中,讓我自己印象深刻的是騰訊三面,可能是傳說中的壓力面之類的。

當時操作系統學的不好,說明了之後,面試官還是在操作系統這方面窮追不舍地發問,從一開始的語氣溫柔、帶着笑意,到後來漸漸嚴肅、帶着凶意,與此同時我也意識到這最後一輪技術面多半是涼透了,要和我 say byebye了。

最後面試官甚至問 你到底有沒有學過操作系統?你是女生,為什麼要學開發?

當時的面試,我沒控制住情緒,為自己的菜流下了委屈的淚水

其實也沒什麼委屈的,畢竟菜是原罪,哈哈哈。可能因為人生第一回總監面,沒見過這種大場面的原因吧,還是要見多識廣啊。

事後反思,這樣實在是不合適,這是頂不住壓力的表現,面試官希望看到的,應該是沉着冷靜的,嘗試去解決問題的求職者,而不是這樣愛哭鼻子的。

一些總結

經歷這近三個月的面試,從開始自我介紹都結巴,到現在可以心跳正常地和面試官交流,收穫還是蠻多的。

我感到實力才是硬道理,結果的決定權在公司手裡,作為求職者,我們總是會被置於與其他同樣水平的人作比較的地位,只能不斷提高實力,才可能脫穎而出。

保持平和的心態會帶來一些好的運氣,還有就是堅持下去,最後一定會收穫好結果的。

從簡歷篩選、筆試、輪輪面試,一步一步過關,每次面試過程會錄音,之後復盤,通過復盤去看自己當時為什麼沒有回答上來,為什麼沒有收到面試官的青睞。

通過復盤,把不會的問題都搞明白,把該加分沒加上的,在後續的面試一定加上。面試完需要儘快查漏補缺,保持心態,堅持下去。

春季實習招聘還是比較寬容的,大廠也沒有因為我學歷不出色而不給面試機會,而且很多家的面試體驗還是很不錯的,有的面試官會引導我、會糾正我的錯誤、給予建議。

即使最後沒有通過,也是學到了一些東西,面試本身就是一種學習。

面試最好的狀態是和面試官交流,而不是硬生的回答。

最後希望秋招時,我可以擁有更平和的心態和更紮實的基礎,收穫自己心儀的offer~ ,也希望和我一起奮鬥的你們都能找到滿意的offer。

高頻考點

這裏列出遇到的面試中高頻的考點(被問到三次以上的那種~):

  • Java 基礎:HashMap源碼、泛型、NIO
  • 數據結構與算法:紅黑樹、堆、 海量數據中找top k 問題、 快速排序、堆排序
  • JVM :垃圾回收機制、Full GC、類加載機制
  • 數據庫:事務、索引、鎖、查詢優化、排查慢查詢
  • Spring 框架:IOC、AOP、事務、SpringMVC、常見註解
  • 操作系統:進程和線程、虛擬內存
  • 網絡:HTTPS、TCP三次握手四次揮手、HTTP狀態碼
  • 手撕算法:基本都是劍指offer上面的原題,還有 生產者-消費者模型
  • 再有,如果有讀過併發包中的源碼,或者對線程安全相關問題有自己的思考,也是很加分的。

這些是非常高頻的面試題,還有一些常規的,就不一一列舉了。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

上周熱點回顧(6.1-6.7)

熱點隨筆:

· Python驗證碼識別 (______null)
· .Net Core 會逆襲成為最受歡迎開發平台嗎? (葡萄城技術團隊)
· 從一年前的1200多人優化到現在200多人,待在這樣的技術團隊是一種什麼體驗? (沛山)
· 字符串太占內存了,我想了各種奇思淫巧對它進行壓縮 (一線碼農)
· 27歲了,程序員寫給自己的一封信 (學習Java的小姐姐)
· 和付費網盤說再見,跟着本文自己起個網盤(Java 開源項目) (削微寒)
· 啪啪,打臉了!領導說:try-catch必須放在循環體外! (Java中文社群)
· 同學叫我一起創業,我不聽,他現在月入10萬,我羡慕死了,我已悟到了成功的秘訣! (jonlan)
· ASP.NET Core Blazor Webassembly 之 數據綁定 (Agile.Zhou)
· MySql輕鬆入門系列——第一站 從源碼角度輕鬆認識mysql整體框架圖 (一線碼農)
· 我終於搞清了啥是 HTTPS 了 (極客挖掘機)
· 六一兒童節,程序員寫給女兒的一封信 (沉默王二)

熱點新聞:

· 月入兩萬的程序員背着電腦送外賣 好隨時改寫代碼
· 《紅警》重製版登上Steam暢銷榜:EA直接放出遊戲源代碼
· 擺攤吧,互聯網人!
· 一鍵“卸載中國應用”這款App,在印度火了
· 鄭皆連院士:中國是橋樑大國卻非橋樑強國 輸在了軟件上
· “刪除中國應用” App被下架,印度人表示氣憤,並喊話劈柴哥出面
· 你常吃的阿莫西林,正在引起一場災難
· 微軟新品被指剽竊!程序員開源兩年的成功項目被迫終結
· 獵鷹與龍飛船基於Linux採用 C++、Chromium與JS開發
· 泥坑裡爬出的任正非
· 唯美的李子柒,世俗的商業化
· 微信支持改 ID 之前,我在好友的微信號里發現了這些秘密

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

過年前9萬預算買輛車!該買什麼車最實用?

其搭載的1。5L地球夢發動機,最大功率131馬力,峰值扭矩155牛米,和CVT變速箱搭配動力響應性出色,加速實力“有點猛”。很好地兼顧了動力以及油耗。空間實用的國貨SUV吉利汽車-遠景SUV指導價:7。49-10。19萬9萬元的預算也可以選擇現在火熱的國產SUV車型,它們空間實用,坐姿高、視野也不錯。

本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

ReentrantLock原理分析

一 UML類圖

1.1、ReentrantLock 

通過類圖ReentrantLock是同步鎖,同一時間只能有一個線程獲取到鎖,其他獲取該鎖的線程會被阻塞而被放入AQS阻塞隊列中。ReentrantLock類繼承Lock接口;內部抽象類Sync實現抽象隊列同步器AbstractQueuedSynchronizer;Sync類有兩個子類NonfairSync(非公平鎖)和FairSync(公平鎖),默認構造方法使用非公平鎖,可以使用帶布爾參數的構造方法指定使用公平鎖;ReentrantLock可以創建多個條件進行綁定。

1.2、AbstractQueuedSynchronizer

AbstractQueuedSynchronizer:抽象隊列同步器,維護一個volatile int state變量標識共享資源和一個FIFO線程阻塞隊列(AQS隊列)。

protected final void setState(int newState):設置state值

protected final int getState():獲取state值

protected final boolean compareAndSetState(int expect, int update):CAS設置state值

AQS有兩種共享資源類型:SHARED(共享)和EXCLUSIVE(獨佔),針對類型有不同的方法:

protected boolean tryAcquire(int arg):獨佔類型獲取鎖

protected boolean tryRelease(int arg):獨佔類型釋放鎖

protected int tryAcquireShared(int arg):共享類型獲取鎖

protected boolean tryReleaseShared(int arg):共享類型釋放鎖

protected boolean isHeldExclusively():是否是獨佔類型

1.3、線程節點類型waitStatus

AQS隊列中節點的waitStatus枚舉值(java.util.concurrent.locks.AbstractQueuedSynchronizer.Node)含義:

 static final int CANCELLED = 1; //線程被取消

static final int SIGNAL = -1; //成功的線程需要被喚醒
static final int CONDITION = -2; //線程在條件隊列中等待
static final int PROPAGATE = -3; //釋放鎖是需要通知其他節點

二 原理分析

2.1 獲取鎖

2.1.1 void lock()方法

調用線程T調用該方法嘗試獲取當前鎖。

①如果鎖未被其他線程獲取,則調用線程T成功獲取到當前鎖,然後設置當前鎖的擁有者為調用線程T,並設置AQS的狀態值state為1,然後直接返回。

②如果調用線程T之前已經獲取當前鎖,則只設置設置AQS的狀態值state加1,然後返回。

③如果當前鎖已被其他線程獲取,則調用線程T放入AQS隊列后阻塞掛起。

public void lock() {
    sync.lock();//委託內部公平鎖和非公平鎖獲取鎖
} 
//非公平鎖
final
void lock() { if (compareAndSetState(0, 1))//設置AQS狀態值為1 setExclusiveOwnerThread(Thread.currentThread());//成功設置鎖的線程擁有者 else acquire(1);//失敗加入到AQS隊列阻塞掛起 } //公平鎖 final void lock() { acquire(1); }

非公平鎖分析:

//調用父類AbstractOwnableSynchronizer方法CAS設置state值,成功返回true,失敗返回false
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
//調用父類AbstractOwnableSynchronizer方法,設置當前線程為鎖的擁有者
protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}
//調用AbstractQueuedSynchronizer父類方法,第一次獲取鎖失敗
public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//排它鎖類型
        selfInterrupt();
}
//NonfairSync子類,重寫父類方法
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
//Sync類非公平鎖嘗試獲取鎖
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {//二次獲取鎖 if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {//當前線程已獲取鎖,AQS狀態值加1 int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
//AbstractQueuedSynchronizer類創建節點,添加到AQS隊列後面
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);//創建AQS隊列的節點,節點類型排它鎖
    Node pred = tail;//尾結點 if (pred != null) {
        node.prev = pred;//新節點的前一個節點是尾結點 if (compareAndSetTail(pred, node)) {//CAS機制添加節點
            pred.next = node;//尾結點執行新的節點 return node;
        }
    }
    enq(node);
    return node;
}
//插入節點到隊列中
private
Node enq(final Node node) { for (;;) {//循環的方式,直至創建成功 Node t = tail;//尾結點 if (t == null) { //尾結點為空,初始化 if (compareAndSetHead(new Node()))//第一步:CAS創建頭結點(哨兵節點)一個空節點 tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) {//第二步:CAS設置尾結點 t.next = node; return t; } } } }
//
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();//前向節點 if (p == head && tryAcquire(arg)) {//如果p節點是頭結點,node作為隊列第二個節點
                setHead(node);//將頭節點設置為node節點,node節點出隊列
                p.next = null; //原頭結點設置為空,便於GC回收
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);//失敗解鎖
    }
}
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}
//阻塞掛起當前線程
static void selfInterrupt() {
    Thread.currentThread().interrupt();
}
//
private
static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * This node has already set status asking a release * to signal it, so it can safely park. */ return true; if (ws > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0);//大於0代表線程被取消 pred.next = node; } else { /* * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } //線程阻塞,打斷線程 private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }

公平鎖分析:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {//與非公平鎖相比,區別就在標紅的位置
            setExclusiveOwnerThread(current);
            return true;
        }
    }else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
  //①h != t:表示AQS隊列頭結點和尾結點不相同,隊列不為空;
  //②(s = h.next) == null || s.thread != Thread.currentThread():頭結點(哨兵節點)為空或者next節點不等於當前線程
return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }

 2.1.2 void lockInterruptibly()方法

與2.2.1方法相似,不同之處在於:該方法對中斷進行響應,其他線程調用當前線程中斷方法,拋出InterruptedException。

2.1.3 boolean tryLock()方法

嘗試獲取鎖。注意:該方法不會引起當前線程阻塞。

2.1.4 boolean tryLock(long timeout, TimeUnit unit)方法

與2.1.3方法相似,不同之處在於:設置了超時時間。

2.2 釋放鎖

嘗試釋放鎖。

如果當前線程T已獲取鎖,則調用該方法更新AQS狀態值減1。如果當前狀態值為0,則釋放鎖;如果當前狀態值部位0,則只是減1操作。

如果當前線程T未獲取鎖,則調用該方法是會拋出IllegalMonitorStateException異常。

2.2.1 void unlock()方法

public void unlock() {
    sync.release(1);
}
//調用AbstractQueuedSynchronizer方法
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//喚醒線程 return true;
    }
    return false;
}
//回調Sync類釋放鎖
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);//設置鎖的擁有線程為空
    }
    setState(c);
    return free;
}
//
private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;//線程阻塞等待狀態 if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);//CAS設置狀態 /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)//遍歷AQS隊列 if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);//喚醒線程
}

 

h != t

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

保存良好!擁2.3米長牙巨象遺骸出土 曾被分屍證據曝光

摘錄自2020年5月21日自由時報報導

德國圖賓根大學(University of Tuebingen)發表新研究報告,稱在該國西北部下薩克森邦一處遺址發現至少10頭巨象的遺骸,其狀態從舊石器時代至今仍保持良好,且按現場痕跡判斷,這些巨象死後曾被獵人分屍取肉,目前無法確定是自然死亡或遭人獵殺。

綜合外媒報導,德國圖賓根大學考古團隊針對下薩克森邦舍寧根遺址(Schoningen)出土的歐洲菱齒象遺骸進行復原與研究,團隊指出,其中一頭體型巨大、保存良好的雌象擁有長達2.3公尺的巨牙,研究人員認為,牠在距今約30萬年前死亡,死後遺骸曾被獵人分割。

負責挖掘行動的考古學家塞蘭格利(Jordi Serangeli)指出,在復原這頭雌象遺骸後推斷其肩高約3.2公尺,重約6.8噸,體型大於現今仍存在於世的非洲象。

生活環境
國際新聞
德國
古菱齒象
化石

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

核災後日本中斷鐵道再開 JR員工憂輻射被曝抗議不斷

文:宋瑞文(媽媽監督核電廠聯盟特約撰述)

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

STM32學習筆記——printf

printf複習

當我們寫printf("%d\n", 1);的時候,printf函數並不能通過C語言語法得知第二個參數是int類型。printf是一個變參函數(variadic function):

int printf(const char *restrict format, ...);

參數的類型都是通過格式串format推導出的。如果參數類型與格式串中指定的不匹配,或提供的參數數量少於需要的,將導致未定義行為。

由於參數類型是動態的,printfscanf比靜態類型的std::coutstd::cin慢,前提是後者的眾多overhead被手動消除。

C為可變參數提供了va_startva_argva_copyva_endva_list等工具,定義在頭文件<stdarg.h>中。va_arg用於取出參數,va_copy用於拷貝參數供多次使用。引用cppreference上的例子:

#include <stdio.h>
#include <stdarg.h>
#include <math.h>
 
double sample_stddev(int count, ...) 
{
    /* Compute the mean with args1. */
    double sum = 0;
    va_list args1;
    va_start(args1, count);
    va_list args2;
    va_copy(args2, args1);   /* copy va_list object */
    for (int i = 0; i < count; ++i) {
        double num = va_arg(args1, double);
        sum += num;
    }
    va_end(args1);
    double mean = sum / count;
 
    /* Compute standard deviation with args2 and mean. */
    double sum_sq_diff = 0;
    for (int i = 0; i < count; ++i) {
        double num = va_arg(args2, double);
        sum_sq_diff += (num-mean) * (num-mean);
    }
    va_end(args2);
    return sqrt(sum_sq_diff / count);
}
 
int main(void) 
{
    printf("%f\n", sample_stddev(4, 25.0, 27.3, 26.9, 25.7));
}

<stdio.h>還定義了vprintf系列函數,與不帶v的相比,可變參數...都換成了va_list的實例:

int vprintf(const char *format, va_list vlist);

可以藉此實現自己的printf

可變參數在傳遞的過程中會被執行默認參數提升(default argument promotion),對於整數類型執行整數提升(提升為intunsigned int),對於float類型提升成double

格式串format中的普通字符直接拷貝到輸出流,由%引導的稱為轉換格式(conversion specification),在%和轉換說明符(conversion specifier)之間可以有若干修飾符,實現對齊、精度等功能,轉換說明符有csdf等,詳見cppreference。

UART實現

單片機開發板並沒有可以用於輸出的控制台,printf調用最後都會歸結為_write函數:

int _write(int file, char* ptr, int len);

_write函數需要把ptr指向的len字節的數據以想要的形式發送,在此就沿用上一篇中的UART異步IO,於是printf就可以打印在串口上了。

為了方便日後使用,我把USART相關的代碼抽離出來放在一個新的源文件里,IDE生成的代碼去掉MX_USART1_UART_InitUSART1_IRQHandler兩個函數,再加上這一對文件就可以使用了。

usart1.h

#include <stdio.h>

void MX_USART1_UART_Init();
void usart1_transmit(char c);
char usart1_receive();

usart1.c

#include "usart1.h"

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "cmsis_gcc.h"
#include "stm32f4xx_hal.h"

typedef char queue_element_t;

typedef struct
{
  uint16_t mask;
  uint16_t head;
  uint16_t tail;
  queue_element_t data[0];
} queue_t;

static inline queue_t* queue_create(uint16_t _size)
{
  if (_size & (_size - 1))
    _size = 256;
  queue_t* q = malloc(sizeof(queue_t) + _size * sizeof(queue_element_t));
  if (q)
  {
    q->mask = _size - 1;
    q->head = q->tail = 0;
  }
  return q;
}

static inline bool queue_empty(const queue_t* _queue)
{
  return _queue->head == _queue->tail;
}

static inline uint16_t queue_size(const queue_t* _queue)
{
  return (_queue->tail - _queue->head) & _queue->mask;
}

static inline uint16_t queue_capacity(const queue_t* _queue)
{
  return _queue->mask;
}

static inline queue_element_t queue_peek(const queue_t* _queue)
{
  return _queue->data[_queue->head];
}

static inline void queue_push(queue_t* _queue, const queue_element_t _ele)
{
  _queue->data[_queue->tail] = _ele;
  _queue->tail = (_queue->tail + 1) & _queue->mask;
}

static inline void queue_pop(queue_t* _queue)
{
  _queue->head = (_queue->head + 1) & _queue->mask;
}

extern UART_HandleTypeDef huart1;
extern void Error_Handler();
queue_t* tx_buffer;
queue_t* rx_buffer;

void USART1_IRQHandler()
{
  uint32_t isrflags   = USART1->SR;
  uint32_t cr1its     = USART1->CR1;
  uint32_t errorflags = 0x00U;
  errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
  if (errorflags == RESET)
  {
    if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
    {
      queue_push(rx_buffer, USART1->DR);
      return;
    }
    if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
    {
      USART1->DR = queue_peek(tx_buffer);
      queue_pop(tx_buffer);
      if (queue_empty(tx_buffer))
        USART1->CR1 &= ~USART_CR1_TXEIE & UART_IT_MASK;
      return;
    }
  }
  HAL_UART_IRQHandler(&huart1);
}

void MX_USART1_UART_Init()
{
  tx_buffer = queue_create(1024);
  rx_buffer = queue_create(1024);
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  USART1->CR1 |= USART_CR1_RXNEIE & UART_IT_MASK;
}

void usart1_transmit(char c)
{
  uint16_t capacity = queue_capacity(tx_buffer);
  bool ok = false;
  while (1)
  {
    __disable_irq();
    ok = capacity - queue_size(tx_buffer) >= 1;
    if (ok)
      break;
    __enable_irq();
    __NOP();
  }
  queue_push(tx_buffer, c);
  USART1->CR1 |= USART_CR1_TXEIE & UART_IT_MASK;
  __enable_irq();
}

char usart1_receive()
{
  bool ok = false;
  while (1)
  {
    __disable_irq();
    ok = !queue_empty(rx_buffer);
    if (ok)
      break;
    __enable_irq();
    __NOP();
  }
  char c = queue_peek(rx_buffer);
  queue_pop(rx_buffer);
  __enable_irq();
  return c;
}

int _write(int file, char* ptr, int len)
{
  for (int i = 0; i != len; ++i)
    usart1_transmit(*ptr++);
  return len;
}

main.c(部分):

#include "main.h"
#include "usart1.h"

UART_HandleTypeDef huart1;
uint8_t count = 0;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  while (1)
  {
    printf("Hello world: %d\n", count);
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    ++count;
    HAL_Delay(500);
  }
}

ITM實現

明明已經用調試器連接了開發板和電腦,還要加個USB轉串口工具就顯得很累贅;IDE和串口監視器兩個窗口的頻繁切換也讓Alt和Tab鍵損壞的幾率增加了幾成。有沒有辦法讓開發板通過調試器和IDE就能輸出呢?

可以用ARM的ITM(Instrumentation Trace Macroblock),通過TRACESWO發送。SWO與JTAG的JTDIO是同一個引腳,用標準ST-LINK的20-pin排線可以連接,但是10-pin的簡版ST-LINK沒有引出SWO,因此要使用ITM調試不能用簡版的4線接法。

ITM無需初始化,直接調用ITM_SendChar函數即可發送,該函數定義在\Drivers\CMSIS\Include\core_cmx.h中。ITM版的_write函數,不過是把usart1_transmit換成ITM_SendChar而已。

#include "main.h"
#include <stdio.h>

void SystemClock_Config(void);
static void MX_GPIO_Init(void);

int _write(int file, char* ptr, int len)
{
  for (int i = 0; i != len; ++i)
    ITM_SendChar(*ptr++);
  return len;
}

uint8_t count = 0;

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  while (1)
  {
    printf("Hello world: %d\n", count);
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    ++count;
    HAL_Delay(500);
  }
}

為了在IDE中看到printf輸出的內容,需要做幾步配置。首先進入Debug模式,在調試選項的Debugger頁啟用SWV:

找到SWV ITM Data Console窗口:

窗口右上角Configure trace,勾選Port 0:

點擊Start Trace。這樣就可以看見printf的輸出了:

雜記

好久沒更博客了。這兩周一直在做搖搖棒,硬件軟件交替着改,總算是做出一個比較穩定的显示效果了。計劃本月再更兩篇。

有一次下載器與搖搖棒的連接有鬆動,數據傳輸錯誤,導致熔絲位被修改,時鐘源選擇了不存在的,程序無法啟動,也無法下載新的程序。還好我帶着這塊STM32開發板,在一個引腳上產生一個較高頻率的方波,連接到單片機的晶振引腳,改回熔絲位,算是把單片機救活了。本來STM32開發板帶着是要寫這篇printf的,博客沒寫,倒是有救場的用途。

printf相對的scanf,我也嘗試過實現,但是有兩個問題,一是我沒有找到在STM32CubeIDE中如何通過ITM向單片機發送,二是_read函數的len參數總是1024,這是想讓我一次性讀1024個字節再返回嗎?

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

好品牌靠實力,這款1.2T發動機將帶來動力新革命

2T發動機,其實也就屬於廠商宣傳的噱頭,與其他品牌趨於一致,但經過研究一番后,裏面涵蓋的技術,確實可以說是每一項都針對着用戶日後用車養車所有遇到的困難之處,例如人們會覺得渦輪車所需要的保養費要貴與自然吸氣,以及不比自然吸氣發動車耐用,但以下這幾點,你看完后,就會發覺被豐田所否定了。

大家在買車的時候,都有對着一些品牌和國家帶有某方面的定義,例如意大利跑車出名於設計,德國奔馳寶馬奧迪出名於技術,而談到代步車耐用省油好開的程度上,則應該歸屬於日本的造車技術了,提到日系車,一定得提到的就是世界上最賺錢的汽車公司——豐田。

根據《2016年福布斯全球企業2000強》榜單,豐田蟬聯世界汽車企業第一強,2015年豐田的營業收入達到2358億美元,利潤達193億美元,是當時大眾71億美元利潤的兩倍多,而在造車品質方面,豐田汽車的品質向來是汽車業界的標杆,在2015年已實施檢查的豐田全球生產線中,能獲得“零缺陷率”評價的僅有7條,而廣汽豐田則是唯一一家全部生產線獲得“零缺陷率”評價的豐田事業體。

品質保證靠的不是檢查,而是每道工序打造出來的

許多企業在追求品質保證的時候,都會重點發展檢查組這樣的管理體系,這是一種偏向於結果的管理,而廣汽豐田則早已在涉入源頭與過程中的管理,“自工序完結”這是一個用日語翻譯過來的專業詞彙,按照其內部人員解釋的意思就是“品質是在每一道工序中打造出來的,而不是靠檢查出來的”,大家都知道日系是個節約成本的國家,且工作上往往會研究出更加重效率少資源利用的高效方法,無疑,這種從源頭開始管理的方法,精髓就在於“作業遵守率”,如果員工能夠苛刻地遵守,那麼生產出來的自然是合格品。

而在生產線上,廣汽豐田也一直堅持“自働化”,人與機器的完美配合,在每個工位上方都有這兩根不同顏色的繩子,一旦在裝配過程中遇到問題,造車技師能夠拉動這條“安東拉繩”,讓值班長協助解決,如果無法解決則停掉整條生產線,待問題解決后再繼續生產。

雖說有“自工序完結”,但造完車就可以出廠?

自工序完結只是一個近乎完美的工序,但為了達到接近百分之一百的合格率,還需要出廠前的檢查,由此,廣汽豐田成立了品質保證部自主監查組,每天會在生產線上各自抽取4台合格的新車,交給戴着黑帽子的監查員,進行最後一道全方位的檢查,他們是豐田汽車高級車輛監查員,具有在雞蛋里挑骨頭的精神,反覆的檢查才能保證廣汽豐田造車的高水平。

造車精神如此嚴謹,開發新技術也是同理

對於我們國人,雖然說現在自主品牌發展迅速,但對於南方城市來說,仍然還是有許多人在買代步車的時候,會首先考慮日系,看重的就是其耐用,省油,故障率低。而其中,豐田是人們一定會考慮的品牌,如今小排量渦輪增壓逐漸以一種新動力方向,取代傳統的1.6L和1.8L自然吸氣排量發動機,現有着大眾的1.2T,福特的1.0T,吉利的1.3T等等,近兩個月來,豐田也出台了其全新1.2T渦輪增壓發動機,與其說它是后發先至欲成為行業的標杆,更不如說這個舉動,其實是豐田沉思許久,熟悉市場,在確保低故障率后才推出來的新生產物。

那麼這台TURBO版的1.2T發動機,有什麼厲害之處?

曾認為這款1.2T發動機,其實也就屬於廠商宣傳的噱頭,與其他品牌趨於一致,但經過研究一番后,裏面涵蓋的技術,確實可以說是每一項都針對着用戶日後用車養車所有遇到的困難之處,例如人們會覺得渦輪車所需要的保養費要貴與自然吸氣,以及不比自然吸氣發動車耐用,但以下這幾點,你看完后,就會發覺被豐田所否定了。

水冷式中央冷卻器: 從發動機研發調校的角度出發,能夠影響到渦輪增壓發動機扭矩數據的參數就是進氣溫度,如果進氣溫度不能很有效地在進入燃燒室之前被冷卻,則會影響到發動機的熱效率,影響功率,與其他採用風冷中央冷卻器的發動機不同,豐田這款1.2T發動機採用水冷裝置,能夠更好地冷卻進氣溫度,同樣這種技術在寶馬和雷克薩斯IS系列上很常見。

燃油頂置直噴:與普通的歧管噴射發動機相比,採用缸內直噴的方式更加直接,既能夠有效地降低燃燒室的溫度,還能有效地抑制爆震,在噴射上更加精準,從而達到節油的目的。

如此精細造車的廣汽豐田,為大家帶來什麼福利?

這款1.2T發動機已搭載於廣汽豐田的熱銷車型雷凌,新款雷凌將取消舊排量的1.6L自然吸氣版本,轉而是1.2T的TURBO版本和1.8L的自然吸氣版本,在外觀上與舊款並沒有太大的區別,在TURBO版本的尾部明顯多了一個標識“D-4T”,這正是上面所提到發動機技術的縮寫。

內飾上,新款的雷凌TURBO版本在車門扶手車加入了細細的紅色飾條,新款運動風格座椅也採用紅色縫線,同時在靠背的地方還綉有“LEVIN”標識語,包裹性更好,配置上更是全新地升級了一番,新款雷凌TURBO和1.8L版本,全系標配智能節油啟停系統,VSC車身穩定控制系統和TRC牽引力控制系統,上坡輔助系統,將每一個安全配置落實到位。

在試駕體驗上,搭載了這款這麼“神”的1.2T發動機的雷凌TURBO,最大功率85KW,峰值扭矩達到了185牛米,強勁的初中段加速,動力能夠持續提升,在1500轉時就能夠達到最大扭矩,搭配8速的S-CVT無級變速器,帶有獨特的柔性鎖止控制技術,動力傳輸更加直接,操控性也大大提升。這款低排量渦輪增壓發動機與無級變速器匹配得不錯,能夠很好地控制油耗,百公里綜合油耗低至5.4L,不易積碳不易燒機油。

總結

還是那句話,談意大利汽車需要提到他們的設計,談到德系車需要提到他們的技術,而說到日系說到豐田,則必須談談他們製造的工藝,嚴謹的自工序完結,交車前的細心檢查,這都是一個企業的素質體現,而搭載新技術的廣汽豐田雷凌TURBO版本,在保養方面也不必擔心需要使用更高的機油品質,只需要使用與正常1.6L自然吸氣發動機一樣的機油即可,養車費用不會變高,所以希望新款雷凌TURBO能夠在低排量代步車級別中帶出不一樣的銷量成績,大家請拭目以待。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

“節氣門”聽得多 “氣門”又是干什麼的?

而上述的16V發動機則是相對於之前8v發動機而言,一台8V的四缸發動機意味着每個氣缸兩個氣門,一個進氣門和一個排氣門,如果你現在在紙上畫一個圓,然後再從圓裏面取等大的圓,結果肯定是取的等大的圓越多,取出來總面積越大。

就在汽車越來越同質化的現在,連尾標也不如以前豐富,美國車競爭最激烈的時候,車內有一個CD機都要在尾標炫耀,而車主在路上碰到同款也會下意識看下他的尾標,是不是有帶電動車窗,現在除了花樣頗多的動力表示,僅僅只有各自的看家技術會單獨掛出來。

而國內汽車的尾標,說到當年愛麗舍上的16V尾標有多少朋友會有印象呢?

要是你當時還是個熊孩子可能會以為是一道沒出完的數學題。

要是你那會剛剛上了物理課,可能會以為這是代表着16V的電源。

其實這裏的V是氣門(valve)的簡稱。

我們都知道四衝程發動機的做工過程分別是吸氣,壓縮,做功,排氣。

從中也可以看出氣門的重要性,如果簡單把發動機比作人的話,那麼進氣量和排氣量就等同於我們人的肺活量,肺活量越高肯定運動能力更強。

像下面的入門玩家。

中等玩家。

高級玩家。

而上述的16V發動機則是相對於之前8v發動機而言,一台8V的四缸發動機意味着每個氣缸兩個氣門,一個進氣門和一個排氣門,如果你現在在紙上畫一個圓,然後再從圓裏面取等大的圓,結果肯定是取的等大的圓越多,取出來總面積越大。

所以單缸四氣門的“肺活量”肯定大於兩個氣門的發動機。

那照這樣說氣門越多越好咯,理論上是這樣,所以 以前還有單缸五個氣門的發動機。像法拉利的355。

其型號就表明了該身份,3.5L排量的發動機,每個氣缸5氣門。

而更接近生活的還有國內就有的捷達王20V(氣門為3進兩出)。

當時所有開過的人對這輛車的印象就是兩個字,好開。畢竟多了一肛。

說得那麼好為什麼現在基本上都是每個氣缸4氣門呢,捷達王的20V那麼好開為什麼沒有繼續發展呢?

我們下期再來聊發動機另外一個關鍵的東西,“凸輪軸“。

本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!