【RTOS學習】FreeRTOS中的鏈表 | 堆的管理

🐱作者:一只大喵咪1201
🐱專欄:《RTOS學習》
🔥格言:你只管努力,剩下的交給時間!
圖

目錄

  • 🥩FreeRTOS中的鏈表
    • 🥞初始化
    • 🥞尾部插入
    • 🥞按順序插入
    • 🥞刪除
  • 🥩堆的管理
    • 🥞heap_1.c
    • 🥞heap_2.c
    • 🥞heap_4.c
    • 🥞heap_5.c
  • 🥩總結

🥩FreeRTOS中的鏈表

鏈表是FreeRTOS的核心結構,它讓系統的功能正常運行,本喵下面來解釋一下FreeRTOS中的鏈表結構以及操作。

圖
如上圖所示是FreeRTOS源碼中的鏈表的定義List_t,這是一個鏈表頭,重要的成員變量有三個:

  • volatile UBaseType_t uxNumberOfItems:表示鏈表中包含的節點個數。
  • ListItem_t * configLIST_VOLATILE pxIndex:這是一個指針變量,表示當前正在操作的節點。
  • MiniListItem_t xListEnd:這是一個結構體變量,從這個變量可以找到鏈表中所有節點。

tu
如上圖所示MinListItem_t結構體定義,它也有三個重要的成員變量:

  • configLIST_VOLATILE TickType_t xItemValue:這是一個值,需要排序的時候才有意義,后面會見識到。
  • struct xLIST_ITEM * configLIST_VOLATILE pxNext:這是一個指針變量,該變量指向鏈表中的第一個鏈表項。
  • struct xLIST_ITEM * configLIST_VOLATILE pxPrevious:這也是一個指針變量,該變量指向鏈表中的最后一個鏈表項。

tu
如上圖所示是鏈表項ListItem_t的定義,它包含五個重要的成員變量:

  • configLIST_VOLATILE TickType_t xItemValue:該值作為鏈表項中的一個數值,在排序時才會起作用。
  • struct xLIST_ITEM * configLIST_VOLATILE pxNext:該指針變量指向下一個鏈表項。
  • struct xLIST_ITEM * configLIST_VOLATILE pxPrevious:該指針變量指向前一個鏈表項。
  • void * pvOwner:這也是一個指針變量,指向該鏈表項所屬的容器。以后本喵會介紹。
  • struct xLIST * configLIST_VOLATILE pxContainer:這也是一個指針變量,指向該鏈表項所在的List_t類型鏈表頭。
  • 從鏈表頭以及鏈表項的定義中就可以看出,FreeRTOS中的鏈表是一個帶頭雙向循環鏈表。

tu
如上圖所示便是FreeRTOS中鏈表的鏈接關系,鏈表頭結構體成員xListEnd中的pxNext指向第一個鏈表項的起始地址,鏈表項中的pxNext指向下一個鏈表項的起始地址,如此下去,最后一個鏈表項中的pxNext指向鏈表頭中的xListEnd

  • 此時鏈表頭和鏈表項中的所有pxNext構成一個環路。

鏈表頭中的結構體成員xListEnd中的pxPrevious指向最后一個鏈表項的起始地址,該鏈表項中的pxPrevious指向前一個鏈表項,如此下去,第一個鏈表項中的pxPrevious指向鏈表頭中的xListEnd

  • 此時鏈表頭和鏈表項中的所有pxPrevious構成另一個環路。

tu
如上圖所示,由于真正的指向關系不直觀,所以本喵后面使用這樣的示意圖來表示這個鏈表,要記住無論是pxNext還是pxPrevious指向的都是鏈表項的起始地址。

🥞初始化

圖
如上圖所示鏈表初始化函數vListInitialise,在該函數中對傳入的鏈表pxList主要進行了四步操作:

  1. 讓鏈表頭中當前操作節點指針pxIndex指向自己的xListEnd
  2. xListEnd中的xItemValue = portMAX_DELAY,用該值來區別這是鏈表尾而不是鏈表項。
  3. xListEnd中的pxNextpxPrevious都指向自己的xListEnd,初步形成兩個環路。
  4. uxNumberOfItems = 0表示當前鏈表中沒有鏈表項,是一個空鏈表。

