编辑
2025-04-07
记录知识
0

在学习cache的时候,有看到一系列的cache策略,这里汇总cache的策略,方便记忆

cache种类

cache分cacheable和non-cacheable
non-cacheable就是数据没有cache,而cacheable则又可以细分为如下

  • read/write allocate
  • write-back cacheable
  • write-through cacheable
  • shareability

分配策略

我们知道到数据miss的时候会分配cache line,那么主要有两种分配方式

  • read allocation

读取操作miss时,分配一个cache line,并记录cache

  • write allocation

写入操作miss时,分配一个cache line,并记录cache

回写策略

数据更新也就是write的时候,cache对回写的策略如下

  • write-back

写操作只会更新到cache,然后标记cache line为dirty,不会更新到内存中

image.png

  • write-through

写操作会直接更新到cache中,并同时更新到内存中

image.png

shareability

cache的共享属性分为 inner和outer,对于共享属性,我们可以这样记忆

  • 对于每个cluster内部,cache是inner shareable属性的
  • 两个cluster之间,cache是outer shareable属性的

image.png

cache可见性

  • PoU: point of unification : 对于inner share的cache,整个cluster内部看到的都是同一份cache的拷贝,不会出现两份不同的cache

image.png

  • PoC: point of coherency : 对于系统的硬件,如GPU,DMA,多个Cluster的CPU之间,看到的都是同一份cache的拷贝,不会出现两份不同的cache

image.png

编辑
2025-04-07
记录知识
0

操作系统在实现调度的时候会使用rb-tree,可见红黑树对于操作系统而言是多么的重要,本文首先介绍一下rb-tree的基本要素,后面连载逐渐了解rb-tree

树的结构

对于一个树的结构,默认的实现应该如下

struct Node { struct Node *left; /* left element */ struct Node *right; /* right element */ }

这里通过Node就是树的节点,left是树的左子树,right是树的右子树

此时我们新增一个parent的Node指针,这样我们可以查找树里面的元素就无需每次从根节点遍历,结构体如下

struct Node { struct Node *left; /* left element */ struct Node *right; /* right element */ struct Node *parent; /* parent element */ }

红黑树结构

对于红黑树,我们需要对Node添加颜色标识,可以如下

struct Node { struct Node *left; /* left element */ struct Node *right; /* right element */ struct Node *parent; /* parent element */ int rbe_color; /* node color */ }

这样,我们基本的红黑树的数据结构就完成了,接下来说明红黑树的特性

红黑树特性

对于红黑树的基本特性,主要是如下四点

  • 根节点是黑色的
  • 叶子节点都是黑色的,这里叶子节点包括最后一个null的节点(左右均null)
  • 相邻的节点之间不能出现连续的红色
  • 从根节点到叶子节点的黑色节点数目相等

这里以 Data Structure Visualizations 网站绘图

https://www.cs.usfca.edu/~galles/visualization/RedBlack.html

下面展示一个从1-10共10个元素的红黑树图

image.png

可以发现:

  • 对于特性1,节点6是黑色的
  • 对于特性2,图中我故意未画出
  • 对于特性3,节点1,3,9并不连续
  • 对于特性4,节点6,4,2和6,4,5和6,8,7和6,8,10的黑色节点数目相等

红黑树基本操作

红黑树的操作主要是左旋和右旋,以及插入和删除和搜索和染色,本文仅讨论左旋和右旋基本操作,插入,删除,搜索,染色等操作后面文章详细介绍。

关于左旋和右旋操作,是在红黑树出现不平衡的情况下,主动通过左旋和右旋调整使得整个红黑树平衡

左旋

对于左旋,就是红黑树不平衡的时候,对不平衡的节点向左旋转
假设有节点 x,y,a,b,r, 对其进行围绕x的左旋,如下所示

image.png

那么其具体步骤如下

  • 将节点x的right断开
  • 设置x的right节点y作为自己的parent
  • 如果节点y有left节点,则将y的left设置为x的right

简单理解就是,基于谁的节点左旋,就是将那个节点的右节点作为自己的父节点,然后重建叶子节点

为了更清楚的了解左旋步骤,我基于最上面10个元素的示例演示,先删除了节点8,则此时的树结构如下

image.png

此时因为节点7没有左孩子,但是节点10有左孩子,这并不平衡,所以需要对节点10进行右旋,右旋我们还没解释,简单理解就是将自己的左孩子作为自己的父亲。所以右旋后结构如下

0003a0705c258bbc994c8c5ade228be.png

