当前位置: 首页 > news >正文

RT_Thread内核源码分析(三)——线程

目录

1. 线程结构

2. 线程创建

2.1 静态线程创建

2.2 动态线程创建

2.3 源码分析

2.4 线程内存结构

3. 线程状态

3.1 线程状态分类

 3.2 就绪状态和运行态

 3.3 阻塞/挂起状态

3.3.1 阻塞工况

3.4 关闭状态

3.4.1 线程关闭接口

3.4.2 静态线程关闭

3.4.3 动态线程关闭

3.6 状态切换

4.线程调度

4.1 调度器相关变量

4.2 调度器初始化

4.3 调度器启动

4.4 调度切换

4.5 同优先级线程轮询

4.6 调度器逻辑图

5. 线程接口

5.1 设置钩子接口

5.2 线程新建

5.3 线程启动

5.4 线程阻塞接口

5.4.1 线程固定周期阻塞

5.4.2 线程固定延时阻塞

5.4.3 线程永久阻塞(挂起)

 5.5 线程强制恢复接口

5.6 线程定时器恢复

5.7 线程同优先级轮询

5.8 线程命令接口

5.9 线程关闭接口

5.9.1 静态线程关闭接口

5.9.2 动态线程关闭接口

5.10 线程退出接口

5.11 线程清除接口

5.12 线程查询

6. 系统线程

6.1 空闲线程

6.1.1 空闲线程作用

6.1.2 空闲线程创建和启动

6.1.3 空闲线程入口函数

6.2 软件定时器线程

6.2.1 定时器线程作用

6.2.2 定时器线程创建和启动

6.2.3 定时器入口函数


        本章讲解RT_Thread Nano实时操作系统的核心部分——线程。单核CPU裸核运行时,一般是多中断+单线程,即前后台系统;如实现多线程并发,须搭载操作系统。有的实时操作系统,使用的是任务并发,其实线程和任务在实时操作系统中本质是一样的。

本章基于RT_Thread Nano V3.1.5版本分析

1. 线程结构

        线程通过线程控制块(rt_thread)维护,rt_thread可分为几个部分:内核对象、线程链表节点、线程栈部分、线程状态部分、线程回调函数部分、线程计数器和定时器部分、线程事件部分、线程优先级部分等。声明如下:

struct rt_thread
{/*内核对象部分-STR*/char        name[RT_NAME_MAX];                    /**<线程内核对象名称 */rt_uint8_t  type;                                 /**<线程内核对象类型 */rt_uint8_t  flags;                                /**<线程内核对象标志 */rt_list_t     list;                               /**<线程内核对象链表节点 *//*线程部分-STR*/rt_list_t     tlist;                              /**< 线程链表节点 */void       *sp;                                   /**< 线程栈指针 */void       *entry;                                /**< 线程入口函数 */void       *parameter;                            /**< 线程入口函数的参数*/void       *stack_addr;                           /**< 线程栈首地址 */rt_uint32_t stack_size;                           /**< 线程栈大小 */rt_err_t    error;                                /**< 线程错误码 */rt_uint8_t  stat;                                 /**< 线程状态 */rt_uint8_t  current_priority;                     /**< 线程当前优先级 */rt_uint8_t  init_priority;                        /**< 线程初始化优先级 *//**< 线程优先级位域*/#if RT_THREAD_PRIORITY_MAX > 32 // 优先级个数大于32个时的位域 rt_uint8_t  number;rt_uint8_t  high_mask;#endifrt_uint32_t number_mask;/**< 线程事件*/#if defined(RT_USING_EVENT)rt_uint32_t event_set;rt_uint8_t  event_info;#endif/**< 线程执行周期*/rt_ubase_t  init_tick;                            /**< 线程执行周期*/rt_ubase_t  remaining_tick;                       /**< 线程剩余执行时间片数 */struct rt_timer thread_timer;                     /**< 线程定时器 */void (*cleanup)(struct rt_thread *tid);           /**< 线程清除函数回调 */rt_uint32_t user_data;                            /**< 线程私有数据*/
};
typedef struct rt_thread *rt_thread_t;

内核对象:线程结构继承了系统对象,即系统对象必须在线程控制块起始位置,内核对象在线程建立后会插入到内核对象容器中,可参考《RT_Thread内核源码分析(二)——链表和对象管理》。

线程栈:*sp、*stack_addr、stack_size三个元素用于管理线程栈,每个线程都会分配一块内存,用于存取线程的局部变量和线程切换时的运行环境,各个线程栈相互独立。*sp指向栈当前使用位置(栈顶);*stack_addr指向栈首地址,stack_size表示线程栈空间大小。

        线程栈有2种访问方式:低地址->高地址、高地址->低地址;具体使用哪一种需要与CPU内核设置保持一致。操作系统通过宏定义ARCH_CPU_STACK_GROWS_UPWARD进行设置。

线程回调函数:*entry指向线程回调函数,*parameter为回调函数参数指针。

优先级部分:init_priority表示线程新建时分配的优先级,数值越小表示优先级越高。current_priority表示实际运行时的优先级,大部分情况current_priority等于init_priority,当出现优先级反转现象时,优先级继承机制会运行,current_priority会被改写。比如:高优先级线程H等待低优先级线程L释放资源而被阻塞,线程H出现优先级翻转现象,优先级继承机制将线程L优先级current_priority修改为与线程H一样,当线程H阻塞解除时,线程L优先级current_priority再修改为init_priority。

        线程调度为了快速检测优先级,通过标志字rt_thread_ready_priority_group的bit位来判断就绪线程优先级的分布,如下所示:

        将就绪线程优先级的位域合并到一起,即可得出总的优先级分布矩阵,当总优先级个数小于32时,仅需要32位无符号整数即可表示所有优先级状态,即线程参数number_mask,比如:优先级为10,则number_mask=10000000000B。

        当总优先级个数大于32时(最大不超过256),使用number、high_mask表示,number表示优先级在数组rt_thread_ready_table中的位置,high_mask表示优先级掩码。比如:优先级为60,则number=60/8=7;因60%8=4,则high_mask=10000B;如果线程切换为就绪状态,则将该线程优先级合并到系统总优先级位域,即rt_thread_ready_table[number]|=high_mask

同级线程轮询参数:       

        如果最高优先级线程有多个,这几个相同优先级的线程会轮询执行,线程轮询切换在 SysTick中断中进行,为防止频繁切换,每个线程通过参数init_tick设置轮询保持时间,通过参数remaining_tick监视执行时间;比如:线程A(pThreadA)、线程B(pThreadB)均为最高优先级线程,当线程A执行时间超过pThreadA->init_tick个时间片后,才会由切换为线程B运行。

        具体代码分析参照章节5.7。

 线程定时器部分

        RT_Thread操作系统是通过软件定时器thread_timer进行线程延时阻塞和恢复,定时器回调函数固定为rt_thread_timeout;其功能为恢复线程(阻塞态->就绪态),具体代码分析参照章节5.6。

2. 线程创建

         线程创建有两种接口:动态线程创建、静态线程创建;

        rt_thread_init:静态线程新建,线程栈和线程控制块均为全局变量,内存无法回收利用,但是方便维护与监视。

       rt_thread_create:动态线程新建,线程栈和线程控制块均从系统栈区动态申请,内存可以回收利用。

        注:内存申请和释放涉及的内存管理模块,本章暂不详细说明,后文的分析均依据动态任务创建的方式分析!

2.1 静态线程创建

        通过静态接口创建线程,需先定义好线程控制器和线程栈,示例如下:

 /*****新建静态线程接口******/
