计算机漫游4-Computer Systems笔记之异常1
清晨十点钟,一股电流准时地唤醒了CPU,随后主板城和linux大佬都开始了忙碌的工作
小主今天学习了下异常和系统调用,迫不及待地在shell大厅调出了vim小弟,敲下了这段代码:
int main()
{
write(1, “hello, world!\n”, 13);
exit(0);
}
Printf函数一看,“糟了,以后要小主都用wirte,那我地位岂不是不保了”,正巧,printf看到执行时间居然比他慢好多,而且打印个字符串还得传入三个参数,于是立马揪着小辫子不依不挠地和write争吵起来。
“你们别吵了,你俩其实根本不是一个类别的函数啊!我给你们先画个关系图:
你们仔细看着,write和exit是属于“system calls”这一块儿的,系统级函数,是内核提供给applications的接口,属于系统的一部分,可以直接申请内核服务;printf,你是属于“library routines”库函数这一块的,属于用户级函数,是语言或applications的一部分,你想要申请内核服务的话,还得去调用wirte呢!”
|使用trace命令:trace ./hello 可以看见最后printf程序调用了write
“可是我hello World时间更快,而且参数也比write它少好多”printf据理力争
“其实所有库函数不一定都像你一样参数少的,之前没有库函数的时候,小主的前辈们在长期敲代码中发现使用system call非常不方便。比如说,我想把linux上的程序拿到windows上去跑,但他们两者的系统级函数不仅实现方式不一样,就连提供给我们的函数名、参数都不同,代码的可移植性太差了,每次都不得不花时间浪费在重写程序上头。于是前辈们就想出来开发一个库函数将常用的系统级函数都封装起来。”
“虽说是这样,但是按道理说我们系统级函数不应该执行更快吗?”write和exit不解
“原因在缓冲,因为总是要进行大量的读写操作,系统调用就得频繁地进行上下文切换(从用户模式切换到内核模式),于是前辈们就让printf在内存中开辟一块缓冲区,这样就能避免频繁的I/O了,所以速度比你们要快些”
“那我们岂不是没啥用了吗?”write和exit垂头丧气
“不是的,你们俩啊各有千秋,看下面这张表就懂得了“
谈到系统调用,就不得不说说异常了,因为系统调用是陷阱在用户程序与内核之间的一个接口,而陷阱又是异常的一个分类。
先回忆下CPU老实头执行指令的过程:
程序通过编译系统家四兄弟被编译为可执行目标文件-->从磁盘送入主存-->通过公交总站(北桥)-->坐上去往CPU寄存器的专车(系统总线)到达寄存器-->CPU从其寄存器中一条条取得指令,同时PC中存着下一条指令的地址
今天的故事发生在CPU老实头一条条执行指令那会儿
”CPU老实头,你咋还不赶紧执行命令!“Linux大佬又再催促CPU老实头
”Linux大佬,不是我不执行啊,平时我都是处理一些平滑的控制流,可是你看,这条ai号指令下边却要我去执行ai+2号指令,我没有可以响应这些状况的程序啊!“
”额,这还真是个问题,让我瞅瞅为啥会这样,哦!原来这是程序中的跳转指令,他们主要是为了程序能够对由程序变量表示的内部程序状态的变化做出反应,怪不得不平滑呢,这样吧,我把这种情况称为‘异常’,再把你可能遇到的每种类型的异常都弄一个异常处理程序,然后又给他们每个都分配一个唯一的非负整数异常号(exception number)。
你每次遇到控制流突变,就去这些异常号里头找到其对应异常处理程序的地址,把控制权给他,让他帮你处理“
”那我要去哪里取这些异常号哟?“
”每次开机后我就先给你准备好这个跳转表,放到你的寄存器里头吧“
”哦哦!好的,那我专门分一个‘异常表基址寄存器’出来,Linux大佬你就把这个异常号跳转表的起始地址放进来就好了,因为我容量小,能节省一点是一点,我自己到时候顺着这个地址去找大佬提供的异常处理程序就行!“
”等等,你别急着溜,你记性不好,我再给你画一遍你的工作过程!省的你到时候又来找我问,看好:
“谢谢Linux大佬!”
CPU老实头高兴地继续工作去了。
总结:
异常是控制流的突变,响应CPU状态发生的变化,这种变化称为“事件”。
在运行时(当系统在执行某个程序时),CPU检测到发生了一个事件,并且确定了相应的异常好k。随后,CPU触发异常,方法是执行简介过程调用,通过异常表的条目k转到相应的处理程序。
异常表的起始地址存放在一个叫做“异常基址寄存器(Exception table base register)”的特殊CPU寄存器里。