纯用户空间的抢占式多线程库其实是很麻烦的一件事,在设计之前首先必须明白抢占式多线程的意义,其本质就是古老的unix多道程序设计,策略可以是分时的,也可以是其它任何的调度策略,不管什么策略,机制要素都是底层的OS内核和机器硬件提供的,对于x86上的linux来说,这些要素包括:分页机制--提供进程间相同虚拟地址不冲突的栈,线程间不同虚拟地址不冲突的栈;时钟中断以及任意中断机制--可以在不通知用户进程的情况下中断之,然后进行调度抉择,该机制是调度策略的前提;fork机制--启动新线程。只有完全模拟出以上等机制,多线程才是抢占式的。网上有篇文章用setjmp和longjmp实现了一个协作多线程,由于何时调度必须由线程自己决定,因此那不能算是抢占式的。由于x86上的linux内核的分层设计并没有提供下层对上层的调用,因此实现一个纯用户空间的抢占式多线程真的很麻烦。
纯用户空间抢占式多线程的外部环境有两个要点:要点之一是抽象一台机器,该抽象的机器必须可以在进程外部将进程中断,有一种办法是向进程发信号;要点之二是必须能够得到进程当前的环境,比如所有寄存器,并且能保存这个环境。内部环境也有两个要点:其一是每个线程必须有一个属于自己的栈,由于这是纯用户空间的线程,因此最好自己用诸如malloc的方式动态分配;其二是每个线程必须可以自己启动。下面是MultiThread的部分代码:
//jmp_buf env[2];
//int idx[2];
void interrput_func (int sig)
{
static int flag = 1;
//idx[1] = setjmp(env[1]); //可惜setjmp只能保存当前栈的context,因此在此无法获得被信号中断之前的context,故而必须通过ptrace接口帮助。
if (1 == flag) {
flag ++;
...//创建一个新的堆栈,也就是重新设置esp寄存器,在新的堆栈上启动thread_func2
thread_func2()
}
}
void thread_func1 ()
{
while (1) {
printf("f1---/n");
}
}
void thread_func2()
{
while (1) {
printf ("f2---/n");
}
}
省略创建堆栈的代码,thread_func2和thread_func1必须在不同的堆栈上方可无错误地执行。
在一个父进程中fork-exec上述的程序MultiThread,然后用ptrace接口跟踪之,在发送SIGUSR信号给MultiThread并被父进程得知后,父进程交替使用PTRACE_GETREGS/PTRACE_SETREGS保存并设置上述程序的寄存器环境,如此就可以交替执行thread_func1和thread_func2了。
上述代码中注释调用setjmp的语句,本来用setjmp/longjmp+signal可以很好的模拟操作系统的多线程,可是jmp_buf保存的context在调用函数返回后就会失效,而signal函数是在当前栈或者另分配的栈(使用sigaltstack)上执行的,无论哪种情况,最后都要调用sigreturn,因此在信号处理函数中的setjmp是无效的,setjmp只针对当前栈帧有效,这里的要点是,要想实现抢占式多线程,栈的切换是必然的,栈的切换不能影响寄存器环境的保存,因此必须使用ptrace等机制显式的设置进程的寄存器上下文,我们之所以还是使用了信号机制,那是因为信号机制可以中断进程并且通知ptrace进程,从而给ptrace进程修改MultiThread进程寄存器上下文从而模拟多线程的机会。另外,线程的启动也是一个要点,在一个执行绪的情况下,你几乎不可能在当前的栈帧中启动使用另一个栈的另一个线程,所有的基于冯诺依曼体系的机器本身都是单执行绪的,所谓的x86机器的多线程只是在进程这个层面的下层保留了一系列的上下文环境,然后不断切换它们从而模拟了多个线程,正如OS内核线程的创建及启动需要底层系统调用一样,用户空间的多线程创建及启动需要信号机制(使用信号仅仅是一个例子,也可以用别的),同样的理由,在冯氏机器上实现用户空间多线程必须借助别的执行绪,比如ptrace的帮助。
如果setjmp可以得到被中断前的上下文,并且longjmp可以设置被中断后的上下文,并且不影响全局变量的话,正如kernel的context_switch一样,那么MultiThread的interrput_func就会成为:
static int flag = 0; //flag标识执行绪是信号处理进入的还是longjmp进入的。
void interrput_func (int sig)
{
flag = 1;
idx[1] = setjmp(env[1]); //注释*
if (1 == flag) { //如果是正规的信号处理则切换线程;
flag = 0; //设置全局变量,因为下面的longjmp之后,执行绪将从注释*下面开始,由于已经切换了上下文,故到时将不再切换
longjmp(env[0], idx[0]);
}
//否则信号返回,这个执行绪不是信号处理进入的,而是longjmp进入的。
}
分享到:
相关推荐
包括工作线程和用户界面线程》全面讲解MFC多线程的创建,界面多线程与工作者多线程,多线程的起源、继承与派生,两多线程之间的区别与相同点,定时器与多线程的关系与异同(定时器是定时优先抢占优先消息执行) ...
单片机抢占式嵌入式操作系统的设计分析.pdf
c语言版本,使用数据结构简单实现抢占式动态优先级调度算法
抢占式进程调度算法代码.doc
c#环境编写的一个模拟IO系统实现进程的抢占式优先权调度算法,设计简单。希望可以给大家带来小小帮助
基于单片机的抢占式实时嵌入式操作系统设计.zip单片机设计技术资料下载基于单片机的抢占式实时嵌入式操作系统设计.zip单片机设计技术资料下载基于单片机的抢占式实时嵌入式操作系统设计.zip单片机设计技术资料下载...
c语言版本,使用数据结构简单实现抢占式短进程优先调度算法
1、实验目的 通过动态优先权算法的模拟加深对进程概念和进程调度过程的理解 2、实验内容 (1)用C语言来实现对N个进程采用动态优先权优先算法的进程调度 (2)每个用来标示进程的PCB用结构来描述,包括字段如下: ...
描述操作系统抢占式和非抢占式算法的可视化简单操作界面,主要利用java-swing构建窗口,利用数组完成算法,采用Graphics2D画图完成。
进程管理 抢占式优先级 只有源代码 很完整 主程序都有 欢迎大家下载
一种基于单片机的抢占式实时嵌入式操作系统设计,对单片机的学习很有用,是pdf格式的
Qt线程池,实现多个任务抢占多线程调度功能,用Qt事件循环解决假死
多线程的Log工具,将程序中需要做log的地方输出至文件,无阻塞,无资源抢占,无死锁情况 工程中的ProgramLog为测试程序,做了10个线程,分别写入10000条log。 功能描述: 多线程的Log工具,将程序中需要做log的地方...
现有的求解周期性任务最小相对截止期的方法均假定任务集是采取抢占式EDF调度算法,并不适用于当任务为基于非抢占式EDF调度算法的场合,如实时通信领域。在分析了非抢占式EDF调度算法的可调度性判定条件基础上,提出...
操作系统实验,抢占式短作业优先算法,结合内存管理,实现进程调度
抢占式交通信号控制系统,郑耀明,,本文通过研究交通信号控制系统特点与计算机操作系统作业调度算法,提出了一种新的控制机制--抢占式交通信号控制系统。目的是提高�
实现抢占式优先级队列的进程调度 优先级分配采用动态方式
基于中低端单片机的精简设计,实时操作系统,一些基本的运用,如何实现
进程调度模拟设计--先来先服务、非强占式短进程优先算法 ,我们刚做完这个课程设计,把自己写的和网上下载的都传上来,希望大家稀饭
用实验的方法解释了ecos线程调度的原理和现象,本文档是了解ecos线程调度的最佳文档。