计算机组成原理
App是无法直接操作驱动等内核程序的,需要调用内核程序时,必须向内核进行系统调用。
系统调用与函数调用不是同一个东西。
由于保护模式的存在,我们的程序是无法直接函数调用内核程序,而是通过系统调用。
系统调用,本质就是通过中断执行。
程序中断的过程:
-
程序中,仿佛函数调用似的调用内核程序,(本质上会产生中断)
-
系统调用产生中断,中断将内核程序的中断向量(0~255)存入CPU的寄存器
-
CPU同时会开启一个守护线程,守护我们的程序,以将中断的结果返回
-
CPU根据中断向量,在中断向量表找到对应内核程序的启动地址,调用此内核程序
-
根据守护线程,将结果放回给我们的程序。
在整个过程中,我们如同函数调用般的使用了内核程序,而实际上发生了以上这么多东西。
影响CPU吞吐量:
- 线程过多,浪费在切换线程上
- 中断过多,同样浪费在上下文切换上
服务器的阻塞
来看最原始的ServerSocker
(BIO)
阻塞有两个
server.accpet()
,等待客户端连入。reader.readline()
等待客户端输入。需要多线程执行客户端请求的服务,因为IO会阻塞,不多线程会影响下一个连入的客户端。
几个比较有用的linux命令
strace -ff -o out /usr/jdk1.8/bin/java TestSocket
#ff 代表for fork ,监控当前所有线程的所有系统调用
#-o out 将系统调用记录写成日志文件
#/bin/java 为jvm
#TestSocket 为我们编译好的.class 记得先用javac编译
可以看到,Java本身就是多线程的,在jdk1.4中,启动一个Java程序自带8个线程(守护线程,监控线程等等)。
当
socket
系统调用成功后,会返回一个数字,这个数字叫做file descriptor
。这个数字就是表示代表这个socket。其他生成作用的系统调用同样会产生file descriptor
。
bind
等系统调用,会直接使用file descriptor
。
- bind ():将某个socket和端口绑定
- listen() :开启某个端口的监听
- accept() :等待端口的下一个client连接(会阻塞),并为新来的clinet分配
file descriptor
- recv() : 等待IO输入
netstat -natp
nc localhost 8090
#nc会帮我们建立一个TCP连接 8090是clinet使用的端口
BIO
无论是BIO,NIO,AIO,每当服务器启动时,必然会系统调用这三个内核程序
socket => file descriptor
bind(file descriptor,port)
listen(file descriptor)
BIO,每当有一个client连接,就要新建一个轻量级线程,来处理此客户端I/O(因为IO阻塞,假如不多线程,直接在主线程执行,只要有一个塞住,那无法调用accept,后面的全部塞住。)。
无论是BIO,NIO,AIO,一旦accept了client,就会为这个client分配一条TCP连接,TCP连接可以是新建的,也可以是连接池(TCP长连接)中得到的。
缺点:
- 线程过多,线程内存浪费
- 切换线程、CPU调度消耗
- 根源在于:Blocking IO -> 受制于内核 accept,recv
- 假如不多线程,直接阻塞,可能会把TCP连接池占光。
NIO
socketChannel.configureBlocking(false