什么是 I/O
在计算机操作系统中,所谓的I/O就是输入(Input)和输出(Output),也可以理解为读(Read)和写(Write),针对不同的对象,I/O模式可以划分为磁盘IO模型和网络IO模型。
I/O操作会涉及到用户空间和内核空间的转换,先来理解以下规则:
- 内存空间分为用户空间和内核空间,也称为用户缓冲区和内核缓冲区;
- 用户的应用程序不能直接操作内核空间,需要将数据从内核空间拷贝到用户空间才能使用;
- 无论是Read操作,还是Write操作,都只能在内核空间里执行;
- 磁盘I/O和网络I/O请求加载到内存的数据都是先放在内核空间的。
读(Read)和写(Write)操作:
- 读操作:操作系统检查内核缓冲区有没有需要的数据,如果内核缓冲区已经有需要的数据了,那么就直接把内核空间的数据copy到用户空间,供用户的应用程序使用。如果内核缓冲区没有需要的数据,对于磁盘I/O,直接从磁盘中读取到内核缓冲区(这个过程可以不需要CPU参与)。而对于网络I/O,应用程序需要等待客户端发送数据,如果客户端还没有发送数据,对应的应用程序将被会被阻塞,直到客户端发送了数据,该应用程序才会被唤醒,从Socket协议栈中读取客户端发送的数据到内核空间,然后把内核空间的数据copy到用户空间,供应用程序使用。
- 写操作:用户的应用程序将数据从用户空间copy到内核空间的缓存区中(如果用户空间没有相应的数据,则需要从磁盘--->内核缓冲区--->用户缓冲区依次读取),这时对用户程序来说写操作就已经完成,至于什么时候再写到磁盘或通过网络发送出去,由操作系统决定。除非应用程序显示的调用了sync命令,立即把数据写入磁盘,或执行flush()方法,通过网络把数据发送出去。
绝大多数磁盘I/O和网络I/O的读写操作都是上述过程,除了后面要讲到的零拷贝I/O。
用户空间 & 内核空间
虚拟内存(操作系统中的概念,和物理内存是对应的)被操作系统划分成两块:User Space(用户空间)和Kernel Space(内核空间),本质上电脑的物理内存是不划分这些的,只是操作系统开机启动后在逻辑上虚拟划分了地址和空间范围。
操作系统会给每个进程分配一个独立的、连续的虚拟内存地址空间(物理上可能不连续),以32位操作系统为例,该大小一般是4G,即232。其中将高地址的内存空间分配给系统内核占用(网上查资料得知:Linux下占1G,Windows下占2G),其余的内存地址空间分配给用户进程使用。
因为我们不是要深入学习操作系统,所以这里以32位系统举例。32位的Linux操作系统下,1~3G为用户空间,3~4G为内核空间。
为什么要这样划分出空间范围?
很好理解,毕竟操作系统身份高贵,太重要了,不能和用户应用程序在一起玩耍,各自的数据都要分开存储并且严格控制权限不能越界。这样才能保证操作系统的稳定运行,用户应用程序太不可控了,不同公司或者个人都可以开发,碰到坑爹的误操作或者恶意破坏操作系统数据直接宕机就玩完了。隔离后应用程序要挂你就挂,操作系统可以正常运行。
简单说,内核空间是操作系统内核代码运行的地方,用户空间是用户程序代码运行的地方。当应用进程执行系统调用陷入内核代码中执行时就处于内核态,当应用进程在运行用户代码时就处于用户态。
同时内核空间可以执行任意的命令,而用户空间只能执行简单的运算,不能直接调用系统资源和数据。必须通过操作系统提供接口,向系统内核发送指令。
一旦调用系统接口,应用进程就从用户空间切换到内核空间了,因为开始运行内核代码了。
简单开几行代码,分析下是应用程序在用户空间和内核空间之间的切换过程:
str = "i am friday" // 用户空间
x = x + 2
file.write(str) // 切换到内核空间
y = x + 4 // 切换回用户空间
上面代码中,第一行和第二行都是简单的赋值运算,在用户空间执行。第三行需要写入文件,就要切换到内核空间,因为用户不能直接写文件,必须通过内核安排。第四行有是赋值运算,就切换回用户空间。
用户态切换到内核态的三种方式:
- 系统调用。也称为System Call,是说用户态进程主动要求切换到内核态的一种方式,用户态进程使用操作系统提供的服务程序完成工作,比如上面示例代码中的写文件调用,还有像fork()函数实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现。
- 异常。当CPU在用户空间执行程序代码时发生了不可预期的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,切换到内核态,比如缺页异常。
- 外围设备的中断。当外围设备完成用户请求的某些操作后,会向CPU发送相应的中断信号,这时CPU会暂停执行下一条即将执行的指令转而去执行与中断信号对应的处理程序,如果当前正在运行用户态下的程序指令,自然就发了由用户态到内核态的切换。比如硬盘数据读写完成,系统会切换到中断处理程序中执行后续操作等。
以上三种方式,除了系统调用时进程主动发起切换,异常和外围设备终端时被动切换的。
top
查看CPU时间在User Space 与 Kernel Space 之间的分配情况,可以使用top命令。它的第三行输出就是CPU时间分配统计。
系统资源摘要:
Tasks: 530 total, 1 running, 522 sleeping, 2 stopped, 5 zombie
- 530 total:系统中总共有 530 个进程。
- 1 running:当前正在运行的进程数量是 1。
- 522 sleeping:有 522 个进程处于休眠状态