此時一個初步的鏈表就形成了,操作的都是鏈表頭自己。

圖
如上圖所示鏈表項初始化函數vListInitialiseItem,在該函數中,僅是將鏈表項中的pxContainer設置為NULL,因為此時并不知道該鏈表項屬于哪個鏈表頭。

🥞尾部插入

tu
如上圖所示插入鏈表尾部的函數vListInsertEnd,在該函數中,總體進行了四步操作:

  1. 從鏈表頭中獲得當前正常操作的鏈表項pxIndex
  2. 將新的鏈表項插入到pxIndexpxIndex->pxPrevious之間。
  3. 讓新鏈表項的pxContainer = pxList,讓其找到所屬的鏈表。
  4. 將鏈表頭中記錄鏈表項個數的unNumberOfItems加一。

圖
如上圖所示便是尾部插入新鏈表項的結果,但是它并不是插入到了尾部呀。

假設現在正在操作的是編號為2的鏈表項,所以pxIndex指向它,如果沒有新鏈表項插入的話,這幾個鏈表項的操作順序是2->3->1

此時要插入新的鏈表項,但是并不能影響原本的操作順序,否則就破壞了公平性,所以將新的鏈表項4插入到pxIndexpxIndex->pxPreviou之間,此時這幾個鏈表項的操作順序是2->3->1->4,并不會影響原來三個鏈表項的操作順序。

  • 尾插時要保證公共性,插入于正在操作鏈表項pxIndex和它的前一個鏈表項pxIndex->pxPrevious之間。

上面所演示的是鏈表中已經存在鏈表項時尾插的結果,如果是一個空鏈表呢?

圖
如上圖所示第一次尾插的結果,在插入之前pxIndexpxIndex->pxPrevious都是指向的鏈表頭中的xListEnd,當新節點插入時,pxIndex->pxPrevious指向新節點,pxIndex->pxNext也指向新節點,新節點的pxIndexpxPrevious都指向鏈表頭中的xListEnd

所以說,尾插函數vListInsertEnd可以實現第一次插入和已經存在多個鏈表項時再插入新鏈表項兩個場景。

🥞按順序插入

圖

如上圖所示按照鏈表項中的xItemValue值插入函數vListInsert,該函數中主要進行了四步操作:

  1. 獲取新插入鏈表項中的xItemValue值。
  2. 根據xItemValue值在鏈表中尋找合適位置:
    • xItemValue = portMAX_DELAY說明新的鏈表項應該插入到鏈表的最后面,直接讓插入位置pxIterator等于鏈表的最后一項。
    • xItemValue不是最大,則迭代尋找合適位置,pxIterator從鏈表頭開始,直到pxIterator->pxNext指向的鏈表項中的xItemValue大于新鏈表項的xItemValue,此時得到插入位置就是pxIterator后面。
  3. 插入新鏈表項,改變指向關系。
  4. 修改新鏈表項中的pxContainer,讓其屬于當前鏈表頭,并且修改鏈表頭中的鏈表項個數。

tu
如上圖所示是插入后的結果,原本鏈表中的xItemValue從左到右依次是1->3->4,將xItemValue為2的新鏈表項插入以后,xItemValue成了1->2->3->4

🥞刪除

tu
如上圖所示刪除指定鏈表項的函數uxListRemove,該函數中進行的主要操作也是四步:

  1. 從要刪除的鏈表項中得到它所屬的鏈表pxList
  2. 改變鏈表中的指向關系,讓被刪除鏈表項的前一個直接和被刪除鏈表項的后一個建立互相的指向關系。
  3. 進行判斷,如果刪除的是正在操作的鏈表項,那么需要將pxIndex指向下一個鏈表項,更換正在操作的鏈表項。
  4. 讓被刪除的鏈表項失憶,也就是將其pxContainer = NULL,讓它找不到原本所屬的鏈表,再將鏈表頭中鏈表項的個數減一,最后返回鏈表項的個數。

tu
如上圖所示是刪除后的示意圖,所謂刪除就是改變被刪除鏈表項的前一個和后一個鏈表項的指向關系,讓其從鏈表中脫離出來。

🥩堆的管理

