• zephyr文档部分翻译及学习笔记(二)-Kernel service


    本节描述用于创建、调度和删除指令的独立可执行线程的内核服务。

    线程是一个内核对象,用于处理ISR无法执行的太长或太复杂的应用程序。

    应用程序可以定义任意数量的线程。每个线程都由一个线程id引用,该线程id在派生该线程时被分配。

    一个线程有以下关键属性:

    堆栈区域,是用于线程堆栈的内存区域。堆栈区域的大小可以根据线程处理的实际需要进行调整。存在一些特殊的宏来创建和使用堆栈内存区域。

    线程控制块,用于私有内核记录线程的元数据。这是一个struct k_thread类型的实例。

    入口点函数,在线程启动时调用。最多可以传递3个参数值给这个函数。

    调度优先级,指示内核的调度程序如何为线程分配CPU时间。(见调度。)

    一组线程选项,允许线程在特定环境下接受内核的特殊处理。(见线程选项。)

    启动延迟,它指定内核在启动线程之前应该等待多长时间。

    执行模式,可以是管理员模式或用户模式。默认情况下,线程以管理员模式运行,允许访问特权CPU指令、整个内存地址空间和外设。用户模式减少了线程的特权。这取决于CONFIG_USERSPACE选项的配置

    lifecycle

    Thread Creation

    线程必须先创建,然后才能使用。内核初始化线程控制块以及堆栈部分的一端。线程堆栈的其余部分通常未初始化。

    指定K_NO_WAIT的启动延迟将指示内核立即启动线程执行。或者,通过指定超时值,可以指示内核延迟线程的执行——例如,允许线程使用的设备硬件可用。

    内核允许在线程开始执行之前取消延迟的启动。如果线程已经启动,则取消请求将不起作用。延迟启动被成功取消的线程必须重新派生才能使用。

    Thread Termination

    线程一旦启动,通常会永远执行下去。但是,线程可以通过从入口点函数返回来同步结束执行。这就是所谓的终止。

    终止的线程负责在返回之前释放它可能拥有的任何共享资源(比如互斥和动态分配的内存),因为内核不会自动回收它们。

    内核目前没有对应用程序重新激活一个终止线程的能力做出任何声明。

    在某些情况下,一个线程可能想要休眠,直到另一个线程终止。这可以通过k_thread_join() API来完成。这将阻塞调用线程,直到超时到期、目标线程自退出或目标线程中止(由于k_thread_abort()调用或触发致命错误)。

    Thread aborting

    线程可以通过中止来异步地结束其执行。如果线程触发了致命的错误条件,比如解引用空指针,内核会自动中止线程。

    通过调用k_thread_abort(),一个线程也可以被另一个线程(或它自己)中止。但是,通常最好给线程发出信号,让它优雅地终止自己,而不是中止它。

    与线程终止一样,内核不会收回被中止线程所拥有的共享资源。

    The kernel does not currently make any claims regarding an application’s ability to respawn a thread that aborts.

    Thread Suspension

    如果一个线程被挂起,它将在一段不确定的时间内被阻止执行。k_thread_suspend()函数可以用来挂起任何线程,包括调用线程。挂起已经挂起的线程没有额外的效果。

    一旦挂起,就不能调度线程,直到另一个线程调用k_thread_resume()来删除挂起。

    线程可以使用k_sleep()在指定的时间段内阻止自己执行。但是,这与挂起线程不同,因为当达到时间限制时,休眠线程将自动变为可执行的。

    Thread States

    没有阻碍其执行的因素的线程被认为已经准备好,并且有资格被选择为当前线程。

    有一个或多个因素阻止其执行的线程被视为未准备好,不能被选择为当前线程。

    2. 中断

    zephyr支持中断嵌套。有时,线程在执行特定任务,为了防止被中断打断,在线程中可以使用IRQ lock。可以对中断重复加锁,但是要想再次使能中断,则必须解锁的次数与加锁的次数相同。

    IRQ锁是特定于线程的。如果线程A锁定中断,然后执行一个允许线程B运行的操作(例如给出一个信号量或睡眠N毫秒),一旦线程A被换出,线程的IRQ锁定就不再适用。这意味着中断可以在线程B运行时被处理,除非线程B也使用它自己的IRQ锁锁定了中断。(当内核在两个使用IRQ锁的线程之间切换时,中断是否可以被处理是体系结构特定的。)。当线程A最终再次成为当前线程时,内核重新建立线程A的IRQ锁。这确保了线程A不会被中断,直到它显式地解锁了它的IRQ锁。

    ISR应该快速执行,以确保可预测的系统操作。如果需要耗时的处理,ISR应该将部分或全部处理转移到一个线程,从而恢复内核响应其他中断的能力。

    3.互斥锁

    锁定互斥锁的线程有资格进行优先级继承。这意味着,如果一个优先级更高的线程开始等待互斥锁,内核将临时提高线程的优先级。这允许拥有互斥锁的线程完成它的工作,并以与等待线程相同的优先级执行,从而更快地释放互斥锁。一旦互斥锁被解锁,解锁线程就会将其优先级重置为锁定互斥锁之前的优先级。

    当两个或更多线程等待一个低优先级线程持有的互斥锁时,内核在每次线程开始等待(或放弃等待)时调整拥有互斥锁的线程的优先级。当互斥锁最终被解锁时,解锁线程的优先级将正确地恢复到初始的非提升优先级。

    4.FIFO队列

    FIFO是用链表实现的。可以向FIFO中添加任意大小的数据项。fifo在使用之前必须先初始化。这将其队列设置为空。FIFO数据项必须在4字节边界上进行对齐,因为内核保留一个项的前32位作为队列中下一个数据项的指针。因此,保存N个字节的应用程序数据的数据项需要N+4个字节的内存。如果使用k_fifo_alloc_put()添加数据项,则不存在对齐或保留空间需求,而是从调用线程的资源池临时分配额外的内存。线程或中断函数可以将数据项添加到FIFO中。如果有线程在等待数据项,数据项会直接给到等待线程。如果没有等待线程,数据项会添加到队列中。FIFO中的数据项个数是没有限制的。如果FIFO队列为空,则线程可以选择等待数据项。同一时间可以有多个线程等待一个空FIFO。当数据项被添加时,它会被给到最高优先级的队列中等待时间最久的队列。内核允许中断函数从队列中取走数据,但是如果FIFO为空,中断不能试图等待FIFO。FIFO允许一次操作可以添加多个数据项,前提是这些数据项已经事先链接好。

    FIFO的结构类型和初始化:

    struct k_fifo  my_fifo;
    k_fifo_init(&my_fifo);

    编译时间初始化FIFO,可以调用K_FIFO_DEFINE()

    K_FIFO_DEFINE(my_fifo)

    向FIFO中添加数据项,可以调用k_fifo_put()

     k_fifo_get会从队列中取出头数据项,并将其从队列中删除。如果成功执行,则会返回数据项的地址。否则返回NULL。k_fifo_put将一个数据项加入队列中,k_fifo_put_list将一个链表加入FIFO中。k_fifo_peek_head从FIFO头部读取数据项,但是并不会讲该数据项从FIFO中删除。

    5. LIFO

    LIFO其工作原理是和栈一样的,都是后进先出,但是为什么zephyr还要分别实现LIFO和stack呢?LIFO和stack的区别如下:

     LIFO其实就是用链表实现的可以存储任意大小数据项的栈。主要API接口有:k_lifo_init,k_lifo_put,k_lifo_alloc_put,k_lifo_get

    6. stack

    因为zephyr中栈使用数组实现的,所以可以向栈中添加的数据项是由个数限制的,并且每个数据项必须为32位数据值。

    内核不会检测将数据添加到已达到最大数量的栈的操作。将数据添加到已满的栈,会导致数组溢出,并会导致不可预知的行为。

     栈的声明使用struct k_stack,在使用前必须使用k_stack_init()或k_stack_alloc_init()初始化,使用k_stack_alloc_init()初始化时,是从调用线程的资源池中分配内存给栈。如下:

    #define MAX_ITEMS 10
    
    u32_t my_stack_array[MAX_ITEMS];
    struct k_stack my_stack;
    
    k_stack_init(&my_stack, my_stack_array, MAX_ITEMS);

    压栈操作:

    k_stack_push();

    出栈操作:

    k_stack_pop();

    当使用k_stack_alloc_init()分配栈的内存空间时,若不再使用栈,则调用k_stack_cleanup()释放动态分配的内存空间

    7. message queue消息队列

    消息队列允许线程和中断异步发送和接收固定大小的数据项

    消息队列使用ring buffer实现,接收和发送固定大小的数据项,环形队列中有数据项的最大个数限制。环形队列必须按照2的次幂字节对齐(1,2,4,8...)。为了保证存储在环形队列中的消息是按照边界对齐的,所以数据项的大小必须是N的整数倍。消息队列在使用前必须初始化。这将清空环形队列。

    消息队列用于线程间的异步传输。定义消息队列使用结构struct k_megq。

    主要函数有k_msgq_init(struct k_msgq *q, char *buffer, size_t msg_size, u32_t max_msgs);k_msgq_put(struct k_msgq *q, void *data, s32_t timeout);k_msgq_get(struct k_msgq *q, void * data, s32_t timeout);

  • 相关阅读:
    Java中String的intern方法
    3.7测试复盘
    3.6测试复盘
    LTMU论文解析
    移动机器人相机模型:从相机移动到二维图像
    一步步分析MIPS数据通路(单周期)
    Slam笔记I
    如何理解SiamRPN++?
    如何使用Pytorch迅速写一个Mnist数据分类器
    PySide2的This application failed to start because no Qt platform plugin could be initialized解决方式
  • 原文地址:https://www.cnblogs.com/dwj411024/p/13045815.html
Copyright © 2020-2023  润新知