rt_err_t rt_thread_init(struct rt_thread *thread,      // 线程控制块指针const char       *name,        // 线程名称void (*entry)(void *parameter),// 线程回调函数指针void             *parameter,   // 线程回调函数的参数指针void             *stack_start, // 线程栈首地址rt_uint32_t       stack_size,  // 线程栈大小rt_uint8_t        priority,    // 线程优先级rt_uint32_t       tick)        // 线程同优先级轮询保持时间/*****新建静态线程举例*****/rt_thread g_ThreadA;
unsigned char cStack[0x400];
int iThreadApara;
void vThreadA(*int para)
{while(1);
}
void main()
{// 新建静态线程threadA,回调函数vThreadA,线程栈大小为16K,优先级为10,同优先级线程轮询保持时间为10个时间片rt_thread_init(&g_ThreadA,"threadA",vThreadA,&iThreadApara,cStack,0x4000,10,10);
}

2.2 动态线程创建

        动态创建线程,无需定义线程控制器和线程栈,二者均通过申请动态内存存储。

 /*新建动态线程接口*/
rt_thread_t rt_thread_create(const char *name,              // 线程名称void (*entry)(void *parameter),// 线程回调函数指针void       *parameter,         // 线程回调函数的参数指针rt_uint32_t stack_size,        // 线程栈大小rt_uint8_t  priority,          // 线程优先级rt_uint32_t tick)              // 线程同优先级轮询保持时间/*****新建动态线程举例*****/int iThreadApara;
void vThreadA(*int para)
{while(1);
}
void main()
{// 新建动态线程threadA,回调函数vThreadA,线程栈大小为16K,优先级为10,同优先级线程轮询保持时间为10个时间片rt_thread_create("threadA",vThreadA,&iThreadApara,0x4000,10,10);
}

2.3 源码分析

        静态线程创建,先对线程的系统对象进行初始化,并挂接到线程对象容器,然后进行线程数据初始化,不进行线程链表挂接,所以新建线程状态为初始化(RT_THREAD_INIT)。

 /*新建静态线程*/
rt_err_t rt_thread_init(struct rt_thread *thread,      // 线程控制块指针const char       *name,        // 线程名称void (*entry)(void *parameter),// 线程回调函数指针void             *parameter,   // 线程回调函数的参数指针void             *stack_start, // 线程栈首地址rt_uint32_t       stack_size,  // 线程栈大小rt_uint8_t        priority,    // 线程优先级rt_uint32_t       tick)        // 线程同优先级轮询保持时间
{/* 断言*/RT_ASSERT(thread != RT_NULL);RT_ASSERT(stack_start != RT_NULL);/*内核对象初始化--初始化线程对象,并挂接到线程对象容器 */rt_object_init((rt_object_t)thread, RT_Object_Class_Thread, name);/*线程数据初始化 */return _rt_thread_init(thread,name,entry,parameter,stack_start,stack_size,         priority,tick);
}

         动态线程创建,先申请线程控制器内存,然后初始化系统对象,并挂接到线程对象容器,再进行线程栈内存申请,最后进行线程数据初始化,不进行线程链表挂接,所以新建线程状态为初始化(RT_THREAD_INIT)。

/*新建动态线程*/
rt_thread_t rt_thread_create(const char *name,              // 线程名称void (*entry)(void *parameter),// 线程回调函数指针void       *parameter,         // 线程回调函数的参数指针rt_uint32_t stack_size,        // 线程栈大小rt_uint8_t  priority,          // 线程优先级rt_uint32_t tick)              // 线程同优先级轮询保持时间{struct rt_thread *thread;void *stack_start;/*动态分配线程控制器内存,并初始化线程对象,挂接至线程容器链表*/thread = (struct rt_thread *)rt_object_allocate(RT_Object_Class_Thread, name);if (thread == RT_NULL) return RT_NULL;/*动态分配线程栈内存*/stack_start = (void *)RT_KERNEL_MALLOC(stack_size);if (stack_start == RT_NULL){/* 分配栈失败,释放线程对象*/rt_object_delete((rt_object_t)thread);return RT_NULL;}
}

         静态创建和动态创建线程均通过调用函数_rt_thread_init进行线程数据初始化,代码分析如下:

/* 线程数据初始化*/
static rt_err_t _rt_thread_init(struct rt_thread *thread,      // 线程控制器指针const char       *name,        // 线程名称void (*entry)(void *parameter),// 线程回调函数指针void             *parameter,   // 线程回调函数参数void             *stack_start, // 线程栈起始位置(最小地址)rt_uint32_t       stack_size,  // 线程栈大小rt_uint8_t        priority,    // 线程优先级rt_uint32_t       tick)        // 线程同优先级轮询保持时间
{/*1. 线程链表节点初始化*/rt_list_init(&(thread->tlist));/*2. 线程回调函数和参数设置*/thread->entry = (void *)entry;thread->parameter = parameter;/*3. 线程栈初始化*/thread->stack_addr = stack_start;// 首地址thread->stack_size = stack_size; // 栈大小/*3.1 栈内容初始化为#号 */rt_memset(thread->stack_addr, '#', thread->stack_size);/*3.2 初始化栈指针SP*/#ifdef ARCH_CPU_STACK_GROWS_UPWARD/*3.3 栈访问方式:低地址->高地址,低地址为栈底*/thread->sp=(void*)rt_hw_stack_init(thread->entry, thread->parameter,(void*)((char*)thread->stack_addr),(void*)rt_thread_exit);#else/*3.4 栈访问方式:高地址->底地址,高地址为栈底*/thread->sp=(void*)rt_hw_stack_init(thread->entry, thread->parameter,(rt_uint8_t*)((char*)thread->stack_addr+thread->stack_size - sizeof(rt_ubase_t)),(void*)rt_thread_exit);#endif/*4. 优先级初始化*/RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX);thread->init_priority    = priority;thread->current_priority = priority;/*4.1 优先级位域初始化*/thread->number_mask = 0;
#if RT_THREAD_PRIORITY_MAX > 32thread->number = 0;thread->high_mask = 0;
#endif/*5. 同优先级线程轮询计数初始化*/thread->init_tick      = tick;thread->remaining_tick = tick;/*6. 线程状态初始化 */thread->error = RT_EOK;thread->stat  = RT_THREAD_INIT;/*7. 初始化清除函数和用户数据*/thread->cleanup   = 0;thread->user_data = 0;/*8. 线程定时器初始化*/rt_timer_init(&(thread->thread_timer), // 定时器控制器thread->name,            // 定时器名称,取线程名称rt_thread_timeout,       // 定时器回调函数,固定为rt_thread_timeoutthread,                  // 定时器回调函数参数,取线程指针0,                       // 定时时间RT_TIMER_FLAG_ONE_SHOT); // 定时器类型:单次定时器/*9.新建线程钩子调用*/RT_OBJECT_HOOK_CALL(rt_thread_inited_hook, (thread));return RT_EOK;
}

2.4 线程内存结构

        以动态线程为例,线程动态创建后,内存结构如下所示:

3. 线程状态

        本节重点讲解线程状态及各个状态之间的切换。

3.1 线程状态分类

        RT_Thread中线程挂起状态与阻塞状态宏定义一致,挂起状态实质为永久阻塞状态。

/*线程状态宏定义*/
#define RT_THREAD_INIT                  0x00               /**< 初始化状态*/
#define RT_THREAD_READY                 0x01               /**< 就绪状态*/
#define RT_THREAD_SUSPEND               0x02               /**< 阻塞状态 */
#define RT_THREAD_RUNNING               0x03               /**< 运行状态 */
#define RT_THREAD_BLOCK                 RT_THREAD_SUSPEND  /**< 挂起状态(与阻塞状态相同)*/
#define RT_THREAD_CLOSE                 0x04               /**< 关闭状态*/
#define RT_THREAD_STAT_MASK             0x0f               /**< 状态掩码 */

 3.2 就绪状态和运行态

        线程创建后,为初始化状态(RT_THREAD_INIT),如果实现运行,必须切至就绪状态。调用线程启动函数rt_thread_startup,线程将被挂接到就绪链表,由初始化状态切换为就绪态,如果线程为最高优先级,且被调度选中执行,该线程由就绪态切换为运行态。

        线程如果为阻塞/挂起态,调用线程恢复函数rt_thread_resume,线程重新挂接到就绪链表,由阻塞/挂起态切换为就绪态,如果线程为最高优先级,且被调度选中执行,该线程为由就绪态切换为运行态。

        线程就绪链表实质为一链表数组,优先级作为数组下标,即每个优先级可以挂接多个线程。

extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];   // 线程就绪链表数组

        线程就绪态切换接口如下所示,具体代码分析参照章节5。

/*线程thread启动(初始化->就绪状态)*/
rt_err_t rt_thread_startup(rt_thread_t thread)
/*阻塞线程thread恢复(阻塞/挂起态->就绪态)*/
rt_err_t rt_thread_resume(rt_thread_t thread)

 3.3 阻塞/挂起状态

        当线程进行延时出让,或者等待消息、信号时,会由运行态切换为阻塞/挂起状态,线程节点将从就绪链表移除,并将线程状态修改为阻塞/挂起态。

        线程如果从阻塞/挂起态恢复为就绪状态,有定时器自动恢复和强制恢复2种方式,本质上是将线程移回就绪链表,并修改为就绪状态。

        (1)延时阻塞恢复:通过线程定时器rt_thread->thread_timer延时恢复就绪。

        (2)强制恢复:通过调用rt_thread_resume接口强制恢复就绪,比如挂起状态要想恢复就绪,就必须通过调用rt_thread_resume实现。

3.3.1 阻塞工况

        线程在如下情况下会出现阻塞/挂起:

(1)线程主动延时出让或挂起,执行如下接口可以实现,具体代码分析参考章节5。

/*线程延时(阻塞),调用周期固定inc_tick,tick记录再次进入该接口时间,阻塞时间不固定,单位为时间片*/
rt_err_t rt_thread_delay_until(rt_tick_t *tick, rt_tick_t inc_tick)
/*线程延时(阻塞)一定时间tick,单位时间片*/
rt_err_t rt_thread_delay(rt_tick_t tick)
/*线程延时(阻塞)一定时间ms,单位毫秒(ms)*/
rt_err_t rt_thread_mdelay(rt_int32_t ms)
/*线程thread永久阻塞,即切换为挂起态*/
rt_err_t rt_thread_suspend(rt_thread_t thread)

注1:rt_thread_delay_until表示两次调用函数的周期为inc_tick,阻塞时间不固定,甚至无阻塞。

注2:如果调用rt_thread_suspend挂起当前运行态线程,挂起后必须立刻启动调度rt_schedule。

(2)内存申请阻塞线程:

        内存申请涉及临界数据防护,可能会阻塞线程。

void *rt_mp_alloc(rt_mp_t mp, rt_int32_t time)

(3)IPC通信阻塞:

/*获取信号,当信号被占用时,线程阻塞,最大阻塞时间为time*/
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time)
/*获取互斥信号,当信号被占用时,线程阻塞,最大阻塞时间为time*/
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
/*接收事件,当没有事件时,线程阻塞,最大阻塞时间为timeout*/
rt_err_t rt_event_recv(rt_event_t event,rt_uint32_t set,rt_uint8_t option,rt_int32_t timeout,rt_uint32_t *recved)
/*发送邮件,当邮箱满时,线程阻塞,最大阻塞时间为timeout*/
rt_err_t rt_mb_send_wait(rt_mailbox_t mb, rt_ubase_t value,rt_int32_t timeout)
/*接收邮件,当邮箱空时,线程阻塞,最大阻塞时间为timeout*/
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout)
/*发送队列成员,当队列满时,线程阻塞,最大阻塞时间为timeout*/
rt_err_t rt_mq_send_wait(rt_mq_t mq,const void *buffer,rt_size_t   size,rt_int32_t timeout)
/*接收队列成员,当队列空时,线程阻塞,最大阻塞时间为timeout*/
rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer,rt_size_t  size,rt_int32_t timeout)
/*等待信号,当没有信号时,线程阻塞,最大阻塞时间为timeout*/
int rt_signal_wait(const rt_sigset_t *set, rt_siginfo_t *si, rt_int32_t timeout)

