- 1. SylixOS 内核源程序原理架构分析和基本开发方法(不同对象环境)
- 2. SylixOS 操作系统源程序各组成模块原理和功能分析;程序内容设计修改及运行,现象记录分析,总结对嵌入式 RTOS 技术和编写开发 RTOS 的认识理解。
- 3. 1-2 例基于(Cortex A9)SylixOS RTOS 的原理性应用(App)程序设计编写,运行记录,结果分析;总结所定制开发的 RTOS 环境开发过程;
- 程序 5
- 程序 6
- 程序 7
- 程序8
- 程序 9
- 程序 10
- 4. 基于 POSIX 标准的线程和任务间通信机制和示例应用程序编写方法、中断机制及操作方法总结分析。
- (1) 线程和任务间通信机制
- (i) 线程的介绍
- (ii) 线程常用函数
- (a) 线程标识(线程ID)
- (b) 创建线程函数
- (c) 自行退出当前线程函数
- (d) 线程退出等待函数
- (iii) 线程间通信主要类型:
- (iv) 信号量
- (v) 信号量同步与使用全局变量同步的区别
- (vi) 死锁产生的条件
- (vii) 预防死锁的方法
- (2) 中断机制
- (i) 中断介绍
- (1) 线程和任务间通信机制
- 5. 演示系统分析与设计实现总结
- 参考文献
1. SylixOS 内核源程序原理架构分析和基本开发方法(不同对象环境)
2. SylixOS 操作系统源程序各组成模块原理和功能分析;程序内容设计修改及运行,现象记录分析,总结对嵌入式 RTOS 技术和编写开发 RTOS 的认识理解。
int sched_getparam (pid_t pid, struct sched_param *pschedparam);
- 函数 sched_getparam 原型分析:
- 此函数成功返回 0,失败返回-1 并设置错误号;
- 参数 pid 是进程 ID;
- 输出参数 pschedparam 返回调度参数。
- 如果 pid 等于 0,则设置当前线程的优先级,需要注意的是,如果设置的优先级和指定线程的当前优先级相同,则什么也不做并返回 0。
int sched_setparam(pid_t pid, const struct sched_param *pschedparam);
- 函数 sched_setparam 原型分析:
- 此函数成功返回 0,失败返回-1 并设置错误号;
- 参数 pid 是进程 ID;
- 参数 pschedparam 是调度参数。
int sched_get_priority_max (int iPolicy);
- 函数 sched_get_priority_max 原型分析:
- 功能描述: 获得调度器允许的最大优先级 (pthread 线程不能与 idle 同优先级!)
- 输 入 : iPolicy 调度策略 (目前无用)
- 输 出 : 最大优先级
int sched_get_priority_min (int iPolicy);
- 函数 sched_get_priority_min 原型分析:
- 功能描述: 获得调度器允许的最小优先级
- 输 入 : iPolicy 调度策略 (目前无用)
- 输 出 : 最小优先级
int sched_yield (void);
- 函数 sched_yield 原型分析:
- 功能描述: 将当前任务插入到同优先级调度器链表的最后, 主动让出一次 CPU 调度.
- 输 入 : NONE
- 输 出 : ERROR_NONE
static VOID __sched_set (PLW_CLASS_TCB ptcb, UINT8 *pucPolicy, UINT8 *pucPriority)
- 函数 __sched_set 原型分析:
- 功能描述: 设置指定任务调度器参数 (进入内核后被调用)
- 输 入 :
- ptcb 任务控制块
- pucPolicy 调度策略
- pucPriority 优先级
- 输 出 : NONE
int sched_setscheduler (pid_t pid, int iPolicy, const struct sched_param *pschedparam)
- 函数 sched_setscheduler 原型分析:
- 功能描述: 设置指定任务调度器
- 输 入 :
- pid 进程 / 线程 ID
- iPolicy 调度策略
- pschedparam 调度器参数
- 输 出 : ERROR or OK
int sched_getscheduler (pid_t pid);
- 函数 sched_getscheduler 原型分析:
- 功能描述: 获得指定任务调度器
- 输 入 : pid 进程 / 线程 ID
- 输 出 : 调度策略
int sched_rr_get_interval (pid_t pid, struct timespec *interval);
- 函数 sched_rr_get_interval 原型分析:
- 功能描述: 获得指定任务调度器
- 输 入 :
- pid 进程 / 线程 ID
- interval current execution time limit.
- 输 出 : ERROR or OK
int sched_setaffinity (pid_t pid, size_t setsize, const cpu_set_t *set);
- 函数 sched_setaffinity 原型分析:
- 功能描述: 设置进程调度的 CPU 集合
- 输 入 :
- pid 进程 / 线程 ID
- setsize CPU 集合大小
- set CPU 集合
- 输 出 : ERROR or OK
int sched_getaffinity (pid_t pid, size_t setsize, cpu_set_t *set);
- 函数 sched_getaffinity 原型分析:
- 功能描述: 获取进程调度的 CPU 集合
- 输 入 :
- pid 进程 / 线程 ID
- setsize CPU 集合大小
- set CPU 集合
- 输 出 : ERROR or OK
int sched_settimeslice (UINT32 ticks);
- 函数 sched_settimeslice 原型分析:
- 功能描述: 设置系统调度时间片
- 输 入 : ticks 时间片
- 输 出 : ERROR or OK
unsigned int sched_gettimeslice (void);
- 函数 sched_gettimeslice 原型分析:
- 功能描述: 获得系统调度时间片
- 输 入 : NONE
- 输 出 : 时间片
int pthread_create (pthread_t *pthread,
const pthread_attr_t *pattr,
void *(*start_routine)(void *),
void *arg)
- 函数 pthread_create 原型分析:
- 功能描述: 创建一个 posix 线程.
- 输 入 :
- pthread 线程 id (返回).
- pattr 创建属性
- start_routine 线程入口
- arg 线程入口参数
- 输 出 : ERROR CODE
int pthread_join (pthread_t thread, void **ppstatus);
- 函数 pthread_join 原型分析:
- 线程退出等待函数:此函数用来将调用函数的线程挂起,直至指定线程退出。
3. 1-2 例基于(Cortex A9)SylixOS RTOS 的原理性应用(App)程序设计编写,运行记录,结果分析;总结所定制开发的 RTOS 环境开发过程;
程序 5
SylixOS基于POSIX标准创建的线程,包括线程优先级的修改
-
NUM=5时,A、B两个线程所执行的函数分别打印5次
-
NUM=10时,A、B两个线程所执行的函数分别打印10次
先创建线程A,后创建线程B,故线程A的优先级高于线程B的优先级,所以先执行线程A的任务函数。
程序 6
基于Sylix基于POSIX标准创建线程程序2(6.c),包括线程优先级的修改功能
-
先创建线程A,后创建线程B,故线程A的优先级高于线程B的优先级,所以先执行线程A的任务函数。
程序 7
线程间通信,采用mutex互斥锁
基于Sylix基于POSIX标准创建线程程序2(6.c),包括线程优先级的修改功能
- mystr函数中在打印字符串的前后增加了互斥锁,以防止线程B抢占线程A。
void mystr(char *c, int len)
{
int i = 0;
pthread_mutex_lock(&lock);
for(i=0; i
- 在main函数的开头需要初始化互斥锁
pthread_mutex_init(&lock, NULL);
- 在程序结尾需要删除互斥锁
pthread_mutex_destroy(&lock);
程序8
线程间通信,采用信号量
基于Sylix基于POSIX标准创建线程程序2(6.c),包括线程优先级的修改功能
- 两个线程之间采用信号量进行通信,两个线程各执行一次然后让对方执行一次,相当于两个线程轮流占用时间片,由于程序执行时间很短,达到多线程的效果。
ret = sem_init(&sem1, 0 ,0);
ret = sem_init(&sem2, 0 ,0);
- 创建一个匿名的 posix 信号量.
PVOID tTestA (PVOID pvArg)
{
int count = 0;
char str[5]="ABCD ";
while(count++<1000)
{
sem_wait(&sem1);
mystr(str, 5);
sem_post(&sem2);
}
}
PVOID tTestB (PVOID pvArg)
{
int count = 0;
char str[5]="1234 ";
while(count++<1000)
{
sem_wait(&sem2);
mystr(str, 5);
sem_post(&sem1);
}
}
- 线程间通过信号量进行通信。
sem_post(&sem1);
-
释放一个 posix 信号量.
程序 9
-
由于互斥锁设计的不合理,所以发生了死锁
源代码使用的是顺序锁,删除tL函数中了一个sleep(2),使得程序运行过程中不会正好发生冲突,但治标不治本,无法从根本上避免死锁的发生。
实际过程中,可以通过超时检测,来检查程序是否发生死锁,如果发生了死锁,则按顺序将锁打开,退出优先级低的线程,来执行优先级高的线程。
由于实际过程中可能不止两个线程需要运行,可以使用邻接图这一数据结构来存储哪些程序用到了这个锁,按照邻接图的指示,将锁依次打开。
在实际过程中,死锁的发生是由于程序结构设计的不合理,其他方法只提供在发生死锁的情况下尽可能地让程序继续运行起来,不可避免地会浪费时间片资源。
PVOID tL (PVOID pvArg)
{
printf("LOW start\n");
pthread_mutex_lock(&lock2);
printf("LOW lock2\n");
b=7;
printf("LOW get b\n");
//sleep(2); /* 删除此行,死锁解决 */
pthread_mutex_lock(&lock1);
printf("LOW lock1\n");
a=5;
printf("LOW sum: %d, %d\n", a, b);
sleep(5);
pthread_mutex_unlock(&lock1);
printf("LOW unlock1\n");
pthread_mutex_unlock(&lock2);
printf("LOW unlock2\n");
}
decoration:underline">
程序 10
优先级反转
param.sched_priority += 10;
- 将线程原有的优先级+10,原线程的优先级为55,+10后优先级为65.
-
因为优先级低的任务占用了CPU资源,当高优先级的任务需要被执行时,低优先级任务无法解开互斥锁,所以无法及时释放CPU资源,这时为了让它能尽快释放掉CPU资源,可以提高它的优先级与高优先级任务相同,这样就能优先让这个占用CPU资源的任务执行掉。这一解决方案依然耽误了高优先级任务的执行,效率无法达到最优。
4. 基于 POSIX 标准的线程和任务间通信机制和示例应用程序编写方法、中断机制及操作方法总结分析。
(1) 线程和任务间通信机制
(i) 线程的介绍
线程是允许应用程序并发执行多个任务的一种机制,是程序运行后的任务处理单元,也是SylixOS操作系统任务调度的最小单元。在多核CPU中,同时可以有多个线程在执行,实现真正意义上的并行处理。
(ii) 线程常用函数
(a) 线程标识(线程ID)
每个线程都有其对应的线程ID,线程ID使用pthread_t数据类型表示,在特定线程内,使用pthread_self函数可以获取自身的线程ID。
pthread_t pthread_self(void);
(b) 创建线程函数
int pthread_create( pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void*),
void *arg);
(c) 自行退出当前线程函数
此函数在线程内部调用,当某线程需要退出时,在线程内的实体函数调用此函数即可退出当前线程。使用return会隐式调用此函数退出当前线程。
void pthread_exit(void *status);
(d) 线程退出等待函数
此函数用来将调用函数的线程挂起,直至指定线程退出。
int pthread_join(pthread_t thread, void **ppstatus);
(iii) 线程间通信主要类型:
- 互斥型通信:共享资源需要独占访问,可以使用信号量、互斥量进行互斥型通信;
- 通知型通信:上述的A线程通知B线程,可以用信号量、事件集、条件变量进行通知型通信;
-
消息型通信:某线程或中断服务程序只负责采集数据,但不直接加工数据,而是将数据传递给另一个线程进行数据加工,可以使用消息队列进行消息型通信。
(iv) 信号量
信号量是一个在进程和线程中都可以使用的同步机制。信号量类似于一个通知,某个线程发出一个通知,等待此通知的线程收到通知后,会执行预先设置的工作。当接收通知的线程没有收到通知前,会处于阻塞状态。
信号量可以连续发送多次,处理线程同样也会处理多次。信号量实质是一个计数器,信号量发送一次,计数值增加1,信号量每获取一次,计数值就减1,当计数值为0时,等待信号量线程阻塞。等待信号量过程中,还可以设置等待时间,超过设定时间,等待信号量的线程就不会继续等待,而是继续执行后续任务。
信号量分为命名信号量和未命名信号量.
未命名信号量的使用流程如下:
- 使用sem_t声明一个信号量;
- 使用sem_init函数初始化信号量;
- 使用sem_post函数发送信号量;
- 在等待信号量的线程内使用sem_wait函数等待信号量,若需要设置等待期限需要使用sem_timewait/sem_reltimedwait_np函数;
- 信号量使用完毕需要使用sem_destroy函数将信号量销毁。
(v) 信号量同步与使用全局变量同步的区别
- 使用全局变量会增加耦合度,降低内聚性,不符合软件设计思想
- 信号量其实就是把全局变量维护到了一个全局数据结构内
- 使用全局变量影响封装性,移植性和可读性
- 使用全局变量会严重降低代码可维护性和稳定性
- 信号量和消息队列等可以将任务阻塞,避免抢占CPU
(vi) 死锁产生的条件
- 互斥条件
- 请求与保持条件
- 不可剥夺条件
- 循环等待条件
(vii) 预防死锁的方法
- 破坏“请求与保持”条件
- 方法一:静态分配,每个进程在开始执行时就申请他所需要的全部资源。
- 方法二:动态分配,每个进程在申请所需要的资源时他本身不占用系统资源。
- 破坏“不可剥夺”条件
- 一个进程不可获得其所需要的全部资源便处于等待状态,等待期间他占用的资源将被隐式的释放重新加入到系统的资源列表中,可以被其他进程使用,而等待的进程只有重新获得自己原有的资源以及新申请的资源才可以重新启动,执行。
- 破坏“循环等待”条件
- 采用资源有序分配的基本思想。将系统中的资源顺序进行编号,将紧缺的、稀少的资源采用较大的编号,申请资源时必须按照编号的顺序执行,一个进程只有较小编号的进程才能申请较大编号的进程[1]。
(2) 中断机制
(i) 中断介绍
中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。
当中断产生时,CPU执行完当前指令后,PC指针将跳转到异常向量表的相应地址去执行。该地址处是一句跳转指令,PC指针继续跳转到系统定义的总中断服务函数里面去执行,然后系统进行任务上下文的保存、中断向量号的获得、具体中断服务函数的执行等,执行结束后,恢复被中断任务的上下文,继续执行任务。中断处理流程如图所示。
5. 演示系统分析与设计实现总结
实验利用虚拟机和实体机A9进行了此次实验,了解了线程之间的通信方法,以及线程之间的死锁和优先级反转的问题,这两个问题对于实时操作系统是致命的,在程序架构设计的时候就要防止此类问题的发生。
如果发生了中断,CPU需要执行一系列的保护和恢复的操作以保证程序的正常运行。
参考文献
- [1] CSDN: 死锁概念,死锁产生的四个必要条件,如何避免和预防死锁https://blog.csdn.net/ZWE7616175/article/details/79881236