我们发现节点9不平衡,其左孩子的黑色个数是3,而其他个数是4,所以将其染色如下

image.png

现在重点来了,但是此时节点7的左孩子是空,也就是其黑色节点数是3,我们需要左旋,步骤如下:

  1. 此时左旋的操作是针对节点7
  2. 对于7,断开节点9的连接,让其成为7的父节点
  3. 然后让节点7成为节点9的左孩子
  4. 因为节点9没有左孩子,所以无需进一步操作

这样左旋完成之后,结果如下

34d97652a4d4deb029511c5fce407b3.png

此时还是黑色个数不对,我们将节点10染色成黑色即可。如下

01ee401dbf545346968218c0ea4a8c4.png

右旋

我们介绍了左旋,对于右旋,就是红黑树不平衡的时候,对不平衡的节点向右旋转
假设有节点 x,y,a,b,r, 对齐进行围绕x的右旋,如下所示

image.png

那么其具体步骤如下

  • 将节点x的left断开
  • 设置x的left节点y作为自己的parent
  • 如果节点y有right节点,则将y的right设置为x的left

简单理解就是,基于谁的节点右旋,就是将那个节点的左节点作为自己的父节点,然后重建叶子节点

还是为了示例,还是基于上图删掉节点8之后的红黑树,此时我删除节点6,这里节点5会取代节点6,如下

image.png

此时红黑树在节点4上不平衡了,我们需要针对节点4进行右旋
步骤如下:

  1. 此时左旋的操作是针对节点4
  2. 对于4,断开节点2的连接,让其成为4的父节点
  3. 然后让节点4成为节点2的右孩子
  4. 因为节点2有右孩子,所以将节点2的右孩子给节点4作为节点4的左孩子

这样右旋完成之后,结果如下

image.png

最后发现节点2的黑色节点不平衡,个数不对,所以将节点1染色成黑色即可完成,如下

image.png

总结

本文作为红黑树介绍的开头,简单介绍了红黑树的基础,例如红黑树的特性,红黑树的左旋和右旋操作。

编辑
2025-04-07
记录知识
0

rtems不仅支持基于优先级调度的算法,也支持简单优先级调度算法,简单优先级算法相比于优先级调度算法而言减少了bitmap,直接通过优先级和队列FIFO特性来更新调度优先级。本文介绍rtems中简单优先级的实现方法

调度器结构体

我们通过_Scheduler_simple_Initialize函数可以知道此调度算法的结构体构造,如下

void _Scheduler_simple_Initialize( const Scheduler_Control *scheduler ) { Scheduler_simple_Context *context = _Scheduler_simple_Get_context( scheduler ); _Chain_Initialize_empty( &context->Ready ); }

首先,需要构造一个Scheduler_simple_Context结构体,其成员如下

typedef struct { /** * @brief Basic scheduler context. */ Scheduler_Context Base; /** * @brief One ready queue for all ready threads. */ Chain_Control Ready; } Scheduler_simple_Context;

可以发现,此结构体的上下文只包含了调度器上下文和一个链表,这个链表就是此调度算法的实现
我们知道此调度算法通过链表,所以需要初始化链表如下

static inline void _Chain_Initialize_empty( Chain_Control *the_chain ) { Chain_Node *head; Chain_Node *tail; _Assert( the_chain != NULL ); head = _Chain_Head( the_chain ); tail = _Chain_Tail( the_chain ); head->next = tail; head->previous = NULL; tail->previous = head; }

执行调度

对于基于队列的调度算法,其schedule函数就是将最高优先级的元素出队,如下

static inline Thread_Control *_Scheduler_simple_Get_highest_ready( const Scheduler_Control *scheduler ) { Scheduler_simple_Context *context = _Scheduler_simple_Get_context( scheduler ); return (Thread_Control *) _Chain_First( &context->Ready ); }

因为队列的FIFO结构,默认最高优先级的元素在队列头。所以直接从head->next拿即可。

阻塞任务

对于基于队列的调度算法,阻塞任务的方式就是从队列中解压出此任务,让其不在就绪队列中,如下