3.4 关闭状态

        有些线程是临时建立的,完成使命后不再使用,可以切换至关闭状态,如果是动态线程,状态切换后,还需通过空闲线程(tidle)进行内存释放。

3.4.1 线程关闭接口

        线程关闭接口如下所示,具体代码分析参照章节5.9。

/*删除静态线程,即切换至关闭状态*/
rt_err_t rt_thread_detach(rt_thread_t thread)
/*删除动态线程,即切换至关闭状态,线程插入到无效线程链表rt_thread_defunct */
rt_err_t rt_thread_delete(rt_thread_t thread)

3.4.2 静态线程关闭

        (1)将线程从就绪链表移除。

        (2) 如果线程设置了线程清理回调,则调用线程清理。

        (3)将线程定时器从定时器链表移除,将线程定时器基类从定时器容器移除。

        (4)将线程基类从线程容器移除。

        (5)线程状态修改为关闭状态。

3.4.3 动态线程关闭

        (1) 将线程从就绪链表移除。

         (2)如果线程设置了线程清理回调,则调用线程清理。

         (3)将线程定时器从定时器链表移除,将线程定时器基类从定时器容器移除。

         (4)线程状态修改为关闭状态。

         (5)将线程插入到无效线程链表rt_thread_defunct。

        (6)由系统空闲线程统一释放无效线程内存,并从线程容器移除线程。

3.6 状态切换

1.创建线程→初始化态:线程创建后,为初始化状态。

2.初始化态→就绪态:线程启动后,根据优先级将线程连接至就绪链表,等待调度器进行调度。

3.就绪态←→运行态:系统调度器启动后,指针rt_current_thread指向优先级最高的线程控制块,取该线程执行,该线程状态为运行态,同时rt_current_thread指向的原任务切换为就绪态。

4.就绪/运行态←→阻塞/挂起态:正在运行的线程发生阻塞(主动延时出让、读信号量等待)时,会从就绪列表中删除,线程由运行态变成阻塞/挂起态,然后触发线程调度,切换其他线程执行;当该线程阻塞时间到或者调用强制恢复时,线程会重新插入就绪列表,然后触发线程调度,如果该线程为最高优先级,则切为运行态。

5.关闭态:当线程完成使命后,调用线程关闭函数,线程从所关联的链表中移除;动态线程还需要释放内存。

4.线程调度

        RT_Thread操作系统支持抢占式线程调度相同优先级线程轮询执行

4.1 调度器相关变量

        系统调度器涉及的变量如下:

/*1.就绪线程链表(每个优先级一张链表),用于维护就绪线程*/
rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];/*2.无效线程链表,用于连接关闭的动态线程,在空闲线程中统一释放内存*/
rt_list_t rt_thread_defunct;/*3.就绪线程优先级矩阵,用于快速定位有效优先级*/
rt_uint32_t rt_thread_ready_priority_group;// 32个优先级
#if RT_THREAD_PRIORITY_MAX > 32
/* Maximum priority level, 256 */
rt_uint8_t rt_thread_ready_table[32];      // 256个优先级
#endif/*4.中断挂起标志*/ 
extern volatile rt_uint8_t rt_interrupt_nest;/*5.调度器挂起标志*/ 
static rt_int16_t rt_scheduler_lock_nest;/*6.运行态线程*/ 
struct rt_thread *rt_current_thread = RT_NULL;/*7.当前运行态线程优先级*/ 
rt_uint8_t rt_current_priority;

4.2 调度器初始化

        调度器初始化主要对调度器相关变量初始化,接口分析如下:

/*初始化调度器*/
void rt_system_scheduler_init(void)
{register rt_base_t offset;rt_scheduler_lock_nest = 0;RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,("start scheduler: max priority 0x%02x\n", RT_THREAD_PRIORITY_MAX));/*1.就绪链表数组初始化*/for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++){rt_list_init(&rt_thread_priority_table[offset]);}/*2.最高优先级变量初始化*/ rt_current_priority = RT_THREAD_PRIORITY_MAX - 1;/*3.运行态线程初始化*/ rt_current_thread = RT_NULL;/*4.优先级位域初始化 */rt_thread_ready_priority_group = 0;// 最大32个优先级
#if RT_THREAD_PRIORITY_MAX > 32rt_memset(rt_thread_ready_table, 0, sizeof(rt_thread_ready_table));// 最大256个优先级
#endif/*5.无效线程链表初始化 */rt_list_init(&rt_thread_defunct);
}

4.3 调度器启动

        调度器启动之前是不会执行任何线程的,系统线程最好在调度启动之前完成创建和启用。调度器选出当前优先级最高的线程,通过触发PendSv中断进行上下文切换,退出PendSv中断后,将CPU切换至用户线程模式运行该线程。

        CPU切换至用户线程模式后,将在用户线程模式和handle中断模式交替切换,如无故障,将不会再返回handle线程模式;所以调度启动之后的代码不会被执行到。

/**调度器启动 */
void rt_system_scheduler_start(void)
{register struct rt_thread *to_thread;register rt_ubase_t highest_ready_priority;#if RT_THREAD_PRIORITY_MAX > 32register rt_ubase_t number;number = __rt_ffs(rt_thread_ready_priority_group) - 1;highest_ready_priority = (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;
#elsehighest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
#endif/*获取最高优先级的第一个线程*/to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,struct rt_thread,tlist);rt_current_thread = to_thread;/* 切换最高优先级线程执行 */rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp);
}

注:RT_Thread启动分析可以参考《RT_Thread内核源码分析(一)——CM3内核和上下文切换》

4.4 调度切换

        调度切换函数rt_schedule()可以在线程、时间片中断、其他中断中调用,当运行态线程被阻塞或有新线程切换为就绪状态时,就会主动调用rt_schedule()进行线程切换。线程切换最终在PendSv中断中进行,rt_schedule()主要为PendSv中断进行参数传递,并挂起PendSv中断。具体上下文切换汇编代码分析分析参考《RT_Thread内核源码分析(一)——CM3内核和上下文切换》。

        调度器切换场景:

(1)运行态线程被阻塞,线程主动调用rt_schedule()进行调度切换;

(2)挂起/阻塞态线程切换至就绪态,主动调用rt_schedule()进行调度切换;

(3)有新线程新建并启动,线程模式主动调用rt_schedule()进行调度切换;

(4)时间片中断检测到高优先级线程,调用rt_schedule()进行调度切换;

(5)时间片中断检测到同等优先级线程,调用rt_schedule()进行线程轮询执行;

        调度器切换源码分析:

