Real-time operating system memory management - TLSF algorithm

hhhtsdyzx

foreword

Vue框架:Learn from the project Vue
OJ算法系列:magical machine hundreds of refining - algorithm detailed explanation
Linux操作系统:of Fenghou Qimen - linux
C++11:Tongtianlu - C++11
Python常用模块:Tongtianlu - Python

TLSF algorithm:

  • Memory Management Algorithm of Real-time Operating System
  • The allocation speed of TLSF is fast, but the relative memory utilization rate is low, and internal fragmentation is prone to occur, so it is more commonly used in embedded scenarios.

Why memory is also called memory "block":

  • Suppose we bought a new 16G memory module now, before we installed it on the motherboard, the memory used is actually less than 16G:

    • There are two pointers in the initial memory, one points to the first address of the first available space, and the other points to the "end" of the memory bar
    • The size of the first available space is 16G-2*sizeof(pointer), and the size of the "sentinel block" at the end of the memory stick is 0
      insert image description here
  • So there is a memory "block" name source 1: There are only two initial memory blocks, "free block" & "sentinel block"

  • Finding free block pointers in 16G memory is very simple, directly 111...111111, but finding sentinel block pointers requires skill

    • Looking at the figure, the sentinel block pointer is adjacent to the memory block pointer, so only 111...111111 - sizeof(pointer) is needed to find it
    • Here sizeof(pointer) refers to the size of the unit with 111...111111 as the first address
    • That is to say, we need to know the size of the "unit" where the current pointer is located, and this size is not always equal to the result of sizeof(). For example, we have allocated 64 bytes, but we have not used up 64 bytes, and only stored an int. At this time sizeof()=4
    • So in addition to these two pointers, other memory blocks need a size field to indicate their own size
    • At this point, we can draw the first draft of the structure of the physical "block" of memory (the size of the sentinel block is not accurate):
      size
  • Therefore, there is a memory "block" name source 2: each memory block has a size field to specify the size of the block

  • With the help of the size field of a physical block, we can easily find the next block of its physical address, so do we need to use other information for the previous block?

    • If it is only required to reach the previous block, the current block block head pointer -1 will reach the last byte of the previous block
    • But to get to the block head address of the previous block, we still need to use the pointer, which is the last sizeof(Pointer) bytes of the previous block
    • From this we can draw the finalized memory block on the physical space:
      insert image description here
  • In order to prevent the user from directly operating the physical memory, the OS gives the user a controllable address/pointer, which is actually the next address adjacent to size:
    insert image description here

  • When the user uses up the Busy Block area, the prev_phys_block field is used as user data, and it no longer points to the first address of the physical block.

  • At this time we need to notify the next physical block that the prev_phys_block pointer is invalid

  • The notification method is very simple. Since the size+sizeof (size field) of each physical block is a multiple of 8Bytes or 4Bytes, the last two digits of the size of each physical block must be 0.

  • Use the 0th bit of the size field to identify whether the current physical block is free, and the 1st bit to identify whether the previous physical block is free. 1 is idle, 0 is used by the user. This is why the requested memory must be an integer multiple of 4 or 8.

  • When the last physical block is used by malloc() for the user, before leaving, you need to set the 0th position of your own size to 0, and set the first position of the size of the next physical block to 0

  • When searching for the header of the previous physical block, each physical block must first check whether the first bit of size is 1. If it is 1, pre_phy_block is valid

  • Therefore, there is a memory "block" name source 3: each memory block must know whether the previous block is used by the user

  • Finally, just like the logical address and physical address when we write the linked list, when controlling the physical block, we need to use struct to make some logical changes:

    • Each physical block starts with size and ends with pre_phys_blocks
    • The logical Block struct adds the end of the previous block on the basis of the physical block, prev_phys_blocks, which is used to find the starting address of the previous block
    • The starting address of the struct is always one pointer size before the starting address of the physical block
      insert image description here
  • Therefore, there is a memory "block" called source 4: os manages each memory block through struct

