页面加载中,请稍候
来源:Amadeus发布时间:2021-08-10561浏览
询问 AI计算机系统中有很多程序员习以为常但又十分神秘的存在:函数调用、系统调用、进程切换、线程切换以及中断处理。
函数调用能让程序员提高代码可复用性,系统调用能让程序员向操作系统发起请求,进程线程切换让多任务成为可能,中断处理能让操作系统管理外部设备。
这些机制是计算机系统中的基石,可是你知道这些机制是如何实现的吗?
这篇文章将告诉你答案,其背后的实现如此优雅且一致。
速度与激。。寄存器
你有没有想过,CPU为什么需要寄存器?原因很简单:速度。
通常CPU可以在一个时钟周期内访问一次寄存器,CPU访问内存的速度大概要比访问寄存器慢100倍左右。因此如果CPU没有寄存器而完全依赖内存的话,那么计算速度将比现在慢的多。作为程序员来说,当我们使用高级语言编写的程序时,其操作的数据都存放在内存中,而对于负责运算类的机器指令来说其操作的数据都存放在寄存器中。实际上寄存器和内存没有什么本质的区别,都是用来存储信息的。当然,除了临时保存中间计算结果之外,还有很多有趣的寄存器。根据用途,寄存器有很多类型,但是,我们感兴趣的有以下几种寄存器。
最为程序员我们知道,函数在运行时会有一块独立的内存空间,用来保存函数内定义的局部变量、传递的参数以及返回值信息等,这块独立的内存空间就叫栈帧,随着函数调用层次的加深,栈帧也随之增加;当函数调用完成后栈帧由按照相反的顺序依次减少,这些栈帧就构成了栈区。
函数的运行时栈信息是关于程序运行状态最重要的信息之一。那么其它的之一呢?
原来,奥秘就藏在指令地址寄存器中。程序在启动时会把机器指令的首地址写入到PC寄存器中,这样CPU需要做的就是根据PC寄存器中的地址去内存中取出指令。通常来说,指令都是顺序执行的,也就是说PC寄存器中的值不断的+1,但对于一些涉及控制转移的机器指令来说,这些指令会把一个新的指令地址放到PC寄存器中,这包括分支跳转——也就是if语句、函数调用以及返回等。控制了CPU的PC寄存器就掌握了CPU的航向,机器指令自己会根据执行状态指挥CPU接下来该去执行哪些指令,这才是真正的自动驾驶,非常amazing有没有!
指令地址寄存器是关于程序运行时状态另一个最重要的信息之一。
那么我们怎么知道当前程序运行在哪种状态呢?答案就在CPU内部的状态寄存器中,该寄存器中有特定的比特位来标记当前CPU正工作在哪种模式下。现在你应该知道寄存器的重要作用了吧。
而程序在运行过程中逃不出函数调用、系统调用、进程切换、线程切换以及中断处理这几项操作,由此可见上下文信息的保存和恢复在计算机科学中重要的作用。那么上下文信息又该如何保存呢?保存到哪里呢?又该怎么恢复呢?函数调用、系统调用、进程切换、线程切换以及中断处理又是怎样实现的呢?A-> B -> C那么很显然只有完成任务C你才能继续任务B,完成任务B才能继续任务A,因此任务完成顺序是这样的:C->B -> A我们可以看到任务完成顺序和任务依赖顺序是相反的:先来的反而后完成。这天然适合栈来表示。
这里特别值得注意的是,栈是一种机制,和其本身是怎么实现的没有关系,你可以用软件来实现栈,也可以用硬件来实现栈。栈是一种如此简单的结构,却又如此强大。栈是实现计算机系统的一种极为重要的基础机制,接下来的讲解就能让你意识到栈的重要作用。
这块用来保存context的空间就是栈帧,当然这里不止保存上下文信息,还保存有函数参数,局部变量等信息。从这里我们可以看到,栈+上下文让我们实现了函数调用。当然限于篇幅,这里关于函数运行时栈的讲解非常简略,关于这一部分更加详细的讲解关注公众号码农的荒岛求生并回复关键词运行时栈即可。
当用户线程需要请求操作系统服务时利用系统调用切换到内核模式,这时内核开始代表该用户态线程执行,内核的执行过程需要的运行时栈就放在了上图中的内核栈中。让我们来看一下系统调用的过程。开始时,程序运行在用户态,此时内核栈还是空的,假设用户态执行到functionD时需要请求操作系统服务,假设functionD需要调用open函数,该函数内部包含就系统调用,被编译器翻译后会生成一条int指令,此时CPU执行到该指令:
该指令的执行将触发CPU的状态切换,此时CPU从用户态切换为内核态,并找到该用户态线程对应的内核线程,注意重点来了,此时用户态线程的执行上下文信息(寄存器信息)被保存在内核栈中:
此后CPU开始在内核中执行open相关的操作,后续内核栈会像用户态运行时栈一样随着函数的调用和返回增长以及减少:
当系统调用执行完成后,根据内核栈中保存的用户态程序上下文信息恢复CPU状态,并从内核态切换回用户态,这样用户态线程就可以继续运行了:
现在你应该明白这个过程了吧。那么操作系统为什么要这么麻烦的费心维护用户态以及内核态呢?用户态程序为什么要利用系统调用来请求操作系统服务呢?不能直接像普通函数一样调用操作系统的代码吗?关于这些问题的答案,你可以参考博主的深入理解操作系统第2章,关注公众号码农的荒岛求生并回复操作系统这几个字即可。中断处理函数是没有自己特定的栈的,中断处理函数依赖内核栈来完成中断处理。
中断处理函数有自己特定的栈,被称之为ISR栈,ISR是interrupt service routine的简写,即中断处理函数栈。由于处理中断的是CPU,因此在这种方案下每个CPU都有一个自己的中断处理栈。
为了简单起见,我们以中断处理函数共享内核栈为例来讲解。实际上你会发现中断处理函数和系统调用比较类似,不同的是系统调用是用户态程序主动发起的,而中断处理是外部设备发起的,也就是说CPU在执行完用户态的任何一条指令后都可能因为中断产生而暂停当前程序的执行转而去执行中断处理函数,如图所示:
此后的故事和系统调用类似,CPU从用户态切换为内核态,并找到该用户态线程对应的内核线程,并将用户态线程的执行上下文信息保存在内核栈中:
此后CPU跳转到中断处理函数起始地址,中断处理函数在运行过程中内核栈会像用户态运行时栈一样随着函数的调用和返回增长以及减少:
当中断处理函数执行完成后,根据内核栈中保存的用户态程序上下文信息恢复CPU状态,并从内核态切换回用户态,这样用户态线程就可以继续运行了。每一次你敲击键盘、滑动鼠标、下载文件等都会有一次上述过程。关于中断处理更加完整的阐述参见博主深入理解操作系统第3章,关注公众号码农的荒岛求生并回复操作系统即可。既然你已经知道了中断是如何实现的,接下来让我们看下最有意思的线程切换是如何实现的。新闻来源:strongerHuang,文中所述为作者独立观点,不代表icspec立场。更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj。
暂无评论哦,快来评论一下吧!
2026-07-16

2026-07-08

2026-07-22