网络通信IO的演变过程(一)(一个门外汉的理解)

摘要:
OS将在名为GDT(全局描述符表)的表中标识内核空间的位置。此时,CPU一次只执行一个程序。CPU将用户程序1的数据(字段)从CPU的高速寄存器高速缓存到用户程序的空间。CPU将消耗系统调用“进程调度”的次数:]0.3。软中断(陷阱)假设用户程序想要访问计算机的相机或硬盘。此时,用户程序将调用内核的系统调用。

以前从来不懂IO的底层,只知道一个大概,就是输入输出的管道怼到一起,然后就可以传输数据了。
最近看了周志垒老师的公开课后,醍醐灌顶。
所以做一个简单的记录。

0 计算机组成原理相关

0.1. 计算机的基本组成大家都了解一点,如下图,当操作系统启动的时候,首先进入内存的除了BIOS,然后就是Linux内核程序。

网络通信IO的演变过程(一)(一个门外汉的理解)第1张

  • 内核暂时先理解成系统程序,比如我们想通过键盘获取到用户的输入,想打开网卡录取视频。这些硬件是受系统保护的,只能交给内核控制。不可能把控制权交给用户程序。
  • 用户程序如果想访问硬件,只能用户调用内核暴露的一些调用,我们称这个为系统调用。
  • 操作系统启动的时候,会把内核程序所在的地址空间设为绝对安全的空间,这个空间称为内核空间,这种机制称为保护模式。其他的空间即提供给用户程序使用,称为用户空间。比如JVM,QQ,微信对内核来说都是用户App。
  • 操作系统启动后,OS会在一个叫做GDT(全局描述符表)的表里标识出内核空间的位置。

0.2. 中断

假设在单核CPU的电脑上,安装一个操作系统,系统中运行了N多的程序,包括内核程序和用户程序。
此时在一个瞬间CPU只会执行一个程序。

CPU是怎么进行各个进程的调度的呢?
网络通信IO的演变过程(一)(一个门外汉的理解)第2张
Step1:

  • 晶振1秒钟震动1千次或者1万次,晶振每震荡一次,都会传递一个时钟中断给CPU,假设当前运行的是用户程序1,晶振震动之后,CPU会把用户程序1的数据(现场)从CPU的高速寄存器中缓存到这个用户程序的空间中。

Step2:

  • 操作系统启动的时候,会有一个中断向量表(中断表述符表IDT(Interrupt Descriptor Table)),OS启动时,内核程序注册的。
  • 中断产生了之后,存在一个中断回调程序。这个回调程序是由内核注册的。比如这里的:时钟中断产生之后,CPU把现场保存了,CPU本不知道下一步要干什么。但是在OS启动的时候,内核已经注册了,说“CPU,当你收到时钟中断之后,先保护现场,然后来调用我内核程序中的一个调用,这个调用叫'进程调度'”。这个注册的东西就是注册中断向量表。代表“某一个中断发生了,CPU应该去这个表上查它下一步应该干啥”
  • 所以当时钟中断产生之后,用户程序现场被保存下来,然后CPU调用内核的“进程调度”这个方法。

Step3:

  • CPU调用内核的“进程调度”后,内核告诉CPU,现在可以执行用户程序2了,CPU就去执行用户程序2了,因为之前可能用户程序2被调度过,所以中断后的进程调度第一步就是恢复现场。

所以这里的中断会导致1:CPU的保护现场和恢复现场,会使CPU高速寄存器和内存之间的数据传递,会有消耗。
所以这里的中断会导致2:CPU进行系统调用"进程调度"的次数会有消耗。
【证明01:程序运行的越多,单位时间内,CPU浪费在内核调度上的时间会变多,浪费在寄存器与内存数据传递的时间会变多,真正运行程序的时间会变少。】

0.3. 软中断(陷阱)

假设用户程序想访问电脑的摄像头或者硬盘,此时用户程序会调用内核的系统调用。这种调用我们称为软中断或者陷阱(INT x80)

从CPU的角度考虑一下这个调用,CPU首先在执行用户的程序,但是用户程序写了一行“读取网卡输入”的代码,这个时候用户的编程语言的编译器会在指令里加入一个软中断int x80,表示需要进行系统调用。当CPU读取到int x80的指令的时候,内核之前已经注册了中断向量表(中断表述符表IDT(Interrupt Descriptor Table)),系统调用处理程序 system_call() 的入口地址放在系统的中断表述符表IDT(Interrupt Descriptor Table)中,Linux系统初始化时,由 trap_init() 将其填写完整,其设置系统调用处理程序的语句为:

set_system_gate(0x80, &system_call)

经过初始化以后,每当执行 int 0x80 指令时,产生一个异常使系统陷入内核空间并执行128号异常处理程序,即系统调用处理程序 system_call() 。

这一列骚操作会让CPU在用户态和内核态之间转换。所以依然会让CPU在单位时间产生更大的消耗。

【证明02:当程序调用了系统调用访问外设这种操作,会让CPU消耗更多的时间在用户态和内核态之间】

1. BIO (BlockingIO)

先上代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class TestBio {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8090); //监听端口8090,本机作为服务端
        System.out.println("step1 new ServerSocket(8090)");
        while(true) {
            Socket client = serverSocket.accept(); //一旦客户端连接上来,新开辟一个线程处理客户端输入
            System.out.println("client port :" + client.getPort());

            new Thread(new Runnable() {
                Socket ss;

                public Runnable setSS(Socket s) {
                    ss = s;
                    return this;
                }

                @Override
                public void run() {
                    try {
                        InputStream is = ss.getInputStream();
                        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
                        while(true) {
                            System.out.println(reader.readLine());
                        }
                    } catch(IOException e) {
                        e.printStackTrace();
                    }
                }
            }.setSS(client)).start();
        }
    }
}

