精华内容
下载资源
问答
  • 进程切换与线程切换

    2020-09-07 21:44:56
    对于linux来说,线程和进程的最大区别就在于地址空间,对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的。 切换的性能消耗: 线程上下文切换和进程上下问切换一个最主要的区别是线程的切换虚拟内存...

    为了更好的了解上下文切换,需要我们了解虚拟内存的概念。

    虚拟内存是操作系统为每个进程提供的一种抽象,每个进程都有属于自己的、私有的、地址连续的虚拟内存,当然我们知道最终进程的数据及代码必然要放到物理内存上,那么必须有某种机制能记住虚拟地址空间中的某个数据被放到了哪个物理内存地址上,这就是所谓的地址空间映射,也就是虚拟内存地址与物理内存地址的映射关系,那么操作系统是如何记住这种映射关系的呢,答案就是页表,页表中记录了虚拟内存地址到物理内存地址的映射关系。有了页表就可以将虚拟地址转换为物理内存地址了,这种机制就是虚拟内存。

    每个进程都有自己的虚拟地址空间,进程内的所有线程共享进程的虚拟地址空间。

    进程切换

    切换虚拟地址空间,切换内核栈和硬件上下文

    线程切换

    切换内核栈和硬件上下文

    上下文切换的开销

    1. 最显著的性能损耗是将保存寄存器中的内容

    2. CPU高速缓存失效

    3. 页表查找是一个很慢的过程,因此通常使用Cache来缓存常用的地址映射,这样可以加速页表查找,这个cache就是TLB.当进程切换后页表也要进行切换,页表切换后TLB就失效了,cache失效导致命中率降低,那么虚拟地址转换为物理地址就会变慢,表现出来的就是程序运行会变慢

    更详细的来说

    进程切换分两步:

    1. 切换页目录以使用新的地址空间

    2. 切换内核栈和硬件上下文

    对于linux来说,线程和进程的最大区别就在于地址空间,对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的。

    切换的性能消耗:

    1. 线程上下文切换和进程上下问切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。

    2. 另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲(processor’s Translation Lookaside Buffer (TLB))或者相当的神马东西会被全部刷新,这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中,不会出现这个问题。

    系统调用:处于进程上下文

    系统调用是在进程上下文中,并没有tasklet之类的延迟运行,系统调用本身可以休眠,这些可以参见内核代码

    虽然系统调用实与其他中断实现有点类似,通过IDT表查找入口处理函数,但是系统调用与其他中断最大的不同是,系统调用是代表当前进程执行的,所以current宏/task_struct是有意义的,这个休眠可以被唤醒

    系统调用,异常,中断(其中中断是异步时钟,异常时同步时钟),也可以把系统调用成为异常

    中断上下文:

    在中断中执行时依赖的环境,就是中断上下文(不包括系统调用,是硬件中断)

    进程上下文:

    当一个进程在执行时,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文

    1、首先,这两个上下文都处于内核空间。

    2、其次,两者的区别在于,进程上下文与当前执行进程密切相关,而中断上下文在逻辑上与进程没有关系。

    进程上下文主要是异常处理程序和内核线程。内核之所以进入进程上下文是因为进程自身的一些工作需要在内核中做。例如,系统调用是为当前进程服务的,异常通常是处理进程导致的错误状态等。所以在进程上下文中引用current是有意义的。

    内核进入中断上下文是因为中断信号而导致的中断处理或软中断。而中断信号的发生是随机的,中断处理程序及软中断并不能事先预测发生中断时当前运行的是哪个进程,所以在中断上下文中引用current是可以的,但没有意义。事实上,对于A进程希望等待的中断信号,可能在B进程执行期间发生。例如,A进程启动写磁盘操作,A进程睡眠后现在时B进程在运行,当磁盘写完后磁盘中断信号打断的是B进程,在中断处理时会唤醒A进程。

    上下文这个词会让人想到进程的CPU寄存器状态,但好像进入进程上下文(异常处理系统调用)和进入中断上下文(中断处理),内核所做的工作没有太大区别。所以,这两个上下文的主要区别,我认为在于是否与进程相关。

    运行于进程上下文的内核代码是可抢占的,但中断上下文则会一直运行至结束,不会被抢占。因此,内核会限制中断上下文的工作,不允许其执行如下操作:

    (1) 进入睡眠状态或主动放弃CPU;

    由于中断上下文不属于任何进程,它与current没有任何关系(尽管此时current指向被中断的进程),所以中断上下文一旦睡眠或者放弃CPU,将无法被唤醒。所以也叫原子上下文(atomic context)。

    (2) 占用互斥体;

    为了保护中断句柄临界区资源,不能使用mutexes。如果获得不到信号量,代码就会睡眠,会产生和上面相同的情况,如果必须使用锁,则使用spinlock。

    (3) 执行耗时的任务;

    中断处理应该尽可能快,因为内核要响应大量服务和请求,中断上下文占用CPU时间太长会严重影响系统功能。在中断处理例程中执行耗时任务时,应该交由中断处理例程底半部来处理。

    (4) 访问用户空间虚拟内存。

    因为中断上下文是和特定进程无关的,它是内核代表硬件运行在内核空间,所以在中断上下文无法访问用户空间的虚拟地址

    (5) 中断处理例程不应该设置成reentrant(可被并行或递归调用的例程)。

    因为中断发生时,preempt和irq都被disable,直到中断返回。所以中断上下文和进程上下文不一样,中断处理例程的不同实例,是不允许在SMP上并发运行的。

    (6)中断处理例程可以被更高级别的IRQ中断。(不能嵌套中断)使用软中断,上部分关中断,也就是禁止嵌套,下半部分使用软中断

    如果想禁止这种中断,可以将中断处理例程定义成快速处理例程,相当于告诉CPU,该例程运行时,禁止本地CPU上所有中断请求。这直接导致的结果是,由于其他中断被延迟响应,系统性能下降。

    软中断是一种延时机制,代码执行的优先级比进程要高,比硬中断要低。相比于硬件中断,软中段是在开中断的环境中执行的(长时间关中断对系统的开销太大), 代码是执行在中断/线程上下文的,是不能睡眠的,虽然每个cpu都有一个对应的ksoftirqd/n线程来执行软中断,但是do_softirq这个函数也还会在中断退出时调用到,因此不能睡眠(中断上下文不能睡眠的原因是由于调度系统是以进程为基本单位的,调度时会把当前进程的上下文保存在task_struct这个数据结构中,当进程被调度重新执行时会找到执行的断点,但是中断上下文是没有特定task_struct结构体的,当然现在有所谓的线程话中断,可以满足在中断处理函数执行阻塞操作,但是实时性可能会有问题。还有就是中断代表当前进程执行的概念,个人感觉有点扯淡,毕竟整个内核空间是由所有进程共享的,不存在代表的概念)

    上面我们介绍的可延迟函数运行在中断上下文中(软中断的一个检查点就是do_IRQ退出的时候),于是导致了一些问题:软中断不能睡眠、不能阻塞。由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。但可阻塞函数不能用在中断上下文中实现,必须要运行在进程上下文中,例如访问磁盘数据块的函数。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。

    展开全文
  • 进程切换、线程切换

    2019-03-20 22:11:20
    对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的。所以明显是进程切换代价大 线程上下文切换和进程上下问切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两...

    线程上下文切换和进程上下文切换的区别

    进程切换分两步
    1.切换页目录以使用新的地址空间
    2.切换内核栈和硬件上下文。

    对于linux来说,线程和进程的最大区别就在于地址空间。
    对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的。所以明显是进程切换代价大

     

    线程上下文切换和进程上下问切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。

    另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲(processor’s Translation Lookaside Buffer (TLB))或者相当的神马东西会被全部刷新,这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中,不会出现这个问题。

    展开全文
  • 文章目录示例代码关键结构体调度链表初始化线程堆栈线程切换被动切换主动切换线程调度总结 之前我们已经了解过线程的等待链表和调度链表,为了更好的学习Windows的线程切换,我们要先读一份代码 示例代码 ...


    之前我们已经了解过线程的等待链表和调度链表,为了更好的学习Windows的线程切换,我们要先读一份代码

    示例代码

    ThreadSwitch.h

    #pragma once
    #include <windows.h>
    #include "stdio.h"
    
    //最大支持的线程数
    #define MAXGMTHREAD 100
    
    //线程信息的结构
    typedef struct
    {
    	char* name;							//线程名 相当于线程ID
    	int Flags;							//线程状态
    	int SleepMillsecondDot;				//休眠时间
    
    	void* initialStack;					//线程堆栈起始位置
    	void* StackLimit;					//线程堆栈界限
    	void* KernelStack;					//线程堆栈当前位置,也就是ESP
    
    	void* lpParameter;					//线程函数的参数
    	void(*func)(void* lpParameter);		//线程函数
    }GMThread_t;
    
    void GMSleep(int MilliSeconds);
    int RegisterGMThread(char* name, void(*func)(void*lpParameter), void* lpParameter);
    void Scheduling();
    

    ThreadSwitch.cpp

    #include "ThreadSwitch.h"
    
    
    //定义线程栈的大小
    #define GMTHREADSTACKSIZE 0x80000
    
    //当前线程的索引
    int CurrentThreadIndex = 0;
    
    //线程的列表
    GMThread_t GMThreadList[MAXGMTHREAD] = { NULL, 0 };
    
    //线程状态的标志
    enum FLAGS
    {
    	GMTHREAD_CREATE = 0x1,
    	GMTHREAD_READY = 0x2,
    	GMTHREAD_SLEEP = 0x4,
    	GMTHREAD_EXIT = 0x8,
    };
    
    //启动线程的函数
    void GMThreadStartup(GMThread_t* GMThreadp)
    {
    	GMThreadp->func(GMThreadp->lpParameter);
    	GMThreadp->Flags = GMTHREAD_EXIT;
    	Scheduling();
    
    	return;
    }
    
    //空闲线程的函数
    void IdleGMThread(void* lpParameter)
    {
    	printf("IdleGMThread---------------\n");
    	Scheduling();
    	return;
    }
    
    //向栈中压入一个uint值
    void PushStack(unsigned int** Stackpp, unsigned int v)
    {
    	*Stackpp -= 1;//esp - 4
    	**Stackpp = v;//
    
    	return;
    }
    
    //初始化线程的信息
    void initGMThread(GMThread_t* GMThreadp, char* name, void(*func)(void* lpParameter), void* lpParameter)
    {
    	unsigned char* StackPages;
    	unsigned int* StackDWordParam;
    
    	//结构初始化赋值
    	GMThreadp->Flags = GMTHREAD_CREATE;
    	GMThreadp->name = name;
    	GMThreadp->func = func;
    	GMThreadp->lpParameter = lpParameter;
    
    	//申请空间
    	StackPages = (unsigned char*)VirtualAlloc(NULL, GMTHREADSTACKSIZE, MEM_COMMIT, PAGE_READWRITE);
    	//初始化
    	ZeroMemory(StackPages, GMTHREADSTACKSIZE);
    	//初始化地址地址
    	GMThreadp->initialStack = StackPages + GMTHREADSTACKSIZE;
    	//堆栈限制
    	GMThreadp->StackLimit = StackPages;
    	//堆栈地址
    	StackDWordParam = (unsigned int*)GMThreadp->initialStack;
    
    	//入栈
    	PushStack(&StackDWordParam, (unsigned int)GMThreadp);		//通过这个指针来找到线程函数,线程参数
    	PushStack(&StackDWordParam, (unsigned int)0);				//平衡堆栈的(不用管)
    	PushStack(&StackDWordParam, (unsigned int)GMThreadStartup);	//线程入口函数 这个函数负责调用线程函数
    	PushStack(&StackDWordParam, (unsigned int)5);				//push ebp
    	PushStack(&StackDWordParam, (unsigned int)7);				//push edi
    	PushStack(&StackDWordParam, (unsigned int)6);				//push esi
    	PushStack(&StackDWordParam, (unsigned int)3);				//push ebx
    	PushStack(&StackDWordParam, (unsigned int)2);				//push ecx
    	PushStack(&StackDWordParam, (unsigned int)1);				//push edx
    	PushStack(&StackDWordParam, (unsigned int)0);				//push eax
    
    	//当前线程的栈顶
    	GMThreadp->KernelStack = StackDWordParam;
    
    	//当前线程状态
    	GMThreadp->Flags = GMTHREAD_READY;
    
    	return;
    }
    
    //将一个函数注册为单独线程执行
    int RegisterGMThread(char* name, void(*func)(void*lpParameter), void* lpParameter)
    {
    	int i;
    	for (i = 1; GMThreadList[i].name; i++)
    	{
    		if (0 == _stricmp(GMThreadList[i].name, name))
    		{
    			break;
    		}
    	}
    	initGMThread(&GMThreadList[i], name, func, lpParameter);
    
    	return (i & 0x55AA0000);
    }
    
    //切换线程	1:当前线程结构体指针 2:要切换的线程结构体指针
    __declspec(naked) void SwitchContext(GMThread_t* SrcGMThreadp, GMThread_t* DstGMThreadp)
    {
    	__asm
    	{
    		//提升堆栈
    		push ebp
    		mov ebp, esp
    
    		//保存当前线程寄存器
    		push edi
    		push esi
    		push ebx
    		push ecx
    		push edx
    		push eax
    
    		mov esi, SrcGMThreadp
    		mov edi, DstGMThreadp
    
    		mov[esi + GMThread_t.KernelStack], esp
    
    		//经典线程切换,另外一个线程复活
    		mov esp, [edi + GMThread_t.KernelStack]
    
    		pop eax
    		pop edx
    		pop ecx
    		pop ebx
    		pop esi
    		pop edi
    
    		pop ebp
    		ret	//把startup(线程函数入口)弹到eip 执行的就是线程函数了
    	}
    }
    
    //这个函数会让出cpu,从队列里重新选择一个线程执行
    void Scheduling()
    {
    	int TickCount = GetTickCount();
    
    	GMThread_t* SrcGMThreadp = &GMThreadList[CurrentThreadIndex];
    	GMThread_t* DstGMThreadp = &GMThreadList[0];
    
    
    	for (int i = 1; GMThreadList[i].name; i++)
    	{
    		if (GMThreadList[i].Flags & GMTHREAD_SLEEP)
    		{
    			if (TickCount > GMThreadList[i].SleepMillsecondDot)
    			{
    				GMThreadList[i].Flags = GMTHREAD_READY;
    			}
    		}
    		if (GMThreadList[i].Flags & GMTHREAD_READY)
    		{
    			DstGMThreadp = &GMThreadList[i];
    			break;
    		}
    	}
    
    	CurrentThreadIndex = DstGMThreadp - GMThreadList;
    
    	SwitchContext(SrcGMThreadp, DstGMThreadp);
    
    	return;
    }
    
    void GMSleep(int MilliSeconds)
    {
    	GMThread_t* GMThreadp;
    	GMThreadp = &GMThreadList[CurrentThreadIndex];
    
    	if (GMThreadp->Flags != 0)
    	{
    		GMThreadp->Flags = GMTHREAD_SLEEP;
    		GMThreadp->SleepMillsecondDot = GetTickCount() + MilliSeconds;
    	}
    
    	Scheduling();
    	return;
    }
    

    main.cpp

    #include "ThreadSwitch.h"
    
    extern int CurrentThreadIndex;
    
    extern GMThread_t GMThreadList[MAXGMTHREAD];
    
    void Thread1(void*)
    {
    	while (1)
    	{
    		printf("Thread1\n");
    		GMSleep(500);
    	}
    }
    
    void Thread2(void*)
    {
    	while (1)
    	{
    		printf("Thread2\n");
    		GMSleep(200);
    	}
    }
    
    void Thread3(void*)
    {
    	while (1)
    	{
    		printf("Thread3\n");
    		GMSleep(10);
    	}
    }
    
    void Thread4(void*)
    {
    	while (1)
    	{
    		printf("Thread4\n");
    		GMSleep(1000);
    	}
    }
    
    
    int main()
    {
    	RegisterGMThread((char*)"Thread1", Thread1, NULL);
    	RegisterGMThread((char*)"Thread2", Thread2, NULL);
    	RegisterGMThread((char*)"Thread3", Thread3, NULL);
    	RegisterGMThread((char*)"Thread4", Thread4, NULL);
    
    	for (;;)
    	{
    		Sleep(20);
    		Scheduling();
    	}
    
    	return 0;
    }
    

    关键结构体

    //线程信息的结构
    typedef struct
    {
    	char* name;							//线程名 相当于线程ID
    	int Flags;							//线程状态
    	int SleepMillsecondDot;				//休眠时间
    
    	void* initialStack;					//线程堆栈起始位置
    	void* StackLimit;					//线程堆栈界限
    	void* KernelStack;					//线程堆栈当前位置,也就是ESP
    
    	void* lpParameter;					//线程函数的参数
    	void(*func)(void* lpParameter);		//线程函数
    }GMThread_t;
    

    在Windows里,每一个线程都有一个结构体叫ETHREAD,这个结构体就是模拟的线程结构体,只不过把和线程无关的属性删掉了,只保留了线程切换必须要用到的成员

    调度链表

    线程在不同的状态下存储的位置是不一样的,正在运行中的线程在KPCR里,等待状态中的线程在等待链表里,就绪的线程在32个就绪链表里

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uk3ROhFd-1581302990410)(assets/1581237190834.png)]

    模拟线程切换的代码没有写的那么复杂,而是用一个数组来表示所有线程,然后通过Flags来区分线程的状态。所谓的创建线程,就是创建一个结构体,并且挂到这个数组里,此时线程处于创建状态,这个创建的过程就结束了。

    这里面有一个小细节,在这个数组里存结构体的时候,是从下标为1的位置开始存的,而不是从最开始的位置开始存的;最开始的这个位置是给当前的线程用的

    当我们把结构体挂到数组以后,当前线程处于创建状态,还不能进行调度;因为如果一个线程想要执行,一定要有自己的初始化的一些环境,例如寄存器的值要确定,当前线程的堆栈在哪里也要确定。现在仅仅有这么一个结构体,这些初始化的值还没有。

    所以接下来还要做一件非常重要的事情,就是初始化当前线程的堆栈

    初始化线程堆栈

    在这里插入图片描述

    当我们创建一个线程的时候,第一件事情就是为线程结构体赋值,把线程名 线程函数以及线程函数的参数填充到这个结构体。

    当这些值赋好了以后,当前的线程处于创建状态

    在这里插入图片描述

    接下里就要为当前的线程准备一份堆栈。首先通过VirtualAlloc函数申请了一个堆栈。然后给堆栈的相关参数赋值。当线程初始化代码执行完成之后,线程状态如图所示:

    在这里插入图片描述

    灰色的部分就是给当前线程分配的一块堆栈,VirtualAlloc分配的地址加上堆栈的大小指向的就是堆栈开始的位置,中间的一部分就是这个线程所需要的堆栈。

    StackLimit指向当前堆栈的边界。KernelStack是当前线程的栈顶,相当于ESP
    在这里插入图片描述

    接下来往堆栈里push了一堆数据,这些数据就是当线程开始执行的时候必须要用到的一些数据。

    所谓的创建线程就是创建一个线程结构体,所谓的初始化线程就是为当前的线程再准备一份堆栈

    线程切换

    被动切换

    接下来看一下线程是如何进行调度的

    在这里插入图片描述

    当代码执行到for循环的时候,已经创建好了四个线程。也就是说四个线程结构体已经挂到了数组里,四个线程所需要的堆栈也分配完成了。

    接着代码模拟系统时钟每隔20毫秒进行线程切换,系统时钟的存在会让线程每隔一段时间进行切换,这种切换是被动的。

    主动切换

    还有一种切换是主动切换

    在这里插入图片描述

    这里的GMSleep模拟的是W32 API,只要进程调用了API,就会主动产生线程切换。

    接下来看看线程是如何进行主动切换的,跟进GMSleep这个函数,这里要把它当成所有的W32 API

    在这里插入图片描述

    这里会修改线程的状态为Sleep,然后调用Scheduling函数进行线程切换和调度

    线程调度

    那么线程是如何进行调度的呢?

    在这里插入图片描述

    这个函数首先会做一件事情,就是遍历这个数组,找到第一个处于READY状态的线程

    在这里插入图片描述

    真正进行线程切换的是SwitchContext函数,这个函数有两个参数,第一个参数当前线程的线程结构体指针,第二个参数是要切换的线程的线程结构体指针。

    在这里插入图片描述

    这个函数首先执行了一堆压栈的操作,将当前线程用到的寄存器存储到堆栈里。

    在这里插入图片描述

    此时的ESI是当前线程的结构体指针,EDI里存储的是要切换的线程结构体指针。接着把当前线程的ESP存储在当前线程的KernelStack里。

    然后再把要切换的线程的KernelStack赋值给ESP,此时堆栈切换,另一个线程复活,如图:
    在这里插入图片描述

    此时当前的ESP指向目标线程的EAX

    在这里插入图片描述

    接着执行一系列pop指令使线程的堆栈指向Startup,接着ret将Startup(线程的函数入口)弹到eip,接下来执行的就是线程函数了。

    这个函数是线程切换的核心,也是两个线程的转折点,上半部分一个进程进来,下半部分另一个线程出去。

    总结

    1. 线程不是被动切换的,而是主动让出CPU
    2. 线程切换并没有使用TSS来保存寄存器,而是使用堆栈
    3. 线程切换的过程就是堆栈切换的过程
    展开全文
  • 进程切换与线程切换的区别?

    万次阅读 多人点赞 2019-07-25 17:00:04
    注意这个题目问的是进程切换与线程切换的区别,不是进程与线程的区别。当然这里的线程指的是同一个进程中的线程。 这个问题能很好的考察面试者对进程和线程的理解深度,有比较高的区分度。 要想正确回答这个问题,...

    注意这个题目问的是进程切换与线程切换的区别,不是进程与线程的区别。当然这里的线程指的是同一个进程中的线程。

    这个问题能很好的考察面试者对进程和线程的理解深度,有比较高的区分度。

    要想正确回答这个问题,面试者需要理解虚拟内存。

     

    虚拟内存解放生产力

    对于程序员来说,我们在编程时实际上是不怎么操心内存问题的,对于使用Java、Python、JavaScript等类型语言的程序员来说更是如此,自动内存回收机制的引入使得使用这类语言的程序员几乎完全不用关心内存问题;即使对于编译型语言C/C++来说,程序员需要关心的也仅仅是内存的申请和释放。

    总的来说,作为程序员(无论使用什么类型的语言)我们根本就不关心数据以及程序被放在了物理内存的哪个位置上(设计实现操作系统的程序员除外),我们可以简单的认为我们的程序独占内存,比如在32位系统下我们的进程占用的内存空间为4G;并且我们可以申请超过物理内存大小的空间,比如在只有256MB的系统上程序员可以申请1G大小的内存空间,这种假设极大的解放了程序员的生产力。

    而这种假设实现的背后功臣就是虚拟内存。

     

    什么是虚拟内存

    虚拟内存是操作系统为每个进程提供的一种抽象,每个进程都有属于自己的、私有的、地址连续的虚拟内存,当然我们知道最终进程的数据及代码必然要放到物理内存上,那么必须有某种机制能记住虚拟地址空间中的某个数据被放到了哪个物理内存地址上,这就是所谓的地址空间映射,也就是虚拟内存地址与物理内存地址的映射关系,那么操作系统是如何记住这种映射关系的呢,答案就是页表,页表中记录了虚拟内存地址到物理内存地址的映射关系。有了页表就可以将虚拟地址转换为物理内存地址了,这种机制就是虚拟内存。

    每个进程都有自己的虚拟地址空间,进程内的所有线程共享进程的虚拟地址空间。

    现在我们就可以来回答这个面试题了。

     

    进程切换与线程切换的区别

    进程切换与线程切换的一个最主要区别就在于进程切换涉及到虚拟地址空间的切换而线程切换则不会。因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。

    举一个不太恰当的例子,线程切换就好比你从主卧走到次卧,反正主卧和次卧都在同一个房子中(虚拟地址空间),因此你无需换鞋子、换衣服等等。但是进程切换就不一样了,进程切换就好比从你家到别人家,这是两个不同的房子(不同的虚拟地址空间),出发时要换好衣服、鞋子等等,到别人家后还要再换鞋子等等。

    因此我们可以形象的认为线程是处在同一个屋檐下的,这里的屋檐就是虚拟地址空间,因此线程间切换无需虚拟地址空间的切换;而进程则不同,两个不同进程位于不同的屋檐下,即进程位于不同的虚拟地址空间,因此进程切换涉及到虚拟地址空间的切换,这也是为什么进程切换要比线程切换慢的原因。

    有的同学可能还是不太明白,为什么虚拟地址空间切换会比较耗时呢?
    这也是面试官紧接会问的第二个问题。
    关于这个问题的答案,关注公众号“码农的荒岛求生”并回复“地址切换”几个字你就能得到答案啦。

    最后,有很多同学问有没有书单,我也仔细回想自己认真读过的计算机书籍,在这里也给出自认为很经典的几本,书单这东西贵精不贵多,我在这里精心挑选了10本 ,不要贪心,如果你真能把这里推荐的 10 本书读通,可以说你已经能超越 90% 的程序员了,供大家参考:程序员必看经典书单
     

    总结

    虚拟内存是现代操作系统极其重要的一部分,当然在这里限于篇幅我们只能简单介绍,关于虚拟内存的详细讲解见后续操作系统教程,敬请期待 😃

    更多计算机内功文章,欢迎关注微信公共账号:码农的荒岛求生

    在这里插入图片描述

    计算机内功决定程序员职业生涯高度

    展开全文
  • linux 线程切换

    千次阅读 2018-10-11 22:35:21
    1.什么引起线程切换 时间片轮转 线程阻塞 线程主动放弃时间片
  • kvm线程-004-线程切换

    千次阅读 2019-07-23 15:32:48
    本文介绍kvm中线程切换的实现. 通过kvm线程-001的介绍.可以得到如下事实: 对于通过new Thread() 创建的线程,其分配的时间片为:priority * 1000.其代码如下: void Java_java_lang_Thread_setPriority0(void) { ...
  • gdb多线程下禁止线程切换

    千次阅读 2016-11-25 13:45:40
    多线程下禁止线程切换: set scheduler-locking on|off|step
  • RxJava 线程切换流程

    2020-06-08 18:56:56
    RxJava 线程切换流程 在客户端开发中有些时候需要多线程切换 作为Android开发者中最经典的模型是Handler+Looper+Message,这种写法稍有不慎会有内存泄漏的风险,后出了RxJava+RxAndroid 因为使用Rxjava切换线程比较...
  • linux线程切换和进程切换的方法

    千次阅读 2019-06-08 02:35:00
    进程切换和线程切换在效率上略有不同,相比之下进程切换耗费资源较大,效率要差一些,原因可以参考下面这篇文章 原文链接:https://www.jb51.net/article/102004.htm 进程切换分两步: 1.切换页目录以使用新的地址...
  • RxJava的线程切换

    2018-03-31 18:48:35
    在Android开发中,RxJava的线程切换是很重要的操作,那使用线程切换我们又要注意些什么呢,本篇教你学会如何流畅的使用RxJava在Android中的线程切换。那么废话这么多,就来看一看RxJava中的Scheduler的线程调度。 ...
  • 线程切换的开销

    2020-09-15 23:04:57
    从单线程应用到多线程应用带来的不仅仅是好处。也会带来开销。不要仅仅在一个应用中使用多线程仅仅是因为你能够(会)使用多线程。你应该能确定使用多线程带来的好处要远远多于它带来...线程切换开销 当一个cpu从一个线
  • 进程切换与线程切换的代价比较

    万次阅读 2018-05-30 17:18:45
    切换内核栈和硬件上下文对于linux来说,线程和进程的最大区别就在于地址空间,对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的。切换的性能消耗:1、线程上下文切换和进程上下问切换一个最主要的区别...
  • RxJava线程切换原理

    千次阅读 2016-12-21 20:15:47
    RxJava在圈子里越来越火,相信很大的一个原因就是它的线程切换。它的线程切换可以用优雅来形容,链式调用,简单、方便。今天,就让我们来窥探一下RxJava的线程切换原理。本次拆轮子,还是按原样,通过小例子,研读...
  • 线程及切换线程:比...即PC的切换,线程的切换也是进程切换的重要组成部分进程及切换进程的切换包括:指令的切换和内存(映射表)的切换(资源的切换)线程切换举例用户打开某个网站浏览网页,网页内容有文本、图...
  • 5.模拟线程切换

    2018-10-19 18:18:02
    模拟Windows线程切换(ThreadSwitch) 正在运行的线程在KPCR里,等待的线程在等待链表中,调度中的线程在那32个调度链表中。 创建它是从下标1的位置开始存的而不是0,因为main需要一个线程。 创建的线程还不能调度还...
  • 进程切换与线程切换的区别 为什么切换线程比切换进程开销小? 由于保存线程的上下文明显比进程的上下文小,因此系统切换线程时,必然开销更小。 而且进程涉及到虚拟空间,页表的切换
  • 线程切换的几个核心问题 切换线程切换了什么 什么设备管理了切线程的切换 CPU上下文 内核态,用户态 如何量化线程切换引起的开销 总的切换流程
  • 文章目录主动切换(调用API)KiSwapContext函数分析哪些API调用了SwapContext函数总结时钟中断切换如何中断一个正在执行的程序系统时钟时钟中断的...之前我们已经学习了模拟Windows线程切换的代码,里面用于线程切换的...
  • 绝大部分系统内核函数都会调用SwapContext函数,来实现线程的切换,那么这种切换是线程主动调用的。 如何中断一个正在执行的程序? 异常 比如缺页,或者INT N指令 ...线程切换的几种情况: 1) 主动调用AP...
  • Windows进程与线程学习笔记(六)—— 线程切换前言主动切换时钟中断异常处理 前言 一、学习自滴水编程达人中级班课程,官网:https://bcdaren.com 二、海东老师牛逼! 主动切换 Windows中绝大部分API都调用了...
  • Rxjava2 线程切换

    千次阅读 2018-05-28 10:14:30
    Rxjava2的线程切换使用subscribeOn、observeOn实现。 subscribeOn subscribeOn用于指定subscribe时,所处的线程,只可指定一次。 Flowable.create(new FlowableOnSubscribe&lt;String&gt;() { @...
  • 线程切换比进程快

    2020-11-30 17:53:57
    我们都知道线程切换的开销比进程切换的开销小,那么小在什么地方?切换的过程是怎样的? 无论是在多核还是单核系统中,一个CPU看上去都像是在并发的执行多个进程,这是通过处理器在进程间切换来实现的。 操作系统...
  • 本文从linux中的进程、线程实现原理开始,扩展到linux线程模型,最后简单解释线程切换的成本。 刚开始学习,不一定对,好心人们快来指正我啊啊啊!!! linux中的进程与线程 首先明确进程与进程的基本概念: ...
  • 先说结论:协程切换比线程切换快主要有两点: (1)协程切换完全在用户空间进行,线程切换涉及特权模式切换,需要在内核空间完成; (2)协程切换相比线程切换做的事情更少。 协程切换 协程切换只涉及基本的CPU...
  • 前言 突发奇想想搞一个同步切换线程的Kotlin协程,而不用各种withContext(){},可以减少嵌套且逻辑更清晰,想实现的结果如下图: 分析 实现我们想要的结果,...其实控制线程切换是协程库内内置的一个拦截器类:Continu
  • Rxjava线程切换原理

    千次阅读 2018-12-15 09:57:23
    昨天朋友面试被问Rxjava线程切换原理,当时有点蒙圈了,这里我写一篇博客讲一下。 PS:建议您对 RxJava 有一些了解或使用经验再看此文章,推荐结合源码品尝RxJava入门文章【Rxjava详解】  [给 Android 开发者的 ...
  • 进程和线程切换的开销

    千次阅读 2020-03-19 13:19:28
    线程切换开销 切换CPU上下文 切换内核栈 虚拟地址空间 每个进程都有自己的虚拟地址空间,进程内的所有线程共享进程的虚拟地址空间。 虚拟内存是操作系统为每个进程提供的一种抽象,每个进程都有属于自己的、...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 26,231
精华内容 10,492
关键字:

线程切换