Skip to content

Commit 2799a59

Browse files
authored
[storm] Refactor IPC to use circular queue (#153)
This was a rather complex change which was discussed with Henrik Doverhill on Slack a bit. Because our memory allocation tend to degenerate over time, we looked into other ways of handling the IPC than the previous approach (which would incur a memory allocation and deallocation for _every single message_ being posted via the IPC mechanism) The new approach uses a circular queue, which was written in an isolated and reusable way, completely separate from the mailbox code. This means that `mailbox_send` and `mailbox_receive` got a whole lot more readable in the process, as an added bonus. The implementation was greatly helped by the fact that we could unit test the circular queue in isolation, in VS Code on Linux (with full `gdb` debugger support). This was an _incredible_ help in getting this done with a reasonable level of effort. Kernel development has never felt this "easy" before, relatively speaking. I still ran into a bug (in `mailbox_receive`) but it was literally a 10-minute fix. After it was done, the system would boot up correctly again, which felt really nice.
1 parent 435dbe4 commit 2799a59

19 files changed

+734
-173
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
*.orig
88

99
storm_tests/x86/x86_tests
10+
storm_tests/generic/generic_tests
11+
1012
storm/current-arch
1113
storm/include/storm/current-arch
1214
storm/raspberrypi/storm

Rakefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
TARGET_ARCH = ENV['ARCH']
66

77
FOLDERS = if TARGET_ARCH == 'x86'
8-
%i[storm libraries programs servers].freeze
8+
%i[storm libraries programs servers storm_tests].freeze
99
elsif TARGET_ARCH == 'raspberrypi'
1010
# Nothing else has been ported to this arch yet.
1111
[:storm].freeze

libraries/ipc/ipc.c

+3-2
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,9 @@ return_type ipc_service_connection_request(ipc_structure_type *ipc_structure)
176176
message_parameter_type message_parameter;
177177
uint8_t data[100];
178178

179-
// FIXME: Make it possible to specify the size of the mailbox. For now, we just set it to one meg and hope it's enough.
180-
if (system_call_mailbox_create(&ipc_structure->input_mailbox_id, 1 * MB, PROCESS_ID_NONE,
179+
// FIXME: Make it possible to specify the size of the mailbox. For now, we just set it to a
180+
// fixed size and hope it's enough.
181+
if (system_call_mailbox_create(&ipc_structure->input_mailbox_id, 64 * KB, PROCESS_ID_NONE,
181182
CLUSTER_ID_NONE, THREAD_ID_NONE) != STORM_RETURN_SUCCESS)
182183
{
183184
// FIXME: Handle the possible causes of this.

storm/generic/Rakefile

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ def objects
99
avl_rotate.o
1010
avl_update.o
1111
avl_delete.o
12+
circular_queue.o
1213
debug.o
1314
idle.o
1415
init.o

storm/generic/circular_queue.c

+237
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
// Abstract: Circular queue implementation
2+
// Author: Per Lundberg <[email protected]>
3+
//
4+
// © Copyright 1999 chaos development
5+
6+
#include <storm/generic/circular_queue.h>
7+
#include <storm/generic/defines.h>
8+
#include <storm/generic/memory.h>
9+
10+
void circular_queue_initialize(circular_queue_type *queue, int queue_size)
11+
{
12+
queue->head = queue->data;
13+
queue->tail = NULL;
14+
queue->size = queue_size - sizeof(circular_queue_type);
15+
queue->data_end = queue->data + queue->size;
16+
}
17+
18+
bool circular_queue_enqueue(circular_queue_type *queue, void *message, int length)
19+
{
20+
int total_length = length + sizeof(int);
21+
22+
if (queue->head == queue->tail)
23+
{
24+
// The head has reached the tail. There is absolutely no free space in this queue. Indicate
25+
// this to the caller.
26+
return FALSE;
27+
}
28+
29+
if (queue->head > queue->tail)
30+
{
31+
//
32+
// The queue looks roughly like this:
33+
//
34+
// +--------------------+------+------+------+-------------------------+
35+
// | free tail space | msg1 | msg2 | msg3 | free head space |
36+
// +--------------------+------+------+------+-------------------------+
37+
// ^ ^
38+
// tail head
39+
//
40+
// Note: this branch also handles the tail == NULL case. Remember that NULL is just a
41+
// convenient alias for 0 in C pointer arithmetics.
42+
//
43+
int free_space_after_head = queue->data_end -
44+
queue->head;
45+
46+
if (free_space_after_head >= total_length)
47+
{
48+
if (queue->tail == NULL)
49+
{
50+
// This is the first message in the queue; the tail pointer must be initialized.
51+
queue->tail = queue->head;
52+
}
53+
54+
*(int *)queue->head = length;
55+
memory_copy(queue->head + sizeof(int), message, length);
56+
57+
queue->head += total_length;
58+
59+
if (queue->head >= queue->data_end)
60+
{
61+
// All space at the end of the queue has been used up; move the pointer to the
62+
// free tail space.
63+
queue->head = queue->data;
64+
}
65+
66+
return TRUE;
67+
}
68+
else
69+
{
70+
if (queue->tail == NULL)
71+
{
72+
// There is no tail space at all, since there are no messages in the queue. This
73+
// likely means that the message is too large to fit in the head space.
74+
return FALSE;
75+
}
76+
77+
int free_space_before_tail = queue->tail -
78+
queue->data;
79+
80+
if (free_space_before_tail >= total_length)
81+
{
82+
*(int *)queue->data = length;
83+
memory_copy(queue->data + sizeof(int), message, length);
84+
85+
queue->head = queue->data + total_length;
86+
87+
// No need to check if queue->tail is NULL at this stage since it's already been
88+
// asserted a few lines above. This message is never the first message being
89+
// enqueued in this queue.
90+
return TRUE;
91+
}
92+
else
93+
{
94+
// Not enough free tail space.
95+
return FALSE;
96+
}
97+
}
98+
}
99+
else // queue->tail > queue->head
100+
{
101+
//
102+
// The queue looks roughly like this:
103+
//
104+
// +------+ -----+-----------------+------+------+------+
105+
// | msg4 | msg5 | free tail space | msg1 | msg2 | msg3 |
106+
// +------+------------------------+------+------+------+
107+
// ^ ^
108+
// head tail
109+
//
110+
111+
int free_space_between_head_and_tail = queue->tail - queue->head;
112+
113+
if (free_space_between_head_and_tail >= total_length)
114+
{
115+
*(int *)queue->data = length;
116+
memory_copy(queue->data + sizeof(int), message, length);
117+
118+
queue->head += total_length;
119+
120+
// No need to check if queue->tail is NULL at this stage since it's already been
121+
// asserted a few lines above. This message is never the first message being
122+
// enqueued in this queue.
123+
return TRUE;
124+
}
125+
else
126+
{
127+
// Not enough free tail space.
128+
return FALSE;
129+
}
130+
131+
return FALSE;
132+
}
133+
}
134+
135+
void *circular_queue_dequeue(circular_queue_type *queue)
136+
{
137+
if (queue->tail == NULL)
138+
{
139+
// There are no messages in this queue at the moment.
140+
return NULL;
141+
}
142+
143+
int length = *(int *)queue->tail;
144+
void *result = queue->tail + sizeof(int);
145+
146+
if (length > queue->size)
147+
{
148+
// The message in the queue is corrupted; the length of an individual message can never
149+
// exceed the whole queue size. Indicate this to the caller.
150+
return NULL;
151+
}
152+
153+
queue->tail += length + sizeof(int);
154+
155+
if (queue->tail >= queue->data_end)
156+
{
157+
// We have reached the end of the buffer; move the pointer back to the very start of it.
158+
queue->tail = queue->data;
159+
}
160+
161+
if (queue->tail == queue->head)
162+
{
163+
// We have reached the head => there are no more messages waiting in the queue. Update the
164+
// tail pointer accordingly.
165+
queue->tail = NULL;
166+
167+
// We can also reset the head to the beginning of the buffer, to simplify the
168+
// enqueueing logic.
169+
queue->head = queue->data;
170+
}
171+
172+
return result;
173+
}
174+
175+
int circular_queue_peek(circular_queue_type *queue)
176+
{
177+
if (queue->tail == NULL)
178+
{
179+
// There are no messages in this queue at the moment.
180+
return -1;
181+
}
182+
183+
int length = *(int *)queue->tail;
184+
185+
return length;
186+
}
187+
188+
int circular_queue_get_maximum_enqueue_size(circular_queue_type *queue)
189+
{
190+
if (queue->head == queue->tail)
191+
{
192+
// The head has reached the tail. There is absolutely no free space in this queue.
193+
return 0;
194+
}
195+
196+
if (queue->head > queue->tail)
197+
{
198+
//
199+
// The queue looks roughly like this:
200+
//
201+
// +--------------------+------+------+------+-------------------------+
202+
// | free tail space | msg1 | msg2 | msg3 | free head space |
203+
// +--------------------+------+------+------+-------------------------+
204+
// ^ ^
205+
// tail head
206+
//
207+
// Note: this branch also handles the tail == NULL case. Remember that NULL is just a
208+
// convenient alias for 0 in C pointer arithmetics.
209+
//
210+
int free_space_after_head = queue->data_end - queue->head;
211+
int free_space_before_tail = 0;
212+
213+
if (queue->tail != NULL)
214+
{
215+
// There is space before the tail, calculate its size.
216+
free_space_before_tail = queue->tail - queue->data;
217+
}
218+
219+
return MAX_OF_TWO(free_space_after_head, free_space_before_tail);
220+
}
221+
else // queue->tail > queue->head
222+
{
223+
//
224+
// The queue looks roughly like this:
225+
//
226+
// +------+ -----+-----------------+------+------+------+
227+
// | msg4 | msg5 | free tail space | msg1 | msg2 | msg3 |
228+
// +------+------------------------+------+------+------+
229+
// ^ ^
230+
// head tail
231+
//
232+
233+
int free_space_between_head_and_tail = queue->tail - queue->head;
234+
235+
return free_space_between_head_and_tail;
236+
}
237+
}

0 commit comments

Comments
 (0)