很多人把"堆棧"相提并論,其實"堆"、"棧"是完全沒有聯系。"棧"的作用我們前面已經講了很多,"堆"是什么?

  • 在匯編代碼里指定一個AREA:在匯編代碼里,使用SPACE命令可以分配一段空間,這段空間就是堆:
    圖
  • 在C代碼里,定義一個全局數組:該數組的大小是17KB,這段空間就是堆:

圖

"堆"就是一塊或者多塊內存,我們可以從中申請一小塊內存來使用,使用完畢后可以釋放這一小塊內存。

簡單地說,一開始,"堆"是一些空閑內存,我們可以:

  • 使用malloc函數從中申請、獲得一小塊內存
  • 使用free函數釋放這一小塊內存
  • 這些malloc、free函數就是用來管理這些內存的
  • malloc、free函數可以有其他名稱,比如FreeRTOS里是pvPortMalloc、vPortFree。

在FreeRTOS的源碼中,默認提供了5個文件,對應內存管理的5種方法:

文件優點缺點
heap_1.c分配簡單,時間確定只分配、不回收
heap_2.c動態分配、最佳匹配碎片、時間不定
heap_3.c調用標準庫函數速度慢、時間不定
heap_4.c相鄰空閑內存可合并可解決碎片問題、時間不定
heap_5.c在heap_4基礎上支持分隔的內存塊可解決碎片問題、時間不定

只能使用上訴五種方式的中的一種來管理堆,其中heap_3.c由于使用的是標準庫函數中的mallocfree,速度比較慢,所以本喵這里也不做介紹,只介紹其他四種。

🥞heap_1.c

圖
如上圖所示在堆區上申請空間的函數pvPortMalloc,其中xWantedSize是要申請的空間大小,申請過程主要分為四步:

  1. 創建返回地址pvReturn和對齊地址pucAlignedHeap,讓這兩個指針都為0。
  2. 對申請的空間大小進行對齊。

tu
如上圖所示宏定義#define portBYTE_ALIGNMENT 8規定了8字節對齊,什么是字節對齊呢?

tu
如上圖所示,假設現在有9個字節的空間在內存中連續排列,而我們的CPU是32位的,也就是說一次讀取或者寫入數據必須操作的是4字節大小的空間。

現在一共9字節,操作兩次以后還剩下一個字節,再操作時仍然是4字節,所以要想操作第9個字節,就會額外多操作三個字節,此時就會導致效率低下。

  • 甚至有的CPU并不支持這種不對齊訪問。

由于是8字節對齊,所以申請的空間大小必須是8的整數倍,如果不符合8字節對齊,就需要向上對齊,如申請100個字節,實際上申請的是對齊后的104字節。

  1. 找到堆區的對齊地址pucAlignedHeap

圖
如上圖所示,假設整個堆區ucHeap在內存上的起始地址是0x20000001,按照CPU每次操作4字節的規律,該地址無論如何也無法作為單次CPU的起始地址,所以操作起來不方便。

按照代碼中所寫,所以對齊地址求出來就是0x20000008,該地址一定是CPU操作時4字節中的起始地址。

此時對齊地址pucAlignedHeap就是指向這里,而且讓xNextFreeByte + = xWantedSize,得到此次申請空間大小。

  1. 獲取申請空間

tu
如上圖所示,如果是第一次申請空間,那么最后返回的就是對齊地址pucAlignedHeap所指向的地址,如果不是第一次申請,那么在pucAlignedHeap基礎上增加上次空間大小的偏移量pxNextFreeByte得到的就是返回地址。

簡單來說,heap_1管理堆的方式就是,通過pucAlignedHeappxNextFreeByte兩個全局變量,每申請一次,就返回上一次申請后剩余空間的起始位置。


tu
如上圖釋放動態空間函數vPortFree,并不支持釋放空間。

  • heap_1.c里,只能用pvPortMalloc函數來申請空間,無法使用vPortFree函數來釋放空間。

🥞heap_2.c

heap_2.c里,使用鏈表來管理內存。鏈表結構體為:

tu
這個結構體用來表示空閑塊:

  • pxNextFreeBlock:指向下一個空閑塊
  • xBlockSize:當前空閑塊的內存大小

