標籤: 台北網頁設計

  • 解Bug之路-中間件”SQL重複執行”

    解Bug之路-中間件”SQL重複執行”

    前言

    我們的分庫分表中間件在線上運行了兩年多,到目前為止還算穩定。在筆者將精力放在處理各種災難性事件(例如中間件物理機宕機/數據庫宕機/網絡隔離等突發事件)時。竟然發現還有一些奇怪的corner case。現在就將排查思路寫成文章分享出來。

    Bug現場

    應用拓撲

    應用通過中間件連後端多個數據庫,sql會根據路由規則路由到指定的節點,如下圖所示:

    錯誤現象

    應用在做某些數據庫操作時,會發現有比較大的概率失敗。他們的代碼邏輯是這樣:

    	int count = updateSql(sql1);
    	...
    	// 偽代碼
    	int count = updateSql("update test set value =1 where id in ("100","200") and status = 1;
    	if( 0 == count ){
    		throw new RuntimeException("更新失敗");
    	}
    	......
    	int count = updateSql(sql3);
    	...
    

    即每做一次update之後都檢查下是否更新成功,如果不成功則回滾並拋異常。
    在實際測試的過程中,發現經常報錯,更新為0。而實際那條sql確實是可以更新到的(即報錯回滾后,我們手動執行sql可以執行並update count>0)。

    中間件日誌

    筆者根據sql去中間件日誌裏面搜索。發現了非常奇怪的結果,日誌如下:

    2020-03-13 11:21:01:440 [NIOREACTOR-20-RW] frontIP=>ip1;sqlID=>12345678;rows=>0;sql=>update test set value =1 where id in ("1","2") and status = 1;start=>11:21:01:403;time=>24266;
    2020-03-13 11:21:01:440 [NIOREACTOR-20-RW] frontIP=>ip1;sqlID=>12345678;rows=>2;sql=>update test set value =1 where id in ("1","2") and status = 1;start=>11:21:01:403;time=>24591;
    

    由於中間件對每條sql都標識了唯一的一個sqlID,在日誌表現看來就好像sql執行了兩遍!由於sql中有一個in,很容易想到是否被拆成了兩條執行了。如下圖所示:

    這條思路很快被筆者否決了,因為筆者explain並手動執行了一下,這條sql確實只路由到了一個節點。真正完全否決掉這條思路的是筆者在日誌裏面還發現,同樣的SQL會打印三遍!即看上去像執行了三次,這就和僅僅只in了兩個id的sql在思路上相矛盾了。

    數據庫日誌

    那到底數據真正執行了多少條呢?找DBA去撈一下其中的sql日誌,由於線下環境沒有日誌切割,日誌量巨大,搜索時間太慢。沒辦法,就按照現有的數據進行分析吧。

    日誌如何被觸發

    由於當前沒有任何思路,於是筆者翻看中間件的代碼,發現在update語句執行后,中間件會在收到mysql okay包后打印上述日誌。如下圖所示:

    注意到所有出問題的update出問題的時候都是同一個NIOREACTOR線程先後打印了兩條日誌,所以筆者推斷這兩個okay大概率是同一個後端連接返回的。

    什麼情況會返回多個okay?

    這個問題筆者思索了很久,因為在筆者的實際重新執行出問題的sql並debug時,永遠只有一個okay返回。於是筆者聯想到,我們中間件有個狀態同步的部分,而這些狀態同步是將set auto_commit=0等sql拼接到應用發送的sql前面。即變成如下所示:

    sql可能為
    set auto_commit=0;set charset=gbk;>update test set value =1 where id in ("1","2") and status = 1;
    

    於是筆者細細讀了這部分的代碼,發現處理的很好。其通過計算出前面拼接出的sql數量,再在接收okay包的時候進行遞減,最後將真正執行的那條sql處理返回。其處理如下圖所示:

    但這裏確給了筆者一個靈感,即一條sql文本確實是有可能返回多個okay包的。

    真相大白

    在筆者發現(sql1;sql2;)這樣的拼接sql會返回多個okay包后,就立刻聯想到,該不會業務自己寫了這樣的sql發給中間件,造成中間件的sql處理邏輯錯亂吧。因為我們的中間件只有在對自己拼接(同步狀態)的sql做處理,明顯是無法處理應用傳過來即為拼接sql的情況。
    由於看上去有問題的那條sql並沒有拼接,於是筆者憑藉這條sql打印所在的reactor線程往上搜索,發現其上面真的有拼接sql!

    2020-03-1311:21:01:040[NIOREACTOR-20RW]frontIP=>ip1;sqlID=>12345678;rows=>1;
    sql=>update test_2 set value =1 where id=1 and status = 1;update test_2 set value =1 where id=2 and status = 1;
    

    如上圖所示,(update1;update2)中update1的okay返回被驅動認為是所有的返回。然後應用立即發送了update3。前腳剛發送,update2的okay返回就回來了而其剛好是0,應用就報錯了(要不是0,這個錯亂邏輯還不會提前暴露)。那三條”重複執行”也很好解釋了,就是之前的拼接sql會有三條。

    為何是概率出現

    但奇怪的是,並不是每次拼接sql都會造成update3″重複執行”的現象,按照筆者的推斷應該前面只要是多條拼接sql就會必現才對。於是筆者翻了下jdbc驅動源碼,發現其在發送命令之前會清理下接收buffer,如下所示:

    MysqlIO.java
    final Buffer sendCommand(......){
    	......
    	// 清理接收buffer,會將殘存的okay包清除掉
    	clearInputStream();
    	......
    	send(this.sendPacket, this.sendPacket.getPosition());
    	......
    }
    

    正是由於clearInputStream()使得錯誤非必現(暴露),如果okay(update2)在應用發送第三條sql前先到jdbc驅動會被驅動忽略!
    讓我們再看一下不會讓update3″重複執行”的時序圖:

    即根據okay(update2)返回的快慢來決定是否暴露這個問題,如下圖所示:

    同時筆者觀察日誌,確實這種情況下”update1;update2″這條語句在中間件裏面日誌有兩條。

    臨時解決方案

    讓業務開發不用這些拼接sql的寫法后,再也沒出過問題。

    為什麼不連中間件是okay的

    業務開發這些sql是就在線上運行了好久,用了中間件后才出現問題。
    既然不連中間件是okay的,那麼jdbc必然有這方面的完善處理,筆者去翻了下mysql-connect-java(5.1.46)。由於jdbc裏面存在大量的兼容細節處理,筆者這邊只列出一些關鍵代碼路徑:

    MySQL JDBC 源碼
    MySQLIO
    stack;
    executeUpdate
    	|->executeUpdateInternel
    		|->executeInternal
    			|->execSQL
    				|->sqlQueryDirect
    					|->readAllResults (MysqlIO.java)
    readAllResults: //核心在這個函數的處理裏面
    ResultSetImpl readAllResults(......){
    		......
           while (moreRowSetsExist) {
    			  ......
    			  // 在返回okay包的保中其serverStatus字段中如果SERVER_MORE_RESULTS_EXISTS置位
    			  // 表明還有更多的okay packet
                moreRowSetsExist = (this.serverStatus & SERVER_MORE_RESULTS_EXISTS) != 0;
            }
            ......
    }
    

    正確的處理流程如下圖所示:

    而我們中間件的源碼確實這麼處理的:

    @Override
    public void okResponse(byte[] data, BackendConnection conn) {
    	......
    	// 這邊僅僅處理了autocommit的狀態,沒有處理SERVER_MORE_RESULTS_EXISTS
    	// 所以導致了不兼容拼接sql的現象
    	ok.serverStatus = source.isAutocommit() ? 2 : 1;
    	ok.write(source);
    	......
    }
    

    select也”重複執行”了

    解決完上面的問題后,筆者在日誌里竟然發現select盡然也有重複的,這邊並不會牽涉到okay包的處理,難道還有問題?日誌如下所示:

    2020-03-13 12:21:01:040[NIOREACTOR-20RW]frontIP=>ip1;sqlID=>12345678;rows=>1;select abc;
    2020-03-13 12:21:01:045[NIOREACTOR-21RW]frontIP=>ip2;sqlID=>12345678;rows=>1;select abc;
    

    從不同的REACTOR線程號(20RW/21RW)和不同的frontIP(ip1,ip2)來看是兩個連接執行了同樣的sql,但為何sqlID是一樣的?任何一個詭異的現象都必須一查到底。於是筆者登錄到應用上看了下應用日誌,確實應用有兩個不同的線程運行了同一條sql。
    那肯定是中間件日誌打印的問題了,筆者很快就想通了其中的關竅,我們中間件有個對同樣sql緩存其路由節點結構體的功能(這樣下一次同樣sql就不必解析,降低了CPU),而sqlID信息正好也在那個路由節點結構體裏面。如下圖所示:

    這個緩存功能感覺沒啥用(因為線上基本是沒有相同sql的),於是筆者在筆者優化的閃電模式下(大幅度提高中間件性能)將這個功能禁用掉了,沒想到為了排查問題而開啟的詳細日誌碰巧將這個功能開啟了。

    總結

    任何系統都不能說百分之百穩定可靠,尤其是不能立flag。在線上運行了好幾年的系統也是如此。只有對所有預料外的現象進行細緻的追查與深入的分析並解決,才能讓我們的系統越來越可靠。

    公眾號

    關注筆者公眾號,獲取更多乾貨文章:

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

    【其他文章推薦】

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

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

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

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

    新北清潔公司,居家、辦公、裝潢細清專業服務

  • 多圖解釋Redis的整數集合intset升級過程

    多圖解釋Redis的整數集合intset升級過程

    redis源碼分析系列文章

    [Redis源碼系列]在Liunx安裝和常見API 

    為什麼要從Redis源碼分析 

    String底層實現——動態字符串SDS 

    雙向鏈表都不懂,還說懂Redis?

    面試官:說說Redis的Hash底層 我:……(來自閱文的面試題)

    Redis的跳躍表確定不了解下

     

    前言

    大噶好,今天仍然是元氣滿滿的一天,拋開永遠寫不完的需求,拒絕要求賊變態的客戶,單純的學習技術,感受技術的魅力。(哈哈哈,皮一下很開森)

    前面幾周我們一起看了Redis底層數據結構,如動態字符串SDS雙向鏈表Adlist字典Dict跳躍表,如果有對Redis常見的類型或底層數據結構不明白的請看上面傳送門。

    今天來說下set的底層實現整數集合,如果有對set不明白的,常見的API使用這篇就不講了,看上面的傳送門哈。

    整數集合概念

    整數集合是Redis設計的一種底層結構,是set的底層實現,當集合中只包含整數值元素,並且這個集合元素數據不多時,會使用這種結構。但是如果不滿足剛才的條件,會使用其他結構,這邊暫時不講哈。

    下圖為整數集合的實際組成,包括三個部分,分別是編碼格式encoding,包含元素數量length,保存元素的數組contents。(這邊只需要簡單看下,下面針對每個模塊詳細說明哈)

    整數集合的實現

    我們看下intset.h裏面關於整數集合的定義,上代碼哈:

    //整數集合結構體
    typedef struct intset {
        uint32_t encoding;  //編碼格式,有如下三種格式,初始值默認為INTSET_ENC_INT16
        uint32_t length;    //集合元素數量
        int8_t contents[];  //保存元素的數組,元素類型並不一定是ini8_t類型,柔性數組不佔intset結構體大小,並且數組中的元素從小到大排列。
    } intset;               
    
    #define INTSET_ENC_INT16 (sizeof(int16_t))   //16位,2個字節,表示範圍-32,768~32,767
    #define INTSET_ENC_INT32 (sizeof(int32_t))   //32位,4個字節,表示範圍-2,147,483,648~2,147,483,647
    #define INTSET_ENC_INT64 (sizeof(int64_t))   //64位,8個字節,表示範圍-9,223,372,036,854,775,808~9,223,372,036,854,775,807

     

     

    編碼格式encoding

    包括INTSET_ENC_INT16,INTSET_ENC_INT32,INTSET_ENC_INT64三種類型,其分別對應着不同的範圍,具體看上面代碼的註釋信息。

    因為插入的數據的大小是不一樣的,為了盡可能的節約內存(畢竟都是錢,平時要省着點用),所以我們需要使用不同的類型來存儲數據。

    集合元素數量length

    記錄了保存數據contents的長度,即有多少個元素。

    保存元素的數組contents

    真正存儲數據的地方,數組是按照從小到大有序排序的,並且不包含任何重複項(因為set是不含重複項,所以其底層實現也是不含包含項的)。

    整數集合升級過程(重點,手動標星)

    上面的圖我們重新看下,編碼格式encoding為INTSET_ENC_INT16,即每個數據佔16位。長度length為4,即數組content裏面有四個元素,分別是1,2,3,4。如果我們要添加一個数字位40000,很明顯超過編碼格式為INTSET_ENC_INT16的範圍-32,768~32,767,應該是編碼格式為INTSET_ENC_INT32。那麼他是如何升級的呢,從INTSET_ENC_INT16升級到INTSET_ENC_INT32的呢?

    1.了解舊的存儲格式

    首先我們看下1,2,3,4這四個元素是如何存儲的。首先要知道一共有多少位,計算規則為length*編碼格式的位數,即4*16=64。所以每個元素佔用了16位。

    2.確定新的編碼格式

    新的元素為40000,已經超過了INTSET_ENC_INT16的範圍-32,768~32,767,所以新的編碼格式為INTSET_ENC_INT32。

    3.根據新的編碼格式新增內存

    上面已經說明了編碼格式為INTSET_ENC_INT32,計算規則為length*編碼格式的位數,即5*32=160。所以新增的位數為64-159。

    4.根據編碼格式設置對應的值

    從上面知道按照新的編碼格式,每個數據應該佔用32位,但是舊的編碼格式,每個數據佔用16位。所以我們從後面開始,每次獲取32位用來存儲數據。

    這樣說太難懂了,看下圖。

    首先,那最後32位,即128-159存儲40000。那麼第49-127是空着的。

    接着,取空着的49-127最後的32位,即96到127這32位,用來存儲4。那麼之前4存儲的位置48-6349-127剩下的64-95這兩部分組成了一個大部分,即48-95,現在空着啦。

    在接着在48-95這個大部分,再取后32位,即64-95,用來存儲3。那麼之前3存儲位置32-4748-95剩下的48-63這兩部分組成了一個大部分,即32-63,現在空着啦。

    再接着,將32-63這個大部分,再取后32位,即還是32-63,用來存儲2。那麼之前2存儲位置16-31空着啦。

    最後,將16-31和原來0-31合起來,存儲1。

    至此,整個升級過程結束。整體來說,分為3步,確定新的編碼格式,新增需要的內存空間,從后往前調整數據。

    這邊有個小問題,為啥要從后往前調整數據呢?

    原因是如果從前往後,數據可能會覆蓋。也拿上面個例子來說,數據1在0-15位,數據2在16-31位,如果從前往後,我們知道新的編碼格式INTSET_ENC_INT32要求每個元素佔用32位,那麼數據1應該佔用0-31,這個時候數據2就被覆蓋了,以後就不知道數據2啦。

    但是從后往前,因為後面新增了一些內存,所以不會發生覆蓋現象。

    升級的優點

     節約內存

    整數集合既可以讓集合保存三種不同類型的值,又可以確保升級操作只在有需要的時候進行,這樣就節省了內存。 

    不支持降級

    一旦對數組進行升級,編碼就會一直保存升級后的狀態。即使後面把40000刪掉了,編碼格式還是不會將會INTSET_ENC_INT16。

    整數集合的源碼分析

    創建一個空集合 intsetnew

    這個方法比較簡單,是初始化整數集合的步驟,即下圖部分。

    主要的步驟是分配內存空間,設置默認編碼格式,以及初始化數組長度length。

    intset *intsetNew(void) {
        intset *is = zmalloc(sizeof(intset));//分配內存空間 
        is->encoding = intrev32ifbe(INTSET_ENC_INT16);//設置默認編碼格式INTSET_ENC_INT16 
        is->length = 0;//初始化length 
        return is;
    }

    添加元素並升級insetAdd流程圖(重點)

    添加元素並升級insetAdd源碼分析

    可以根據上面的流程圖,對照着下面的源碼分析,這邊就不寫啦哈。

    //添加元素
    //輸入參數*is為原整數集合
    //value為要添加的元素
    //*success為是否添加成功的標誌量 ,1表示成功,0表示失敗 
    intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
        //確定要添加的元素的編碼格式 
        uint8_t valenc = _intsetValueEncoding(value);
        
        uint32_t pos;
        //如果success沒有初始值,則初始化為1 
        if (success) *success = 1;
    
       //如果新的編碼格式大於現在的編碼格式,則升級並添加元素 
        if (valenc > intrev32ifbe(is->encoding)) {
            //調用另一個方法 
            return intsetUpgradeAndAdd(is,value);
        } else {
            //如果編碼格式不變,則調用查詢方法 
            //輸入參數is為原整數集合 
            //value為要添加的數據
            //pos為位置 
            if (intsetSearch(is,value,&pos)) {//如果找到了,則直接返回,因為數據是不可重複的。 
                if (success) *success = 0;
                return is;
            }
    
            //設置length 
            is = intsetResize(is,intrev32ifbe(is->length)+1);
            if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
        }
        //設置數據 
        _intsetSet(is,pos,value);
        is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
        return is;
    }
    
    
    //#define INT8_MAX 127
    //#define INT16_MAX 32767
    //#define INT32_MAX 2147483647
    //#define INT64_MAX 9223372036854775807LL 
    static uint8_t _intsetValueEncoding(int64_t v) {
        if (v < INT32_MIN || v > INT32_MAX)
            return INTSET_ENC_INT64;
        else if (v < INT16_MIN || v > INT16_MAX)
            return INTSET_ENC_INT32;
        else
            return INTSET_ENC_INT16;
    }
    
    
    //根據輸入參數value的編碼格式,對整數集合is的編碼格式升級 
    static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
        //當前集合的編碼格式 
        uint8_t curenc = intrev32ifbe(is->encoding);
        //根據對value解析獲取新的編碼格式 
        uint8_t newenc = _intsetValueEncoding(value);
        //獲取集合元素數量 
        int length = intrev32ifbe(is->length);
        //如果要添加的數據小於0,則prepend為1,否則為0 
        int prepend = value < 0 ? 1 : 0;
    
       //設置集合為新的編碼格式,並根據編碼格式重新設置內存 
        is->encoding = intrev32ifbe(newenc);
        is = intsetResize(is,intrev32ifbe(is->length)+1);
    
        //逐步循環,直到length小於0,挨個重新設置每個值,從后往前 
        while(length--)
            _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
    
        //如果value為負數,則放在最前面 
        if (prepend)
            _intsetSet(is,0,value);
        else//如果value為整數,設置最末尾的元素為value 
            _intsetSet(is,intrev32ifbe(is->length),value);
        //重新設置length 
        is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
        return is;
    }
    
    
    //找到is集合中值為value的下標,返回1,並保存在pos中,沒有找到返回0,並將pos設置為value可以插入到數組的位置
    static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
        int min = 0, max = intrev32ifbe(is->length)-1, mid = -1;
        int64_t cur = -1;
    
        //如果集合為空,那麼位置pos為0 
        if (intrev32ifbe(is->length) == 0) { 
            if (pos) *pos = 0;
            return 0;
        } else {
            //因為數據是有序集合,如果要添加的數據大於最後一個数字,那麼直接把要添加的值放在最後即可,返回最大值下標 
            if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) {
                if (pos) *pos = intrev32ifbe(is->length);
                return 0;
            } else if (value < _intsetGet(is,0)) { //如果這個數據小於數組下標為0的數據,即為最小值 ,返回0 
                if (pos) *pos = 0;
                return 0;
            }
        }
        //有序集合採用二分法 
        while(max >= min) {
            mid = ((unsigned int)min + (unsigned int)max) >> 1;
            cur = _intsetGet(is,mid);
            if (value > cur) {
                min = mid+1;
            } else if (value < cur) {
                max = mid-1;
            } else {
                break;
            }
        }
    
        //確定找到 
        if (value == cur) {
            if (pos) *pos = mid;//設置參數pos,返回1,即找到位置 
            return 1;
        } else {//如果沒找到,則min和max相鄰,隨便設置都行,並返回0 
            if (pos) *pos = min; 
            return 0;
        }
    }

     

    結語

    該篇主要講了Redis的SET數據類型的底層實現整數集合,先從整數集合是什麼,,剖析了其主要組成部分,進而通過多幅過程圖解釋了intset是如何升級的,最後結合源碼對整數集合進行描述,如創建過程,升級過程,中間穿插例子和過程圖。

    如果覺得寫得還行,麻煩給個贊,您的認可才是我寫作的動力!

    如果覺得有說的不對的地方,歡迎評論指出。

    好了,拜拜咯。

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

    【其他文章推薦】

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

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※幫你省時又省力,新北清潔一流服務好口碑

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

  • 香港環保署:去年及今年首9月 空氣中二噁英濃度沒異常

    摘錄自2019年11月17日香港電台網站、頭條日報報導

    港警近月大量使用催淚彈,其中催淚彈不時出現大量火光,有民眾關注高溫會產生二噁英。二噁英是常見的持久性環境污染物,能在環境中長存數十年,不受破壞,毒性極高,會導致生育和發育問題、破壞免疫系統、干擾荷爾蒙分泌以及致癌。

    香港環保署在中西區和荃灣設監測站,監測空氣中二噁英的濃度,但上月數字至今未更新。香港環保署表示,去年及今年至9月的數據顯示,二噁英日均濃度是每立方米0.009至0.086皮克,並沒發現有異常情況出現。香港環保署表示,現在沒有空氣中二噁英的濃度指引,但過去3年香港取得的二噁英濃度,遠低於加拿大安大略省的二噁英每立方米0.1皮克,和日本每立方米0.6皮克的指標。

    參選「啟德中及南」選區候選人梁咏欣表示,近日收到居民反映,指有大量懷疑含有「二噁英」的雜物,被棄置在距離啟德約400米的宏照道路政署地盤內。她要求當局提供環境調查報告及實地分析數據,以確保附近環境不被有害物質污染。

    香港環保署稱已將上月份收集的二噁英樣本,送交政府化驗所作化學分析,一般需數星期,預期於本月(11月)底會完成,收到報告後會盡快公布。

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

    【【其他文章推薦】

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

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

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

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

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

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

  • 印尼蘇門答臘象身首異處慘死 象牙遭盜採

    摘錄自2019年11月8日自由時報屏東報導

    印尼保育官員今天表示,1頭列為極危(critically endangered)物種的蘇門答臘象屍體被發現,牠的頭被砍下且象牙被拔走,顯然是一宗盜獵案件。

    蘇門答臘島廖內省(Riau)一名農園工人昨天發現這頭40歲公象腐爛的屍體。當地保育機關主管蘇哈尤諾(Suharyono)在聲明中說:「大象的頭被砍下,切斷的象鼻落在距離象身1公尺處。」當局正在追查犯案人士。

    森林濫伐造成蘇門答臘象的天然棲地縮減,導致牠們和人類的衝突加劇。另一方面,蘇門答臘象象牙在野生動物黑市交易中價值連城。

    去年在印尼亞齊省(Aceh)也發現一具顯然被毒死的蘇門答臘象屍體,當時牠的象牙也不見。印尼環境部估計,境內野生蘇門答臘象只剩不到2000頭。

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

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※推薦評價好的iphone維修中心

  • 無視巴黎氣候協定 全球化石燃料產量遠高於限制

    摘錄自2019年11月20日中央通訊社報導

    聯合國和頂尖研究團體今天(20日)發布報告警告,全球已規劃或準備進行的石油、天然氣和煤炭產量,將遠遠超越抑制全球暖化讓地球維持適合人居所需的產量目標。

    聯合國環境規劃署(UN Environment Programme)和4個氣候變遷研究中心聯合發布報告指出,全球預計生產的化石燃料總量,較為了讓地表溫度較工業革命前水準升高不超過攝氏2度所容許的燃燒量,超出達50%。

    若將地表升溫幅度限制在攝氏1.5度,則計劃中的化石燃料產量將較容許數量超出1倍多。2015年達成的巴黎氣候協定,要求將全球暖化限制在遠低於攝氏2度水準,可能的話僅升溫攝氏1.5度。

    儘管截至目前全球僅升溫攝氏1度,但全世界已出現逐漸增強的致命熱浪、洪災和超級風暴,而超級風暴因海平面加速上升而破壞力更強大。研究人員警告,煤炭、石油和天然氣供應的「過度投資」,與未來數十年必須大幅縮減溫室氣體排放的目標,兩者直接相衝突。

    聯合國去年發布的報告斷定,若要抑制地表升溫僅攝氏1.5度,則全球二氧化碳排放必須在2030年底前減少45%,並於2050年底前達到「淨零排放」。

    斯德哥爾摩環境研究所(Stockholm Environment Institute)美國中心主任賴薩魯斯(Michael Lazarus)表示:「我們首次展現,巴黎(氣候協定的)溫度目標,和各國煤炭、石油和天然氣的生產計畫及政策,兩者間落差有多巨大。」

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

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

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

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

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

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※回頭車貨運收費標準

  • 法國黃背心運動滿週年 萬人上街抗議

    摘錄自2019年11月19日公視報導

    11月17號適逢法國黃背心運動一週年,去年不滿總統馬克宏調漲油價的民眾發起抗議行動,在各大城市遍地開花,並逐漸演變成反對馬克宏政府政策的社運。直到現在,仍有部分示威者,每星期六都會固定集會,提醒政府他們怒火難平。今年再度出現幾萬人走上街頭的盛大場面。有群眾和鎮暴警察發生衝突,還有人闖進巴黎的老佛爺百貨,讓業者被迫停業一天。

    抗議民眾表示,「我很高興能夠在這向馬克宏宣告:我們就在這裡,我們還在這裡,黃背心運動不死。儘管他們多方試圖摧毀黃背心,但黃背心屹立不搖,我們都是為了法國。」

    近幾個月來,黃背心運動趨於和緩,但週年紀念又讓情勢再度激化,數萬人走上街頭。部分抗議人士推翻路邊車輛,點燃垃圾桶等物,還向鎮暴警察扔擲石頭,而警方也以催淚瓦斯和水柱還擊和驅散人群。

    根據法國內政部的說法,法國全國各地星期六一共逮捕了264人,其中巴黎就佔了六成以上。黃背心支持者表示,目前他們考慮加入其他工會行動,參與12月5號開始反對馬克宏年金改革的無限期大罷工。

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

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

    ※超省錢租車方案

  • 敢反抗開發就坐牢 原民反土地改革惡法 印尼動亂中媒體忽略的訴求

    敢反抗開發就坐牢 原民反土地改革惡法 印尼動亂中媒體忽略的訴求

    環境資訊中心外電;姜唯 翻譯;林大利 審校;稿源:Mongabay

    9月下旬,印尼史上繼1998年獨裁者蘇哈托倒台以來最大規模的抗爭行動,佔據國際新聞版面。英國、美國]、和其他外國媒體頭條報導成千上萬民眾在全國各地大城市示威遊行,抗議婚前性行為納入刑法。

    撤銷新刑法法條,包括不得侮辱總統和提供避孕資訊,只是抗議者的訴求之一。根據網上流傳的七點聲明,訴求還包括反對廢除一條弱化反貪腐機構的新法、停止蘇門答臘和婆羅洲的森林大火,以及從印尼東邊的巴布亞地區撤軍。印尼對分裂主義的軍事鎮壓行動已經進行了數十年。

    撤銷土地利用新法也在訴求清單上。

    這條尚未被媒體大幅報導,但觀察者表示,它的重要性不亞於其他幾乎立法完成的爭議性法案。

    評論家說,該法案定義了新的罪行,並增加了刑罰,使當局更容易監禁抵抗開發商入侵的農村居民,使農業公司能保留土地特許經營權更長的時間。

    印尼巴布亞省一條切開森林的公路,涉嫌剝奪了原住民的土地所有權。Nanang Sujana攝;來源:Mongabay。

    在批評者眼中,最令人髮指的是,該法案設定了一個兩年期限,要求公民必須在期限內向政府註冊土地,否則土地將收歸國有,成為佐科威總統土地改革計畫的一部分重新分配,或授權私人公司使用。

    但是,原住民尋求正式承認其土地就起碼要花兩年,通常還要更久,才能通過層層官僚關卡。因此,兩年期限對印尼弱勢的原住民權利來說更是一記沈重的打擊,印尼原住民組織「AMAN」副秘書長卡亞迪(Erasmus Cahyadi)說。

    2013年印尼憲法法院作出歷史性裁決,駁回了州政府對原住民森林所有權的主張,此後總統佐科威陸續承認55個原住民群體對森林的權利,總面積達248平方公里(96平方英里)。但是AMAN說他們欲承認的原民土地有77,600平方公里(30,000平方英里),屬於704個原住民族。

    AMAN法律與人權事務負責人穆罕默德(Arman Muhammad)說,該法案違反了憲法的精神。

    印尼大學生走上雅加達街頭,反對新法律弱化反貪腐機構。Hans Nicholas Jong攝;來源:Mongabay。

    該法案的支持者則認為,通過該法案對於佐科威的大型土地改革計畫來說是必要的。

    佐科威於今年4月當選第二個五年任期,他已承諾賦予農村社區更大的權力,控制其21.7萬平方公里(84,000平方英里)的土地,但是實踐進度緩慢。

    截至10月,控制該國約一半土地的環境和林業部僅分配了總計28,000平方公里的土地,遠低於目標127,000平方公里。

    民主黨國會議員卡隆(Herman Khaeron):「土地改革計畫的土地很難找。」

    為了解決這個問題,卡隆說,該法案要求建立一個新的機構,稱為土地管理署,負責收購、管理和分配兩年的時間內沒有被公民登記的土地,這些土地將自動收歸國有。

    根據該法案,土地管理署將充當「土地銀行」,能夠透過租賃或出售土地產生收入,同時仍以非營利組織的形式運作。該機構必須保證為「社會利益」和「發展利益」提供土地。

    法案的措詞含糊,批評者擔心該機構會將土地當作商品出售給強勢投資者,以犧牲普通市民為代價。

    「誰將能夠使用這個土地銀行?小農嗎?當然不是,」印尼茂物農業研究所(Bogor Institute of Agriculture, IPB)人類生態學系研究員卡友諾(Eko Cahyono)說,「這個土地銀行將為有大量資本、企業和開發計畫的人服務。」

    評論家說,該法案中的其他條款將使企業受益,傷害農村社區。

    該法案將允許農業公司持有「HGU」耕種權許可證,有效期長達90年,而現行規定為60年。油棕公司可以拖更久的時間才將小農地釋出給當地社區。

    此外,該法條還規定,凡訂定「引起土地糾紛之惡意協議」者,將被判處5至15年監禁,「妨礙土地機關僱員和/或執法人員執行任務」者可判處兩年監禁。

    倡議組織土地改革聯盟(Consortium for Agrarian Reform, KPA)秘書長卡蒂卡(Dewi Kartika)向記者,根據後項條款,反抗土地掠奪的原住民、行動人士等人可被入罪。

    「它賦予警察將任何人定罪的合法性。最極端的詮釋之下,可被用來逮捕任何人。例如,當居民試圖阻止他們的土地被用來建造機場時,就可能會被逮捕。」

    成百上千的印尼村莊在和農業公司對抗時陷入困境,社區成員常常以肉身擋推土機,甚至縱火燒毀公司設備。

    9月26日,一名21歲的大學生在蘇拉威西省東南部省會肯達里的大規模抗議活動中被警察槍殺。抗議活動演變成暴亂後,另一名19歲學生卡德哈維(Yusuf Qardhawi)因鈍器重創頭部而死亡。

    「我們非常沮喪和失望,」參加抗議活動的社區組織者、23歲的馬斯庫里(Mando Maskuri)說,「國家應該保護人民,但他們卻在殺死人民。」

    。與印尼其他地方一樣,當地人往往缺乏證明土地所有權的文件,這使得州政府很容易在未經他們同意的情況下引入企業投資者。

    馬斯庫里說,許多旺尼居民嘗試向州政府登記其土地。但他擔心土地法案設定的時間表不切實際,最終導致居民失去土地,逼他們搬走。

    大學生在肯達里用行動劇抗議礦場開發。Kamaruddin攝,來源:Mongabay

    在9月抗議活動的高峰期,對土地法案和其他爭議性法案的審議被。跛鴨議會會期即將結束,新的議員宣誓就職。

    現任議員在最後一刻同意將土地法案移交至新議會會期,這表示新任議員可以從同一階段繼續審議,不必從頭開始。

    根據調查性新聞機構Tempo和非政府組織Auriga Nusantara的,接下來五年負責立法工作的575名議員中,將近一半是與至少1,016家企業有聯繫的商人,其中包括採礦公司和油棕公司。

    佐科威總統說,他想,使之對投資者更加友善。許多觀察家表示,不少與已列入清單。

    研究人員卡友諾說,如果議員試圖通過土地法案,反對者可以在最後一道關卡提出司法審查。

    同時,馬斯庫里說他準備再次走上街頭,「如果議會要強行通過該法案,將面臨來自農民、漁民和民間社會團體的巨大阻力。」

    Indonesia protests: Land bill at center of unrest by Basten Gokkon, Hans Nicholas Jong, Philip Jacobson on 3 November 2019

    • In recent weeks, Indonesia has seen its largest mass protests since the “people power” movement that forced President Suharto to step down in 1998.
    • Among a variety of pro-democracy demands, the protesters want lawmakers to scrap a controversial bill governing land use in the country.
    • The bill defines new crimes critics say could be used to imprison indigenous and other rural citizens for defending their lands against incursions by private companies.
    • It also sets a two-year deadline by which citizens must register their lands with the government, or else watch them pass into state control. Activists say the provision would deal a “knockout blow” to the nation’s indigenous rights movement.

    JAKARTA — In late September, international news outlets caught flak for their coverage of Indonesia’s largest mass protests since the 1998 uprising that led to the fall of the dictator Suharto.

    Headlines published by the , , and other foreign media implied the demonstrations, involving tens of thousands of people in major cities across the country, had arisen in response to a proposed new criminal code that would ban sex before marriage.

    “I did not get tear-gassed so Australians could keep having sex in Bali,” one netizen on Twitter, among a barrage of reactions to the reductive reports. “This is about the future of the country.”

    Scrapping the criminal code changes — which also include new penalties for insulting the president and providing information about contraception — was just one of the protesters’ demands, enumerated in a seven-point declaration that has circulated online. They also want the government to repeal a new law weakening the nation’s anti-corruption agency, stop forest fires in Sumatra and Borneo, and withdraw troops from Indonesia’s easternmost Papua region, where a military crackdown against separatists has been going on for decades.

    Also on the list: scrap a proposed new law governing land use.

    Though the land bill has gotten scant media coverage, observers say it is among the most potentially transformative of a raft of on the verge of being passed into law.

    The bill defines new crimes and introduces increased penalties that, critics say, would make it easier for authorities to imprison rural citizens for defending their lands against incursions by developers. It would also allow plantation companies to retain vast land concessions for longer periods of time.

    Most damningly in the eyes of critics, the bill sets a two-year deadline by which citizens must register their lands with the government, or else watch them pass into state control, where they could be redistributed as part of President Joko Widodo’s land reform program or licensed out to private firms.

    But indigenous groups seeking formal recognition of their lands already spend at least that long, and often far longer, jumping through bureaucratic hoops. The two-year deadline would therefore constitute a “knockout blow” for the nation’s embattled indigenous rights movement, Erasmus Cahyadi, deputy secretary-general of AMAN, Indonesia’s main advocacy group for indigenous peoples, told Mongabay.

    Since 2013, when a landmark Constitutional Court ruling struck down the state’s claim to indigenous peoples’ forests, President Joko Widodo has recognized the rights of 55 indigenous groups to forests spanning a total of 248 square kilometers (96 square miles). But AMAN says it has mapped more than 77,600 square kilometers (30,000 square miles) of land it says belongs to 704 indigenous communities.

    “The bill is contrary to the spirit of the constitution,” said Arman Muhammad, AMAN’s law and human rights director.



    University students protest the new corruption law in Jakarta. Image by Hans Nicholas Jong/Mongabay.

    The bill’s supporters argue its passage is necessary to support President Widodo’s flagship land reform program.

    Widodo, who was elected to a second five-year term in April, has promised to give rural communities greater control over 217,000 square kilometers (84,000 square miles) of land. But progress has been slow.

    As of October, the Ministry of Environment and Forestry, which controls around half of the nation’s land, had only distributed a total of 28,000 square kilometers (10,800 square miles), far short of its target of 127,000 square kilometers (49,000 square miles).

    “It’s hard to find land for the agrarian reform [program],” Democrat Party lawmaker Herman Khaeron at a recent panel event in Jakarta.

    To solve that, Herman said, the bill calls for the creation of a new body called the Land Management Agency to acquire, manage and distribute land that had gone unclaimed by citizens during the two-year window, that therefore automatically fell under state control.

    The bill says the agency will function as a “land bank,” implying it will be able to generate an income from leasing or selling lands, while still operating as a “nonprofit,” according to the bill. The agency must guarantee the availability of land for “social interests” as well as “development interests.”

    The language in the bill is vague, but critics fear the agency would treat land as a commodity to be sold to powerful investors at the expense of ordinary citizens.

    “Who would be able to access this land bank? Small farmers? Of course not,” Eko Cahyono, a researcher in the Department of Human Ecology at the Bogor Institute of Agriculture (IPB), told Mongabay. “The ‘land bank’ would serve those with big capital, companies and development projects.”

    Other provisions in the bill would benefit corporations at the expense of rural communities, critics say.

    The bill would allow plantation companies to hold a right-to-cultivate permit, known as an HGU, for 90 years, up from 60 years under the current rules.

    It would also let oil palm firms wait longer before providing smallholdings to local communities, a requirement under existing laws.

    Furthermore, the legislation stipulates prison time of five to 15 years for anyone who makes an “evil agreement that gives rise to a land dispute,” and a jail term of two years for those who “obstruct an employee and/or law enforcement officer from carrying out tasks in the land sector.”

    The latter provision could be used to “criminalize indigenous peoples, activists or anyone who tries to organize” against a land grab, Dewi Kartika, the secretary-general of the Consortium for Agrarian Reform (KPA), an advocacy group, reporters in Jakarta recently.

    “It grants the police legal legitimacy to criminalize anyone,” she said. “Of course this will be interpreted to the maximum extent possible, to freely arrest anyone. For example, if residents try to stand in the way of their land being used to build an airport.”

    Hundreds, if not thousands, of Indonesian villages are embroiled in conflict with natural resources firm, with community members often resorting to physically blocking bulldozers or even setting fire to company facilities.

    On Sept. 26, a 21-year-old college student in Kendari, the capital of Southeast Sulawesi province and one of the cities where mass protests took place in September, was shot dead by police. Another student in Kendari, 19-year-old Yusuf Qardhawi, died of blunt-force head injuries after a protest turned into a violent riot.

    “We were all so upset and disappointed,” Mando Maskuri, 23, a community organizer who joined the protests in Kendari, told Mongabay. “The state is supposed to protect us, but they’re killing us.”

    Residents of Mando’s home island of Wawonii are with mining firms that hold permits to operate on their lands. As elsewhere in Indonesia, locals tend to lack documents backing their land claims, making it easy for the state to bring in corporate investors without their consent.

    Many people in Wawonii are trying to register their lands with the state, Mando said. But he fears the land bill sets an unrealistic timeline that will eventually cause residents to lose their lands, forcing them to migrate to other parts of the country.



    Students in Kendari stage a mock burial in early 2019 to express their opposition to the mining in Wawonii. Image by Kamaruddin for Mongabay.

    At the height of the protests in September, deliberations on the land bill and other controversial legislation were . The lame-duck parliament was nearing the end of its session. New lawmakers have since been sworn in.

    In their final hour, however, the previous lawmakers agreed to “carry over” the land bill to the current parliament session, meaning deliberations can be resumed from the same stage by the new batch of legislators, rather than having to start all over again.

    Nearly half of the 575 lawmakers for the next five years are businesspeople who are affiliated with at least 1,016 companies, including mining and oil palm, according to an by investigative journalism outlet Tempo and Auriga Nusantara, an NGO.

    President Widodo says he wants to to make them friendlier to investors; many observers have said are on the list.

    If lawmakers try to pass the land bill, opponents could file a judicial review in a last-ditch attempt to oppose it, said Eko, the researcher.

    In the meantime, Mando says he is ready to take to the streets again.

    “If parliament tries to pass the bill, there will be massive resistance from farmers, fishermen, and civil society groups,” he said.

    ※ 全文及圖片詳見:

    作者

    如果有一件事是重要的,如果能為孩子實現一個願望,那就是人類與大自然和諧共存。

    於特有生物研究保育中心服務,小鳥和棲地是主要的研究對象。是龜毛的讀者,認為龜毛是探索世界的美德。

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

    【其他文章推薦】

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

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

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

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

    新北清潔公司,居家、辦公、裝潢細清專業服務

  • 韓媒:台灣值得被納入全球氣候變遷體系

    摘錄自2019年11月19日中央社報導

    韓國「韓民族新聞」和英文報The Korea Times今(19日)同步刊載中華民國行政院環保署長張子敬署名的投書專文,呼籲國際社會接納台灣成為全球氣候變遷體系的一員。張子敬在分別以Taiwan: valuable partner in fighting climate change和「台灣也應當參與全球氣候變遷協約」為標題,向韓國報紙投書闡述台灣欲加入全球氣候變遷體系的立場。

    專文指出,台灣整合中央相關部會工作,制訂「國家氣候變遷調適行動方案」,從災害、維生基礎設施、水資源、國土安全、海岸、能源及產業、農業、健康等8個面向,建構因應氣候變遷的韌性體制;在醫療領域上特別著重強化醫療衛生及防疫系統預防、減災、應變及復原能力,維護全民健康並優先保障弱勢住民。

    另外在生態保育領域上,將維護農業生產資源及生物多樣性,加強監測與預警機制、強化天然災害救助及保險體系、整合科技提升農林漁牧產業抗逆境能力,並完善自然保護區經營管理、建構長期生態監測體系、強化物種及基因的多樣性保存與合理利用,以確保糧食安全並建構適應氣候風險的永續農業。

    專文認為,台灣因政治成見被排除在國際組織之外,是相當不公平的,非但不符合氣候公約籲請所有國家對全球氣候變遷進行廣泛合作的精神,忽視巴黎協定強調「氣候正義」及呼籲各國採取氣候行動的重要性,更違背聯合國憲章宗旨,也弱化國際架構而對世界造成傷害。

    專文強調,面對國際社會,台灣是負責任、肯貢獻的真誠朋友,樂於分享在環境治理制度、防災預警系統、能源效率提升技術、科技創新運用等相關領域的經驗,台灣努力希望能讓世界更美好,而台灣也真的值得被納入全球氣候變遷體系的一員。

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

    【其他文章推薦】

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

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※幫你省時又省力,新北清潔一流服務好口碑

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

  • 前端工程師必備:從瀏覽器的渲染到性能優化

    前端工程師必備:從瀏覽器的渲染到性能優化

    摘要:本文主要講談及瀏覽器的渲染原理、流程以及相關的性能問題。

    問題前瞻

    1. 為什麼css需要放在頭部?
    2. js為什麼要放在body後面?
    3. 圖片的加載和渲染會阻塞頁面DOM構建嗎?
    4. dom解析完才出現頁面嗎?
    5. 首屏時間根據什麼來判定?

    瀏覽器渲染

    1.瀏覽器渲染圖解

    [來自google開發者文檔]

    瀏覽器渲染頁面主要經歷了下面的步驟:

    1.處理 HTML 標記並構建 DOM 樹。
    2.處理 CSS 標記並構建 CSSOM 樹。
    3.將 DOM 與 CSSOM 合併成一個渲染樹。
    4.根據渲染樹來布局,以計算每個節點的幾何信息。
    5.將各個節點繪製到屏幕上。

    為構建渲染樹,瀏覽器大體上完成了下列工作:

    從 DOM 樹的根節點開始遍歷每個可見節點。
    
    某些節點不可見(例如腳本標記、元標記等),因為它們不會體現在渲染輸出中,所以會被忽略。
    某些節點通過 CSS 隱藏,因此在渲染樹中也會被忽略,例如,上例中的 span 節點---不會出現在渲染樹中,---因為有一個顯式規則在該節點上設置了“display: none”屬性。
    對於每個可見節點,為其找到適配的 CSSOM 規則並應用它們。
    
    發射可見節點,連同其內容和計算的樣式。

    根據以上解析,DOM樹和CSSOM樹的構建對於頁面性能有非常大的影響,沒有DOM樹,頁面基本的標籤塊都沒有,沒有樣式,頁面也基本是空白的。所以具體css的解析規則是什麼?js是怎麼影響頁面渲染的?了解了這些,我們才能有的放矢,對頁面性能進行優化。

    2.css解析規則

    1
    <div id="div1">
    2
    <div class="a">
    3
    <div class="b">
    4
    ...
    5
    </div>
    6
    <div class="c">
    7
    <div class="d">
    8
    ...
    9
    </div>
    10
    <div class="e">
    11
    ...
    12
    </div>
    13
    </div>
    14
    </div>
    15
    <div class="f">
    16
    <div class="c">
    17
    <div class="d">
    18
    ...
    19
    </div>
    20
    </div>
    21
    </div>
    22
    </div>

     

    1
    #div1 .c .d {}
    2
    .f .c .d {}
    3
    .a .c .e {}
    4
    #div1 .f {}
    5
    .c .d{}

    從左向右的匹配規則

    從右向左的匹配規則

    如果css從左向右解析,意味着我們需要遍歷更多的節點。不管樣式規則寫得多細緻,每一個dom結點仍然需要遍歷,因為整個style rules還會有其它公共樣式影響。如果從右向左解析,因為子元素只有一個父元素,所以能夠很快定位出當前dom符不符合樣式規則。

    3.js加載和執行機制

    首先明確一點,我們可以通過js去修改網頁的內容,樣式和交互等,這一意味着js會影響頁面的dom結構,如果js和dom構建并行執行,那麼很容易會出現衝突,所以js在執行時必然會阻塞dom和cssom的構建過程,不論是外部js還是內聯腳本。

    js的位置是否影響dom解析?

    首先我們為什麼提倡把js放在body標籤的後面去加載,因為從demo上看無論是放在head還是放在body后加載js,頁面domcontentload的時間都是一樣的:

    我們從圖中可以看出js的加載和執行是阻塞dom解析的,但是因為頁面並不是一次就渲染完成,所以我們需要做的是盡量讓用戶看到首屏的部分被渲染出來,js放在頭部,則頁面的內容區域還沒有解析到就被阻塞了,導致用戶看到的是白屏,而js放在body後面,儘管此時頁面dom仍然沒有解析完成,但是已經渲染出一部分樓層了,這也是為什麼我們比較看重頁面的首屏時間。

    只有DOM和CSSOM樹構建好后併合並成渲染樹才能開始繪製頁面圖形,那是不是把整個DOM樹和CSSOM樹構建好后才能開始繪製頁面?這顯然是不符合我們平時訪問頁面的認知的,實際上:

    為達到更好的用戶體驗,呈現引擎會力求儘快將內容显示在屏幕上。它不必等到整個 HTML 文檔解析完畢之後,就會開始構建呈現樹和設置布局。在不斷接收和處理來自網絡的其餘內容的同時,呈現引擎會將部分內容解析並显示出來。

    具體瀏覽器什麼時候進行首次繪製?可以查看本文對瀏覽器首次渲染時間點的探究。

    4.圖片的加載和渲染機制

    首先我們解答一下上面的問題:圖片的加載與渲染會不會阻塞頁面渲染?答案是圖片的加載和渲染不會影響頁面的渲染。

    那麼標籤中的圖片和樣式中的圖片的加載和渲染時間是什麼樣的呢?

    解析HTML【遇到標籤加載圖片】 —> 構建DOM樹
    加載樣式 —> 解析樣式【遇到背景圖片鏈接不加載】 —> 構建樣式規則樹
    加載javascript —> 執行javascript代碼
    把DOM樹和樣式規則樹匹配構建渲染樹【遍歷DOM樹時加載對應樣式規則上的背景圖片】
    計算元素位置進行布局
    繪製【開始渲染圖片】

    當然把DOM樹和樣式規則樹匹配構建渲染樹時,只會把可見元素和它對應的樣式規則結合一起產出到渲染樹,這就意味有不可見元素,當匹配DOM樹和樣式規則樹時,若發現一個元素的對應的樣式規則上有display:none,瀏覽器會認為該元素是不可見的,因此不會把該元素產出到渲染樹上。

    性能優化

    css優化

    1.盡量減少層級

    1
    #div p.class {
    2
    color: red;
    3
    }
    4
    
    5
    .class {
    6
    color: red;
    7
    }

    層級減少,意味者匹配時遍歷的dom就少。
    關於less嵌套的書寫規範也基於這個道理。

    2.使用類選擇器而不是標籤選擇器

    減少匹配次數

    3.按需加載css

    1
    (function(){
    2
    window.gConfig = window.gConfig || {};
    3
    window.gConfig.isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
    4
    var hClassName;
    5
    if(window.gConfig.isMobile){
    6
    hClassName = ' phone';
    7
    
    8
    document.write('<link rel="stylesheet" href="https://res.hc-cdn.com/cpage-pep-discount-area-v6/2.0.24/m/index.css" />');
    9
    document.write('<link rel="preload" href="//res.hc-cdn.com/cpage-pep-discount-area-v6/2.0.24/m/index.js" crossorigin="anonymous" as="script" />');
    10
    
    11
    }else{
    12
    hClassName = ' pc';
    13
    
    14
    document.write('<link rel="stylesheet" href="https://res.hc-cdn.com/cpage-pep-discount-area-v6/2.0.24/pc/index.css" />');
    15
    document.write('<link rel="preload" href="//res.hc-cdn.com/cpage-pep-discount-area-v6/2.0.24/pc/index.js" crossorigin="anonymous" as="script" />');
    16
    
    17
    }
    18
    var root = document.documentElement;
    19
    root.className += hClassName ;
    20
    
    21
    })();

    async 與 defer

    [來自https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html]

    使用

    • 如果腳本是模塊化的並且不依賴於任何腳本,請使用async。
    • 如果該腳本依賴於另一個腳本或由另一個腳本所依賴,則使用defer。

    減少資源請求

    瀏覽器的併發數量有限,所以為了減少瀏覽器因為優先加載很多不必要資源,以及網絡請求和響應時間帶來的頁面渲染阻塞時間,我們首先應該想到的是減少頁面加載的資源,能夠盡量用壓縮合併,懶加載等方法減少頁面的資源請求。

    延遲加載圖像

    儘管圖片的加載和渲染不會影響頁面渲染,但是為了盡可能地優先展示首屏圖片和減少資源請求數量,我們需要對圖片做懶加載。

    1
    document.addEventListener("DOMContentLoaded", function() {
    2
    let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
    3
    let active = false;
    4
    
    5
    const lazyLoad = function() {
    6
    if (active === false) {
    7
    active = true;
    8
    
    9
    setTimeout(function() {
    10
    lazyImages.forEach(function(lazyImage) {
    11
    if ((lazyImage.getBoundingClientRect().top <= window.innerHeight && lazyImage.getBoundingClientRect().bottom >= 0) && getComputedStyle(lazyImage).display !== "none") {
    12
    lazyImage.src = lazyImage.dataset.src;
    13
    lazyImage.srcset = lazyImage.dataset.srcset;
    14
    lazyImage.classList.remove("lazy");
    15
    
    16
    lazyImages = lazyImages.filter(function(image) {
    17
    return image !== lazyImage;
    18
    });
    19
    
    20
    if (lazyImages.length === 0) {
    21
    document.removeEventListener("scroll", lazyLoad);
    22
    window.removeEventListener("resize", lazyLoad);
    23
    window.removeEventListener("orientationchange", lazyLoad);
    24
    }
    25
    }
    26
    });
    27
    
    28
    active = false;
    29
    }, 200);
    30
    }
    31
    };
    32
    
    33
    document.addEventListener("scroll", lazyLoad);
    34
    window.addEventListener("resize", lazyLoad);
    35
    window.addEventListener("orientationchange", lazyLoad);
    36
    });

    詳情參考延遲加載圖像和視頻

    大促活動實踐

    2.1 懶加載與異步加載

    懶加載與異步加載是大促活動性能優化的主要手段,直白的說就是把用戶不需要或者不會立即看到的頁面數據與內容全都挪到頁面首屏渲染完成之後去加載,極限減小頁面首屏渲染的數據加載量與js,css執行帶來的性能損耗。

    2.1.1 導航下拉的異步加載

    導航的下拉內容是一塊結構非常複雜的html片段,如果直接加載,瀏覽器渲染的時間會拖慢頁面整體的加載時間:

    所有我們需要通過異步加載方式來獲取這段html片段,等頁面首屏渲染結束后再添加到頁面上,大致的代碼如下:

    1
    $.ajax({
    2
    url: url, async: false, timeout: 10000,
    3
    success: function (data) {
    4
    container.innerHTML = data;
    5
    var appendHtml = $('<div class="footer-wrapper">' + container.querySelector('#footer').innerHTML + '</div>');
    6
    var tempHtml = '<div style="display:none;">' + '<script type="text/html" id="header-lazyload-html-drop" class="header-lazyload-html" data-holder="#holder-drop">' + appendHtml.find('#header-lazyload-html-drop').html() + '<\/script><script type="text/html" id="header-lazyload-html-mbnav" class="header-lazyload-html" data-holder="#holder-mbnav">' + appendHtml.find('#header-lazyload-html-mbnav').html() + '<\/script></div>';
    7
    $('#footer').append(tempHtml);
    8
    feloader.onLoad(function () {
    9
    feloader.use('@cloud/common-resource/header', function () {
    10
    });
    11
    $('#footer').css('display', 'block');
    12
    });
    13
    },
    14
    error: function (XMLHttpRequest, textStatus, errorThrown) {
    15
    console.log(XMLHttpRequest.status, XMLHttpRequest.readyState, textStatus);
    16
    },
    17
    });

    2.1.2 圖片懶加載

    官網的cui套件中已經有lazyload的插件支持圖片懶加載,使用方法頁非常簡單:

    1
    <div class="list">
    2
    <img class="lazyload" data-src="http://www.placehold.it/375x200/eee/444/1" src="佔位圖片URL" />
    3
    <img class="lazyload" data-src="http://www.placehold.it/375x200/eee/444/2" src="佔位圖片URL" />
    4
    <img class="lazyload" data-src="http://www.placehold.it/375x200/eee/444/3" src="佔位圖片URL" />
    5
    <div class="lazyload" data-src="http://www.placehold.it/375x200/eee/444/3"></div>
    6
    ...
    7
    </div>

    從代碼我們差不多可以猜出圖片懶加載的原理,其實就是我們通過覆蓋img標籤src屬性,使得img標籤開始加載時由於沒有src的具體圖片地址而不去加載圖片,等到重要資源加載完之後,通過監聽onload的時間或者滾動條的滾動時機再去重寫對應標籤的src值來達到圖片懶加載:

    1
    /**
    2
    * load image
    3
    * @param {HTMLElement} el - the image element
    4
    * @private
    5
    */
    6
    _load(el) {
    7
    let source = el.getAttribute(ATTR_IMAGE_URL);
    8
    if (source) {
    9
    let processor = this._config.processor;
    10
    if (processor) {
    11
    source = processor(source, el);
    12
    }
    13
    
    14
    el.addEventListener('load', () => {
    15
    el.classList.remove(CLASSNAME);
    16
    });
    17
    // 判斷是否是什麼元素
    18
    if (el.tagName === 'IMG') {
    19
    el.src = source;
    20
    } else {
    21
    // 判斷source是不是一個類名,如果是類名的話,則加到class裏面去
    22
    if (/^[A-Za-z0-9_-]+$/.test(source)) {
    23
    el.classList.add(source);
    24
    } else {
    25
    let styles = el.getAttribute('style') || '';
    26
    styles += `;background-image: url(${source});`;
    27
    el.setAttribute('style', styles);
    28
    el.style.backgroundImage = source; // = `background-image: url(${source});`;
    29
    }
    30
    }
    31
    
    32
    el.removeAttribute(ATTR_IMAGE_URL);
    33
    }
    34
    }

    具體的插件代碼大家可以查看https://git.huawei.com/cnpm/lazyload

    同時官網的頁腳部分也採用了採用其它的加載方式也實現了懶加載的效果,頁腳的圖片都在css中引用,想要延遲加載頁腳圖片就需要延遲加載頁腳的css,但是延遲加載css造成的後果就是頁面加載的一瞬間頁腳會因為樣式確實而显示錯亂,所以我們可以在css樣式加載前強勢隱藏掉頁腳部分,等css加載完成后,頁腳dom自帶的display:block會自動显示頁腳。(==因為頁腳的seo特性沒有對其進行懶加載==)

    2.1.3 樓層內容的懶加載

    基於xtpl自帶的懶加載能力,配合pep定製頁面模板的邏輯,我們可以實現html的懶加載。在頁面初次渲染的時候,只有每個樓層的大體框架和標題等關鍵信息,如果需要的話可以給默認圖片等佔位,或設置最小高度佔位,防止錨點定位失效。
    當頁面滾動到該樓層的位置,js代碼方會執行,在初始化函數中,對該樓層的html進行加載,渲染,實現樓層圖片和html的懶加載,減少了首屏時間。
    具體代碼如下:

    1
    <div class="nov-c6-cards j-content">
    2
    </div>

     

    1
    public render(){
    2
    this.$el.find('.j-content').html(new Xtemplate(tpl).render(mockData))
    3
    ...
    4
    }

    2.1.4 套餐數據懶加載

    套餐數據的加載一直以來都是令人頭疼的,本次雙十一對於套餐腳本也做了優化,不僅對數據進行了緩存,同時也可以在指定的範圍進行套餐數據的渲染——和上述所說的樓層懶加載配合,可以做到未展示的樓層,套餐數據不請求,下拉框不渲染,詢價接口不調用,在首屏不出現大量套餐的情況下,可以大大提升首屏加載的性能。

    2.2.資源整合

    2.2.1.頁頭頁尾資源統一維護

    基礎模板的優化涉及到資源的合併,壓縮與異步加載,dom的延遲加載和圖片的懶加載。首先我們給出官網基礎模板引用的一部分js資源的表格:

    這部分js存在問題是分散在pep的各個資產庫路徑維護,有些壓縮了,有些沒有壓縮,js的加載也基本是順序執行,所以我們對這個部分的js和css資源進行了一個整合,進行的操作是遷移,合併,壓縮。

    建立common-resource倉庫去統一維護管理頁頭頁腳及公共資源代碼。

    2.2.2.合併加載方式相同的基礎功能js並壓縮

    common.js

    1
    import './common/js/AGrid';
    2
    import './common/js/jquery.base64';
    3
    import './common/js/lang-tips';
    4
    import './common/js/setLocaleCookie';
    5
    import './common/js/pepDialog';

    如上面代碼,將官網中用的分散的基礎功能js合併成一個common.js,經過伏羲流水線發布,cui套件會自動將js壓縮,這樣做的效果當然是減少官網頁面請求資源數,減小資源大小。

    2.2.3.資源異步加載

    觀察2.2.1中的表格可以發現,官網大部分js都是放在頭部或者是body后順序加載的,這些資源的加載時間必定是在DOMOnLoad之前

    這些js都是會阻塞頁面的渲染,導致頁面首屏加載變慢,我們需要做的就是通過之前頭尾資源的整理得出哪些資源是可以在onload之後去加載的,這些我們就可以把頁面加載時不需要執行的js和css全部移到頁面渲染完成後去加載,少了這部分的js邏輯執行時的阻塞,頁面首屏渲染的時間也會大大降低。

    通過cui套件中的feloader插件,我們可以比較便捷的控制js和css加載的時機:

    1
    feloader.onLoad(function () {
    2
    feloader.use([
    3
    '@cloud/link-to/index',
    4
    '@cloud/common-resource/uba',
    5
    '@cloud/common-resource/footer',
    6
    '@cloud/common-resource/header',
    7
    '@cloud/common-resource/common',
    8
    '@cloud/common-resource/prompt.css',
    9
    '@cloud/common-resource/footer.css',
    10
    ]);
    11
    });

    下圖可以明顯看到js的加載都轉移到onload之後了:

    2.2.4 圖片壓縮

    除了對設計給出的圖片有壓縮要求外,我們還通過對一部分不常更新的小圖標圖片進行base64編碼來減少頁面的圖片請求數量。

    2.3預解析與預加載

    除了延遲加載外,基礎模板還進行了諸如dns預解析,資源預加載的手段來提前解析dns和加載頁面資源。

    2.3.1 DNS 預解析

    當用戶訪問過官網頁面后,DNS預解析能夠使用戶在訪問雙十一活動頁之前提前進行DNS解析,從而減少雙十一活動頁面的dns解析時間,提高頁面的訪問性能,其實寫法也很簡單:

    1
    <link rel="dns-prefetch" href="//res.hc-cdn.com">
    2
    <link rel="dns-prefetch" href="//res-static1.huaweicloud.com">
    3
    <link rel="dns-prefetch" href="//res-static2.huaweicloud.com">
    4
    <link rel="dns-prefetch" href="//res-static3.huaweicloud.com">

    2.3.2 preload 預加載

    活動頁的部分js還使用了preload預加載的方式來提升頁面加載性能,preload的為什麼可以達到這種效果,我們需要看下面這段摘錄:

    Preloader 簡介

    HTML 解析器在創建 DOM 時如果碰上同步腳本(synchronous script),解析器會停止創建 DOM,轉而去執行腳本。所以,如果資源的獲取只發生在解析器創建 DOM時,同步腳本的介入將使網絡處於空置狀態,尤其是對外部腳本資源來說,當然,頁面內的腳本有時也會導致延遲。

    預加載器(Preloader)的出現就是為了優化這個過程,預加載器通過分析瀏覽器對 HTML 文檔的早期解析結果(這一階段叫做“令牌化(tokenization)”),找到可能包含資源的標籤(tag),並將這些資源的 URL 收集起來。令牌化階段的輸出將會送到真正的 HTML 解析器手中,而收集起來的資源 URLs 會和資源類型一起被送到讀取器(fetcher)手中,讀取器會根據這些資源對頁面加載速度的影響進行有次序地加載。

    基於以上原理,我們對官網相對重要的js資源進行preload預加載,以使得瀏覽器可以儘快地加載頁面所需的重要資源。

    1
    <link rel="preload" href="//res.hc-cdn.com/cnpm-feloader/1.0.6/feloader.js" as="script"/>
    2
    <link rel="preload" href="//polyfill.alicdn.com/polyfill.min.js?features=default,es6" as="script"/>
    3
    <link rel="preload" href="https://res-static3.huaweicloud.com/content/dam/cloudbu-site/archive/commons/3rdlib/jquery/jquery-1.12.4.min.js" as="script"/>
    4
    <link rel="preload" href="//res.hc-cdn.com/cnpm-wpk-reporter/1.0.6/wpk-performance.js" as="script"/>
    5
    
    6
    <link rel="preload" href="//res.hc-cdn.com/cpage-pep-2019nov-promotion/1.1.15/components/activity-banner/images/banner_mb.jpg" as="image" media="(max-width: 767px)">

    優化效果

    3.總結

    前端性能優化的方法手段並不僅限於文章陳述,官網前端團隊還會在前端性能優化的道路上學習更多,探索更多,將華為雲官網頁面的加載性能做到極致!

     

    點擊關注,第一時間了解華為雲新鮮技術~

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

    【【其他文章推薦】

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

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

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

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

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

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

  • 痞子衡嵌入式:利用i.MXRT1xxx系列ROM提供的FlexSPI driver API可輕鬆IAP

    痞子衡嵌入式:利用i.MXRT1xxx系列ROM提供的FlexSPI driver API可輕鬆IAP

      大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是i.MXRT系列ROM中的FlexSPI驅動API實現IAP

      痞子衡的技術交流群里經常有群友提問: i.MXRT中的FlexSPI驅動API到底怎麼用啊?這個問題已經出現過好幾次了,本來痞子衡不打算專門為這個寫文章的,因為這部分內容在芯片手冊System Boot章節里的最後一節ROM APIs里其實介紹得非常詳細了,但是既然還是有不少朋友在問這個,看起來手冊里的內容藏得有點深,這麼好的東西被埋沒太可惜了,那麼今天痞子衡就跟大家再認真聊一聊。

    一、ROM API簡介

    1.1、API產生背景

      i.MXRT系列都是Flashless(沒有內置NVM)的芯片,所以BootROM必不可少。BootROM是個很特殊的東西,本質上它是一個完整的C代碼寫成的系統級App,這個系統級App專門用於從外部存儲器中加載用戶級App執行。簡單地說,BootROM就是PC機里的BIOS。

      BootROM代碼是存放在專門的ROM區域的(前面講i.MXRT系列沒有內置NVM,其實不夠準確,其實是有內部ROM空間的,只不過這個ROM區域用戶無法下載程序使用,因此等效於沒有NVM),ROM顧名思義Readonly,所以BootROM代碼只能隨着芯片一起Tapeout,代碼無法更改(其實也有ROM patch機制,以後再介紹)。

      ROM空間其實挺大的,從64KB到512KB不等,因芯片啟動功能複雜程度而異。下圖是i.MXRT1050系列的BootROM所佔空間,ROM起始地址是0x200000(起始地址在i.MXRT上都一樣),ROM大小為96KB(這是標準啟動功能所要的代碼長度。在i.MXRT1010上是64KB – 精簡啟動功能,在i.MXRT1170上是256KB – 複雜啟動功能)。

      BootROM代碼其實並沒有佔滿全部ROM空間,總有些剩餘空間(因為工藝原因,ROM空間都是8/16KB倍數),這部分空間浪費了着實可惜。如果我們能把SDK里的一些常用模塊驅動(比如WDOG)順便放進去供用戶調用,既充分利用ROM空間,也為用戶節省Flash空間,豈不是一舉兩得。此外,BootROM功能代碼中也有一些現成模塊驅動(比如各種啟動設備存儲器驅動接口)可以一併導出,這便是API由來。

    1.2、API設計實現

      有了API想法,現在就是設計實現了。其實i.MXRT ROM API設計並不是重頭開始的,在這個MCU系列被主推之前,Kinetis系列也曾當紅過,Kinetis中也內置了ROM,並且提供了ROM API,痞子衡之前為此寫過一篇文章 《飛思卡爾Kinetis系列MCU啟動那些事(11)- KBOOT特性(ROM API)》。 i.MXRT ROM API設計思路完全復用了Kinetis ROM API的設計。

      API說到底就是一個個功能函數的結合,我們知道工程代碼都是由鏈接器自動分配的,因此每個函數實際鏈接地址是無法預期的(在鏈接文件里給每個函數分配固定地址鏈接這種方法不在考慮範疇,當函數數量眾多時,這種方法太麻煩),業界上一個比較通用的做法是定義成員是函數指針的結構體,i.MXRT ROM API就是採用的業界通用方式,下面bootloader_api_entry_t便是i.MXRT1060中API原型,g_bootloaderTree就是實例:

    typedef struct
    {
        const uint32_t version;
        const char *copyright;
        void (*runBootloader)(void *arg);
        const hab_rvt_t *habDriver;
    
        //!< FlexSPI NOR Flash API
        const flexspi_nor_driver_interface_t *flexSpiNorDriver;
    
        const nand_ecc_driver_interface_t *nandEccDriver;
        const clock_driver_interface_t *clockDriver;
        const rtwdog_driver_interface_t *rtwdogDriver;
        const wdog_driver_interface_t *wdogDriver;
        const stdlib_driver_interface_t *stdlibDriver;
    } bootloader_api_entry_t;
    
    // Bootloader API Tree
    const bootloader_api_entry_t g_bootloaderTree = {
        .copyright = "Copyright 2018 NXP",
        .version = MAKE_VERSION(1, 0, 0),
        .runBootloader = run_bootloader,
        .habDriver = &hab_rvt,
    
        .flexSpiNorDriver = &g_flexspiNorDriverInterface,
    
        .nandEccDriver = &g_nandEccDriverInterface,
        .clockDriver = &g_clockDriverInterface,
        .rtwdogDriver = &g_rtwdogDriverInterface,
        .wdogDriver = &g_wdogDriverInterface,
        .stdlibDriver = &g_stdlibDriverInterface,
    };
    

      從上面代碼我們可以看出,bootloader_api_entry_t成員好像並不是函數指針,是的,為了分組方便,bootloader_api_entry_t成員還是一個個結構體,它的這些結構體成員(比如flexspi_nor_driver_interface_t)才是真正包含一個個函數指針的結構體。API從功能來分一共提供了7類:HAB、FlexSPI NOR、NAND ECC、Clock、RT-WDOG、WDOG、stdlib。

      設計到這裏,我們通過g_bootloaderTree結構體常量就可以調用所有的API函數了,最後剩下的問題就是如何在ROM里找一個確定的地方保存隨機鏈接的g_bootloaderTree地址(只要4字節即可)。是的,還是Kinetis ROM API用的那個巧妙的方法,下面是BootROM工程的startup文件(Keil版),BootROM將g_bootloaderTree的地址放到了中斷向量表第8個向量的位置處(該向量為ARM Cortex-M未定義的系統向量),因此0x20001c處開始的4bytes便固定是g_bootloaderTree地址。

                    PRESERVE8
                    THUMB
    
    ; Vector Table Mapped to Address 0 at Reset
    
                    AREA    RESET, DATA, READONLY
                    EXPORT  __Vectors
                    EXPORT  __Vectors_End
                    EXPORT  __Vectors_Size
                    IMPORT  |Image$$ARM_LIB_STACK$$ZI$$Limit|
                    IMPORT  g_bootloaderTree
    
    __Vectors       DCD     |Image$$ARM_LIB_STACK$$ZI$$Limit|
                    DCD     Reset_Handler
                    DCD     DefaultISR
                    DCD     HardFault_Handler
                    DCD     DefaultISR
                    DCD     DefaultISR
                    DCD     DefaultISR
                    DCD     g_bootloaderTree
                    DCD     0
                    DCD     0
                    DCD     0
                    DCD     SVC_Handler
                    DCD     DefaultISR
                    DCD     0
                    DCD     DefaultISR
                    DCD     DefaultISR
    		        ;; ...
    

    1.3、API調用方法

      了解了前面介紹的ROM API產生背景與設計實現,它的調用方法就非常簡單了,以WDOG API調用為例,只需要如下簡單3句代碼:

    // 找到API根結構體
    #define g_bootloaderTree (*(bootloader_api_entry_t **)0x0020001c)
    // 定義WDOG模塊配置變量
    wdog_config_t config;
    // 調用API中WDOG_Init()
    g_bootloaderTree->wdogDriver->WDOG_Init(WDOG1, config);
    

    1.4、支持API的i.MXRT型號

      截止目前,i.MXRT1xxx系列一共出了7款型號,但並不是每個型號都開放了ROM API,最早誕生的三款型號(105x、1021、1015)就並沒有開放API(不是沒有API,而是沒有嚴格測試),其餘型號都支持API。

    RT芯片型號 是否支持ROM API
    i.MXRT117x 支持
    i.MXRT1064 支持
    i.MXRT106x 支持
    i.MXRT105x 未開放
    i.MXRT1021 未開放
    i.MXRT1015 未開放
    i.MXRT1011 支持

    二、API之FlexSPI驅動

      前面鋪墊了太多ROM API設計細節,到這裏才算進入正題,本文其實主要是要跟大家聊如何利用API里的FlexSPI NOR驅動實現IAP。痞子衡在前面鋪墊那麼多的原因其實主要是想告訴大家,API里的每個驅動都是經過完善測試的,尤其是這個FlexSPI NOR驅動,更是經過了千錘百鍊,無論是易用性、運行穩定性還是Flash型號的支持度上都是首屈一指的。

      對於JESD216標準下的串行SPI接口Flash驅動,大家知道更多的可能是RT-Thread技術總監朱天龍大神的開源 SFUD 項目,但痞子衡告訴你,i.MXRT ROM API里的這個串行Flash驅動也毫不遜色(持續維護與優化了近6年,歷經多款MCU的ROM,是真正的產品級),只是不如開源項目那麼知名,不過它的源代碼也是開源在SDK里的(\SDK\middleware\mcu-boot\src\drivers\flexspi_nor),BSD-3-Clause許可證。

    2.1 FlexSPI驅動原型

      flexspi_nor_driver_interface_t便是FlexSPI NOR驅動的原型,尋常的讀寫擦功能自然不在話下,除此以外,API裏面還有一個非常厲害的xfer()函數,這個函數可以用來實現其他定製化的Flash操作函數,有興趣的朋友可以進一步去研究。

    typedef struct
    {
        uint32_t version;
        status_t (*init)(uint32_t instance, flexspi_nor_config_t *config);
        status_t (*program)(uint32_t instance, flexspi_nor_config_t *config, uint32_t dst_addr, const uint32_t *src);
        status_t (*erase_all)(uint32_t instance, flexspi_nor_config_t *config);
        status_t (*erase)(uint32_t instance, flexspi_nor_config_t *config, uint32_t start, uint32_t lengthInBytes);
        status_t (*read)(uint32_t instance, flexspi_nor_config_t *config, uint32_t *dst, uint32_t addr, uint32_t lengthInBytes);
        void (*clear_cache)(uint32_t instance);
        status_t (*xfer)(uint32_t instance, flexspi_xfer_t *xfer);
        status_t (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase, uint32_t seqNumber);
        status_t (*get_config)(uint32_t instance, flexspi_nor_config_t *config, serial_nor_config_option_t *option);
    } flexspi_nor_driver_interface_t;
    

    2.2 FlexSPI驅動使用示例

      FlexSPI驅動使用基本三步走,先調用get_config()獲取完整FlexSPI模塊配置,然後調用init()函數去初始化FlexSPI以及訪問Flash獲取SFDP表信息,最後就是調用Flash操作函數(比如erase())。

    // 找到API根結構體
    #define g_bootloaderTree (*(bootloader_api_entry_t **)0x0020001c)
    
    // 定義FlexSPI, Flash配置變量
    flexspi_nor_config_t config;
    serial_nor_config_option_t option;
    option.option0.U = 0xC0000008; // QuadSPI NOR, Frequency: 133MHz
    uint32_t instance = 0;
    
    // 調用API中get_config()函數
    g_bootloaderTree->flexSpiNorDriver->get_config(instance, &config, &option);
    // 調用API中init()函數
    g_bootloaderTree->flexSpiNorDriver->init(instance, &config);
    // 調用API中erase()函數
    g_bootloaderTree->flexSpiNorDriver->erase(instance, &config, 0x40000, 0x1000);
    

    2.3 FlexSPI驅動特點

      因為FlexSPI NOR驅動API來自於BootROM,因此其在使用上有一些小小的限制,也算是其特點吧。FlexSPI驅動API里並沒有提供Flash連接的Pinmux配置,其Pinmux配置已經寫死在init()函數中,就是ROM支持啟動的FlexSPI PORTA上的那些pin(片選是SS0)。

      在上面的使用示例代碼中,你會看到option.option0.U = 0xC0000008代碼,這算是FlexSPI驅動最大的特點了,這是一個簡化的option配置word(其原型可在芯片手冊里找到),通過這個簡化的option,用戶可以輕鬆配置來訪問不同廠商的Flash,下面是常用的Flash模式配置值。

    • QuadSPI NOR - Quad SDR Read: option0 = 0xc0000008 (133MHz)
    • QuadSPI NOR - Quad DDR Read: option0 = 0xc0100003 (60MHz)
    • HyperFLASH 1V8: option0 = 0xc0233009 (166MHz)
    • HyperFLASH 3V0: option0 = 0xc0333006 (100MHz)
    • MXIC OPI DDR (OPI DDR enabled by default): option=0xc0433008(133MHz)
    • Micron Octal DDR: option0=0xc0600006 (100MHz)
    • Micron OPI DDR: option0=0xc0603008 (133MHz), SPI->OPI DDR
    • Micron OPI DDR (DDR read enabled by default): option0 = 0xc0633008 (133MHz)
    • Adesto OPI DDR: option0=0xc0803008(133MHz)
    

    2.4 FlexSPI驅動用作IAP

      IAP其實就是在App中實現Flash擦寫,單純從技術上來說並不是一個很難的東西。但i.MXRT上很多時候App代碼本身也在同一片Flash里執行(也叫XIP),而市面上很多Flash都是不支持RWW(Read-While-Write)的,這就導致一個問題,當你調用Flash操作函數去擦寫Flash時,CPU又需要繼續去Flash獲取指令,違反了RWW,因此你只能把Flash相關操作函數全部放在RAM中去執行(這涉及分散加載了,對於初級嵌入式用戶來說稍微有點難)。

      現在我們有了ROM API,FlexSPI驅動代碼體全部都在ROM空間里,並不佔用Flash空間,因此不存在RWW問題,真是天然為IAP而生,再也不用再管什麼分散加載這麼麻煩的事了。

    三、FlexSPI API業界應用

      最後再介紹一下i.MXRT FlexSPI API在業界的應用,這個API其實並不小眾,目前已被主流IDE和調試工具用作i.MXRT Flash下載算法。

    3.1 用於IAR下載算法

      如果你的IAR版本夠新,能夠支持i.MXRT1060等型號,隨便打開一個i.MXRT1060 SDK工程,在工程Option里找到Debugger,然後進入Flashloader配置,你會看到頁面里有Extra parameters一欄,在下面的解釋里有這個參數的示例,它就是前面2.3節里介紹的option0。有了這種方式設計的Flash下載算法,你再也不用手動更新下載算法文件去支持不同的Flash了,改參數就行了。

    3.2 用於J-Link下載算法

      目前最新的Jlink驅動里的下載算法也是基於ROM API的,痞子衡有一個開源項目,收集了i.MXRT所有型號的下載算法源代碼工程,其中jlink算法是最全的,其他IDE算法還在陸續完善中。

    https://github.com/JayHeng/imxrt-tool-flash-algo

      至此,i.MXRT系列ROM中的FlexSPI驅動API實現IAP痞子衡便介紹完畢了,掌聲在哪裡~~~

    歡迎訂閱

    文章會同時發布到我的 博客園主頁、CSDN主頁、微信公眾號 平台上。

    微信搜索”痞子衡嵌入式“或者掃描下面二維碼,就可以在手機上第一時間看了哦。

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

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※推薦評價好的iphone維修中心