void rt_schedule(void)
{rt_base_t level;struct rt_thread *to_thread;struct rt_thread *from_thread;/* 进入临界区:关中断*/level = rt_hw_interrupt_disable();/1.调度器非闭锁状态/ if (rt_scheduler_lock_nest == 0){/* 1.1 获取线程最高优先级*/register rt_ubase_t highest_ready_priority;#if RT_THREAD_PRIORITY_MAX <= 32highest_ready_priority= __rt_ffs(rt_thread_ready_priority_group)-1;#elseregister rt_ubase_t number;number = __rt_ffs(rt_thread_ready_priority_group) - 1;highest_ready_priority=(number<<3)+__rt_ffs(rt_thread_ready_table[number])- 1;#endif/* 1.2 获取最高优先级线程指针  */to_thread=rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,struct rt_thread, tlist);/* 1.3 最高优先级线程非当前线程,需要进行上线文切换 */if (to_thread != rt_current_thread){/*1.3.1 更新运行态线程优先级*/ rt_current_priority = (rt_uint8_t)highest_ready_priority;/*1.3.2 更新原运行态线程*/ from_thread         = rt_current_thread;/*1.3.3 更新即将切换为运行态的线程*/ rt_current_thread   = to_thread; /*1.3.4 执行调度钩子*/RT_OBJECT_HOOK_CALL(rt_scheduler_hook, (from_thread, to_thread));/* 日志打印 */RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,("[%d]switch to priority#%d ""thread:%.*s(sp:0x%p), ""from thread:%.*s(sp: 0x%p)\n",rt_interrupt_nest, highest_ready_priority,RT_NAME_MAX, to_thread->name, to_thread->sp,RT_NAME_MAX, from_thread->name, from_thread->sp));/* 1.3.5 检测线程栈是否存在溢出现象*/ #ifdef RT_USING_OVERFLOW_CHECK_rt_scheduler_stack_check(to_thread); #endif/* 1.3.6 线程中进行切换(判断中断非屏蔽标志)*/ if (rt_interrupt_nest == 0){/* 设置上下文切换标志,传递切换参数,挂起PendSv中断*/ rt_hw_context_switch((rt_ubase_t)&from_thread->sp,(rt_ubase_t)&to_thread->sp);/* 使能中断*/ rt_hw_interrupt_enable(level);/* PendSv中断在中断使能后执行,进行上下文切换*/return ;}/* 1.3.7 中断中进行切换(判断中断非屏蔽标志)*/ else{RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interrupt\n"));/* 设置上下文切换标志,传递切换参数,挂起PendSv中断*/ rt_hw_context_switch_interrupt((rt_ubase_t)&from_thread->sp, (rt_ubase_t)&to_thread->sp);/* PendSv中断优先级低,将在其他中断退出后执行,进行上下文切换*/}}}/* 退出临界区:开中断*/rt_hw_interrupt_enable(level);
}

4.5 同优先级线程轮询

        SysTick中断为操作系统提供时间片,并进行轮询和抢占式调度。功能如下:

(1)时间片计数,时间片是操作系统的心跳,即最小时间单位。

(2)线程切换:如果就绪链表有高优先级线程,切换至高优先级线程,如果当前线程优先级最高并且同优先级线程有多个,轮询切换至同级优先级线程;为防止频繁同级切换,线程参数thread->remaining_tick用作切换周期,即当前任务执行时间超过thread->remaining_tick个时间片后才会进行同级切换。

(3)遍历硬件定时器,并对时间到的定时器进行回调,对时间到的循环定时器进行重置。

/*1、SysTick中断入口*/
void SysTick_Handler(void)
{/*进入临界*/rt_interrupt_enter();/*时间片执行*/rt_tick_increase();/*退出临界 */rt_interrupt_leave();
}
/*2、时间片执行*/
void rt_tick_increase(void)
{struct rt_thread *thread;/*全局时间片计数器*/++ rt_tick;/*检查时间片 */thread = rt_thread_self();/*执行时间是否到,未到不能进行线程切换*/-- thread->remaining_tick;if (thread->remaining_tick == 0){/*更换当前线程初始化时间片*/thread->remaining_tick = thread->init_tick;/*同级线程切换*/rt_thread_yield();}/*检查硬件定时器*/rt_timer_check();
}

4.6 调度器逻辑图

5. 线程接口

        本节主要进行线程相关接口代码分析:

5.1 设置钩子接口

        用户可根据需求设置相关钩子。

static void (*rt_thread_suspend_hook)(rt_thread_t thread);  // 线程阻塞钩子
static void (*rt_thread_resume_hook) (rt_thread_t thread);  // 线程恢复钩子
static void (*rt_thread_inited_hook) (rt_thread_t thread);  // 线程新建钩子/*线程阻塞钩子设置,钩子函数不得有阻塞*/
void rt_thread_suspend_sethook(void (*hook)(rt_thread_t thread))
{rt_thread_suspend_hook = hook;
}
/*线程恢复钩子设置,钩子函数不得有阻塞*/
void rt_thread_resume_sethook(void (*hook)(rt_thread_t thread))
{rt_thread_resume_hook = hook;
}
/*线程新建钩子设置,钩子函数不得有阻塞*/
void rt_thread_inited_sethook(void (*hook)(rt_thread_t thread))
{rt_thread_inited_hook = hook;
}

5.2 线程新建

        线程新建相关接口_rt_thread_init、rt_thread_init、rt_thread_create在章节2中进行了代码分析,本章不再赘述。

5.3 线程启动

        线程启动前必须已经新建,调用启动后,将被挂接到就绪链表,由初始化状态切换为就绪态。

/*启动线程thread*/
rt_err_t rt_thread_startup(rt_thread_t thread)
{/* 断言 */RT_ASSERT(thread != RT_NULL);RT_ASSERT((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_INIT);RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);/* 1. 优先级设置*/thread->current_priority = thread->init_priority;/* 1.1 优先级位域设置*/
#if RT_THREAD_PRIORITY_MAX > 32/* 优先级总个数大于32,number表示位域数组下标,high_mask对应字节的位域*/thread->number      = thread->current_priority >> 3;            /* 5bit */thread->number_mask = 1L << thread->number;thread->high_mask   = 1L << (thread->current_priority & 0x07);  /* 3bit */
#else/* 优先级总个数不大于32,number_mask 表示优先级位域*/thread->number_mask = 1L << thread->current_priority;
#endifRT_DEBUG_LOG(RT_DEBUG_THREAD, ("startup a thread:%s with priority:%d\n",thread->name, thread->init_priority));/* 2.线程状态设置为阻塞状态*/thread->stat = RT_THREAD_SUSPEND;/* 3.恢复线程,线程挂接到就绪链表,阻塞态->就绪态 */rt_thread_resume(thread);/* 4.当前线程存在,启动线程调度,选取最高优先级线程为运行态线程*/if (rt_thread_self() != RT_NULL){rt_schedule();}return RT_EOK;
}

5.4 线程阻塞接口

5.4.1 线程固定周期阻塞

        该接口用于实现线程周期执行,比如:线程循环每隔10ms进行一次,由于线程代码执行时间不固定,阻塞时间也就不固定,甚至阻塞时间为0。所以使用该接口时,需要先测试线程执行时间,设置参数inc_tick需要留出足够的时间裕度。

        该接口使用到了线程软件定时器,定时器回调函数固定为rt_thread_timeout,代码分析参照5.6.

/*线程延时(阻塞),调用周期为inc_tick,tick记录在次进入时间,阻塞时间不固定*/
rt_err_t rt_thread_delay_until(rt_tick_t *tick, rt_tick_t inc_tick)
{register rt_base_t level;struct rt_thread *thread;RT_ASSERT(tick != RT_NULL);/* 当前线程断言 set to current thread */thread = rt_thread_self();RT_ASSERT(thread != RT_NULL);RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);/* 进入临界区 disable interrupt */level = rt_hw_interrupt_disable();/*1. 与上次执行该接口的时间差未到设定的周期时间,阻塞线程,阻塞时间为:设置周期-(当前时间片-上次执行接口的时间片)*/ if(rt_tick_get()-*tick < inc_tick){/* 计算定时时刻*/ *tick = *tick + inc_tick - rt_tick_get();/* 阻塞线程*/rt_thread_suspend(thread);/* 设置定时器*/rt_timer_control(&(thread->thread_timer), RT_TIMER_CTRL_SET_TIME, tick);/* 启动定时器 */ rt_timer_start(&(thread->thread_timer));/* 退出临界区 */rt_hw_interrupt_enable(level);/* 执行线程调度,切换至其他线程*/rt_schedule();/* 定时时间到,阻塞解除后,容错错误码*/if (thread->error == -RT_ETIMEOUT){thread->error = RT_EOK;}}/*2. 与上次执行该接口的时间差已超设定的周期时间,不阻塞*/ else{rt_hw_interrupt_enable(level);}/*3. 更新接口执行完成时间*/*tick = rt_tick_get();return RT_EOK;
}

5.4.2 线程固定延时阻塞

        rt_thread_delay与rt_thread_mdelay接口功能相同,均为线程固定延时阻塞,二者形参单位不同,rt_thread_delay接口形参tick单位为时间片。rt_thread_mdelay形参单位为毫秒。

        rt_thread_mdelay更方便跨平台使用。

/*线程延时(阻塞)一定时间,单位时间片*/
rt_err_t rt_thread_delay(rt_tick_t tick)
{return rt_thread_sleep(tick);
}/*线程延时(阻塞)一定时间,单位ms*/
rt_err_t rt_thread_mdelay(rt_int32_t ms)
{rt_tick_t tick;// 毫秒数转化为时间片数tick = rt_tick_from_millisecond(ms);return rt_thread_sleep(tick);
}
/*线程延时(阻塞)一定时间,单位时间片*/
rt_err_t rt_thread_sleep(rt_tick_t tick)
{register rt_base_t temp;struct rt_thread *thread;/* 进入临界区 */temp = rt_hw_interrupt_disable();/* 当前线程断言 */thread = rt_current_thread;RT_ASSERT(thread != RT_NULL);RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);/*线程阻塞,从就绪链表移除*/rt_thread_suspend(thread);/*启动线程软件计时器,定时延时为tick */rt_timer_control(&(thread->thread_timer), RT_TIMER_CTRL_SET_TIME, &tick);rt_timer_start(&(thread->thread_timer));/* 退出临界区*/rt_hw_interrupt_enable(temp);/* 启动调度 */rt_schedule();/* 定时时间到,容错误码*/if (thread->error == -RT_ETIMEOUT)thread->error = RT_EOK;return RT_EOK;
}