圖
如上圖所示,在這個結構體后面,緊跟著空閑內存。

  • 在堆空間static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]上,空閑空間往前偏移8個字節就得到BlockLink_t結構體的起始地址。

初始化:

tu

如上圖所示堆的初始化函數prvHeapInit,在該文件中定義了兩個BlockLink_t類型的全局變量,一個是鏈表頭xStart,另一個是鏈表尾xEnd,在該函數中主要進行四步操作:

  1. 從整個堆空間上計算對齊地址pucAlignedHeap
  2. 讓鏈表頭xStart中的pxNextFreeBlock指向對齊地址,并且將鏈表頭中的xBlockSize = 0,因為鏈表頭并不管理空閑內存。
  3. 讓鏈表尾xEnd中的pxNextFreeBlock指向空,xBlockSize = configADJUSTED_HEAP_SIZE,這是堆的最大值,為了排序時方便才這樣設計的。
  4. pxFirstFreeBlock指向對齊地址,并且構造BlockLink_t中的成員,讓xBlockSize = configADJUSTED_HEAP_SIZEpxNextFreeBlock指向鏈表尾。

圖
如上圖所示初始化完成的結果,此時在整塊堆空間的中,最前面的8字節就是BlockLink_t結構體,包含該空間的大小,并鏈入了管理空閑堆空間的鏈表中。

分配內存:

圖

如上圖所示分配空間函數pvPortMalloc的部分代碼,主要進行了四步操作:

  1. 如果是第一次調用pvPortMalloc,則先對整個堆區進行初始化,讓整個堆區作為空閑空間鏈入到空閑空間管理鏈表xStartxEnd之間。
  2. 對申請的空間大小進行對齊計算。
  3. 從空閑空間管理鏈表中尋找合適的空間塊。
    • 假設此時鏈表中存在多個含有BlockLink_t的空閑塊,所以根據要申請空間的大小迭代找到比申請空間大的空閑塊。
  4. 通過鏈表找到的是空閑塊的起始地址,包含BlockLink_t,所以向后偏移8個字節,得到該空閑塊的對齊地址作為返回值供申請者使用。

圖
如上圖,返回的是該空閑塊中紅色箭頭指向的地址。

圖
如上圖所示的緊接著前面pvPortMalloc函數剩下的代碼,主要有四步操作,本喵這里接著前面從標號5開始講解:

  1. 將尋找到的空閑塊從空閑鏈表中移除,也就是修改該塊前后的指向關系。
  2. 如果找到的這塊空間大于所申請的字節數,將用不了的部分賦值給pxNewBlockLink
  3. 構造用不了的那部分塊中的BlockLink_t結構體。
  4. 調用prvInsertBlockIntoFreeList將這個剩余的塊作為空閑塊插入到管理鏈表中。

圖

如上圖所示prvInsertBlockIntoFreeList宏函數,用來插入空閑內存塊到管理鏈表中,主要操作分為三步:

  1. 獲取插入內存塊的大小xBlockSize
  2. 根據大小xBlcokSize迭代按照升序找到合適的插入位置pxIterator
  3. 將內存塊pxBlockToInsert插入到pxIterator之后。

tu
如上圖所示是最終分配效果,Block1從管理鏈表中移除,供申請者使用,Blcok2是用不了剩下的部分,重新鏈入到管理鏈表中。

釋放內存:

tu
如上圖所示釋放內存函數vPortFree,主要操作有兩步:

  1. 由于傳入的pv是申請者使用的地址,所以向前偏移8個字節得到該內存塊的BlcokLink_t地址。
  2. 調用pxBlockToInsert將該內存塊鏈入到管理鏈表中。

tu
如上圖所示,假設釋放的是Block1內存塊,此時該內存塊和原本的Block2一起在管理鏈表中。

  • heap_2管理的鏈表,支持內存的申請和釋放。
  • 由于釋放的內存塊直接鏈入到管理鏈表,所以如果再次申請的內存比上次釋放的大,那么釋放的這塊就無法再次利用,就會存在內存碎片。
  • 所以heap_2適合用在頻繁申請和釋放固定大小內存的場景。

🥞heap_4.c

