线程(嵌入式学习)
Posted JiaYu学长
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程(嵌入式学习)相关的知识,希望对你有一定的参考价值。
线程知识精简
概念
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
一个进程可以有很多线程,每条线程并行执行不同的任务。
在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。
定义
线程 是轻量级的进程,为了提高系统的性能引入线程
Linux里同样用task_struct来描述一个线程。
线程和进程都参与统一的调度。
在同一个进程中创建的线程共享该进程的地址空间。
线程私有属性
线程ID (TID)
PC(程序计数器)和相关寄存器
堆栈
局部变量
返回地址
错误号 (errno)
信号掩码和优先级
执行状态和属性
进程和线程区别
共性
都为操作系统提供了并发执行能力
不同点
调度和资源
线程是系统调度的最小单位,进程是资源分配的最小单位
地址空间方面:同一个进程创建的多个线程共享进程的资源;进程的地址空间相互独立
通信
线程通信相对简单,只需要通过全局变量可以实现,但是需要考虑临界资源访问的问题;进程通信比较复杂,需要借助进程间的通信机制(借助3g-4g内核空间)
安全性
线程安全性差一些,当进程结束时会导致所有线程退出;进程相对安全
函数
创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:创建线程
参数:thread:线程标识
attr:线程属性, NULL:代表设置默认属性
start_routine:函数名:代表线程函数
arg:用来给前面函数传参
返回值:成功:0
失败:错误码
注意:编译代码时链接线程库,gcc xx.c -lpthread
退出线程
int pthread_exit(void *value_ptr)
功能:用于退出线程的执行
参数:value_ptr:线程退出时返回的值
返回值:成功 : 0
失败:errno
回收线程
int pthread_join(pthread_t thread, void **value_ptr)
功能:用于等待一个指定的线程结束,阻塞函数
参数:thread:创建的线程对象
value_ptr:指针*value_ptr指向线程返回的参数
返回值:成功 : 0
失败:errno
获取线程ID
pthread_t pthread_self(void)
功能:获取当前线程的ID
返回值:获取的线程ID
线程分离
int pthread_detach(pthread_t thead)
功能:让线程分离,线程结束时自动回收线程资源
参数:thead:线程ID
案例
/*通过线程实现通信;主线程循环从终端输入数据,子线程循环将数据输出,当输入“quit”时,程序结束。输入一次输出一次*/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
void *handler(void *a)
printf("输出:");
printf("%s\\n", ((char *)a));
//printf("%lu\\n", pthread_self());
int main(int argc, char const *argv[])
char a[32] = "";
pthread_t tid;
while (1)
scanf("%s", a);
getchar();
if (strcmp(a, "quit") == 0)
break;
if (pthread_create(&tid, NULL, handler, a) != 0)
perror("hahahahahaha");
return -1;
pthread_detach(tid);
printf("退出成功!\\n");
// printf("var:%d\\n", a);
// printf("%lu\\n", tid);
pthread_detach(tid);//將线程分离,当线程结束时系统自动回收线程资源
pthread_join(tid, NULL);
return 0;
线程同步
概念
同步(synchronization)指的是多个任务(线程)按照约定的顺序相互配合完成一件事情
同步机制
通过信号量实现线程间同步。
信号量:由信号量来决定线程是继续运行还是阻塞等待,信号量代表某一类资源,其值表示系统中该资源的数量
信号量是一个受保护的变量,只能通过三种操作来访问:初始化、P操作(申请资源)、V操作(释放资源)
信号量的值为非负整数
特性
P操作
当信号量的值大于0时,可以申请到资源,申请资源后信号量的值减1
当信号量的值等于0时,申请不到资源,函数阻塞
V操作
不阻塞,执行到释放操作,信号量的值加1
函数
int sem_init(sem_t *sem, int pshared, unsigned int value)
功能:初始化信号量
参数:sem:初始化的信号量对象
pshared:信号量共享的范围(0: 线程间使用 非0:1进程间使用)
value:信号量初值
返回值:成功 0
失败 -1
int sem_wait(sem_t *sem)
功能:申请资源 P操作
参数:sem:信号量对象
返回值:成功 0
失败 -1
注:此函数执行过程,当信号量的值大于0时,表示有资源可以用,则继续执行,同时对信号量减1;当信号量的值等于0时,表示没有资源可以使用,函数阻塞
int sem_post(sem_t *sem)
功能:释放资源 V操作
参数:sem:信号量对象
返回值:成功 0
失败 -1
注:释放一次信号量的值加1,函数不阻塞
线程互斥
概念
临界资源:一次仅允许一个进程/线程所使用的资源
临界区:指的是一个访问共享资源的程序片段
互斥:多个线程在访问临界资源时,同一时间只能一个线程访问
互斥锁:通过互斥锁可以实现互斥机制,主要用来保护临界资源,每个临界资源都由一个互斥锁来保护,线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。
函数
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr)
功能:初始化互斥锁
参数:mutex:互斥锁
attr: 互斥锁属性 // NULL表示缺省属性
返回值:成功 0
失败 -1
int pthread_mutex_lock(pthread_mutex_t *mutex)
功能:申请互斥锁
参数:mutex:互斥锁
返回值:成功 0
失败 -1
注:和pthread_mutex_trylock区别:pthread_mutex_lock是阻塞的;pthread_mutex_trylock不阻塞,如果申请不到锁会立刻返回
int pthread_mutex_unlock(pthread_mutex_t *mutex)
功能:释放互斥锁
参数:mutex:互斥锁
返回值:成功 0
失败 -1
int pthread_mutex_destroy(pthread_mutex_t *mutex)
功能:销毁互斥锁
参数:mutex:互斥锁
死锁
死锁
是指两个或两个以上的进程/线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去
死锁产生的四个必要条件
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
注意:当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。
条件变量(和互斥锁搭配使用实现同步)
步骤
1.条件变量的初始化:定义条件变量
2.等待条件变量产生: 阻塞等待条件产生
如果有条件变量产生结束阻塞,同时上锁;
如果没有条件变量产生函数阻塞,同时解锁
3.产生条件变量:
非阻塞函数,一定要pthread_cond_wait先执行,用于唤醒pthread_cond_wait的阻塞
函数
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
功能:初始化条件变量
参数:cond:是一个指向结构pthread_cond_t的指针
restrict attr:是一个指向结构pthread_condattr_t的指针,一般设为NULL
返回值:成功:0 失败:非0
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
功能:等待信号的产生
参数:restrict cond:要等待的条件
restrict mutex:对应的锁
返回值:成功:0,失败:不为0
注:当没有条件产生时函数会阻塞,同时会将锁解开;如果等待到条件产生,函数会结束阻塞同时进行上锁。
int pthread_cond_signal(pthread_cond_t *cond);
功能:给条件变量发送信号
参数:cond:条件变量值
返回值:成功:0,失败:非0
注:必须等待pthread_cond_wait函数先执行,再产生条件才可以
int pthread_cond_destroy(pthread_cond_t *cond);
功能:将条件变量销毁
参数:cond:条件变量值
返回值:成功:0, 失败:非0
pthread_cond_signal函数和pthread_cond_broadcast区别
pthread_cond_broadcast函数相当于是广播,会将所有等待此条件的线程唤醒;pthread_cond_signal只能唤醒单个等待此条件的线程
嵌入式开发基础之任务管理(线程管理)
嵌入式开发基础之任务管理(线程管理)
引言
RTOS 系统的核心是任务管理,而在实时操作系统中,任务和线程在概念上其实是一样的。所以任务管理也可以叫做线程管理。初步上手 RTOS 系统首先必须掌握的也是任务的创建、删除、挂起和恢复等操作,由此可见任务管理的重要性。在日常生活中,我们要完成一个大任务,一般会将它分解成多个简单、容易解决的小问题,小问题逐个被解决,大问题也就随之解决了。在多线程操作系统中,也同样需要开发人员把一个复杂的应用分解成多个小的、可调度的、序列化的程序单元,当合理地划分任务并正确地执行时,这种设计能够让系统满足实时系统的性能及时间的要求。本文中使用的例子,多是参考与FreeRTOS和RT-Thread。
介绍
多任务系统
多任务系统会把一个大问题(应用)“分而治之”,把大问题划分成很多个小问题,逐步的把小问题解决掉,大问题也就随之解决了,这些小问题可以单独的作为一个小任务来处理。这些小任务是并发处理的,注意,并不是说同一时刻一起执行很多个任务,而是由于每个任务执行的时间很短,导致看起来像是同一时刻执行了很多个任务一样。多个任务带来了一个新的问题,
究竟哪个任务先运行,哪个任务后运行呢?完成这个功能的东西在 RTOS 系统中叫做任务调度器。不同的系统其任务调度器的实现方法也不同。线程管理的主要功能是对线程进行管理和调度,系统中总共存在两类线程,分别是系统线程和用户线程,系统线程是由 RTOS内核创建的线程,用户线程是由应用程序创建的线程,这两类线程都会从内核对象容器中分配线程对象,当线程被删除时,也会被从对象容器中删除。
在多任务系统中,根据程序的功能,我们把这个程序主体分割成一个个独立的,无限循环且不能返回的小程序,这个小程序我们称之为任务。每个任务都是独立的,互不干扰的,且具备自身的优先级,它由操作系统调度管理。
高优先级的任务可以打断低优先级任务的运行而取得 CPU 的使用权,这样
就保证了那些紧急任务的运行。这样我们就可以为那些对实时性要求高的任务设置一个很高的优先级,比如自动驾驶中的障碍物检测任务等。高优先级的任务执行完成以后重新把 CPU 的使用权归还给低优先级的任务,这个就是抢占式多任务系统的基本原理。
任务
什么是任务?
在裸机系统中,系统的主体就是 main 函数里面顺序执行的无限循环,这个无限循环里面 CPU 按照顺序完成各种事情。在多任务系统中,我们根据功能的不同,把整个系统分割成一个个独立的且无法返回的函数,这个函数我们称为任务。在使用 RTOS 的时候一个实时应用可以作为一个独立的任务。每个任务都有自己的运行环境,不依赖于系统中其他的任务或者 RTOS 调度器。任何一个时间点只能有一个任务运行,具体运行哪个任务是由 RTOS 调度器来决定的,RTOS 调度器因此就会重复的开启、关闭每个任务。任务不需要了解 RTOS 调度器的具体行为,RTOS 调度器的职责是确保当一个任务开始执行的时候其上下文环境(寄存器值,堆栈内容等)和任务上一次退出的时候相同。为了做到这一点,每个任务都必须有个堆栈,当任务切换的时候将上下文环境保存在堆栈中,这样当任务再次执行的时候就可以从堆栈中取出上下文环境,任务恢复运行。
任务的大概形式具体见如下代码:
void task_entry(void *pvParameters)
/*任务主体,无限循环且不能返回*/
while()
//任务主体代码
任务状态
RTOS的任务状态大致可分为:新建态、运行态、就绪态、阻塞态(有的操作系统也称为挂起态,有的操作系统同时有阻塞态和挂起态)和终止态。
- 新建态
当线程刚开始创建还没开始运行时就处于初始状态;在初始状态下,线程不参与调度。 - 运行态
当一个任务正在运行时,那么就说这个任务处于运行态,处于运行态的任务就是当前正在使用处理器的任务。如果使用的是单核处理器的话那么不管在任何时刻永远都只有一个任务处于运行态。 - 就绪态
处于就绪态的任务是那些已经准备就绪(这些任务没有被阻塞或者挂起),可以运行的任务,但是处于就绪态的任务还没有运行,因为有一个同优先级或者更高优先级的任务正在运行! - 阻塞态(挂起态)
阻塞态也称挂起态,它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。在挂起状态下,线程不参与调度。
如果一个任务当前正在等待某个外部事件的话就说它处于阻塞态,比如说如果某个任务调用了函数 vTaskDelay()的话就会进入阻塞态,直到延时周期完成。任务在等待队列、信号量、事件组、通知或互斥信号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过这个超时时间任务就会退出阻塞态,即使所等待的事件还没有来临! - 终止态
线程运行结束时将处于关闭状态。关闭状态的线程不参与线程的调度。
任务优先级
线程的优先级是表示线程被调度的优先程度。每个线程都具有优先级,线程越重要,赋予的优先级就应越高,线程被调度的可能才会越大。对于 ARM Cortex-M 系列,普遍采用 32 个优先级。最低优先级默认分配给空闲线程使用,用户一般不使用。在系统中,当有比当前线程优先级更高的线程就绪时,当前线程将立刻被换出,高优先级线程抢占处理器运行。优先级数字越低表示任务的优先级越低,0 的优先级最低。RTOS 调度器确保处于就绪态或运行态的高优先级的任务获取处理器使用权,换句话说就是处于就绪态的最高优先级的任务才会运行。处于就绪态的优先级相同的任务就会使用时间片轮转调度器获取运行时间。
任务控制块
的每个任务都有一些属性需要存储,RTOS 把这些属性集合到一起用一个结构体来表示,这个结构体叫做任务控制块(TCB)。任务控制块就相当于任务的身份证,里面存有任务的所有信息,比如任务的栈指针,任务名称,任务的形参等。有了这个任务控制块之后,以后系统对任务的全部操作都可以通过这个任务控制块来实现。
任务堆栈
RTOS 之所以能正确的恢复一个任务的运行就是因为有任务堆栈在保驾护航,(如果是在有进程的操作系统中,保存和恢复现场是通过PCB完成)任务调度器在进行任务切换的时候会将当前任务的现场(CPU 寄存器值等)保存在此任务的任务堆栈中,等到此任务下次运行的时候就会先用堆栈中保存的值来恢复现场,恢复现场以后任务就会接着从上次中断的地方开始运行。创建任务的时候需要给任务指定堆栈。线程栈还用来存放函数中的局部变量:函数中的局部变量从线程栈空间中申请;函数中局部变量初始时从寄存器中分配(ARM 架构),当这个函数再调用另一个函数时,这些局部变量将放入栈中。
使用方法
创建和删除任务
创建线程
一个线程要成为可执行的对象,就必须由操作系统的内核来为它创建一个线程。
一般情况,创建线程都会分为两种方式,分别是动态创建和静态创建。
比如FreeRTOS的线程创建就是分为xTaskCreate( 使用动态的方法创建一个任务)和xTaskCreateStatic( 使用静态的方法创建一个任务)。
动态创建任务的堆栈由系统分配,而静态创建任务的堆栈由用户自己传递。
新创建的任务默认就是就绪态的,如果当前没有比它更高优先级的任务运行那么此任务就会立即进入运行态开始运行,不管在任务调度器启动前还是启动后,都可以创建任务。
我们均以FreeRTOS为例。
动态创建
xTaskCreate()此函数用来动态创建一个任务,任务需要 RAM 来保存与任务有关的状态信息(任务控制块),任务也需要一定的 RAM 来作为任务堆栈。如果使用函数 xTaskCreate()来创建任务的话那么这些所需的 RAM 就会自动的从 FreeRTOS 的堆中分配,因此必须提供内存管理文件,默认我们使用heap_4.c 这个内存管理文件
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
参数:
名称 | 作用 |
---|---|
pxTaskCode | 任务函数。 |
pcName | 任务名字,一般用于追踪和调试,任务名字长度不能超过configMAX_TASK_NAME_LEN。 |
usStackDepth | 任务堆栈大小,注意实际申请到的堆栈是 usStackDepth 的 4 倍。其中空闲任务的任务堆栈大小为 configMINIMAL_STACK_SIZE。 |
pvParameters | 传递给任务函数的参数。 |
uxPriotiry: | 任务优先级,范围 0~ configMAX_PRIORITIES-1。 |
pxCreatedTask | 任务句柄,任务创建成功以后会返回此任务的任务句柄,这个句柄其实就是任务的任务堆栈。此参数就用来保存这个任务句柄。其他 API 函数可能会使用到这个句柄。 |
返回值:
名称 | 含义 |
---|---|
pdPASS | 任务创建成功。 |
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY | 任务创建失败,因为堆内存不足! |
静态创建
静态创建任务使用xTaskCreateStatic(),但是使用此函数创建的任务所需 的 RAM 需 要 用 用 户 来 提 供 。
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )
参数:
名称 | 作用 |
---|---|
pxTaskCode | 任务函数。 |
pcName | 任务名字,一般用于追踪和调试,任务名字长度不能超过。 |
configMAX_TASK_NAME_LEN。 | |
usStackDepth | 任务堆栈大小,由于本函数是静态方法创建任务,所以任务堆栈由用户给出,一般是个数组,此参数就是这个数组的大小。 |
pvParameters | 传递给任务函数的参数。 |
uxPriotiry | 任务优先级,范围 0~ configMAX_PRIORITIES-1。 |
puxStackBuffer | 任务堆栈,一般为数组,数组类型要为 StackType_t 类型。 |
pxTaskBuffe | 任务控制块。 |
返回值:
名称 | 含义 |
---|---|
NULL | 任务创建失败,puxStackBuffer 或 pxTaskBuffer 为 NULL 的时候会导致这个错误的发生。 |
其他值 | 任务创建成功,返回任务的任务句柄。 |
删除线程
被删除了的任务不再存在,也就是说再也不会进入运行态。任务被删除以后就不能再使用此任务的句柄!如果此任务是使用动态方法创建的,也就是使用函数 xTaskCreate()创建的,那么在此任务被删除以后此任务之前申请的堆栈和控制块内存会在空闲任务中被释放掉,因此当调用函数 vTaskDelete()删除任务以后必须给空闲任务一定的运行时间。只有那些由内核分配给任务的内存才会在任务被删除以后自动的释放掉,用户分配给任务的内存需要用户自行释放掉。
vTaskDelete( TaskHandle_t xTaskToDelete )
参数:
xTaskToDelete: 要删除的任务的任务句柄。
挂起和恢复线程
有时候我们需要暂停某个任务的运行,过一段时间以后在重新运行。这个时候要是使用任务删除和重建的方法的话那么任务中变量保存的值肯定丢失了!RTOS 给我们提供了解决这种问题的方法,那就是任务挂起和恢复,当某个任务要停止运行一段时间的话就将这个任务挂起,当要重新运行这个任务的话就恢复这个任务的运行。
下面还以FreeRTOS为例:
挂起线程
在FreeRTOS中,vTaskSuspend()此函数用于将某个任务设置为挂起态,进入挂起态的任务永远都不会进入运行态。退出挂起态的唯一方法就是调用任务恢复函数 vTaskResume()或 xTaskResumeFromISR()。
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
参数:
xTaskToSuspend: 要挂起的任务的任务句柄,创建任务的时候会为每个任务分配一个任务句柄。如果使用函数 xTaskCreate()创建任务的话那么函数的参数pxCreatedTask 就是此任务的任务句柄,如果使用函数 xTaskCreateStatic()创建任务的话那么函数的返回值就是此任务的任务句柄。也可以通过函数 xTaskGetHandle()来根据任务名字来获取某个任务的任务句柄。注意!如果参数为 NULL 的话表示挂起任务自己。
恢复线程
在FreeRTOS中,vTaskResume()此函数用于将某一个任务从挂起态恢复到就绪态。
void vTaskResume( TaskHandle_t xTaskToResume)
参数:
xTaskToResume: 要恢复的任务的任务句柄。
任务调度器
我们想要任务能够进行调度,就必须依赖于任务调度器,在FreeROTS中调度器开始使用的是vTaskStartScheduler();,这个函数的功能就是开启任务调度器。
开启后我们才可以进行任务调度。
空闲任务
空闲任务就是空闲的时候运行的任务,也就是系统中其他的任务由于各种原因不能运行的时候空闲任务就在运行。空闲任务是 RTOS 系统自动创建的,不需要用户手动创建。任务调度器启动以后就必须有一个任务运行!但是空闲任务不仅仅是为了满足任务调度器启动以后至少有一个任务运行而创建的,空闲任务中还会去做一些其他的事情,如下:
- 判断系统是否有任务删除,如果有的话就在空闲任务中释放被删除任务的任务堆栈和任务控制块的内存。
- 运行用户设置的空闲任务钩子函数。
- 判断是否开启低功耗 tickless 模式,如果开启的话还需要做相应的处理
空闲任务的任务优先级是最低的,为 0.
空闲线程是一个线程状态永远为就绪态的线程.
应用实例
光看枯燥的知识可能不太容易理解,下面我们来举个例子。
下面我们的项目,设计 4 个任务:start_task、key_task、task1_task 和 task2_task,这四个任务的任务功能如下:
start_task:用来创建其他 3 个任务。
key_task: 按键服务任务,检测按键的按下结果,根据不同的按键结果执行不同的操作。
task1_task:应用任务 1。
task2_task: 应用任务 2。
实验需要四个按键,KEY0、KEY1、KEY2 和 KEY_UP,这四个按键的功能如下:
KEY0: 此按键为中断模式,在中断服务函数中恢复任务 2 的运行。
KEY1: 此按键为输入模式,用于恢复任务 1 的运行。
KEY2: 此按键为输入模式,用于挂起任务 2 的运行。
KEY_UP: 此按键为输入模式,用于挂起任务 1 的运行。
(1)、start_task 任务,用于创建其他 3 个任务。
(2)、在 key_tssk 任务里面,KEY_UP 被按下,调用函数 vTaskSuspend()挂起任务 1。
(3)、KEY1 被按下,调用函数 vTaskResume()恢复任务 1 的运行。
(4)、KEY2 被按下,调用函数 vTaskSuspend()挂起任务 2。
(5)、任务 1 的任务函数,用于观察任务挂起和恢复的过程。
(6)、任务 2 的任务函数,用于观察任务挂起和恢复的过程(中断方式)。
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define KEY_TASK_PRIO 2
//任务堆栈大小
#define KEY_STK_SIZE 128
//任务句柄
TaskHandle_t KeyTask_Handler;
//任务函数
void key_task(void *pvParameters);
//任务优先级
#define TASK1_TASK_PRIO 3
//任务堆栈大小
#define TASK1_STK_SIZE 128
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);
//任务优先级
#define TASK2_TASK_PRIO 4
//任务堆栈大小
#define TASK2_STK_SIZE 128
//任务句柄
TaskHandle_t Task2Task_Handler;
//任务函数
void task2_task(void *pvParameters);
int main(void)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化LED
KEY_Init(); //初始化按键
EXTIX_Init(); //初始化外部中断
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
//开始任务任务函数
void start_task(void *pvParameters)
taskENTER_CRITICAL(); //进入临界区
//创建KEY任务
xTaskCreate((TaskFunction_t )key_task,
(const char* )"key_task",
(uint16_t )KEY_STK_SIZE,
(void* )NULL,
(UBaseType_t )KEY_TASK_PRIO,
(TaskHandle_t* )&KeyTask_Handler);
//创建TASK1任务
xTaskCreate((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
//创建TASK2任务
xTaskCreate((TaskFunction_t )task2_task,
(const char* )"task2_task",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
//key任务函数
void key_task(void *pvParameters)
u8 key,statflag=0;
while(1)
key=KEY_Scan(0);
switch(key)
case WKUP_PRES:
statflag=!statflag;
if(statflag==1)
vTaskSuspend(Task1Task_Handler);//挂起任务
printf("挂起任务1的运行!\\r\\n");
else if(statflag==0)
vTaskResume(Task1Task_Handler); //恢复任务1
printf("恢复任务1的运行!\\r\\n");
break;
case KEY1_PRES:
vTaskSuspend(Task2Task_Handler);//挂起任务2
printf("挂起任务2的运行!\\r\\n");
break;
vTaskDelay(10); //延时10ms
//task1任务函数
void task1_task(void *pvParameters)
u8 task1_num=0;
printf("Task1 Run:000");
while(1)
task1_num++; //任务执1行次数加1 注意task1_num1加到255的时候会清零!!
LED0=!LED0;
printf("任务1已经执行:%d次\\r\\n",task1_num);
printf("%d",task1_num); //显示任务执行次数
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
//task2任务函数
void task2_task(void *pvParameters)
u8 task2_num=0;
printf("Task2 Run:000");
while(1)
task2_num++; //任务2执行次数加1 注意task1_num2加到255的时候会清零!!
LED1=!LED1;
printf("任务2已经执行:%d次\\r\\n",task2_num);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
一开始任务 1 和任务 2 都正常运行,当挂起任务 1 或者任务 2 以后,任务 1 或者任务 2 就会停止运行,直到下一次重新恢复任务 1 或者任务 2 的运行。重点是,保存任务运行次数的变量都没有发生数据丢失,如果用任务删除和重建的方法这些数据必然会丢失掉的
后续
如果想了解更多物联网、智能家居项目知识,可以关注我的项目实战专栏和软硬结合专栏。
欢迎关注公众号了解更多。
编写不易,感谢支持。
以上是关于线程(嵌入式学习)的主要内容,如果未能解决你的问题,请参考以下文章