6.4 PDP 11/40的中断类型
系统中的中断主要有下面几种。
6.4.1 电传终端接口输入中断
电传终端接口用于链接PDP 11/40的总线和主要的输入、输出终端,当时该终端是电传打字机,这也是UNIX中用tty(teletypewriter)表示进程所使用的终端类型的原因,事实上电传接口还可以连接彩色显示器(CRT)等。输入中断是用户在终端输入字符时所触发的中断,它的优先级为4级。在中断中这是最低优先级,它的入口函数是klin,其服务函数的C语言部分是klrint函数。
6.4.2 电传终端接口输出中断
该中断在字符显示在电传接口所连接的终端时产生,它的优先级也是4级。它的入口函数是klou,其服务函数的C语言部分是klxint函数。
6.4.3 纸带打孔机输入中断
纸带打孔机是老式计算机经常使用的一种输入输出设备。纸带上有没有小孔代笔二进制数字1和0。这样,打上一系列小孔的纸带就代表了一连串的二进制数。打孔机在读取纸带时,以固定的速率、每隔固定的长度读取二进制数,如果该位置被打上小孔,则认为该值是1(或0,视设计而定),否则认为是相反的数字值。纸带输入中断的优先级是4级,入口函数是pcin,其服务函数的C语言部分是pcrint。
6.4.4 纸带打孔机输出中断
和输入相反,输出操作在光滑的纸带上按照需要输出的数据在特定的位置打上小孔。输出中断的优先级也是4级,入口函数是pcou,其服务函数的C语言部分是pcpint。
6.4.5 时钟中断
它是系统中优先级最高的中断,它的优先级为6。它又可分为两种:线频时钟中断(Line Clock)和可编程时钟中断(RTC—Real Time Clock)。就功能上而言,它们并无大的不同,所以具有相同的中断入口函数kwlp,中断服务函数的C语言部分是clock。
6.4.6 行打印机中断
它只有一个(输出)中断入口:lpou。它的优先级是4,中断服务函数的C语言部分是lpint。
6.4.7 磁盘读写中断
每一次磁盘读写操作完成后,都会产生磁盘读写中断。它的入口函数是rkio,其优先级为5,中断服务函数的C语言部分是rkintr。
6.5 一些常用函数
这里介绍一下m40.s中定义的一些常用函数。
6.5.1 特殊指令
mfpi, mtpi指令主要用于当前模式和之前模式之间的数据传输。mfpi从之前模式的指定地址处读取一个字存放到当前模式下的栈上;mtpi则相反,读取当前模式栈顶的字,写入到之前模式下的指定地址处。
6.5.2 fubyte(fuibyte)
函数原型:char fubyte(int addr);
功能描述:从之前模式下addr地址空间处读取1个字节,并返回其值。如果读取过程出错,函数返回-1。
参数说明:addr是所要读取的数据所在地址值。
|
图6-7 程序进入fubyte后的栈分布
|
在进入fubyte时,栈空间分布如图6-7所示。
图6-7 程序进入fubyte后的栈分布
第7行就是取出参数addr值给r1。
由于读字节时的地址addr可能是奇地址,所以第8行清除最低位(位0)取addr所属的偶地址,如果addr是奇地址的话。比如,地址1属于地址0所在的字,地址3属于地址2所在的字,一般地,有地址2n+1属于地址2n所在的字(n>=0)。当然如果addr本身就是偶地址,那么该操作不会对addr值有影响。
第9行跳转到gword中运行,gword和其他一些函数定义如下。
|
第2行保存PS,为下一行关中断做准备。第3行关闭7级中断,事实上目前系统中优先级最高的中断是6级,这样做为安全起见,而且为将来系统的变化而提供扩展性。
第4行保存nofault值到栈。
第5行赋nofault值为错误处理函数err的地址。
第6行从r1指向的之前模式下的地址空间读取一个字,并存放到当前栈上。这个读取过程有可能成功,也有可能会失败。
— 如果读取成功,那么第7行把读出的字节值存到寄存器r0中,跳转到第23行。第24行恢复nofault的值,第25行恢复PS值为调用gword之前的值,第26行返回到调用者,这里是fubyte,则继续fubyte中第10行执行。第10行比较r1和实际参数addr,这是因为r1是addr所属字的偶地址,如果addr是偶地址,那么r1=addr,否则r1≠addr。r0是从地址r1处读出的一个字的值,所以如果r1=addr,那么所需要读取的字节值就是r0的低字节,因此直接跳转到第14行清除r0的高字节;如果r1≠addr,那么所需要读取的字节值就是r0的高字节,因此第12行交换r0的高低字节。最后在第15行返回到调用者,返回值是r0。
— 如果读取失败,那么大多数情况是由于r1指向的地址不存在而产生总线超时自陷,或者由于该地址不在当前页内而产生内存管理违例自陷,不管产生何种自陷,程序都将被打断而跳转到trap函数中运行。根据第7章的讲述,我们知道trap函数将返回到nofault中保存的错误处理函数——在这里也就是在err函数中运行。这时栈分布如图6-8所示:
图 6-8 mfpi 执行出错后的栈分布
再来看看err函数。
第21~22行恢复nofault和PS的值,第23行增加栈指针,使其指向“返回到fubyte的调用者PC”。第24行设置r0(返回值)为-1,最后在第25行直接返回到fubyte的调用者。这一段处理过程有些复杂,其流程图如图6-9所示:
至此我们可以明白在main函数中,为何当fuibyte()返回-1时,表示该地址不存在。
6.5.3 fuword(fuiword)
函数原型:int fuword(int addr);
功能描述:从之前模式下addr地址处读取1个字,并返回其值。如果读取出错,则返回-1。
参数说明:addr是所要读取的数据所在地址值。
|
该函数的实现过程和fubyte基本一致,但由于是读取整个字的值,因此流程更简单。这里有点疑惑的地方是第4行的声明,似乎没有什么意义。
6.5.4 subyte(suibyte)
函数原型:int subyte(int addr, char ch);
功能描述:向之前模式下addr地址处写入一个字节的值ch。函数返回0表示写入成功,-1表示写入失败。
参数说明:addr是所要写入的地址值。
ch是所要写入的值。
|
进入subyte后的栈分布如图6-10所示:
第3行赋addr给r1。第4行同样清除r1最低位,使其等于addr所属字的偶地址,然后调用gword读出该地址处的字值。之所以这样做,是因为用mtpi指令向之前模式的地址空间写入数据时,只能单个字的写入,所以需要先读出整个字的值,再把需要写入的字节部分改成ch,然后再写入修改后的字。