IO读写的基本规则: 操作系统将内存分为两部分:一部分是用户空间user-space,另外一部分是内核空间kernel-sapce 在Linux系统中,内核模块运行在内核空间中,对应的进程出于 内核态 ;用户程序运行在用户空间中,对应的进程出于 用户态 操作系统是内核程序,独立于普通的应用程序 ,既有权限访问内核空间,也有权限访问硬件设备 内核空间是为操作系统准备的,并且一直驻留在内存中 应用程序不允许直接在内核空间进行读写,也不允直接调用内核定义的函数 每个应用程序进程都有一个单独的用户空间,对应的进程处于用户态,用户态进程不能访问内核空间中的数据,因此需要 将用户进程切换到内核态 才能进行系统调用 用户态进程只能执行简单的运算,不能直接调用系统资源 用户态进程必须通过 系统调用(System Call) 向内核发出指令,完成调用系统资源操作IO读写操作过程: 上层应用通过操作系统的read系统调用把数据 从内核缓冲区 复制到 应用程序的进程缓冲区 ,通过操作系统的write系统调用把数据从应用程序的 进程缓冲区 复制到操作系统的 内核缓冲区 简单来说,应用程序的IO操作实际上不是物理设备级别的读写,而是缓存的复制。read和write两大系统调用都不负责数据在内核缓冲区和物理设备之间的交换。这个底层的读写交换操作是由操作系统内核kernel来完成的。所以, 在应用程序中,无论是对socket的IO操作还是对文件的IO操作,都属于上层应用的开发 ,他们在输入和输出维度上的执行流程是类似的,都是在内核缓冲区和进程缓冲区之间进行数据交换 内核缓冲区和进程缓冲区 缓冲区的作用: 减少与设备之间的频繁物理交换 减少底层系统的频繁中断所导致的时间损耗d、性能损耗 损耗原因: 计算机的外部物理设备与内存和CPU相比,有非常大的差距,外部设备的直接读写涉及操作系统的中断。发生系统中断时,需要保存之前的进程数据和状态等信息,结束中断之后,还需要恢复之前的进程数据和状态等信息 上层应用使用read系统调用时,仅仅把数据从内核缓冲区复制到应用的缓冲区(进程缓冲区);上层应用使用 write 系统调用时,仅仅把数据从应用缓冲区复制到内核缓冲区 内核缓冲区与应用缓冲区在数量上也不同。在 Linux 系统中,操作系统内核只有一个内核缓冲区。每个用户程序(用户进程)都有自己独立的缓冲区,叫做用户缓冲区或者进程缓冲区。在大多数情况下,Linux 系统中用户程序的 IO 读写程序并没有进行实际的 iO操作,而是在用户缓冲区和内核缓冲区之间直接进行数据的交换 IO 操作系统调用过程: 这里以read系统调用为例,看一下一个完整输入流程的两个阶段: • 应用程序等待数据准备好。 • 从内核缓冲区向用户缓冲区复制数据。 如果是读取一个socket(套接字),那么以上两个阶段的具体处理流程如下: • 第一个阶段,应用程序等待数据通过网络到达网卡,当所等待的分组到达时,数据被操作系统复制到内核缓冲区中。这个工作由操作系统自动完成,用户程序无感知。 • 第二个阶段,内核将数据从内核缓冲区复制到应用的用户缓冲区。 在Java客户端和服务端之间完成一次socket请求和响应(包括read和write)的数据交换,其完整的流程如下: • 客户端发送请求:Java客户端程序通过write系统调用将数据复制到内核缓冲区,Linux将内核缓冲区的请求数据通过客户端机器的网卡发送出去。在服务端,这份请求数据会从接收网卡中读取到服务端机器的内核缓冲区。 • 服务端获取请求:Java服务端程序通过read系统调用从Linux内核缓冲区读取数据,再送入Java进程缓冲区。 • 服务端业务处理:Java服务器在自己的用户空间中完成客户端的请求所对应的业务处理。 • 服务端返回数据:Java服务器完成处理后,构建好的响应数据将从用户缓冲区写入内核缓冲区,这里用到的是write系统调用,操作系统会负责将内核缓冲区的数据发送出去。 • 发送给客户端:服务端Linux系统将内核缓冲区中的数据写入网卡,网卡通过底层的通信协议将数据发送给目标客户端。 由于生产环境的Java高并发应用基本都运行在Linux操作系统上,因此以上案例中的操作系统以Linux作为实例。