初始化:
圖
如上圖所示是使用prvHeapInit初始化完的示意圖,heap_4整體上和heap_2的管理方式一樣,也是通過鏈表和BlockLink_t來管理空閑內存塊,但是不一樣的是:

  • 鏈表尾xEnd位于整個堆空間的末尾,它并不是獨立的一個變量,而是依附在空閑空間上。
  • 鏈表尾xEnd中的xBlockSize = 0,和heap_2中的configADJUSTED_HEAP_SIZE不一樣。

分配內存:

tu
如上圖所示,分配內存時和heap_2一樣,將Block1中管理鏈表中移除,將用不完的Block2部分再次鏈入到鏈表中,返回值也是Block1的對齊地址。

在將用不了的內存塊重新鏈入到管理鏈表中時,也會調用prvInsertBlockIntoFreeList函數,heap_4heap_2的區別就在這個函數中。

釋放內存:

釋放內存時,和heap_2一樣,也會調用到prvInsertBlockIntoFreeList函數來將釋放的內存塊鏈入到管理鏈表,來分析一下這個函數:

tu
如上圖所示prvInsertBlockIntoFreeList的部分代碼,這里進行的操作就是尋找合適的插入位置。

  • 沒有獲取插入內存塊的大小。
  • 迭代時按照內存塊的地址尋找,當插入內存塊pxBlockToInsert地址小于等于pxIterator->pxNextFreeBlock時,將內存塊插入到pxIterator后面。

tu
如上圖所示prvInsertBlockIntoFreeList中緊接前面部分的后續代碼,進行的操作主要分為3步:

  1. 在尋找到插入位置pxIterator以后,先判斷插入內存塊pxBlockToInsert和其前面的pxIterator是否緊挨著(pxIterator的地址 + 偏移量 == pxBlockToInsert的地址)。

    • 如果相等,說明pxBlockToInsertpxIterator緊挨著,可以進行合并,此時改變pxBlockToInsert的大小xBlockSizepxNextFreeBlock覆蓋pxIterator
  2. 再判斷判斷插入內存塊pxBlockToInsert和其后面的pxIterator->pxNextFreeBlock是否緊挨著(pxBlockToInsert的地址 + 偏移量 == pxIterator->pxNextFreeBlock的地址)。

    • 如果相等,說明 pxBlockToInsertpxIterator->pxNextFreeBlock是緊挨著,可以進行合并,同樣僅修改pxBlockToInsertBlockLink_t覆蓋pxIterator->pxNextFreeBlock即可。
  • 這里的偏移量就是塊的大小pxBlockToInsert->xBlockSize
  1. 如果找到插入位置pxIterator后,發現pxBlockToInsert和它前面以及后面的內存塊都不挨著,就無法實現合并,只能和heap_2一樣將pxBlockToInsert插入到管理鏈表中。

tu
如上圖所示是在分配內存后將用不了的內存塊插入管理鏈表和釋放內存時將釋放的內存塊插入管理鏈表時,沒有進行合并的結果示意圖,此時和heap_2沒有區別。

tu

如上圖所示是將插入的內存塊與管理鏈表中的其他內存塊合并后的結果示意圖。

  • 管理鏈表中的內存塊合并以后變大了,再次申請比上一次釋放掉的更大內存塊時,合并后的內存可以繼續被利用。
  • 克服了heap_2再次申請更大空間時會有內存碎片的問題。

🥞heap_5.c

heap_5heap_4機會一樣,只是heap_5支持多個不連續區域作為堆。

初始化:

tu
如上圖所示結構體HeapRegion_t是用來描述用作堆的區域的,pucStartAddress表示該區域的起始地址,xSizeInbytes表示該區域的大小。

tu
如上圖所示,在使用heap_5之前,需要先定義一個全局的HeapRegion_t類型的數組array,該數組中存放的就是多個連續的堆區,然后調用vPortDefineHeapRegions來初始化所有的堆。

  • 數組中的最后一項是{NULL, 0},當發現某一個堆區的地址是NULL的時候,說明就遍歷到了數組中的最后一項。

圖
如上圖所示vPortDefineHeapRegions函數,該函數內部主要進行了五步操作:

  1. 創建while循環來遍歷所有不連續的堆區域。
  2. 求每個堆區域的對齊地址和對齊大小。
  3. 處理第一個堆區時,將其鏈入到xStart鏈表頭。
  4. 在每個堆區域末尾構造BlockLink_t
  5. 將所有堆區域首尾相連在一起。

