Skip to content

Commit 5e0d186

Browse files
authored
Add a thread-safe charqueue (#30)
* Add expected spec for charqueue * Implement the spec * Add todo for thread safety * Use spinlock in charqueue operations * In free_charqueue, use spinlock to empty the queue right before freeing * Move memory allocation outside critical section This also makes it safe in case the memory manager is later expanded to have some sort of 'lock mechanism too * We did the todo * Add warning in free_charqueue doc * Address edge case where char was pushed by another process before we checked, resulting in empty new_page while it is required. * Actually initialise the new page properlt
1 parent b75e669 commit 5e0d186

3 files changed

Lines changed: 180 additions & 0 deletions

File tree

kernel/include/kernel/error.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
// Corrupt queue
2525
#define ECORRQ 601
26+
// Empty queue
27+
#define EEMPQ 602
2628

2729
// Cannot execute
2830
#define ENOEXEC 701

kernel/include/utils/charqueue.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#ifndef CHARQUEUE_H
2+
#define CHARQUEUE_H
3+
4+
#include <kernel/memmgt.h>
5+
6+
typedef struct charqueue_page_t charqueue_page_t;
7+
8+
struct charqueue_page_t {
9+
charqueue_page_t* next;
10+
unsigned char data[PAGE_SIZE - sizeof (charqueue_page_t*)];
11+
};
12+
13+
typedef struct {
14+
charqueue_page_t* current_page;
15+
uint64_t offset;
16+
} charqueue_ptr_t;
17+
18+
typedef struct {
19+
charqueue_ptr_t head, tail;
20+
bool lock;
21+
} charqueue;
22+
23+
charqueue* create_charqueue (void);
24+
int free_charqueue (charqueue* queue);
25+
26+
int push_charqueue (charqueue* queue, unsigned char insert);
27+
int pop_charqueue (charqueue* queue, unsigned char* ret);
28+
int peek_charqueue (charqueue* queue, unsigned char* ret);
29+
30+
#endif

kernel/src/utils/charqueue.c

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#include <kclib/string.h>
2+
#include <kernel/error.h>
3+
#include <liballoc/liballoc.h>
4+
#include <utils/charqueue.h>
5+
#include <utils/spinlock.h>
6+
7+
constexpr unsigned int max_offset = PAGE_SIZE - sizeof (charqueue_page_t*) - 1;
8+
9+
/*!
10+
* Check if queue is empty. A queue is empty if it is not initialised, initialised but has no pages
11+
* allocated, or has pages allocated but pointers are identical.
12+
* @param queue queue to check
13+
* @return whether queue is empty
14+
*/
15+
static inline bool is_empty_charqueue (charqueue* queue) {
16+
return queue == nullptr || queue->head.current_page == nullptr ||
17+
(queue->head.current_page == queue->tail.current_page &&
18+
queue->head.offset == queue->tail.offset);
19+
}
20+
21+
/*!
22+
* Create a blank charqueue.
23+
* @return pointer to charqueue, or nullptr if couldn't allocate memory
24+
*/
25+
charqueue* create_charqueue (void) {
26+
charqueue* new_charqueue = kmalloc (sizeof (charqueue));
27+
if (!new_charqueue) return nullptr;
28+
kmemset_explicit ((void*)new_charqueue, 0, sizeof (charqueue));
29+
return new_charqueue;
30+
}
31+
32+
/*!
33+
* Push a single character to a charqueue. Behavior undefined if any input parameter or queue state
34+
* is invalid
35+
* @param queue valid charqueue
36+
* @param insert unsigned char to insert
37+
* @return 0 if successful, else error code
38+
*/
39+
int push_charqueue (charqueue* queue, unsigned char insert) {
40+
charqueue_page_t* new_page = nullptr;
41+
if (queue->tail.current_page == nullptr || queue->tail.offset > max_offset) {
42+
new_page = alloc_vpage (false);
43+
if (new_page == nullptr) return -ENOMEM;
44+
new_page->next = nullptr;
45+
}
46+
47+
uint64_t flags = spinlock_acquire (&queue->lock);
48+
if (queue->tail.current_page == nullptr || queue->tail.offset > max_offset) {
49+
if (new_page == nullptr) {
50+
// TODO: this should be replaced with a quicker allocation function when available
51+
new_page = alloc_vpage (false);
52+
if (new_page == nullptr) {
53+
spinlock_release (&queue->lock, flags);
54+
return -ENOMEM;
55+
}
56+
new_page->next = nullptr;
57+
}
58+
queue->tail.offset = 0;
59+
if (queue->tail.current_page == nullptr) {
60+
queue->head.current_page = queue->tail.current_page = new_page;
61+
queue->head.offset = 0;
62+
} else {
63+
queue->tail.current_page = queue->tail.current_page->next = new_page;
64+
}
65+
new_page = nullptr;
66+
}
67+
68+
queue->tail.current_page->data[queue->tail.offset++] = insert;
69+
spinlock_release (&queue->lock, flags);
70+
71+
if (new_page) free_vpage (new_page);
72+
return 0;
73+
}
74+
75+
/*!
76+
* Pop a single character from a charqueue. Behavior undefined if any input parameter or queue state
77+
* is invalid
78+
* @param queue valid charqueue
79+
* @param ret pointer to unsigned char where value will be stored if successful
80+
* @return 0 if successful, else error code
81+
*/
82+
int pop_charqueue (charqueue* queue, unsigned char* ret) {
83+
uint64_t flags = spinlock_acquire (&queue->lock);
84+
if (is_empty_charqueue (queue)) {
85+
spinlock_release (&queue->lock, flags);
86+
return -EEMPQ;
87+
}
88+
*ret = queue->head.current_page->data[queue->head.offset++];
89+
charqueue_page_t* page_to_free = nullptr;
90+
if (queue->head.offset > max_offset) {
91+
// possible if current page is cleared out; then just reuse the same page
92+
if (queue->head.current_page == queue->tail.current_page) {
93+
queue->head.offset = queue->tail.offset = 0;
94+
spinlock_release (&queue->lock, flags);
95+
return 0;
96+
}
97+
98+
charqueue_page_t* next_page = queue->head.current_page->next;
99+
page_to_free = queue->head.current_page;
100+
queue->head.current_page = next_page;
101+
queue->head.offset = 0;
102+
}
103+
104+
spinlock_release (&queue->lock, flags);
105+
if (page_to_free) free_vpage (page_to_free);
106+
return 0;
107+
}
108+
109+
/*!
110+
* Peek a single character from a charqueue. Behavior undefined if any input parameter or queue
111+
* state is invalid
112+
* @param queue valid charqueue
113+
* @param ret pointer to unsigned char where value will be stored if successful
114+
* @return 0 if successful, else error code
115+
*/
116+
int peek_charqueue (charqueue* queue, unsigned char* ret) {
117+
uint64_t flags = spinlock_acquire (&queue->lock);
118+
if (is_empty_charqueue (queue)) {
119+
spinlock_release (&queue->lock, flags);
120+
return -EEMPQ;
121+
}
122+
*ret = queue->head.current_page->data[queue->head.offset];
123+
spinlock_release (&queue->lock, flags);
124+
return 0;
125+
}
126+
127+
/*!
128+
* Free a charqueue entirely. Behavior undefined if any input parameter or queue state is invalid.
129+
* Behavior undefined for other threads or processes that access the same queue while this function
130+
* is executing or after it has executed.
131+
* @param queue valid charqueue
132+
* @return 0 if successful, else error code
133+
*/
134+
int free_charqueue (charqueue* queue) {
135+
uint64_t flags = spinlock_acquire (&queue->lock);
136+
charqueue_page_t* current_page = queue->head.current_page;
137+
queue->head.current_page = queue->tail.current_page = nullptr;
138+
spinlock_release (&queue->lock, flags);
139+
140+
while (current_page != nullptr) {
141+
charqueue_page_t* page_stat = current_page;
142+
current_page = current_page->next;
143+
free_vpage (page_stat);
144+
}
145+
146+
kfree (queue);
147+
return 0;
148+
}

0 commit comments

Comments
 (0)