O(1) Finding a free block:

  • According to the principle of sacrificing space in exchange for time, TLSF has a well-designed management structure to find free blocks

    • Free blocks similar to the free memory (the size field in the figure above) form a linked list, and eventually there are many free linked lists
    • blocks[][]: two-dimensional array, the first-level index is called fl, the second-level index is called sl, each first-level index fl has the same sl, generally 2^5, blocks[fl][sl] Store the head of the free list
    • fl_bitmap: Bitmap, 1 bits indicate that there is a free linked list under blocks[fl], 0 means no.
    • sl_bitmap[]: bitmap array, when fl is determined, sl_bitmap[fl] is a bitmap, a bit of 1 indicates that there is a free linked list under blocks[fl][sl]
    • When blocks[fl][sl] is not None, bit sl of sl_bitmap[fl] must be 1, and bit fl of fl_bitmap must be 1.
    • The bitmap and blocks[][] must always match, otherwise the OS has crashed and the memory module has been damaged.
        pub fn init_on_heap(mut tmp : Box<TLSFControl,Global>) -> Box<Self,Global>{
          
          
            // TODO: YOUR CODE HERE
            tmp.fl_bitmap = 0;
            tmp.sl_bitmap = [0; FL_INDEX_COUNT];
            for i in 0..FL_INDEX_COUNT {
          
          
                for j in 0..SL_INDEX_COUNT {
          
          
                    tmp.blocks[i][j] = RawList::new();
                }
            }
            tmp
            // END OF YOUR CODE
        }
    
  • TLSF stipulates that different blocks of free memory are linked to different Blocks[fl][sl] according to the following principles:

    1. fl = 0 layer stores free blocks with free memory not exceeding 256Bytes

      fl = 0, sl has 32 indexes, 256 / 32 = 8, then different free ranges correspond to sl as follows:

      /*fl = 0
      sl[0]: 0  ~  7
      sl[1]: 8  ~  15
      sl[2]: 16  ~  23
      ...
      sl[30]: 240 ~ 247
      sl[31]: 248 ~ 255
      */
      
    2. fl = Layer 1 stores free blocks of free memory ranging from 256 to 511Bytes (256 and 511 binary highest bit 1 occupy the same place)

      511 - 256 + 1 = 256, 256 / 32 = 8, then the corresponding sl of different free ranges is as follows:

      /*fl = 1
      sl[0]: 256  ~  263
      sl[1]: 264  ~  271
      sl[2]: 272  ~  279
      ...
      sl[30]: 596 ~ 403
      sl[31]: 404 ~ 511
      */
      
    3. fl = Layer 2 stores free blocks of free memory ranging from 512 to 1023 Bytes (512 and 1023 binary highest bit 1 occupy the same place)

      1023 - 512 + 1 = 512, 512 / 32 = 16, then the corresponding sl of different free ranges is as follows:

      /*fl = 2
      sl[0]: 512  ~  527
      sl[1]: 528  ~  543
      sl[2]: 544  ~  559
      ...
      sl[30]: 992 ~ 1007
      sl[31]: 1008 ~ 1023
      */
      
    4. The remaining fl layers are assigned the same

