纯用户空间的抢占式多线程库其实是很麻烦的一件事,在设计之前首先必须明白抢占式多线程的意义,其本质就是古老的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进入的。
}
分享到:
相关推荐
在编程领域,多线程应用程序设计是至关重要的一个主题,特别是在现代计算机系统中,多核处理器的普及使得并发执行成为提升程序性能的有效手段。...因此,对初学者来说,学习多线程设计是成为优秀程序员的必经之路。
POSIX线程支持抢占式和非抢占式调度,可以使用`pthread_setschedparam()`改变线程调度策略和优先级。 8. **死锁**: 在多线程环境中,线程间的资源竞争可能导致死锁,即多个线程互相等待对方释放资源而无法继续...
线程调度策略包括抢占式和非抢占式调度,以及各种调度参数。了解这些策略可以帮助开发者更好地控制线程的执行顺序,从而提升系统的响应性和整体性能。 线程间通信是多线程程序协同工作的另一种方式,除了同步机制外...
在.NET框架中,多线程编程主要涉及`System.Threading`命名空间,这个命名空间提供了一系列的类和方法,用于创建、管理和同步线程。 首先,我们要理解多线程与多任务的概念。多任务是操作系统层面的概念,它允许系统...
POSIX(Portable Operating System Interface)是一组标准,旨在确保不同...通过学习和实践POSIX多线程程序设计,开发者可以编写出高效、可靠的多线程应用程序,充分利用多核处理器的优势,提升软件性能和用户体验。
Java多线程程序设计是Java开发中的重要组成部分,它允许程序在同一...总之,Java多线程设计涉及面广泛,包括线程创建、同步、通信、异常处理等多个方面。熟练掌握这些知识对于编写高效、安全的多线程Java程序至关重要。
- 多线程是指在一个进程中同时执行多个不同的线程,它们共享同一地址空间,可以并行地完成不同的任务,提高程序的执行效率和响应性。 2. **创建线程** - 在Win32 API中,使用`CreateThread`函数创建新线程,该...
线程优先级、抢占式调度等概念影响着多线程程序的性能。 5. **线程安全**:当多个线程访问同一资源时,需要确保代码是线程安全的,即不会因并发执行而产生错误。这通常涉及到对临界区的保护,避免竞态条件的发生。 ...
尽管多线程程序的设计目标通常是实现并行执行以提高性能,但在操作系统层面上,线程的执行情况取决于多种因素: **1. 线程调度** 线程调度是指操作系统如何安排线程执行的机制。有两种主要的调度类型: - **抢占...
单片机抢占式嵌入式操作系统是一种在多个并发任务中管理任务执行的实时操作系统。为了满足实时性要求,该操作系统能够优先处理高优先级的任务,尤其是当有新的高优先级任务到来时,能够立即中断当前正在执行的任务,...
Java的线程调度由JVM负责,包括抢占式调度(高优先级的线程抢占低优先级线程的CPU时间片)和合作式调度(线程自行决定何时放弃控制权)。Java还提供了`Thread.setPriority()`来设置线程的优先级,以及`Thread.yield...
调度算法可能包括抢占式调度(允许高优先级线程中断正在运行的低优先级线程)和非抢占式调度(线程一旦获得CPU,直到执行完毕或自愿释放CPU)。 线程间通信是多线程编程中不可或缺的一部分。它允许线程之间共享数据...
本项目着重探讨了两种常见的调度算法:抢占式和非抢占式,并通过Java编程语言实现了可视化的操作界面,使得用户能够直观地理解这两种算法的工作机制。 首先,抢占式调度算法允许高优先级的任务中断当前正在执行的...
在编程领域,多线程是实现并发执行任务的关键技术,特别是在C#这样的语言中,它提供了丰富的多线程支持。本文将深入探讨C#中的多线程实例,以帮助开发者理解如何有效地利用多核处理器资源,提高程序的执行效率。 多...
4. **线程调度**:操作系统负责线程调度,根据不同的策略(如抢占式调度、轮转调度等)分配CPU时间片给各个线程。 5. **线程同步与通信**:在多线程环境中,为了防止数据竞争和死锁,需要使用同步机制。包括互斥量...
Win32多线程程序设计是计算机编程领域中一个重要的主题,特别是在开发高效能的应用程序时,多线程技术能够充分利用多核处理器的计算能力,提高程序的响应速度和并发性能。侯捷是一位知名的计算机技术专家,他在该...
在抢占式多任务环境中,如Win32,操作系统负责动态管理线程的执行顺序,即使某个线程陷入死循环,也不会阻塞其他线程的执行。而在协作式多任务环境中,如Windows 3.1,线程的执行依赖于应用程序主动让出控制权,一旦...
在单核CPU的时代,多线程通过时间片轮转的方式模拟并发,每个线程分配到一定的时间片来执行,从用户角度看,多个任务似乎同时进行。而在多核CPU的环境下,线程可以真正地在同一时间并行执行,提高了系统的效率。 ...