本质上讲,如果一个程序不使用任何库、系统调用,所有代码都是自包含的,它是可以直接在CPU上执行的,有疑问的话可以给我发邮件,我乐意受教。
于是问题就来了,一个用户态的程序在调度到CPU上开始执行,这个程序的指令流是否可以为所欲为。
很明显,这是不可能的,在没有内核的CPU上运行的指令流自己会照顾好自己,它确实可以为所欲为;
但在用户态的程序,如果没有系统调用的话你会发现它没有任何操作硬件的能力(就像不会吃饭的傻孩子)
所有要做点什么涉及硬件,你都能找到设计的很贴心的动态库或系统调用之类。
其实本质上的原理与虚拟化的原理是一样的,
用户态程序受到的限制更多,比如说不可能获得中断,不可能访问外设,仅能访问内核为它建立了映射的那一点点内存。
在此背景下,程序如何做到读取文件、存储数据、显示功能呢?
本质上,系统调用即syscall(可以简单理解为open(),close()等,但其实是被封装过的)为程序完成了这一切,
当然,操作系统用户态提供的库完成了很多很多工作,但是很抱歉,这些工作都是在用户态完成的,
一切真实的硬件操作(除了程序的那点内存)都是由syscall完成的。
可怜的用户态程序,这也是它的运行要依赖于操作系统的原因(先不考虑指令集差异引起程序跨平台的问题,还说没到那层)。
在之前知识不充足的时候,我有过很奇怪的想法,
程序是分为在用户态和内核态的,
当程序进入内核态以后,是不是好像产生了一个新的内核线程继续执行程序的?
或是依旧是当前进程,只不过其指令流变成了内核中的代码?
我当真给自己提了一个乱七八糟的问题,
不过现在我可以不知道全面不全面的回答。
程序,假设单进程程序,做为一股指令流(不要杠精的问多进程怎么办,就是多股指令流呗!),
当它执行到库函数的时候,指令流就不归程序管了,
这里说的程序是指我们可怜的小程序员码出来的代码,
当调用到库,库会接管了指令流中指令排布顺序也就是逻辑。
当程序执行到syscall,
我曾简单的想,这一时刻一定就是发生在程序员码的open()被执行的时候(我觉着指令集里有这个指令,是不是很傻),
是的,那时的我还太年轻。
但很明显事实并不是这样发生,open的调用,必然是需要头文件的支持的,open其实是个宏(这里面的故事我就不说了,太长),
而真实进入内核态,是open的用户态部分最终会执行一个指令(在x86是int,Aarch64是SVC),
这个指令会产生异常(好熟悉的两个字:异常,乱七八糟的一通说,竟然又找回到了异常),
要传递的参数会放在规定好的寄存器,
然后程序指令流就被截断了(我之所以说指令流被截断,是想表达用户态的指令不再被执行,不要怀疑,syscall是一种异常),
程序进入了内核态。
认真的讲,现在程序其实已经不归程序员管了,写内核的大神们为所有程序猿发福利:这段代码,我们为你们写!(豪迈语气)
于是回答上面的问题就得到了答案,
程序(哦,不,应该叫线程)还是那个线程,只不过指令流被截断,跑到内核里去了(就好像做梦?不过这梦也太美了,想啥来啥)。
对于是否会产生新的内核线程执行程序这个问题,就有点复杂了,有时候会产生的,看内核里怎么处理开心了,但是有一点是肯定的,这些都在内核控制之下。
其实linux内核的代码量是非常非常。。。(省略号表示多个非常)大的,但其实内核启动后活着的线程并不多,
就为这几个线程,搞了几G代码?(这得几个线程上辈子是不是拯救了无数次世界)不是的,相反,内核活着的线程逻辑大多非常简单;
于是就有一个为什么,
抛开平台相关启动代码不谈,其实这些代码里相当一部分是为用户要执行的程序准备的(是不是很贴心),
这也是为啥写内核大神会很豪迈的发福利。
当然,上面这些其实都是大家公知的,我用不精准但还算简单的语言演绎一下(如有不当之处,请立刻指出),
就是为后续的表述做一点铺垫,以免后续介绍的突兀。
来源:CSDN
作者:ytfy339784578
链接:https://blog.csdn.net/ytfy339784578/article/details/103945964