Zephyr内核对象-数据传递之Stack

Creative Commons
本作品采用知识共享署名

本文简要说明Zephyr Stack的使用和实现。

Zephyr内核对象–数据传递对象简介一文中已经大概介绍了Stack的特性,本文将继续说明Stack的使用和实现。

使用

API

void k_stack_init(struct k_stack stack, stack_data_t buffer, u32_t num_entries);
作用:初始化一个stack,stack的内存由使用者分配
stack: 被初始化的stack
buffer: stack使用的内存
num_entries:stack的成员数量
__syscall s32_t k_stack_alloc_init(struct k_stack *stack, u32_t num_entries);
作用:初始化一个stack,stack使用的内存从thread pool分配
stack: 被初始化的stack
num_entries:stack的成员数量
返回值: 0表示初始化成功
int k_stack_cleanup(struct k_stack *stack);
作用:释放k_stack_alloc_init中为stack分配的内存
stack: 操作的stack
返回值: 0表示free成功,如果有pop在等待数据时会返回非0
__syscall int k_stack_push(struct k_stack *stack, stack_data_t data);
作用:压栈
stack:被压栈的stack
data: 要push的数据
返回值:push成功返回0
__syscall int k_stack_pop(struct k_stack stack, stack_data_t data, s32_t timeout);
作用:出栈
stack:被出栈的stack
data: pop的数据
timeout: stack没数据时可等待超时,单位ms。K_NO_WAIT不等待, K_FOREVER一直等
返回值:pop成功返回0

使用说明

可以在ISR中push stack.也可在ISR内pop stack,但不能等待。
stack满后, push将会失败。
stack的数据成员是指针类型stack_data_t的大小和CPU位数对应

初始化

下面两种方式都可以对stack进行初始化
使用函数

1
2
3
4
5
6
#define MAX_ITEMS 10

stack_data_t my_stack_array[MAX_ITEMS];
struct k_stack my_stack;

k_stack_init(&my_stack, my_stack_array, MAX_ITEMS);

使用宏

1
K_STACK_DEFINE(my_stack, MAX_ITEMS);

栈操作

堆栈当中存储的只是数据的指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct my_buffer_type {
int field1;
...
};
struct my_buffer_type my_buffers[MAX_ITEMS];

void producer_thread(int unused1, int unused2, int unused3)
{
//将数据指针入栈
for (int i = 0; i < MAX_ITEMS; i++) {
k_stack_push(&my_stack, (stack_data_t)&my_buffers[i]);
}
}

void consumer_fifo_thread(int unused1, int unused2, int unused3)
{
struct my_buffer_type *new_buffer;
//将数据指针出栈,并进行操作
k_stack_pop(&buffer_stack, (stack_data_t *)&new_buffer, K_FOREVER);
new_buffer->field1 = ...
}

实现

数据结构

stack的数据结构如下,wait_q用于pop时无数据等待数据,lock用于多线程访问stack时进行线程保护

1
2
3
4
5
6
struct k_stack {
_wait_q_t wait_q;
struct k_spinlock lock;
stack_data_t *base, *next, *top;
u8_t flags;
};

base是栈底, next是栈顶,top是堆栈最大的位置,也就是说next不能超过top.如下图
stack

flag只指示该堆栈的内存是否是从线程池中分配的,如果是用k_stack_alloc_init初始化的堆栈,flag就会被设置为下面的值

1
#define K_STACK_FLAG_ALLOC	((u8_t)1)

初始化

初始化起始就是将stack的各个指针设置正确

1
2
3
4
5
6
7
8
9
10
void 	(struct k_stack *stack, stack_data_t *buffer,
u32_t num_entries)
{
z_waitq_init(&stack->wait_q); //初始化wait_q
stack->lock = (struct k_spinlock) {};
stack->next = stack->base = buffer; //base指向buffer开始
stack->top = stack->base + num_entries; //top执行buffer尾部

z_object_init(stack);
}

push

k_stack_push->z_impl_k_stack_push,分析见注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int z_impl_k_stack_push(struct k_stack *stack, stack_data_t data)
{
struct k_thread *first_pending_thread;
k_spinlock_key_t key;

// stack满了,返回
CHECKIF(stack->next == stack->top) {
return -ENOMEM;
}

key = k_spin_lock(&stack->lock);

//查看是否有thread已经在等待stack数据
first_pending_thread = z_unpend_first_thread(&stack->wait_q);

if (first_pending_thread != NULL) {
//如果有thread在等stack数据,将push的数据直接给该thread
//stack指针不做修改
z_ready_thread(first_pending_thread);

z_thread_return_value_set_with_data(first_pending_thread,
0, (void *)data);
z_reschedule(&stack->lock, key);
} else {
//没有thread等stack,将数据放入stack,然后修改next指针
*(stack->next) = data;
stack->next++;
k_spin_unlock(&stack->lock, key);
}

return 0;
}

pop

k_stack_pop->z_impl_k_stack_pop,分析见注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int z_impl_k_stack_pop(struct k_stack *stack, stack_data_t *data, s32_t timeout)
{
k_spinlock_key_t key;
int result;

key = k_spin_lock(&stack->lock);

//检查stack中是否有数据,有就修改next,然后将数据传出
if (likely(stack->next > stack->base)) {
stack->next--;
*data = *(stack->next);
k_spin_unlock(&stack->lock, key);
return 0;
}

//如果pop不等待,又没有数据就直接退出
if (timeout == K_NO_WAIT) {
k_spin_unlock(&stack->lock, key);
return -EBUSY;
}

//将thread加入wait_q等待stack数据
result = z_pend_curr(&stack->lock, key, &stack->wait_q, timeout);
if (result == -EAGAIN) {
//等待超时,则退出
return -EAGAIN;
}

//等待到数据就传出数据
*data = (stack_data_t)_current->base.swap_data;
return 0;
}

Stack从thread pool分配内存

除了传入stack内存外,stack也可以自己从内存池中分配stack内存
k_stack_alloc_init->z_impl_k_stack_alloc_init 分配内存并初始化stack。
k_stack_cleanup 和z_impl_k_stack_alloc_init配对使用,当不使用时使用该api来做k_free。
分析见代码注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
s32_t z_impl_k_stack_alloc_init(struct k_stack *stack, u32_t num_entries)
{
void *buffer;
s32_t ret;

//从线程池中分配内存
buffer = z_thread_malloc(num_entries * sizeof(stack_data_t));
if (buffer != NULL) {
k_stack_init(stack, buffer, num_entries);
//设置flags,指示该stack的buffer是从内存池中分配
stack->flags = K_STACK_FLAG_ALLOC;
ret = (s32_t)0;
} else {
ret = -ENOMEM;
}


int k_stack_cleanup(struct k_stack *stack)
{
//检查wait_q,如果有线程在等待数据不能clean up stack
CHECKIF(z_waitq_head(&stack->wait_q) != NULL) {
return -EAGAIN;
}
//检查有alloc flag,对stack buffer进行释放
if ((stack->flags & K_STACK_FLAG_ALLOC) != (u8_t)0) {
k_free(stack->base);
stack->base = NULL;
stack->flags &= ~K_STACK_FLAG_ALLOC;
}
return 0;
}

参考

https://docs.zephyrproject.org/latest/reference/kernel/data_passing/stacks.html