static inline void _Chain_Extract_unprotected( Chain_Node *the_node ) { Chain_Node *next; Chain_Node *previous; _Assert( !_Chain_Is_node_off_chain( the_node ) ); next = the_node->next; previous = the_node->previous; next->previous = previous; previous->next = next; #if defined(RTEMS_DEBUG) _Chain_Set_off_chain( the_node ); #endif }

任务就绪

对于基于队列的调度算法,任务就绪主要两个部分

  • 将待插入的任务和就绪队列所有元素对比,找到小于等于的队列位置
  • 插入队列

对于遍历就绪队列的代码如下

while ( next != tail && !( *order )( key, to_insert, next ) ) { previous = next; next = _Chain_Next( next ); }

这里的order就是根据遍历的元素,找到小于等于的队列位置,如下代码实现

static inline bool _Scheduler_simple_Priority_less_equal( const void *key, const Chain_Node *to_insert, const Chain_Node *next ) { const unsigned int *priority_to_insert; const Thread_Control *thread_next; (void) to_insert; priority_to_insert = (const unsigned int *) key; thread_next = (const Thread_Control *) next; return *priority_to_insert <= _Thread_Get_priority( thread_next ); }

根据上面的操作,可以找到待插入的位置,然后执行队列的插入操作即可

static inline void _Chain_Insert_unprotected( Chain_Node *after_node, Chain_Node *the_node ) { Chain_Node *before_node; _Assert( _Chain_Is_node_off_chain( the_node ) ); the_node->previous = after_node; before_node = after_node->next; after_node->next = the_node; the_node->next = before_node; before_node->previous = the_node; }

优先级更新

基于队列的优先级调度算法的更新优先级,主要如下操作

  • 将待更新的任务取出
  • 与任务就绪一致,找到队列中小于等于待更新优先级的位置
  • 插入任务
  • 执行调度

这里取出操作就是阻塞操作中的解压任务_Chain_Extract_unprotected
这里找到匹配的位置插入的操作和就绪任务一致 这里执行调度的操作就是让最高优先级的任务得以调度

总结

至此,我们完全了解的简单优先级调度算法的逻辑了,其相比于优先级调度算法而言,没有使用位图。设计更加简单,因为位图占用一定的内存资源,故此调度器算法内存占用少。而又因为位图的时间复杂度是O(1),遍历队列链表的复杂度是O(n)。
故简单优先级调度算法,减少了内存占用,牺牲了一些性能,比较适合非常简单的任务系统

编辑
2025-04-03
记录知识
0

在rtems中,可以通过region来分配内存,这样分配的内存可以独立于malloc申请的内存,从而实现内存区域的管理。本文基于此分享如何使用内存区域

一、作用

内存区域的作用是单独的内存区域,这个区域是固定的起始地址,大小和属性。使用内存区域的情况下,可以避免内存回收,内存规整等操作带来的性能消耗,同样的,也可以为多个任务创建不同的内存区域。

当然,在rtems中,如果分配内存区域的时候内存不足,也可以将任务进入等待队列,直到内存释放后有可用内存时。

二、代码实现

内存区域的代码如下

rtems_status_code rtems_region_create( rtems_name name, void *starting_address, uintptr_t length, uintptr_t page_size, rtems_attribute attribute_set, rtems_id *id ) { rtems_status_code return_status; Region_Control *the_region; if ( !rtems_is_name_valid( name ) ) { return RTEMS_INVALID_NAME; } if ( id == NULL ) { return RTEMS_INVALID_ADDRESS; } if ( starting_address == NULL ) { return RTEMS_INVALID_ADDRESS; } the_region = _Region_Allocate(); if ( !the_region ) return_status = RTEMS_TOO_MANY; else { _Thread_queue_Object_initialize( &the_region->Wait_queue ); if ( _Attributes_Is_priority( attribute_set ) ) { the_region->wait_operations = &_Thread_queue_Operations_priority; } else { the_region->wait_operations = &_Thread_queue_Operations_FIFO; } the_region->maximum_segment_size = _Heap_Initialize( &the_region->Memory, starting_address, length, page_size ); if ( !the_region->maximum_segment_size ) { _Region_Free( the_region ); return_status = RTEMS_INVALID_SIZE; } else { the_region->attribute_set = attribute_set; *id = _Objects_Open_u32( &_Region_Information, &the_region->Object, name ); return_status = RTEMS_SUCCESSFUL; } } _Objects_Allocator_unlock(); return return_status; }

2.1 解析形参

name:内存区域名称

starting_address:需要申请的内存地址

length:内存区域大小

page_size: 设置内存区域的页大小

attribute_set: 设置内存的调度属性

id: 内存区域id

2.2 内存管理线程

rtems提供两种线程方式:一个是fifo,也就是先进先出调度,另一个是priority,也就是基于优先级的调度,分别如下:

const Thread_queue_Operations _Thread_queue_Operations_FIFO = { .priority_actions = _Thread_queue_Do_nothing_priority_actions, .enqueue = _Thread_queue_FIFO_enqueue, .extract = _Thread_queue_FIFO_extract, .surrender = _Thread_queue_FIFO_surrender, .first = _Thread_queue_FIFO_first }; const Thread_queue_Operations _Thread_queue_Operations_priority = { .priority_actions = _Thread_queue_Priority_priority_actions, .enqueue = _Thread_queue_Priority_enqueue, .extract = _Thread_queue_Priority_extract, .surrender = _Thread_queue_Priority_surrender, .first = _Thread_queue_Priority_first };

这个初始化的线程,会在内存不足的时候阻塞。

2.3 申请堆

通过_Heap_Initialize直接申请堆作为内存区域的Memory成员。地址指向starting_address

2.4 设置属性和申请id

对于这块内存,可以设置region结构体的属性字段,并通过_Objects_Open_u32申请内存区域id。region结构体如下

typedef struct { Objects_Control Object; Thread_queue_Control Wait_queue; /* waiting threads */ const Thread_queue_Operations *wait_operations; uintptr_t maximum_segment_size; /* in bytes */ rtems_attribute attribute_set; Heap_Control Memory; } Region_Control;

三、测试分配内存

测试分配内存区域非常简单,我们可以直接通过rtems_region_create分配即可,如下:

static const rtems_name name = rtems_build_name('K', 'Y', 'L', 'I'); #define CONFIGURE_MAXIMUM_REGIONS 1 static void test_region_create(void) { rtems_status_code sc; rtems_id id; void *seg_addr1; void *seg_addr2; sc = rtems_region_create( name, 0x4000000, 640, 16, RTEMS_DEFAULT_ATTRIBUTES, &id ); printf( "sc=%d\n", sc); sc = rtems_region_get_segment( id, 32, RTEMS_DEFAULT_OPTIONS, RTEMS_NO_TIMEOUT, &seg_addr1 ); printf( "sc=%d seg_addr=%p\n", sc, seg_addr1); sc = rtems_region_get_segment( id, 32, RTEMS_DEFAULT_OPTIONS, RTEMS_NO_TIMEOUT, &seg_addr2 ); printf( "sc=%d seg_addr=%p\n", sc, seg_addr2); sc = rtems_region_return_segment( id, seg_addr1); printf( "sc=%d\n", sc); sc = rtems_region_return_segment( id, seg_addr2); printf( "sc=%d\n", sc); }

3.1 region个数

默认情况下,需要通过一个宏设置内存区域个数CONFIGURE_MAXIMUM_REGIONS,这里定义多少就代表系统最多可以申请多少region

3.2 最小申请大小

内存区域最小分配为64字节,准确来说是大于等于52字节,这样内存区域才能正常工作。否则rtems_region_create返回无效

3.3 获取内存

通过rtems_region_get_segment可以获取region的段,这样就能直接使用region

3.4 释放内存到内存

通过rtems_region_get_segment可以释放region的段,这样下一个程序可以重新分配

3.5 测试程序内存布局

内存布局如下

(gdb) x/14g 0x4000000 0x4000000: 0x0000000004000280 0x0000000000000031 0x4000010: 0x0000000004000060 0x0000000000105d68 0x4000020: 0x0000000000000000 0x0000000000000000 0x4000030: 0x0000000000000030 0x0000000000000030 0x4000040: 0x0000000000105d68 0x0000000000105d68 0x4000050: 0x0000000000000000 0x0000000000000000 0x4000060: 0x0000000000000000 0x0000000000000211

从heap block结构体可以看到,内存分配一切正常

四、总结

通过上述的实验,可以清楚的了解到rtems的region的分配和原理。这里分配固定地址上的内存块,供程序使用

编辑
2025-04-03
记录知识
0

rtems不仅支持malloc,还支持region分配,这两个都是基于heap来分配的,而partition的内存分配是直接通过链表管理的内存池,他不会记录block信息,本文介绍partition分配内存

一、测试代码

复用测试region的代码,稍作修改可以直接测试partition,如下

#define CONFIGURE_MAXIMUM_PARTITIONS 1 static void test_partition_create(void) { rtems_status_code sc; rtems_id id; void *partition_buf1; void *partition_buf2; sc = rtems_partition_create( name, (void* )0x4000000, 128, 32, RTEMS_DEFAULT_ATTRIBUTES, &id ); printf( "sc=%d \n", sc); sc = rtems_partition_get_buffer( id, &partition_buf1); printf( "sc=%d buf1=%p\n", sc, partition_buf1); sc = rtems_partition_get_buffer( id, &partition_buf2); printf( "sc=%d buf2=%p\n", sc, partition_buf2); sc = rtems_partition_return_buffer(id, partition_buf1); printf( "sc=%d \n", sc); sc = rtems_partition_return_buffer(id, partition_buf2); printf( "sc=%d \n", sc); return ; }

二、测试运行

直接测试效果如下:

(gdb) x/14xg 0x4000000 0x4000000: 0x0000000004000020 0x00000000001056d0 0x4000010: 0x0000000000000000 0x0000000000000000 0x4000020: 0x0000000004000040 0x00000000001056d0 0x4000030: 0x0000000000000000 0x0000000000000000 0x4000040: 0x0000000004000060 0x00000000001056d0 0x4000050: 0x0000000000000000 0x0000000000000000 0x4000060: 0x00000000001056d8 0x0000000004000040

可以发现,我申请了128内存,按照32字节划分,那么每块都是0x20大小,如果起始地址是0x4000000,则划分为如下

0x4000000/0x4000020/0x4000040/0x4000060

值得注意的是partition分配的内存,不会带block结构体,也就少了16字节的冗余,这种情况下,也不会参与内存的回收,规整,相当于蛋糕自己随便分,整体效率是最高的。

如果留意这些分区的内存的话,可以发现管理这些内存使用的是16字节的链表。接下来分析代码

三、代码解析

3.1 create

创建分区代码如下

rtems_status_code rtems_partition_create( rtems_name name, void *starting_address, uintptr_t length, size_t buffer_size, rtems_attribute attribute_set, rtems_id *id ) { Partition_Control *the_partition; if ( !rtems_is_name_valid( name ) ) { return RTEMS_INVALID_NAME; } if ( id == NULL ) { return RTEMS_INVALID_ADDRESS; } if ( starting_address == NULL ) { return RTEMS_INVALID_ADDRESS; } if ( length == 0 ) { return RTEMS_INVALID_SIZE; } if ( buffer_size == 0 ) { return RTEMS_INVALID_SIZE; } if ( length < buffer_size ) { return RTEMS_INVALID_SIZE; } /* * Ensure that the buffer size is an integral multiple of the pointer size so * that each buffer begin meets the chain node alignment. */ if ( buffer_size % CPU_SIZEOF_POINTER != 0 ) { return RTEMS_INVALID_SIZE; } if ( buffer_size < sizeof( Chain_Node ) ) return RTEMS_INVALID_SIZE; /* * Ensure that the buffer area starting address is aligned on a pointer * boundary so that each buffer begin meets the chain node alignment. */ if ( (uintptr_t) starting_address % CPU_SIZEOF_POINTER != 0 ) { return RTEMS_INVALID_ADDRESS; } #if defined(RTEMS_MULTIPROCESSING) if ( !_System_state_Is_multiprocessing ) { attribute_set = _Attributes_Clear( attribute_set, RTEMS_GLOBAL ); } #endif the_partition = _Partition_Allocate(); if ( !the_partition ) { _Objects_Allocator_unlock(); return RTEMS_TOO_MANY; } #if defined(RTEMS_MULTIPROCESSING) if ( _Attributes_Is_global( attribute_set ) && !( _Objects_MP_Allocate_and_open( &_Partition_Information, name, the_partition->Object.id, false ) ) ) { _Objects_Free( &_Partition_Information, &the_partition->Object ); _Objects_Allocator_unlock(); return RTEMS_TOO_MANY; } #endif _Partition_Initialize( the_partition, starting_address, length, buffer_size, attribute_set ); *id = _Objects_Open_u32( &_Partition_Information, &the_partition->Object, name ); #if defined(RTEMS_MULTIPROCESSING) if ( _Attributes_Is_global( attribute_set ) ) _Partition_MP_Send_process_packet( PARTITION_MP_ANNOUNCE_CREATE, the_partition->Object.id, name, 0 /* Not used */ ); #endif _Objects_Allocator_unlock(); return RTEMS_SUCCESSFUL; }

这里的重点是

_Partition_Initialize( the_partition, starting_address, length, buffer_size, attribute_set );

可以查到_Partition_Initialize调用的_Chain_Initialize,我们查看_Chain_Initialize的实现如下

void _Chain_Initialize( Chain_Control *the_chain, void *starting_address, size_t number_nodes, size_t node_size ) { size_t count = number_nodes; Chain_Node *head = _Chain_Head( the_chain ); Chain_Node *tail = _Chain_Tail( the_chain ); Chain_Node *current = head; Chain_Node *next = starting_address; _Assert( node_size >= sizeof( *next ) ); head->previous = NULL; while ( count-- ) { current->next = next; next->previous = current; current = next; next = (Chain_Node *) _Addresses_Add_offset( (void *) next, node_size ); } current->next = tail; tail->previous = current; }

可以发现, 这里定义了一个双链表,其中count就是分区的数量,根据分区的数量直接填充链表的成员,链表非常简单,如下:

typedef struct Chain_Node { /** This points to the node after this one on this chain. */ struct Chain_Node *next; /** This points to the node immediate prior to this one on this chain. */ struct Chain_Node *previous; } Chain_Node;

可以发现,和测试运行中的内存布局完全一致。

3.2 get buffer

rtems_partition_get_buffer函数的实现如下:

rtems_status_code rtems_partition_get_buffer( rtems_id id, void **buffer ) { Partition_Control *the_partition; ISR_lock_Context lock_context; void *the_buffer; if ( buffer == NULL ) { return RTEMS_INVALID_ADDRESS; } the_partition = _Partition_Get( id, &lock_context ); if ( the_partition == NULL ) { #if defined(RTEMS_MULTIPROCESSING) return _Partition_MP_Get_buffer( id, buffer ); #else return RTEMS_INVALID_ID; #endif } _Partition_Acquire_critical( the_partition, &lock_context ); the_buffer = _Partition_Allocate_buffer( the_partition ); if ( the_buffer == NULL ) { _Partition_Release( the_partition, &lock_context ); return RTEMS_UNSATISFIED; } the_partition->number_of_used_blocks += 1; _Partition_Release( the_partition, &lock_context ); *buffer = the_buffer; return RTEMS_SUCCESSFUL; }

代码熟悉了就发现非常简单,我们关注_Partition_Allocate_buffer,它调用_Chain_Get_unprotected,然后调用_Chain_Get_first_unprotected

_Chain_Get_first_unprotected的实现如下

static inline Chain_Node *_Chain_Get_first_unprotected( Chain_Control *the_chain ) { Chain_Node *head; Chain_Node *old_first; Chain_Node *new_first; _Assert( !_Chain_Is_empty( the_chain ) ); head = _Chain_Head( the_chain ); old_first = head->next; new_first = old_first->next; head->next = new_first; new_first->previous = head; #if defined(RTEMS_DEBUG) _Chain_Set_off_chain( old_first ); #endif return old_first; }

可以发现,这里get buffer的操作就是取出head,然后通过head拿到next返回,并且将head->next->next返回给head→next,相当于list的del head操作

3.3 return buffer

rtems_partition_return_buffer的实现如下:

rtems_status_code rtems_partition_return_buffer( rtems_id id, void *buffer ) { Partition_Control *the_partition; ISR_lock_Context lock_context; the_partition = _Partition_Get( id, &lock_context ); if ( the_partition == NULL ) { #if defined(RTEMS_MULTIPROCESSING) return _Partition_MP_Return_buffer( id, buffer ); #else return RTEMS_INVALID_ID; #endif } _Partition_Acquire_critical( the_partition, &lock_context ); if ( !_Partition_Is_address_a_buffer_begin( the_partition, buffer ) ) { _Partition_Release( the_partition, &lock_context ); return RTEMS_INVALID_ADDRESS; } _Partition_Free_buffer( the_partition, buffer ); the_partition->number_of_used_blocks -= 1; _Partition_Release( the_partition, &lock_context ); return RTEMS_SUCCESSFUL; }

同样的,我们关注_Partition_Free_buffer,从而关注_Chain_Append_unprotected,其实现如下

static inline void _Chain_Append_unprotected( Chain_Control *the_chain, Chain_Node *the_node ) { Chain_Node *tail; Chain_Node *old_last; _Assert( _Chain_Is_node_off_chain( the_node ) ); tail = _Chain_Tail( the_chain ); old_last = tail->previous; the_node->next = tail; tail->previous = the_node; old_last->next = the_node; the_node->previous = old_last; }

这里就是对双链表做了尾插,list add tail。

四、总结

至此,关于rtems partition 内存的操作介绍完毕了。可以发现,如果不需要碎片管理,则partition的内存分配相当于内存池,简单高效。