Determine fl:

  • Propose the required size space, how to determine fl & sl:

    • First, size must be 8-byte aligned:

          pub fn malloc<T>(&mut self, size: usize) -> *mut T {
              
              
              let adjust = if size > 0 {
              
              
                  align_up(size, ALIGN_SIZE)
              } else {
              
              
                  panic!("size must be greater than 0");
              };
              //size = adjust 或之后直接以adjust传参...
      
  • When size < 256Bytes, first go to fl = 0 to find free blocks.

  • When size > 256Bytes, search for free blocks according to the formula: [ ] means round down
    fl = [ logxsize ] fl = [log_{x}^{size}]fl=[logxsize]

  • From the perspective of value equality, the above formula can be replaced by the bit where the highest binary bit 1 is located:

    #[inline]
    fn ffs(word: usize) -> u32 {
          
          
        (word as u32).trailing_zeros() as u32
    }
    

Determine sl:

  • When size < 256Bytes, sl = (size - 0) / 8

  • When size > 256Bytes, search for free blocks according to the formula: SLI is the number of sl corresponding to each fl to take log2 sl
    = ( size − 2 fl ) 2 f / 2 SLI sl = \frac{(size - 2^{fl})} {2^{f}/2^{SLI}}sl=2f/2SLI(size2fl)

  • That is, especially when size<256, 2^fl = 2^0 = 1 != 0

Upgrade application:

  • If you apply for free memory with a size of 519 at this time:
    1. 519 is rounded up to 8 to get 520
    2. The highest bit of 520 binary is the 9th bit, because the highest bit of 256 corresponding to fl=0 is the 8th bit, so fl = 9-8+1=2
    3. sl = (520 - 512)/16 = 0
    4. Look at the above table: sl[0]: 512 ~ 527, it seems that blocks[2][0] can be used
    5. However, because it is not sure whether the free size in blocks[2][0] is 512 or 527, it may not be able to accommodate 520Bytes content
    6. Make sure that the size of the free block applied for each time can be stored, and we initiate an application for blocks with larger free blocks
  • Find free blocks with larger free space and closest to size:
    1. Query the xth bit in sl_bitmap[fl] from low to high, requiring x>sl, and the x bit is 1
    2. If x is not found in sl_bitmap[fl], then query in fl_bitmap
    3. Query the xth bit in fl_bitmap from low to high, requiring x>fl, and the x bit is 1

Split block:

  • Assuming that the current memory stick is 1024 bytes, and the initial two pointers occupy 8 bytes, then 1008 bytes are available

    1008 bytes are divided into free blocks of size 1008 and sentinel blocks of size 0

    Now only apply for 8 bytes, according to the method of searching fl and sl above, the free block found must be 1008

  • It is impossible for us to directly use free blocks that are much larger than the size used for the application, so there are a lot of internal fragments, so it is necessary to divide the block, and one that cannot be divided into a block with a size of 0.

  • Given a Block struct A, it is divided into B block & of size a and C block whose size is not 0:

    1. Block B is directly changed to use the size field of the original A. After splitting, we need to increase the size of block C at the physical layer, and the overhead of this size is 8
    2. To ensure that A.size > a + 8, since the smallest unit is 8 bytes, it is equivalent to A.size >= a + 8 + 8
  • Determine the starting position of the C block size:

    • User data_ptr + current block size = end of next physical block size
    • The starting position of the next block size = end - 8

    insert image description here

    • Since the memory block that the user applies for malloc needs to be written into user data, the prev_phys_blocks of the divided C block is invalid. The first bit of the C block size must be set to 0 to prevent the C block from accessing other memory through the chaotic prev_phys_blocks.

    It is also necessary to set the pre_phys_block of the C block, and the pre_phys_block of the next block of the original A block

  • If it is not aligned with 8 bytes in the malloc stage, the last 2 bits of the size of the current block must be invalid, causing memory access confusion.

How do free blocks form a linked list?

  • After the above analysis, we found that the size field and the busy block used by the user have occupied all physical blocks.

    So where is the next & prev of the linked list stored?

  • The busy block of the free block has no user data written at this time, so we can write next & prev into the busy block below the size:

insert image description here

  • In this way, we get any memory block, and by looking at the last two digits of its size, we can determine whether there is prev & next, and whether we can access the previous physical block

Reduce external debris:

  • When malloc, the purpose of our division is to reduce internal fragmentation.

    When free, we try to physically merge the current block and the upper and lower blocks into a larger block in order to reduce external fragmentation.

Find upper and lower blocks:

  • Find the previous block:
    1. First look at whether the 0th bit of size is 1
    2. Then use prev_phys_block to access the starting address of the previous block
  • Find the next block:
    1. First move the current block head address pointer backwards, move size-8 units
    2. Then use the first bit of the next block size to judge whether the next block is free

Merge blocks:

  • Merging is equivalent to combining BC blocks into A blocks, and the size of A is B.size + C.size + sizeof(size field)=8

  • Block A after merging is free, except that the last two digits of its size need to be modified

    There is also the pre_phys_block of the next block of the C block and the last 2 bits of its size

doubt:

  • At the beginning, it said that the TLSF manager is on the heap, and the heap is also in the memory. Why does init_block() not consider subtracting the overhead of the TLSF manager?
  • Does the size field of the sentinel block occupy memory?
    My understanding is that the sentinel block is most likely just one byte

Guess you like

Origin blog.csdn.net/buptsd/article/details/130650292