Skip to content

Commit 8138476

Browse files
committed
shell: history rewrite to use k_heap instead of ring_buffer
The ring_buffer API was not ideal for this use case, as it is designed for concurrent access, which is not needed here. The new implementation using k_heap is more readable and easier to maintain. There is a slight penalty in memory usage & effectiveness due to the overhead of k_heap, but since this isn't a production-critical path, the trade-off is acceptable. Signed-off-by: Måns Ansgariusson <Mansgariusson@gmail.com>
1 parent 36bc2f3 commit 8138476

File tree

5 files changed

+42
-143
lines changed

5 files changed

+42
-143
lines changed

include/zephyr/shell/shell_history.h

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ extern "C" {
1919

2020

2121
struct shell_history {
22-
struct ring_buf *ring_buf;
22+
struct k_heap *heap;
2323
sys_dlist_t list;
2424
sys_dnode_t *current;
2525
};
@@ -28,18 +28,12 @@ struct shell_history {
2828
* @brief Create shell history instance.
2929
*
3030
* @param _name History instance name.
31-
* @param _size Memory dedicated for shell history.
31+
* @param _size Memory size dedicated for shell history.
3232
*/
33-
#define Z_SHELL_HISTORY_DEFINE(_name, _size) \
34-
static uint8_t __noinit __aligned(sizeof(void *)) \
35-
_name##_ring_buf_data[_size]; \
36-
static struct ring_buf _name##_ring_buf = \
37-
{ \
38-
.size = _size, \
39-
.buffer = _name##_ring_buf_data \
40-
}; \
41-
static struct shell_history _name = { \
42-
.ring_buf = &_name##_ring_buf \
33+
#define Z_SHELL_HISTORY_DEFINE(_name, _size) \
34+
K_HEAP_DEFINE(_name##_heap, _size); \
35+
static struct shell_history _name = { \
36+
.heap = &_name##_heap, \
4337
}
4438

4539

subsys/shell/Kconfig

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,6 @@ config SHELL_HELP_ON_WRONG_ARGUMENT_COUNT
229229
config SHELL_HISTORY
230230
bool "History in shell"
231231
default y if !SHELL_MINIMAL
232-
select RING_BUFFER
233232
help
234233
Enable commands history. History can be accessed using up and down
235234
arrows or Ctrl+n and Ctrl+p meta keys.

subsys/shell/shell_history.c

Lines changed: 34 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,11 @@
1212
* new string. When new item is added then first it is compared if it is not
1313
* the same as the last one (then it is not stored). If there is no room in the
1414
* buffer to store the new item, oldest one is removed until there is a room.
15-
*
16-
* Items are allocated and stored in the ring buffer. Items then a linked in
17-
* the list.
18-
*
19-
* Because stored strings must be copied and compared, it is more convenient to
20-
* store them in the ring buffer in a way that they are not split into two
21-
* chunks (when ring buffer wraps). To ensure that item is in a single chunk,
22-
* item includes padding. If continues area for new item cannot be allocated
23-
* then allocated space is increased by the padding.
24-
*
25-
* If item does not fit at the end of the ring buffer padding is added: *
26-
* +-----------+----------------+-----------------------------------+---------+
27-
* | header | "history item" | | padding |
28-
* | padding | | | |
29-
* +-----------+----------------+-----------------------------------+---------+
30-
*
31-
* If item fits in the ring buffer available space then there is no padding:
32-
* +-----------------+------------+----------------+--------------------------+
33-
* | | header | "history item" | |
34-
* | | no padding | | |
35-
* +-----------------+------------+----------------+--------------------------+
36-
*
37-
* As an optimization, the added padding is attributed to the preceding item
38-
* instead of the current item. This way the padding will be freed one item
39-
* sooner.
4015
*/
16+
4117
struct shell_history_item {
4218
sys_dnode_t dnode;
4319
uint16_t len;
44-
uint16_t padding;
4520
char data[];
4621
};
4722

@@ -56,72 +31,40 @@ bool z_shell_history_get(struct shell_history *history, bool up,
5631
struct shell_history_item *h_item; /* history item */
5732
sys_dnode_t *l_item; /* list item */
5833

59-
if (sys_dlist_is_empty(&history->list)) {
60-
*len = 0U;
61-
return false;
62-
}
63-
64-
if (!up) { /* button down */
65-
if (history->current == NULL) {
66-
/* Not in history mode. It is started by up button. */
67-
*len = 0U;
68-
69-
return false;
70-
}
71-
72-
l_item = sys_dlist_peek_prev_no_check(&history->list,
73-
history->current);
74-
} else { /* button up */
34+
if (up) { /* button up */
7535
l_item = (history->current == NULL) ?
76-
sys_dlist_peek_head_not_empty(&history->list) :
36+
sys_dlist_peek_head(&history->list) :
7737
sys_dlist_peek_next_no_check(&history->list, history->current);
38+
} else { /* button down */
39+
l_item = sys_dlist_peek_prev(&history->list, history->current);
40+
}
7841

42+
if (l_item == NULL) {
43+
/* Reached the end of history. */
44+
*len = 0U;
45+
return false;
7946
}
8047

8148
history->current = l_item;
8249
h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode);
83-
84-
if (l_item) {
85-
memcpy(dst, h_item->data, h_item->len);
86-
*len = h_item->len;
87-
dst[*len] = '\0';
88-
return true;
89-
}
90-
91-
*len = 0U;
92-
return false;
93-
}
94-
95-
static void add_to_head(struct shell_history *history,
96-
struct shell_history_item *item,
97-
uint8_t *src, size_t len, uint16_t padding)
98-
{
99-
item->len = len;
100-
item->padding = padding;
101-
memcpy(item->data, src, len);
102-
sys_dlist_prepend(&history->list, &item->dnode);
50+
memcpy(dst, h_item->data, h_item->len);
51+
*len = h_item->len;
52+
dst[*len] = '\0';
53+
return true;
10354
}
10455

10556
/* Returns true if element was removed. */
10657
static bool remove_from_tail(struct shell_history *history)
10758
{
108-
sys_dnode_t *l_item; /* list item */
109-
struct shell_history_item *h_item;
110-
uint32_t total_len;
59+
sys_dnode_t *node;
11160

11261
if (sys_dlist_is_empty(&history->list)) {
11362
return false;
11463
}
11564

116-
l_item = sys_dlist_peek_tail(&history->list);
117-
sys_dlist_remove(l_item);
118-
119-
h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode);
120-
121-
total_len = offsetof(struct shell_history_item, data) +
122-
h_item->len + h_item->padding;
123-
ring_buf_get(history->ring_buf, NULL, total_len);
124-
65+
node = sys_dlist_peek_tail(&history->list);
66+
sys_dlist_remove(node);
67+
k_heap_free(history->heap, CONTAINER_OF(node, struct shell_history_item, dnode));
12568
return true;
12669
}
12770

@@ -134,77 +77,39 @@ void z_shell_history_purge(struct shell_history *history)
13477
void z_shell_history_put(struct shell_history *history, uint8_t *line,
13578
size_t len)
13679
{
137-
sys_dnode_t *l_item; /* list item */
138-
struct shell_history_item *h_item, *h_prev_item;
80+
sys_dnode_t *node;
81+
struct shell_history_item *new, *h_prev_item;
13982
uint32_t total_len = len + offsetof(struct shell_history_item, data);
140-
uint32_t claim_len;
141-
uint32_t claim2_len;
142-
uint16_t padding = (~total_len + 1) & (sizeof(void *) - 1);
143-
144-
/* align to word. */
145-
total_len += padding;
146-
147-
if (total_len > ring_buf_capacity_get(history->ring_buf)) {
148-
return;
149-
}
15083

15184
z_shell_history_mode_exit(history);
152-
15385
if (len == 0) {
15486
return;
15587
}
15688

157-
l_item = sys_dlist_peek_head(&history->list);
158-
h_prev_item = CONTAINER_OF(l_item, struct shell_history_item, dnode);
89+
node = sys_dlist_peek_head(&history->list);
90+
h_prev_item = CONTAINER_OF(node, struct shell_history_item, dnode);
15991

160-
if (l_item &&
92+
if (node &&
16193
(h_prev_item->len == len) &&
16294
(memcmp(h_prev_item->data, line, len) == 0)) {
16395
/* Same command as before, do not store */
16496
return;
16597
}
16698

167-
do {
168-
if (ring_buf_is_empty(history->ring_buf)) {
169-
/* if history is empty reset ring buffer. Even when
170-
* ring buffer is empty, it is possible that available
171-
* continues memory in worst case equals half of the
172-
* ring buffer capacity. By resetting ring buffer we
173-
* ensure that it is capable to provide continues memory
174-
* of ring buffer capacity length.
175-
*/
176-
ring_buf_reset(history->ring_buf);
177-
}
178-
179-
claim_len = ring_buf_put_claim(history->ring_buf,
180-
(uint8_t **)&h_item, total_len);
181-
/* second allocation may succeed if we were at the end of the
182-
* buffer.
183-
*/
184-
if (claim_len < total_len) {
185-
claim2_len =
186-
ring_buf_put_claim(history->ring_buf,
187-
(uint8_t **)&h_item, total_len);
188-
if (claim2_len == total_len) {
189-
/*
190-
* We may get here only if a previous entry
191-
* exists. Stick the excess padding to it.
192-
*/
193-
h_prev_item->padding += claim_len;
194-
total_len += claim_len;
195-
claim_len = total_len;
196-
}
197-
}
198-
199-
if (claim_len == total_len) {
200-
add_to_head(history, h_item, line, len, padding);
201-
ring_buf_put_finish(history->ring_buf, claim_len);
99+
for (;;) {
100+
new = k_heap_alloc(history->heap, total_len, K_NO_WAIT);
101+
if (new) {
102+
/* Got memory, add new item */
202103
break;
104+
} else if (!remove_from_tail(history)) {
105+
/* Nothing to remove, cannot allocate memory. */
106+
return;
203107
}
108+
}
204109

205-
ring_buf_put_finish(history->ring_buf, 0);
206-
remove_from_tail(history);
207-
} while (1);
110+
new->len = len;
111+
memcpy(new->data, line, len);
112+
sys_dlist_prepend(&history->list, &new->dnode);
208113
}
209114

210115
void z_shell_history_init(struct shell_history *history)

tests/subsys/shell/shell_history/prj.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ CONFIG_SHELL_METAKEYS=n
77
CONFIG_SHELL_HISTORY=y
88
CONFIG_LOG=n
99
CONFIG_ZTEST=y
10+
CONFIG_TEST_EXTRA_STACK_SIZE=1024

tests/subsys/shell/shell_history/src/shell_history_test.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
#include <zephyr/shell/shell_history.h>
1616

17-
#define HIST_BUF_SIZE 160
17+
#define HIST_BUF_SIZE 256
1818
Z_SHELL_HISTORY_DEFINE(history, HIST_BUF_SIZE);
1919

2020
static void init_test_buf(uint8_t *buf, size_t len, uint8_t offset)

0 commit comments

Comments
 (0)