5.4.3 线程永久阻塞(挂起)

        如果挂起的是当前线程,挂起后,必须执行rt_schedule()进行调度切换。

        线程挂起后,如无定时器恢复,只能通过线程恢复接口(rt_thread_resume)切回就绪态。

 /*挂起就绪线程(挂起态),如果挂起当前线程,挂起后,必须执行rt_schedule()进行调度切换*/
rt_err_t rt_thread_suspend(rt_thread_t thread)
{register rt_base_t temp;/* 断言*/RT_ASSERT(thread != RT_NULL);RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);RT_DEBUG_LOG(RT_DEBUG_THREAD, ("thread suspend:  %s\n", thread->name));if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_READY){RT_DEBUG_LOG(RT_DEBUG_THREAD, ("thread suspend: thread disorder, 0x%2x\n",         thread->stat));return -RT_ERROR;}/* 进入临界区*/temp = rt_hw_interrupt_disable();/* 从就绪链表移除线程*/rt_schedule_remove_thread(thread);/* 更新线程状态为阻塞态*/thread->stat = RT_THREAD_SUSPEND | (thread->stat & ~RT_THREAD_STAT_MASK);/* 停止定时器*/rt_timer_stop(&(thread->thread_timer));/* 退出临界区*/rt_hw_interrupt_enable(temp);/*线程阻塞调用钩子*/RT_OBJECT_HOOK_CALL(rt_thread_suspend_hook, (thread));return RT_EOK;
}

 5.5 线程强制恢复接口

        将线程从阻塞链表切换到就绪链表(阻塞态->就绪态) ,与线程定时器回调恢复接口(rt_thread_timeout)不同,该函数为手动强制切换,对于挂起的线程只能通过该接口恢复,该函数执行成功后一般需要执行调度切换。

rt_err_t rt_thread_resume(rt_thread_t thread)
{register rt_base_t temp;/* 断言thread check */RT_ASSERT(thread != RT_NULL);RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);RT_DEBUG_LOG(RT_DEBUG_THREAD, ("thread resume:  %s\n", thread->name));/*非阻塞状态*/if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_SUSPEND){RT_DEBUG_LOG(RT_DEBUG_THREAD, ("thread resume: thread disorder, %d\n",thread->stat));return -RT_ERROR;}/* 进入临界区*/temp = rt_hw_interrupt_disable();/* 从阻塞链表移除线程*/rt_list_remove(&(thread->tlist));/* 停止定时器*/rt_timer_stop(&thread->thread_timer);/* 退出临界区*/rt_hw_interrupt_enable(temp);/* 将线程插入到就绪链表*/rt_schedule_insert_thread(thread);/* 线程恢复钩子函数执行*/RT_OBJECT_HOOK_CALL(rt_thread_resume_hook, (thread));return RT_EOK;
}

5.6 线程定时器恢复

        从章节5.4中看出,线程延时阻塞会启动线程定时器,回调函数为 rt_thread_timeout(void *parameter),在定时器线程中,定时时间到后自动调用,将线程切换到就绪链表,线程状态修改为就绪态(阻塞态->就绪态)。

 /*阻塞时间到,线程恢复*/
void rt_thread_timeout(void *parameter)
{struct rt_thread *thread;thread = (struct rt_thread *)parameter;/*断言*/RT_ASSERT(thread != RT_NULL);RT_ASSERT((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND);RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);/* 超时标志*/thread->error = -RT_ETIMEOUT;/* 从阻塞链表移除线程*/rt_list_remove(&(thread->tlist));/* 插入到就绪链表*/rt_schedule_insert_thread(thread);/* 启动调度*/rt_schedule();
}

5.7 线程同优先级轮询

        章节4.4 中提到,如果最高优先级线程有多个,时间片中断会对这几个线程轮流执行。

/*同优先级级切换*/
rt_err_t rt_thread_yield(void)
{register rt_base_t level;struct rt_thread *thread;/* 进入临界区*/level = rt_hw_interrupt_disable();/* 当前线程*/thread = rt_current_thread;/* 线程状态就绪,并且有同优先级的其他线程*/if ((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_READY &&thread->tlist.next != thread->tlist.prev){/* 将当前线程从就绪链表移除从头部移至尾部*/rt_list_remove(&(thread->tlist));rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),& (thread->tlist));/* 退出临界区*/rt_hw_interrupt_enable(level);/*切换线程*/rt_schedule();return RT_EOK;}/* 退出临界区*/rt_hw_interrupt_enable(level);return RT_EOK;
}

5.8 线程命令接口

        该接口通过下发命令方式进行线程操作,具体命令如下:

(1)启动优先级继承命令,用于互斥信号使用过程出现优先级翻转的情况。
(2)启动线程,等同于章节5.3。
(3)关闭线程,等同于章节5.9。
(4)线程绑定内核,用于多核CPU ,Nano版本中没有该功能。

 /*线程控制,控制命令cmd:
*  RT_THREAD_CTRL_CHANGE_PRIORITY 优先级继承
*  RT_THREAD_CTRL_STARTUP               启动线程;
*  RT_THREAD_CTRL_CLOSE                 关闭线程
*  RT_THREAD_CTRL_BIND_CPU              线程绑定内核.
*/
rt_err_t rt_thread_control(rt_thread_t thread, int cmd, void *arg)
{register rt_base_t temp;/* 线程断言*/RT_ASSERT(thread != RT_NULL);RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);switch (cmd){/*优先级继承功能*/case RT_THREAD_CTRL_CHANGE_PRIORITY:/* 进入临界区*/temp = rt_hw_interrupt_disable();/* 就绪任务*/if ((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_READY){/* 移除就绪线程*/rt_schedule_remove_thread(thread);/* 修改为继承后的优先级*/thread->current_priority = *(rt_uint8_t *)arg;/* 更新优先级位域*/#if RT_THREAD_PRIORITY_MAX > 32thread->number      = thread->current_priority >> 3;            /* 5bit */thread->number_mask = 1 << thread->number;thread->high_mask   = 1 << (thread->current_priority & 0x07);   /* 3bit */#elsethread->number_mask = 1 << thread->current_priority;#endif/* 线程插入至新优先级就绪链表*/rt_schedule_insert_thread(thread);}/*非就绪任务,只更新优先级和优先级位域*/else{thread->current_priority = *(rt_uint8_t *)arg;#if RT_THREAD_PRIORITY_MAX > 32thread->number      = thread->current_priority >> 3;            /* 5bit */thread->number_mask = 1 << thread->number;thread->high_mask   = 1 << (thread->current_priority & 0x07);   /* 3bit */#elsethread->number_mask = 1 << thread->current_priority;#endif}/* 退出临界区*/rt_hw_interrupt_enable(temp);break;case RT_THREAD_CTRL_STARTUP:/*启动线程*/return rt_thread_startup(thread);case RT_THREAD_CTRL_CLOSE:/*关闭线程*/if (rt_object_is_systemobject((rt_object_t)thread) == RT_TRUE)// 静态线程{return rt_thread_detach(thread);}#ifdef RT_USING_HEAPelse{return rt_thread_delete(thread);// 动态线程}#endifdefault:break;}return RT_EOK;
}

5.9 线程关闭接口

        有些线程是临时建立的,完成使命后不再使用,可以进行关闭。

5.9.1 静态线程关闭接口

注:接口rt_thread_detach也可以用于动态线程关闭。

t_err_t rt_thread_detach(rt_thread_t thread)
{rt_base_t lock;/* 线程断言 */RT_ASSERT(thread != RT_NULL);RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);RT_ASSERT(rt_object_is_systemobject((rt_object_t)thread));if ((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_CLOSE)return RT_EOK;/* 非初始化线程,先从就绪链表移除*/if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_INIT){rt_schedule_remove_thread(thread);}/* 线程清除回调,由用户自定义*/_thread_cleanup_execute(thread);/* 释放线程定时器 */rt_timer_detach(&(thread->thread_timer));/* 线程状态修改为关闭态*/thread->stat = RT_THREAD_CLOSE;/* 静态线程:将线程基类从线程容器移除*/if (rt_object_is_systemobject((rt_object_t)thread) == RT_TRUE){rt_object_detach((rt_object_t)thread);}/* 动态线程:将线程插入到无效线程链表*/else{/* 进入临界 */lock = rt_hw_interrupt_disable();/* 插入线程到无效线程链表rt_thread_defunct */rt_list_insert_after(&rt_thread_defunct, &(thread->tlist));/* 进入临界退出 */rt_hw_interrupt_enable(lock);}return RT_EOK;
}

5.9.2 动态线程关闭接口

 /*删除线程*/
rt_err_t rt_thread_delete(rt_thread_t thread)
{rt_base_t lock;/*断言*/RT_ASSERT(thread != RT_NULL);RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);RT_ASSERT(rt_object_is_systemobject((rt_object_t)thread) == RT_FALSE);if ((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_CLOSE)return RT_EOK;/* 非初始化状态线程,从就绪链表移除 */if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_INIT){rt_schedule_remove_thread(thread);}/* 线程清除回调,由用户自定义*/_thread_cleanup_execute(thread);/* 释放线程定时器*/rt_timer_detach(&(thread->thread_timer));/* 进入临界区 */lock = rt_hw_interrupt_disable();/* 设置线程状态为关闭态 */thread->stat = RT_THREAD_CLOSE;/*线程移至阻塞链表rt_thread_defunct */rt_list_insert_after(&rt_thread_defunct, &(thread->tlist));/* 退出临界区*/rt_hw_interrupt_enable(lock);return RT_EOK;
}

