clo3d代做网站,动图从哪个网站做,什么APP可以做网站,wordpress招聘模板作者 | 陆小凤来源 | 码农的荒岛求生我们都知道操作系统最重要的功能之一是多任务能力#xff0c;也就是可以运行超过CPU数量的程序——即进程#xff0c;要想实现这一功能就必须具备将有限的CPU资源在多个进程之间分配的能力#xff0c;在程序员看来#xff0c;我们的程序… 作者 | 陆小凤来源 | 码农的荒岛求生我们都知道操作系统最重要的功能之一是多任务能力也就是可以运行超过CPU数量的程序——即进程要想实现这一功能就必须具备将有限的CPU资源在多个进程之间分配的能力在程序员看来我们的程序在一直运行而在CPU看来程序其实在“走走停停”程序的一走一停就涉及到进程切换那么进程切换的本质是什么呢从本质上讲函数调用和进程切换是非常类似的有的同学可能会有疑问这怎么可能呢别着急看完这篇你就明白啦。函数调用我们先来看一下函数调用函数调用是这样的A函数调用B函数当B函数执行完成时会跳转回A函数此时A函数和B函数位于同一个进程void B() {...
}
void A() {...
}这个过程是这样的B函数执行完成后会将控制权转给A所谓控制权是指告诉CPU继续执行函数A。but你有没有想过A函数调用B函数B函数执行完成时一定要跳转回A函数吗不一定的既然B函数可以将控制权转给A那么就能将控制权转给函数C。听上去很神奇有没有A函数调用B函数当B函数执行完成时竟然可以跳转到C函数可这该怎样做到呢让我们来看一段神奇的代码。一段神奇的代码有这样一段代码#include stdio.h
#include stdlib.hvoid funcC() {printf(jump to funcC !!!\n) ;exit(-1) ;
}void funcB() {long *p NULL ;p (long*)p ;*(p2) (long)funcC ;
}void funcA() {funcB();
}int main() {funcA() ;return 0 ;
}想一想这段代码运行后会输出什么有的同学可能会说main函数调用了funcAfuncA函数调用了funcBfuncB函数看上就是一堆赋值执行完成后返回了funcAfuncA又返回main函数因此执行完毕后什么都不会输出。真的是这样的吗让我们编译运行一下(小风哥使用的是5.2.0版gcc64位机器未开启编译优化不同编译器版本运行效果可能不同)。$ ./a.out
jump to funcC !!!有的同学也许会大吃一惊这怎么可能这段明明没有调用funcC可为什么funcC函数却运行了程序员经常说“代码之中没有秘密”这句话不全对应该是“机器指令中没有秘密”后来我想了想这句话也不全对因为对我们来说CPU是如何执行机器指令这回事其实对我们来说是黑盒的我们只能从大体的原理来说CPU是怎样执行一条机器指令的但这里真正的细节只有处理器生产商比如intel/AMD等知道而一些魔鬼恰恰就在这些细节中。魔鬼在细节扯远了让我们回到这篇文章的主题先来看看生成的机器指令是什么样的0000000000400586 funcC:400586: 55 push %rbp400587: 48 89 e5 mov %rsp,%rbp40058a: bf 74 06 40 00 mov $0x400674,%edi40058f: e8 bc fe ff ff callq 400450 putsplt400594: bf ff ff ff ff mov $0xffffffff,%edi400599: e8 e2 fe ff ff callq 400480 exitplt000000000040059e funcB:40059e: 55 push %rbp40059f: 48 89 e5 mov %rsp,%rbp4005a2: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp)4005a9: 00 4005aa: 48 8d 45 f8 lea -0x8(%rbp),%rax4005ae: 48 89 45 f8 mov %rax,-0x8(%rbp)4005b2: 48 8b 45 f8 mov -0x8(%rbp),%rax4005b6: 48 83 c0 10 add $0x10,%rax4005ba: ba 86 05 40 00 mov $0x400586,%edx4005bf: 48 89 10 mov %rdx,(%rax)4005c2: 90 nop4005c3: 5d pop %rbp4005c4: c3 retq这些指令在说什么呢我们先来看普通的函数调用当函数B执行完毕后此时的栈帧为函数B的最后一条机器指令通常为ret这条指令的目的是将当前栈顶的内容弹出到%rip寄存器中CPU会根据rip中的值从内存中取出指令并执行显然ret指令会将之前保存的返回地址放入rip寄存器中这样CPU就可以继续执行A函数中的后续代码了也就是a这行代码。有的同学可能已经看出来如果我们有办法修改A栈帧上的返回地址不就能实现“指哪打哪”了吗实际上代码中“*(p2) (long)funcC ;”这行会将本来指向funcB的返回地址修改为指向funcC即这样当funcB函数运行完成后会直接跳转到funcC函数从而实现可控的执行流切换进程切换的本质与此别无二致只不过进程切换需要连带着把栈也切换过去(以及地址空间)同时还会保存被切换进程的上下文。有的同学可能已经看出来了上述过程叫做缓冲区溢出攻击要实现的目的和进程切换一样实现控制权的转移只不过缓冲区溢出攻击是非法的不符合预期的(符合黑客的预期但不符合操作系统设计者制定的游戏规则)而进程切换是合法的符合预期的(符合操作系统设计者的预期)。而有时(真正意义上的)黑客与操作系统设计者其实是一伙人。怎么样现在你应该对进程切换有较为直观的认知了吧当然真实的进程切换绝不像这里讲解的这样简单呦~往期推荐高并发下的 HashMap 为什么会死循环操作系统如何实现什么是宏内核、微内核Redis 内存满了怎么办这样置才正确如何在 Kubernetes Pod 内进行网络抓包点分享点收藏点点赞点在看