tu
如上圖所示是將兩個不連續堆區初始化后首尾相連在一起的結果示意圖。

其他操作:

在初始化以后,就可以將這些不連續的堆看成是一個堆,其他操作完全按照heap_4來就可以。

🥩總結

對于FreeRTOS的鏈表操作,一定要理解透徹,因為后面的任務TCB等結構體完全依賴于鏈表操作,尤其注意尾部插入時不能破壞原本鏈表的公平性。

對于多種堆的管理方式,要知道它們適用的場合和大致的原理,根據適用場景適用合適的管理方式。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/214080.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/214080.shtml
英文地址,請注明出處:http://en.pswp.cn/news/214080.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

OpenWRT搭建本地web站點并結合內網穿透實現公網遠程訪問

文章目錄 前言1. 檢查uhttpd安裝2. 部署web站點3. 安裝cpolar內網穿透4. 配置遠程訪問地址5. 配置固定遠程地址 前言 uhttpd 是 OpenWrt/LuCI 開發者從零開始編寫的 Web 服務器,目的是成為優秀穩定的、適合嵌入式設備的輕量級任務的 HTTP 服務器,并且和…

【Windows】MCSM面板搭建Mycraft服務器,實現公網遠程聯機

文章目錄 前言1.Mcsmanager安裝2.創建Minecraft服務器3.本地測試聯機4. 內網穿透4.1 安裝cpolar內網穿透4.2 創建隧道映射內網端口 5.遠程聯機測試6. 配置固定遠程聯機端口地址6.1 保留一個固定TCP地址6.2 配置固定TCP地址 7. 使用固定公網地址遠程聯機 前言 MCSManager是一個…

[香橙派]Orange pi zero 3命令行配網方法——建立ssh連接——Ubuntu配置WIFI自動連接

一、前言 前面我們給Orange Pi安裝了Ubuntu系統,并通過MobaXterm進行了串口連接,但其實并不方便,在日常開發中,我們希望能夠使用更方便的ssh連接來進行操作,因此配置網絡是必要的。 本章介紹的方法無需網線、HDMI線等&…

upload-labs

01 隨便上傳個文件 發現對于上傳類型有限制 查看頁面代碼發現是js的過濾直接關閉js 上傳成功 右鍵圖片在新建標簽頁打開文件 這里直接抓包改名字也行 02 抓包修改后綴名 03 發現后端做了檢測抓包修改失敗 大小寫繞過失敗,php特性php1等會被當成php執行 這里圖片的…

私域流量:探索營銷新紀元的高效之路

在當前的數字營銷領域,私域流量已經嶄露頭角,成為企業和品牌爭相追逐的新路徑。私域流量指的是企業或品牌通過自有渠道,如網站、APP、微信公眾號等,直接觸達的用戶流量。這種流量具有更高的自主性和可控性,對于提升營銷…

MS1242,替代ADS1242,24bit 高精度、低功耗模數轉換器

產品簡述 MS1242/MS1243 是一款高精度、寬動態范圍、 ?-Σ 模數轉 換芯片,其工作電壓為 2.7V 至 5.25V ,可以達到 24bit 無失碼轉 換,有效精度可達 21bit 。 MS1242/MS1243 可以廣泛使用在工 業控制、稱重、液體 / 氣體化學分析、血液分…

spark 寫入 hudi時數據類型報錯

報錯信息如下: Caused by: org.apache.spark.sql.execution.QueryExecutionException: Parquet column cannot be converted in file hdfs://master:9000/user/hive/warehouse/ods_ds_hudi.db/order_info/19971201/77687054-08d3-4045-9529-1ca38e7de10b-0_0-65-57…

selenium相關地址匯總

webdriver下載 Chrome瀏覽器驅動下載地址:https://chromedriver.storage.googleapis.com/index.html Edge瀏覽器驅動下載地址:https://developer.microsoft.com/zh-cn/microsoft-edge/tools/webdriver) 或 https://msedgewebdriverstorage.z22.web.cor…

手眼標定 - 最終精度和誤差優化心得