5.10 线程退出接口

        与线程关闭不同,线程退出接口指针挂接在CPU的LR寄存器,当线程回调函数退出时自动执行。而线程关闭是个主动执行的过程,线程回调函数不会退出,故执行线程关闭接口不会调用到线程退出。

        线程退出可能出现的情况

(1)程序员故意设计的执行,即线程进行有限次循环后退出。

(2)线程循因环异常退出(break)。

/* 线程退出 */
void rt_thread_exit(void)
{struct rt_thread *thread;register rt_base_t level;/* 当前线程 */thread = rt_current_thread;/* 进入临界区:关中断*/level = rt_hw_interrupt_disable();/* 线程清理*/_thread_cleanup_execute(thread);/* 从就绪链表移除*/rt_schedule_remove_thread(thread);/* 线程状态修改为关闭状态 */thread->stat = RT_THREAD_CLOSE;/* 从线程定时器移除线程条目*/rt_timer_detach(&thread->thread_timer);/* 如果是静态线程,从线程容器移除该基类*/if (rt_object_is_systemobject((rt_object_t)thread) == RT_TRUE){rt_object_detach((rt_object_t)thread);}/*如果是动态线程,将线程插入到失效链表*/else{/* 插入到失效链表 */rt_list_insert_after(&rt_thread_defunct, &(thread->tlist));}/* 切换线程 */rt_schedule();/* 退出临界区:开中断*/rt_hw_interrupt_enable(level);
}

5.11 线程清除接口

        用户根据需求可以设置线程清除函数,设置后,当线程关闭或退出时会自动调用该函数,可以当做线程退出的钩子看待。

/* 线程清理(退出时必须清理) */
static void _thread_cleanup_execute(rt_thread_t thread)
{register rt_base_t level;/* 进入临界区:关中断*/level = rt_hw_interrupt_disable();/* 调用线程清理函数 */if (thread->cleanup != RT_NULL)thread->cleanup(thread);/* 退出临界区:开中断*/rt_hw_interrupt_enable(level);
}

5.12 线程查询

        根据线程名称获取线程句柄,用于遍历和监视线程。

/*定位线程(通过名称)*/
rt_thread_t rt_thread_find(char *name)
{return (rt_thread_t)rt_object_find(name, RT_Object_Class_Thread);
}

6. 系统线程

        一般实时操作系统会自带2个系统线程:空闲线程、定时器线程。

6.1 空闲线程

        空闲线程优先级设置为最低。

6.1.1 空闲线程作用

        1.执行钩子函数:钩子函数由用户定义,可以是一些优先级很低的功能模块,如空闲时间计算监视等,但是和其他钩子函数一样,不得有进入阻塞状态或访问系统临界区的代码。

        2.释放无效的线程:动态线程关闭、退出操作,本质上是将线程插入到无效链表rt_thread_defunct;在此处对动态线的内存释放统一释放,并将线程从线程容器移除。

        3.进入芯片低功耗模式:当无就绪线程,且线程长期处于阻塞状态时,可以从空闲线程进入低功耗模式,此时芯片可以暂时关闭系统心跳。

6.1.2 空闲线程创建和启动

void rt_thread_idle_init(void)
{/* 初始化空闲线程(静态) */rt_thread_init(&idle,                       /*空闲线程句柄*/"tidle",                     /*空闲线程名称*/rt_thread_idle_entry,        /*空闲线程入口函数*/RT_NULL,                     /*空闲线程入口函数参数*/&rt_thread_stack[0],         /*空闲线程栈低地址,全局数组*/sizeof(rt_thread_stack),     /*空闲线程栈大小*/RT_THREAD_PRIORITY_MAX - 1,  /*空闲线程优先级,最低优先级*/32);                         /*空闲线程定时器线程统计轮询周期 32个时间片*//*启动线程*/rt_thread_startup(&idle);
}

6.1.3 空闲线程入口函数