这段代码非常之简单,不多做解释。

利用自己的虚拟机进入Linux系统,把这份程序拷贝到Linux环境下运行。再利用strace命令跟踪java bio在linux系统中的调用过程。

什么是strace?

按照strace官网的描述, strace是一个可用于诊断、调试和教学的Linux用户空间跟踪器。我们用它来监控用户空间进程和内核的交互,比如系统调用、信号传递、进程状态变更等。
strace底层使用内核的ptrace特性来实现其功能。

[root@hadoop-senior code]# strace -ff -o out java TestBio执行结果:

[root@hadoop-senior code]# strace -ff -o out java TestBio
step1 new ServerSocket(8090)

可以理解,此时服务器端(即自己的虚拟机)已经在8090监听来自客户端的连接。但是目前还没有任何连接建立,所以打印完输出文字之后就阻塞在这里。

通过ll查看strace命令自动生成的线程跟踪文件:

[root@hadoop-senior code]# ll
total 252
-rw-r--r-- 1 root root   9424 Jun 26 15:23 out.6228
-rw-r--r-- 1 root root 177741 Jun 26 15:23 out.6229
-rw-r--r-- 1 root root   2123 Jun 26 15:23 out.6230
-rw-r--r-- 1 root root    931 Jun 26 15:23 out.6231
-rw-r--r-- 1 root root   1066 Jun 26 15:23 out.6232
-rw-r--r-- 1 root root    975 Jun 26 15:23 out.6233
-rw-r--r-- 1 root root   5003 Jun 26 15:23 out.6234
-rw-r--r-- 1 root root   3705 Jun 26 15:23 out.6235
-rw-r--r-- 1 root root    931 Jun 26 15:23 out.6236
-rw-r--r-- 1 root root  17321 Jun 26 15:23 out.6237
-rw-r--r-- 1 root root   1092 Jun 26 14:29 TestBio$1.class
-rw-r--r-- 1 root root   1128 Jun 26 14:29 TestBio.class
-rw-r--r-- 1 root root   1326 Jun 26 14:28 TestBio.java
[root@hadoop-senior code]# 

可以发现生成了很多out开头的线程跟踪文件,通过less或者tail -f查看这些文件。
通过jpsnetstat -natp查看服务器各个端口使用情况。

因为java是一个多线程的程序,查看最大size的文件:out.6229,这个文件跟踪的即是main方法的输出。前面4位数字是这个文件的行号,不用在意。

   2421 socket(PF_INET6, SOCK_STREAM, IPPROTO_IP) = 5
   2422 setsockopt(5, SOL_IPV6, IPV6_V6ONLY, [0], 4) = 0
   2423 fcntl(5, F_GETFL)                       = 0x2 (flags O_RDWR)
   2424 fcntl(5, F_SETFL, O_RDWR|O_NONBLOCK)    = 0
   2425 setsockopt(5, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
   2426 lseek(3, 58578982, SEEK_SET)            = 58578982
   2427 read(3, "PK34

免责声明:内容来源于网络,仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇常用模块之subprocess模块常用工具类总结下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

图像质量测评

目录 1. 图像质量 2. 原理 2.1. MTF的基本介绍 2.2. MTF曲线解读 2.3. MTF的计算 3. 测试工具3.1. DxO Analyzer 4. Imatest 4.1. 成像质量测试 4.2. 色彩还原测试 4.3. 分析动态范围 5. ImageJ 测试 6. 开源项目:image-quality-asses...

Windiows server 2012:开机密码忘记处理办法

Windiows server 2012,开机密码忘记处理办法 一、让windows系统进入【疑难解答】界面,开机进入欢迎界面时按下F8时可进入,但这对Windows 10 、2012好象不好使,不过还有以下两种办法解决:    办法1、直接接服务器重启键,在启动过程中手动关闭,然后再重新打开服务器的电源,重复两次以上操作,使Windows以为是出了故障,...

sudoers的深入剖析

一、sudo权限的配置 1. 编辑 sudo权限 命令 visudo visudo 命令实际修改的是 /etc/sudoers 文件 2. /etc/sudoers 配置文件 [root/etc]# cat /etc/sudoers ... 省略部分内容 ... ## Allow root to run any commands anywhere #...

Springboot使用MessageSource读取资源文件

1、项目开发过程中的提示文字信息可以在资源文件中进行定义,而且资源文件是实现国际化技术的主要手段。如果想在SpringBoot里面进行资源文件的配置,只需要做一些简单的application.yml配置即可,而且所有注入的资源文件都可以像最初的Spring处理那样,直接使用MessageSource进行读取。 首先,在src/main/resources源...

Jeecg-Boot 2.0.0 版本发布,基于Springboot+Vue 前后端分离快速开发平台

项目简介 Jeecg-boot 是一款基于代码生成器的智能开发平台!采用前后端分离技术:SpringBoot,Mybatis,Shiro,JWT,Vue & Ant Design。提供强大的代码生成器, 前端页面和后台代码一键生成,不需要写任何代码,保持jeecg一贯的强大,绝对是全栈开发者福音!! JeecgBoot的宗旨是降低前后端分离的开发成...

ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(十) 之 自定义系统消息和总结

前言   本篇主要讲解一个东西,就是我们自定义系统消息。效果如下:   首先我们要做的准备工作就是改写 layim 的消息模板,如果不改的话就成为某个用户发送的消息了,那么体验就稍微差一些。找到模板我们看一下。   注意,红框部分是我更改后的,简单读一下可以看出来,我只是给聊天消息加了个参数 system,如果有这个参数,那么我们直接给加一个div就...