JAVA程序员需要知道的计算机底层基础07-中断
中断是和操作系统内核通信的一种机制,它本身也是一种信号,举个例子,一个应用程序运行过程中出现了卡顿,此时CPU在全力执行这个程序的进程,我们想通过键盘或者鼠标的某个按键来告诉操作系统将这个进程进行关闭(也就是想通过硬件的操作来让软件有个反馈),此时就需要用到中断,也就是说发送了一个紧急信号,希望内核命令应用程序可以停下手头的工作,然后优先进行处理我的某个操作。
中断分为硬中断和软中断,我们上面描述的例子,其实就是硬中断的一个过程。
硬中断
1、键盘按下了一个键。
2、中断控制器(一个芯片)收到了来自键盘的电信号,然后会传达给CPU,CPU有一个专门的针脚用于接收中断电信号进行接受,那CPU怎么知道这个中断电信号是来自键盘的还是网络的?大致来说,实质上在CPU内部的某个寄存器上会存放着对应的电信号数和类型的对应关系。此时例如接受到电信号0就是鼠标,1就是键盘。
3、CPU接下来会把中断信号发给内存中的某个固定位置的处理程序(操作系统内核已经固定写好的程序)
4、处理程序接受到电信号,包装一下这是个什么类型的电信号,并且要匹配的中断处理程序是啥(根据CPU传来的中断信号类型在中断向量表中找到这种中断信号类型对应的那个处理程序),然后传给操作系统内核处理。
5、操作系统内核接受到包装好类型的中断电信号,将它交给对应中断类型(键盘还是鼠标还是什么)的中断处理程序进行执行
6、中断处理程序分为上半场和下半场,上半场就是操作系统内核对于中断电信号根据对应类型的处理,然后下半场就是交由用户态的执行程序(例如office)真正进行处理,如何确定是哪个执行程序? 根据操作系统的规则,比如windows可能是最上方窗口的应用程序,它在等待输入,那么将会把这个信号传递给它。
7、应用程序接受到具体的中断事件类型,根据类型(keydown,keyup)进行逻辑代码处理。(我们日常中真正交涉的地方)
软中断(80中断/80H)
软中断意思是通过软件内部发出中断信号给内核,来请求内核停下手头工作立刻执行某件事情,常见的就是我们想要读取一个本地文件,就需要调用read函数,这是一个系统调用操作,此时就需要借助内核来完成,这个过程就叫做软中断。
刚刚提到,硬中断在中断表中,不同中断信号代表着不同的硬件设备,此外,在中断向量表中,对于软中断信号统一用Int 0x80(interrupt 80)中断信号进行表示。而软中断对应着有一堆系统调用函数。
1、在过去,给系统内核一个中断信号,以前的方式是通过int 0x80函数执行,因为软中断非常常用,因此现在比较新的CPU在硬件级别直接进行了支持,直接内置在了CPU自身,在汇编层面直接支持,有一个汇编码叫sysenter,通过sysenter原语,相较于int 0x80还有些C语言的调用函数的过程,sysenter这种机器语言直接支持,效率更高。
2、通过ax寄存器存放调用时指定的系统调用函数的映射号,bx,cx,dx,si,di寄存器来存放参数(5个寄存器已经完全支持所有的内核系统调用函数的参数)
3、执行成功后,将返回结果放入ax寄存器返回
java中读取文件的例子
1、jvm调用read方法
2、底层调用c库的read api方法
3、c的read api方法先在ax存储系统调用函数的编号值,使用int 0x80或者sysenter原语 中断调用进入内核态
4、内核中调用system_call()内核函数去指定位置ax寄存器找到数值,去系统调用表中匹配对应调用的系统内核调用函数sys_read(),并去另外的bx,cx,dx,si,di寄存器找到参数,进行执行系统调用。
5、执行完成后,将结果值存放在ax寄存器中,返回到中断处理函数,中断处理函数返回到C API,C API返回给java,这样一层一层,最终得到文件内容。
从一个简单的汇编程序来理解软中断
搭建汇编环境
yum install nasm
汇编程序代码
;hello.asm
;write(int fd, const void *buffer, size_t nbytes)
;这段代码实现的是调用了内核的write函数,向控制台输出内容,write函数需要传输3个参数,写入的文件描述符,写入的内容,写入的内容大小
section data
msg db "Hello", 0xA
len equ $ - msg
section .text
global _start
_start:
mov edx, len
mov ecx, msg
mov ebx, 1 ;文件描述符1 std_out
mov eax, 4 ;write函数系统调用号 4
int 0x80
mov ebx, 0
mov eax, 1 ;exit函数系统调用号
int 0x80
汇编程序执行
编译
nasm -f elf hello.asm -o hello.o
编译就是把一对汇编代码进行编译成二进制文件。
链接
ld -m elf_i386 -o hello hello.o
编译生成的二进制文件中可能有一些与系统的相关的依赖,因此还需要对其进行链接。
执行
./hello
输出结果
Hello
汇编程序代码解释
在汇编代码中,分号开头的代表是注释。
整段汇编代码实现的是调用了内核的write函数,向控制台输出内容,write函数需要传输3个参数:写入的文件描述符,写入的内容,写入的内容大小。
write(int fd, const void *buffer, size_t nbytes)
在linux中,一切皆文件。因此,控制台console也是作为一个文件描述符进行标识的。
重点其实只需要关注这几行:
我们之前说,进行系统调用需要进行软中断。在汇编层面,进行一个系统调用,其实和我们之前描述的过程是一样的:
1、在eax中放入我们要调用的系统函数号,write函数对应的调用号是4。
2、在ebx中放入write函数的第一个参数,写入的对应文件描述符,console对应的文件描述符就是1号
3、在ecx中放入我们要写入的内容。
4、在edx中写入我们写入内容的长度。
5、发起80软中断,让操作系统去根据前几步预设在各个寄存器的数值,进行对应的系统调用。
在这个过程中,我们可以发现:一个程序的执行过程,要么处于用户态,要么处于内核态。
在第5步发起软中断的时候,其实就是进入了内核态去进行了操作。
阻塞?非阻塞?
这里我们不禁可以回头思考一下,我们常说的所谓的阻塞IO操作和非阻塞IO操作,实质上说的就是,在当用户态切换到内核态进行系统调用的时候,作为应用程序的用户空间来说,还可不可以继续进行其他的工作?
如果傻傻的同步等待内核态系统调用的完成,那就是阻塞的,如果与此同时还可以去做一些其他的事情,那就是非阻塞的。
系统调用中断参考文章:
https://blog.csdn.net/xiaominthere/article/details/17287965
https://www.cnblogs.com/jiading/p/12606978.html