手眼標定 - 標定誤差優化項 一、TCP標定誤差優化1、注意標定針擺放范圍2、TCP標定時的點次態與工作姿態盡可能保持相近 二、深度相機對齊矩陣誤差1、手動計算對齊矩陣 三、手眼標定拍照姿態1、TCP標定姿態優先2、水平放置棋盤格優先 為減少最終手眼標定的誤差,可做或…

Get職場新知識:做分析,用大數據分析工具

為什么企業每天累積那么多的數據,也做數據分析,但最后決策還是靠經驗?很大程度上是因為這些數據都被以不同的指標和存儲方式放在各自的系統中,這就導致了數據的分析口徑和標準不一致,無法在同一個分析軟件上做綜合分析…

Rsync+Sersync

服務器相關參數 源服務器 192.168.17.101 目標服務器(同步到的服務器) 192.168.17.103 ##目標服務器配置 ###1、配置rsync服務 1、安裝rsync yum -y install rsync 2、配置rsync vim /etc/rsyncd.conf 配置文件內容 uid root gid root use c…

Module build failed : Error : Vue packages version mismatch:

Vue packages version mismatch: - vue2.7.15 (E:\Workspace_ce\erp\erp-web\node_modules\vue\dist\vue.runtime.common.js) - vue-template-compiler2.6.11 (E:\Workspace_ce\erp\erp-web\node_modules\vue-template-compiler\package.json) 【問題解決了,我很不…

bootstrap:選項卡功能DEMO

<!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>選項卡</title> <link rel"stylesheet" type"text/css" href"/cdn.bootcss.com/bootstrap/3.3.2/css/bootstrap.min.css" />…

記一次測試環境git翻車經歷

本來想拉一個功能分支進行新的功能開發&#xff0c;合并代碼發現沒有沖突居然有文件被修改了&#xff0c;貿然選擇最近的一次回滾提交&#xff0c;沒想到不假思索的push -f 導致一部分dev主干的代碼不見了。 事故記錄 開發分支origin/dev&#xff0c;功能分支file 合并之后發…

Java 實現 文檔 添加 水印 工具類

一、pom 文件引用 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>…

MeterSphere實戰(一)

MeterSphere是一位朋友講到的測試平臺&#xff0c;說這東西是開源的&#xff0c;因為我是做測試的&#xff0c;很樂意了解一些新鮮事物。在我看來&#xff0c;測試就是要專注一些領域&#xff0c;然后要啥都會一點點&#xff0c;接著融會貫通起來&#xff0c;這樣就可以萬變不離…

C語言--不使用庫函數,把一個數字轉為字符串【詳細解釋】

一.題目描述 輸入一個數字&#xff0c;把他轉為字符串 比如&#xff1a;輸入數字&#xff1a;12345 輸出&#xff1a;12345&#xff08;這里的12345是字符串12345&#xff09; 二.思路分析 比如給定一個數字12345&#xff0c;先把它轉為字符54321&#xff08;“54321”&#…

線程互斥與同步

用戶級線程 內核的LWP Linux線程 OS概念中經常說的 用戶級線程 和 內核級線程 也就是線程實現真的是在OS內部實現&#xff0c;還是應用層或用戶層實現 很明顯Linux是屬于用戶級線程 用戶級執行流&#xff08;用戶級線程&#xff09; &#xff1a;內核lwp 1 : 1 也有1&…

驍龍8 Gen 3 vs A17 Pro

驍龍8 Gen 3 vs A17 Pro——誰會更勝一籌&#xff1f; Geekbench、AnTuTu 和 3DMark 等基準測試在智能手機領域發揮著至關重要的作用。它們為制造商和手機愛好者提供了設備性能的客觀衡量標準。這些測試有助于評估難以測量的無形方面。然而&#xff0c;值得注意的是&#xff0c…

騷操作:NanoDrop測蛋白濃度

?大家好&#xff0c;最近實驗室的BCA儀器壞了&#xff0c;偶然發現nanodrop也可以測蛋白濃度&#xff0c;省不少時間&#xff01;本方法原理是&#xff1a;紫外吸收 友情提示&#xff1a;由于表格的存在&#xff0c;用電腦看本推文&#xff0c;效果更好 紫外吸收法 較為靈…