QNX----第2章QNXNeutrino线程进程与调度策略(1部分)的实现
从历史上看,QNX的软件系统的"应⽤压⼒"是由内存有限的嵌⼊式系统从内存有限的嵌⼊式系统中得到的,⼀直到⾼端的SMP(对称多处理器)计算机,有千兆字节的物理内存。因此,QNX中微⼦的设计⽬标同时适⽤于这两种看似唯⼀的功能集。追求这些⽬标的⽬的是扩展系统的范围,远远超出其他操作系统实现所能解决的范围。
实时和线程扩展
由于QNX Neutrino RTOS直接在微内核中实现了⼤部分的实时和线程服务,所以即使没有额外的OS模块,这些服务也是可⽤的。此
外,POSIX定义的⼀些概要⽂件表明,这些服务在不需要进程模型的情况下存在。为了适应这⼀点,操作系统提供了对线程的直接⽀持,但是依赖于它的进程管理器部分将此功能扩展到包含多个线程的进程。注意,许多实时管理系统和内核只提供⼀个⾮内存保护的线程模型,根本没有进程模型和/或受保护的内存模型。没有进程模型,就⽆法实现完全POSIX遵从性。
微内核有内核调⽤来⽀持以下内容:
·线程
·消息传递
·信号
·时钟
·定时器
·中断处理程序
·信号量
·互斥锁(互斥锁)
·条件变量(condvars)
·隔离
整个操作系统是建⽴在这些调⽤之上的。操作系统是完全可抢占的,即使在进程之间传递消息;它恢复消息传递,然后在抢占之前停⽌。
微内核的最⼩复杂度有助于在经过内核的最长不可抢占的代码路径上设置上限,⽽较⼩的代码⼤⼩使解决复杂的多处理器问题成为⼀个易于处理的问题。选择服务作为包含在微内核中的基础,因为服务的执⾏路径很短。需要⼤量⼯作的操作(例如,进程加载)被分配给外部进程/线程,与线程中为服务请求所做的⼯作相⽐,进⼊该线程上下⽂的⼯作是⽆关紧要的。
严格地应⽤这个规则来划分内核和外部进程之间的功能,这破坏了⼀个神话,即⼀个微内核操作系统必须⽐⼀个单⼀的内核操作系统带来更⾼的运⾏时开销。考虑到上下⽂切换(隐含在消息传递中)的⼯作,以及从简化的内核中获得的快速上下⽂切换的时间,所花的时间运⾏的上下⽂切换就变成了为服务的⼯作所做的⼯作所做的⼯作的“丢失”的过程中传递的信息传递的过程中所传递的信息传递的过程中所传递的信息。
下图显⽰了non-SMP内核抢占细节(x86)实现。
图6:QNX Neutrino抢占的详细信息
中断是被禁⽤的,或者抢占是被延迟的,只有⾮常短的间隔(通常是⼏百纳秒的顺序)
在构建应⽤程序时(实时、嵌⼊式、图形化或其他),开发⼈员可能希望应⽤程序中的⼏种算法并发执⾏。这种并发性是通过使⽤POSIX线程模型实现的,该模型将进程定义为包含⼀个或多个执⾏线程。
线程可以被认为是最⼩的“执⾏单元”,即微内核中的调度和执⾏单元。另⼀⽅⾯,进程可以被认为是线程的“容器”,定义线程执⾏
的“地址空间”。进程将始终包含⾄少⼀个线程。
根据应⽤程序的性质,线程可能独⽴执⾏,⽽不需要在算法之间通信(不太可能),或者它们可能需要紧密耦合,具有⾼带宽通信和紧密同步。为了帮助这种通信和同步,QNX中微⼦RTOS提供了丰富的IPC和同步服务。
下⾯的pthread_* (POSIX线程)库调⽤不包含任何微内核线程调⽤:
· pthread_attr_destroy()
· pthread_attr_getdetachstate()
· pthread_attr_getinheritsched()
· pthread_attr_getschedparam()
· pthread_attr_getschedpolicy()
· pthread_attr_getscope()
· pthread_attr_getstackaddr()
· pthread_attr_getstacksize()
· pthread_attr_init()
· pthread_attr_setdetachstate()
· pthread_attr_setinheritsched()
· pthread_attr_setschedparam()
·
pthread_attr_setschedpolicy()
· pthread_attr_setscope()
· pthread_attr_setstackaddr()
· pthread_attr_setstacksize()
· pthread_cleanup_pop()
· pthread_cleanup_push()
· pthread_equal()
· pthread_getspecific()
· pthread_setspecific()
· pthread_key_create()
· pthread_key_delete()
·
pthread_self()
下表列出了POSIX线程调⽤,它有⼀个相应的微内核线程调⽤,允许您选择其中⼀个接⼝:
可以将操作系统配置为提供线程和进程的混合(由POSIX定义)。每个进程都受到mmu的保护,每个进程可能包含⼀个或多个共享进程地址空间的线程。
尽管进程中的线程共享进程地址空间中的所有内容,但每个线程仍然拥有⼀些“私有”数据。在某些情况下,这些私有数据在内核中受到保护(例如tid或线程ID),⽽其他私有数据则不受保护在进程的地址空间中(例如,每个线程都有⾃⼰的堆栈)。⼀些更值得注意的线程私有资源有:
tid: 每个线程由整数线程ID标识,从1开始。tid是线程进程中唯⼀的。
Priority: 每个线程都有⼀个优先级,帮助确定它什么时候运⾏。线程从⽗线程继承其初始优先级,但是优先级可以改变,这取决于调度策略、线程进⾏的显式更改或发送给线程的消息。
Name: 从QNX中微⼦核⼼OS 6.3.2开始,可以为指定⼀个名称线程;请参阅QNX中微⼦C库引⽤中的pthread_getname_np()和
pthread_setname_np()条⽬。像dumper和pidin这样的实⽤程序⽀持线程名。线程名称是⼀个QNX中微⼦扩展。
Register set: 每个线程都有⾃⼰的指令指针(IP)、堆栈指针(SP)和其他处理器寄存器上下⽂。
Stack: 每个线程在它⾃⼰的堆栈上执⾏,存储在地址空间中它的过程。一个线程可以包含多个进程
Signal mask: 每个线程都有⾃⼰的信号掩码。
Thread local storage: 线程有⼀个系统定义的数据区域,称为“线程本地存储”(TLS)。TLS⽤于存储“每个线程”的信息(例如tid、pid、堆栈基数、errno和线程特定的键/数据绑定)。⽤户应⽤程序不需要直接访问TLS。线程可以拥有与特定于线程的数据键相关联的⽤户定义数据。
Cancellation handlers: 当线程终⽌时执⾏的回调函数。在pthread库中实现并存储在TLS中的特定于线程的数据提供了⼀种将进程全局整数键与每个线程的唯⼀数据值关联起来的机制。使⽤线程特定的数据,⾸先创建⼀个新键,然后将唯⼀的数据值绑定到键(每个线程)。例如,数据值可以是整数或指向动态分配的数据结构的指针。随后,键可以返回每个线程的绑定数据值。线程特定数据的典型应⽤程序是⽤于线程安全的函数,该函数需要为每个调⽤线程维护上下⽂。
图7:稀疏矩阵(tid,key)到值的映射
可以使⽤以下函数来创建和操作这些数据:
进程中线程的数量可以有很⼤的变化,线程是动态创建和销毁的。线程创建(pthread_create())涉及到在进程的地址空间(例如线程堆栈)中分配和初始化必要的资源,并在地址空间中的某个函数上启动线程的执⾏。
线程终⽌(pthread_exit(), pthread_cancel())涉及到停⽌线程并回收线程的资源。当⼀个线程执⾏时,它的状态通常可以被描述为“就绪”或“阻塞”。“更具体地说,它可以是以下⼏项之⼀:
图8:可能的线程状态。注意,除了上⾯显⽰的转换之外,线程还可以从任何状态(死状态除外)移动到就绪状态
CONDVAR: 线程被阻塞在⼀个条件变量上(例如,它被称为pthread_cond_wait())。
DEAD: 线程已经终⽌,正在等待另⼀个线程的连接。
INTERRUPT: 线程被阻塞,等待中断。,它叫做InterruptWait())。
JOIN: 线程被阻塞,等待加⼊另⼀个线程(例如,它调⽤pthread_join())。
MUTEX: 线程被阻塞在互斥锁上(例如,它调⽤pthread_mutex_lock())。
NANOSLEEP: 线程睡眠的时间很短(例如,它被称为nanosleep())。
NET_REPL Y: 线程正在等待通过⽹络(即,它叫MsgReply *())。
NET_SEND: 线程正在等待通过⽹络传递脉冲或信号(即它称为MsgSendPulse()、MsgDeliverEvent()或SignalKill())。
READY: 线程正在等待执⾏,⽽处理器正在执⾏另⼀个线程优先级相等或更⾼的线程。
RECEIVE: 线程被阻塞在消息receive(例如,它称为MsgReceive())上。
REPL Y: 线程在消息应答(即,它调⽤MsgSend()服务器收到消息)。
RUNNING: 线程是由处理器执⾏的。内核使⽤⼀个数组(系统中每个处理器有⼀个条⽬)来跟踪正在运⾏的线程。
SEM: 线程正在等待⼀个信号量被发布(例如。,它叫SyncSemWait())。
SEND: 线程在消息发送时被阻塞(例如,它调⽤MsgSend(),但是服务器还没有收到消息)。
SIGSUSPEND: 线程被阻塞,等待⼀个信号(即,它叫做sigsuspend())。
SIGWAITINFO: 线程被阻塞,等待⼀个信号(即,它叫做sigwaitinfo())。
STACK: 线程正在等待为线程的堆栈分配虚拟地址空间(⽗线程将调⽤ThreadCreate())。
STOPPED:线程被阻塞,等待SIGCONT信号。
WAITCTX: 线程正在等待⼀个⾮整数(例如浮点)上下⽂可⽤。
WAITPAGE: 线程正在等待为虚拟地址分配物理内存。
WAITTHREAD: 线程正在等待⼦线程完成⾃⼰的创建(即。,它叫ThreadCreate())。
内核的部分⼯作是确定哪个线程在什么时候运⾏。
⾸先,让看看内核什么时候做出调度决策。
当作为内核调⽤、异常或硬件中断的结果输⼊微内核时,正在运⾏的线程的执⾏将暂时暂停。当任何
线程的执⾏状态发⽣变化时,就会做出调度决策——线程可能驻留在哪个进程中并不重要。所有进程的线程都是全局调度的。
正常情况下,挂起的线程将继续执⾏,但是当运⾏的线程:
·被阻塞
·被抢占
·被调度
什么时候线程被阻塞?
当运⾏的线程必须等待某个事件发⽣时(响应IPC请求,等待互斥锁等),线程就会被阻塞。阻塞的线程将从正在运⾏的数组中删除,然后运⾏最⾼优先级的就绪线程。当阻塞的线程随后被解除阻塞时,它被放在优先级级别的就绪队列的末尾。
线程何时被抢占?

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。