static void rt_thread_idle_entry(void *parameter)
{while (1){/*1.空闲线程钩子函数扫描和执行*/#ifdef RT_USING_IDLE_HOOKrt_size_t i;for (i = 0; i < RT_IDLE_HOOK_LIST_SIZE; i++){if (idle_hook_list[i] != RT_NULL){idle_hook_list[i]();}}#endif/*2.释放无效线程的内存*/rt_thread_idle_excute();/*3.低功耗模块*/
#ifdef RT_USING_PMrt_system_power_manager();
#endif}
}

6.2 软件定时器线程

        为保证定时器时效性,定时器线程优先级一般设置为最高。

6.2.1 定时器线程作用

        1、实现线程延时阻塞:线程延时阻塞是通过线程控制块定时器(rt_thread->thread_timer)实现,回调函数固定为rt_thread_timeout(void *parameter),其作用阻塞间到后,将线程移至就绪链表,并将线程状态修改为就绪状态。

        2、管理软件定时器:用户可以自定义软件定时器,定时器单位为时间片,定时时间到,执行回调函数;对循环定时器,执行完回调函数后,重新启动。

6.2.2 定时器线程创建和启动

// 定时器进程
void rt_system_timer_thread_init(void)
{#ifdef RT_USING_TIMER_SOFTint i;/*1、初始化软件定时器链表 */for (i = 0;i < sizeof(rt_soft_timer_list) / sizeof(rt_soft_timer_list[0]); i++){rt_list_init(rt_soft_timer_list + i);}/*2、初始化软件定时器线程(静态)*/rt_thread_init(&timer_thread,             /* 定时器线程句柄*/"timer",                   /* 定时器线程名称*/rt_thread_timer_entry,     /* 定时器线程入口函数*/RT_NULL,                   /* 定时器线程入口函数参数,无参数*/&timer_thread_stack[0],    /* 定时器线程栈低地址*/sizeof(timer_thread_stack),/* 定时器线程栈大小,默认为512B*/RT_TIMER_THREAD_PRIO,      /* 定时器线程优先级,此处为0,优先级最高*/10                         /* 定时器线程统计轮询周期 10个时间片*/);/*3、启动线程 */rt_thread_startup(&timer_thread);#endif
}

6.2.3 定时器入口函数

static void rt_thread_timer_entry(void *parameter)
{rt_tick_t next_timeout;while (1){/*1.获取下一个定时时刻*/next_timeout = rt_timer_list_next_timeout(rt_soft_timer_list);/*2.无有效定时器运行*/if (next_timeout == RT_TICK_MAX){/*2.1 没有软件定时器,定时器线程阻塞,直至有定时器启动后才解除阻塞*/rt_thread_suspend(rt_thread_self());/*2.2 申请调度*/rt_schedule();}/*3.有有效定时器运行*/else{rt_tick_t current_tick;/* 3.1 取当前时间片*/current_tick = rt_tick_get();/* 3.2 定时时刻未到*/if((next_timeout - current_tick) < RT_TICK_MAX / 2){/* 3.2.1 定时器线程延时(阻塞),阻塞时间为next_timeout - current_tick*/next_timeout = next_timeout - current_tick;rt_thread_delay(next_timeout);}}/*4. 检查软件定时器4.1 遍历软件定时器4.2 有定时器时间到,执行其回调函数4.3 对循环定时器,执行其回调函数后,重新启动*/rt_soft_timer_check();}
}
#endif

相关文章:

RT_Thread内核源码分析(三)——线程

目录 1. 线程结构 2. 线程创建 2.1 静态线程创建 2.2 动态线程创建 2.3 源码分析 2.4 线程内存结构 3. 线程状态 3.1 线程状态分类 3.2 就绪状态和运行态 3.3 阻塞/挂起状态 3.3.1 阻塞工况 3.4 关闭状态 3.4.1 线程关闭接口 3.4.2 静态线程关闭 3.4.3 动态线程关…...

淘宝 NPM 镜像源

npm i vant/weapp -S --production npm config set registry https://registry.npmmirror.com 要在淘宝 NPM 镜像站下载项目或依赖&#xff0c;你可以按照以下步骤操作&#xff1a; 1. 设置淘宝 NPM 镜像源 首先&#xff0c;你需要设置淘宝 NPM 镜像源以加速下载。可以通过…...

机器学习—学习曲线

学习曲线是帮助理解学习算法如何工作的一种方法&#xff0c;作为它所拥有的经验的函数。 绘制一个符合二阶模型的学习曲线&#xff0c;多项式或二次函数&#xff0c;画出交叉验证错误Jcv&#xff0c;以及Jtrain训练错误&#xff0c;所以在这个曲线中&#xff0c;横轴将是Mtrai…...

Rust 智能指针

Rust 智能指针 引言 Rust 是一种系统编程语言,以其内存安全性、并发性和高性能而闻名。Rust 的核心特性之一是其独特的所有权模型,它确保了内存安全,同时避免了垃圾收集。在 Rust 中,智能指针是一种特殊的数据结构,它们不仅存储数据,还负责管理数据的生命周期。智能指针…...

云原生周刊:Kubernetes v1.32 要来了

开源项目推荐 Woodpecker Woodpecker 是一款轻量级且功能强大的 CI/CD 引擎&#xff0c;以其高度可扩展性和易用性著称。它支持多种版本控制系统与编程语言&#xff0c;能够灵活适配不同开发流程&#xff0c;帮助团队实现高效的持续集成与交付。无论是个人项目还是大型团队&a…...

Easyexcel(3-文件导出)

相关文章链接 Easyexcel&#xff08;1-注解使用&#xff09;Easyexcel&#xff08;2-文件读取&#xff09;Easyexcel&#xff08;3-文件导出&#xff09; 响应头设置 通过设置文件导出的响应头&#xff0c;可以自定义文件导出的名字信息等 //编码格式为UTF-8 response.setC…...

php:使用socket函数创建WebSocket服务

一、前言 闲来无事&#xff0c;最近捣鼓了下websocket&#xff0c;但是不希望安装第三方类库&#xff0c;所以打算用socket基础函数创建个服务。 二、构建websocket服务端 <?phpclass SocketService {// 默认的监听地址和端口private $address 0.0.0.0;private $port 8…...

ubuntu20.04中编译安装gcc 9.2.0

ubuntu20.04中编译安装gcc 9.2.0,步骤如下&#xff1a; #install compile dependence libraries 1&#xff1a;$ sudo apt install libgmp-dev libisl-dev libmpc-dev libmpfr-dev # install gcc 9.2.0 # download source code 2&#xff1a;$ wget http://ftp.gnu.org/gn…...

ssm158企业人事管理系统的设计与实现+jsp(论文+源码)_kaic

设计题目&#xff1a;企业人事管理系统的设计与实现 摘 要 进入信息时代以来&#xff0c;很多数据都需要配套软件协助处理&#xff0c;这样可以解决传统方式带来的管理困扰。比如耗时长&#xff0c;成本高&#xff0c;维护数据困难&#xff0c;数据易丢失等缺点。本次使用数据…...

OceanBase数据库产品与工具介绍

OceanBase&#xff1a;蚂蚁集团自主研发的分布式关系数据库 1、什么是 OceanBase&#xff1f; OceanBase 是由蚂蚁集团完全自主研发的企业级分布式关系数据库&#xff0c;始创于 2010 年。它具有以下核心特点&#xff1a; 数据强一致性&#xff1a;在分布式架构下确保数据强…...

PakePlus将任何网页免费打包为mac/windows跨平台软件程序教程,只需要一个Github token就可以了

PakePlus是一个开源免费的软件&#xff0c;支持 Mac、Windows 和 Linux&#xff0c;很快也将支持 Android 和 iOS。无需在本地安装复杂的依赖环境&#xff0c;只需要一个Github Token就可以了。查看 README 以获取热门包和自定义开发信息。欢迎在讨论区分享你的建议。开源地址&…...

Element UI 组件库详解【Vue】

文章目录 一、引言二、安装并使用1. 安装2. 使用 三、常见组件说明1. 基础组件2. 布局组件3. 布局容器4. 选择框组件5. 输入框组件6. 下拉框组件7. 日期选择器8. 上传组件9. 表单组件10. 警告组件11. 提示组件12. 表格组件 一、引言 官方网站&#xff0c;element.eleme.cn El…...

Android kotlin之配置kapt编译器插件

配置项目目录下的gradle/libs.versions.toml文件&#xff0c;添加kapt配置项&#xff1a; 在模块目录下build.gradle.kt中增加 plugins {alias(libs.plugins.android.application)alias(libs.plugins.jetbrains.kotlin.android)// 增加该行alias(libs.plugins.jetbrains.kotl…...

基于 MUSA 的大语言模型推理和服务框架vLLM

1. 引言​ vLLM是一个高性能且内存高效的大语言模型推理和服务框架&#xff0c;也是当前业界使用范围最广的大模型推理框架&#xff0c;截至目前github star数28.4k。该框架性能优秀&#xff0c;而且部署容易&#xff0c;使用CUDA/ROCm提供GPU加速能力。但vLLM目前不支持使用摩…...

k8s 集群安装

安装rockylinux https://www.jianshu.com/p/a5fe20318b8e https://www.cnblogs.com/haoee/p/18290506 配置VirtualBox双网卡 https://www.cnblogs.com/ShineLeBlog/p/17580311.html https://zhuanlan.zhihu.com/p/341328334 https://blog.csdn.net/qq_36544785/article/deta…...

Linux-服务器辨别实体机OR虚拟机

快速命令 ① lscpu(查看CPU信息) lscpu Hypervisor vendor: KVM (超管理器厂商&#xff1a;KVM。虚拟机&#xff1a;KVM / VMware) Virtualization: VT-x&#xff08;虚拟化&#xff1a;VT-x。实体机&#xff1a;VT-x / AMD-V&#xff09; ② systemd-detect-virt(检测当前系…...

堤防安全监测系统方案

一、背景情况 堤防是开发利用水资源和防治水灾害的重要工程措施之一&#xff0c;对防洪、供水、生态、发电、航运等至关重要。我国现有堤防9.8万多座&#xff0c;其中大中型堤防4700多座、小型堤防9.4万座&#xff0c;80%以上修建于上世纪50至70年代。由于堤防管护力量薄弱&am…...

Leetcode 求根节点到叶节点数字之和

使用深度优先搜索 DFS 来做 我提供的代码使用的是 深度优先搜索&#xff08;DFS&#xff0c;Depth-First Search&#xff09; 算法。以下是具体的算法思想和实现步骤的解释&#xff1a; 算法思想 树的路径代表数字&#xff1a; 树中每条从根节点到叶子节点的路径可以看作一个整…...

Git分布式版本控制系统

1. 版本控制系统 版本控制系统主要分为两类&#xff1a;集中式、分布式。 集中式&#xff1a;SVM(工作流程&#xff1a;所有的文件都保存在中央服务器上&#xff0c;每个电脑上只保存了一个副本&#xff0c;当需要修改时&#xff0c;先下载中央服务器上的最新版本文件&#xf…...

数据库审计工具--Yearning 3.1.9普民的使用指南

1 页面登录 登录地址:18000 &#xff08;不要勾选LDAP&#xff09; 2 修改用户密码 3 DML/DDL工单申请及审批 工单申请 根据需要选择【DML/DDL/查询】中的一种进行工单申请 填写工单信息提交SQL检测报错修改sql语句重新进行SQL检测&#xff0c;如检测失败可以进行SQL美化后…...

VMware Workstation 17.6.1

概述 目前 VMware Workstation Pro 发布了最新版 v17.6.1&#xff1a; 本月11号官宣&#xff1a;针对所有人免费提供&#xff0c;包括商业、教育和个人用户。 使用说明 软件安装 获取安装包后&#xff0c;双击默认安装即可&#xff1a; 一路单击下一步按钮&#xff1a; 等待…...

unity 中 RectTransform 的常用几个属性

RectTransform rectTransform this.GetComponent<RectTransform>(); rectTransform this.transform as RectTransform; Vector3 vector1 rectTransform.position; //自身轴心点相对于锚点的位置&#xff08;编译器显示的pos&#xff09; …...

23种设计模式-模板方法(Template Method)设计模式

文章目录 一.什么是模板方法模式&#xff1f;二.模板方法模式的特点三.模板方法模式的结构四.模板方法模式的应用场景五.模板方法模式的优缺点六.模板方法模式的C实现七.模板方法模式的JAVA实现八.代码解析九.总结 类图&#xff1a; 模板方法设计模式类图 一.什么是模板方法模…...

网络安全之国际主流网络安全架构模型

目前&#xff0c;国际主流的网络安全架构模型主要有&#xff1a; ● 信息技术咨询公司Gartner的ASA&#xff08;Adaptive Security Architecture自适应安全架构&#xff09; ● 美国政府资助的非营利研究机构MITRE的ATT&CK&#xff08;Adversarial Tactics Techniques &…...

SpringCloud多机部署,负载均衡-LoadBalance

一.负载均衡 1.1问题描述 //根据应用名称获取服务列表 List<ServiceInstance> instancesdiscoveryClient.getInstances("product-service"); //一个微服务可能有多个实例&#xff0c;获取第一个 EurekaServiceInstance instance(EurekaServiceInstance)insta…...

前端开发调试之 PC 端调试学习笔记

一、引言 在前端开发过程中&#xff0c;调试是至关重要的一个环节。它能帮助我们快速定位代码中的问题&#xff0c;无论是页面布局错乱、交互效果异常还是性能不佳等情况&#xff0c;通过有效的调试手段都可以找到根源并进行修复。而在 PC 端进行调试有着其特定的方法和技巧&am…...

视频流媒体播放器EasyPlayer.js无插件直播流媒体音视频播放器Android端webview全屏调用无效问题

流媒体播放器的核心技术与发展趋势正在不断推动着行业的变革。未来&#xff0c;随着技术的不断进步和应用场景的不断拓展&#xff0c;流媒体播放器将为用户带来更加便捷、高效、个性化的观看体验。同时&#xff0c;流媒体播放器也会成为数字娱乐产业的重要组成部分&#xff0c;…...

使用 cnpm 安装 Electron,才是正确快速的方法

当然&#xff0c;下面是总结的几种安装 Electron 的方法&#xff0c;包括使用 npm 和 cnpm&#xff0c;以及一些常见的问题解决技巧。 ### 1. 使用 npm 安装 Electron #### 步骤 1: 初始化项目 在你的项目目录中初始化一个新的 Node.js 项目&#xff1a; bash npm init -y …...

pytest日志总结

pytest日志分为两类&#xff1a; 一、终端&#xff08;控制台&#xff09;打印的日志 1、指定-s&#xff0c;脚本中print打印出的信息会显示在终端&#xff1b; 2、pytest打印的summary信息&#xff0c;这部分是pytest 的默认输出&#xff08;例如测试结果PASSED, FAILED, S…...

【Ubuntu24.04】使用服务器

目录 0 背景1 将文件传送到服务器1.1 主机上的虚拟机1.2 另一台独立的计算机(包括上面的虚拟机)1.3 远程文件传输1.3.1 scp2 操作服务器2.1 ssh2.2 Termius2.2.1 下载2.2.2 安装2.2.3 使用2.2.4 小结3 总结0 背景 你是一个开发者,有自己的测试环境:一个Linux服务器(假设是…...

Spark 之 Aggregate

Aggregate 参考链接&#xff1a; https://github.com/PZXWHU/SparkSQL-Kernel-Profiling 完整的聚合查询的关键字包括 group by、 cube、 grouping sets 和 rollup 4 种 。 分组语句 group by 后面可以是一个或多个分组表达式&#xff08; groupingExpressions &#xff09;…...

ubuntu没有了有线网络如何修复

今天打开ubuntu之后发现有线网络连接没有了&#xff0c;如下图&#xff0c;此时是修复好之后的&#xff0c;“有线”部分存在&#xff0c;出现问题时是不存在的 此时只需要修改NetworkManager.conf配置文件&#xff0c;将managedfalse更改为managedtrue,保存退出就可以了 sudo…...

2411rust,异步函数

原文 Rust异步工作组很高兴地宣布,在实现在特征中使用异步 fn的目标方面取得了重大进度.将在下周发布稳定的Rust1.75版,会包括特征中支持impl Trait注解和async fn. 稳定化 自从RFC#1522在Rust1.26中稳定下来以来,Rust就允许用户按函数的返回类型(一般叫"RPIT")编…...

解决IDEA报包不存在,但实际存在的问题

前言 最近在把一个亿老项目交割给同事&#xff0c;同事在导入项目运行时遇到IDEA报包不存在&#xff0c;但实际存在的问题&#xff0c;最终通过以下方式解决 现象 在IDEA里启动运行项目&#xff0c;报某个类有问题&#xff0c;引入的包不存在。 点击这个引入的包&#xff0c;可…...

解决Ubuntu18.04及以上版本高分辨率下导致字体过小问题

解决Ubuntu18.04及以上版本高分辨率下导致字体过小问题 Chapter1 解决Ubuntu18.04及以上版本高分辨率下导致字体过小问题Chapter2 windows主机和ubuntu互传文件的4种方法 博文链接&#xff1a;Chapter3 安装好VMware tools后无法复制粘贴文本、拖拽文件的解决办法 Chapter1 解…...

SpringBoot与MongoDB深度整合及应用案例

SpringBoot与MongoDB深度整合及应用案例 在当今快速发展的软件开发领域&#xff0c;NoSQL数据库因其灵活性和可扩展性而变得越来越流行。MongoDB&#xff0c;作为一款领先的NoSQL数据库&#xff0c;以其文档导向的存储模型和强大的查询能力脱颖而出。本文将为您提供一个全方位…...

计算机网络(12)介质访问控制

ok通过前面的学习我们已经知道数据链路层提供的服务有帧封装&#xff0c;物理地址&#xff0c;流量控制&#xff0c;错误控制&#xff0c;访问控制。今天就来记录最后的访问控制。 介质访问控制 (MAC)详解 介质访问控制&#xff08;Media Access Control&#xff0c;简称 MAC&…...

Axios 响应拦截器与未登录状态的统一处理

目录 前言1. 响应拦截器的作用与应用场景1.1 什么是响应拦截器&#xff1f;1.2 响应拦截器的应用场景 2. 代码解读&#xff1a;响应拦截器中的未登录处理2.1 原始代码分析 3. 完善未登录处理逻辑3.1 未登录状态的用户体验优化3.2 改进后的代码实现 4. 实践中的场景4.1 登录态的…...

【MySQL系列】深入理解MySQL中的存储、排序字符集

前言 在创建数据库时&#xff0c;我们经常会需要填写数据库的所用字符集、排序规则&#xff0c;字符集和排序规则是两个非常重要的概念&#xff0c;它们决定了数据库如何存储和比较字符串数据。在 MySQL 中&#xff0c;常用的存储字符集有 utf8、utf8mb4&#xff0c;而排序字符…...

【ARM Coresight OpenOCD 系列 5.1 -- OpenOCD 无法识别CPUID 问题: xxx is unrecognized】

请阅读【嵌入式开发学习必备专栏】 文章目录 OpenOCD 无法识别CPUID 问题ARM CPUIDCPUID 特性CPUID 寄存器字段OpenOCD 无法识别CPUID 问题 在使用OpenOCD 进行CPU debug的过程中有时会报出 无法识别CPUID的问题,本文将会介绍如何解决这个问题。首先我们来学习下什么是CPUID,…...

YOLOv11融合针对小目标FFCA-YOPLO中的FEM模块及相关改进思路

YOLOv11v10v8使用教程&#xff1a; YOLOv11入门到入土使用教程 YOLOv11改进汇总贴&#xff1a;YOLOv11及自研模型更新汇总 《FFCA-YOLO for Small Object Detection in Remote Sensing Images》 一、 模块介绍 论文链接&#xff1a;https://ieeexplore.ieee.org/document/10…...

【Docker容器】一、一文了解docker

1、什么是docker&#xff1f; 1.1 docker概念 Docker是一种容器化平台&#xff0c;通过使用容器技术&#xff0c;Docker允许开发人员将应用程序和其依赖项打包到一个独立的、可移植的容器中。每个容器具有自己的文件系统、环境变量和资源隔离&#xff0c;从而使应用程序可以在…...

【团购核销】抖音生活服务商家应用快速接入①——基础工作

文章目录 一、前言二、抖音开放平台&#xff08;服务商平台&#xff09;三、认证服务能力四、第三方生活服务商家应用五、APPID和AppSecret六、申请接口权限七、开发配置八、参考 一、前言 目的&#xff1a;将抖音团购核销的功能集成到我们自己开发的App和小程序中 【团购核销】…...

MyBatis实践:提高持久化层数据处理效率

一、MyBatis简介: 1.简介:https://mybatis.org/mybatis-3/zh/index.html?spmwolai.workspace.0.0.66162306mX2SuC MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下&#xff…...

如何理解Lua 使用虚拟堆栈

虚拟堆栈的基本概念 Lua使用虚拟堆栈来实现Lua和C&#xff08;或其他宿主语言&#xff09;之间的交互。这个虚拟堆栈是一个数据结构&#xff0c;用于存储Lua的值&#xff0c;如数字、字符串、表、函数等。它在Lua状态机&#xff08;lua_State&#xff09;内部维护&#xff0c;为…...

UE5 腿部IK 解决方案 footplacement

UE5系列文章目录 文章目录 UE5系列文章目录前言一、FootPlacement 是什么&#xff1f;二、具体实现 前言 在Unreal Engine 5 (UE5) 中&#xff0c;腿部IK&#xff08;Inverse Kinematics&#xff0c;逆向运动学&#xff09;是一个重要的动画技术&#xff0c;用于实现角色脚部准…...

SMMU软件指南之概述

安全之安全(security)博客目录导读 目录 1. 概述 1.1 开始之前 2. SMMU 的功能 1. 概述 本博客描述了 ARM 系统内存管理单元(SMMUv3)的基本操作及其使用案例,包括: • SMMU 架构概念、术语和操作 • 与 SMMU 功能相关的系统级考虑因素 • 典型 SMMU 使用案例的知识 1…...

Vue_Router权限控制:不同角色显示不同路由

写在前面 在Vue中&#xff0c;Router是一个官方提供的用于处理应用程序路由的插件。它允许我们创建单页应用程序&#xff08;SPA&#xff09;&#xff0c;其中不同的页面和组件可以通过URL进行导航和展示。使我们可以轻松地创SPA&#xff0c;并实现可复用和可组合的组件…...

机器学习4

九、线性回归 1、概念 假设存在多个点&#xff0c;需要使用一条线来保障尽量拟合这些点&#xff0c;寻找这条线的过程就叫回归。 机器学习中一种有监督学习的算法,回归问题主要关注的是因变量(需要预测的值)和一个或多个数值型的自变量(预测变量)之间的关系。 2、损失…...

Linux中系统的延迟任务及定时任务

一、延时任务 at 命令&#xff0c;即用即消 如 at 11&#xff1a;30 rm -rf /mnt/* ctrld运行 &#xff08;过一秒即可执行&#xff09; -v 使用较明显的时间格式&#xff0c;列出at调度中的任务列表 -l 可列出目前系统上面的所有该用户的at调度 -c 可以列出后面接…...