Please enable Javascript to view the contents

系统级 I/O

 ·  ☕ 4 分钟

名词解释:


进程:本文通指用户级进程和内核级进程

线程:本文仅指操作系统的内核线程

C进程:本文仅指由C标准库实现的用户态进程

C线程:本文仅指由C标准库实现的用户态线程 (POSIX标准)

Python进程:指由Python虚拟机实现的用户态进程

Python线程:指由Python虚拟机实现的用户态线程

楔子


说到系统I/O,我最早是在学习「Python实现多线程编程」这一概念
的时候接触到的——「Python多线程编程模型适用于I/O密集型进程,不适用于
CPU密集型进程,原因在于I/O密集型进程涉及较多的系统调用,Python虚拟机在一 个线程产生系统调用时会保存当前线程的栈帧,并将系统资源(CPU时间片、运行时 栈 ……)分配给另一个线程对象,切换到这另一个线程对象执行」。这一段话,当时 看得我云里雾里:

  1. 什么是Python线程?
  2. 什么是I/O密集型进程?
  3. 什么是CPU密集型进程?
  4. 系统调用是个啥?
  5. C语言栈空间、堆空间;操作系统运行时栈;

首先我们需要明确几个重要的概念:

系统调用


​ 先说说系统调用。系统调用指的是用户态进程调用系统内核专门为用户态程序开放的一系列用来实现与底层硬件设备(的驱动程序)通信的接口,具体由运行在操作系统内核态里的I/O函数来实现最终的调用,如:sys_read(), sys_write(), sys_fork() …… 像这样的操作函数属于「原子型」的操作,与我们所熟知的C标准库函数(如:fopen(), fwrite(), fread() )甚至是Python标准库函数(f.open(), f.write(), f.read())不同,这样的原子型操作细粒度较高,对于操作系统及其上运行的任何进程而言,系统调用这部分的代码复用性极高,不论是我们打的网游(需要内存与硬盘、内存与网卡、内存与显卡的交互 ……)这些交互最终都要依靠多次的系统调用来实现。系统调用的的接口由C定义,具体的实现由汇编语言完成,在保证执行效率高的前提下,方便了上层调用。

​ 由C定义的那一部分接口,成为了现在 C 标准库函数的主要部分


系统级 I/O


C标准库 I/O


我习惯用纯 C + STL 来做题,一来, C 是一门接近底层的语言,众所周知的 C「指针」这一概念 便很好地印证了这一点,赋予开发者操作用户内存空间的权限,这一点是 Python 、Java、Go 等自带 GC 的语言所不具备的特性。二来因为 C 的语法精简,在使用C的时候,总能让我感觉到舒服,malloc、calloc、scanf、printf … … 很显眼的关键字:if … else if … else 、switch … case …… Python在这一点上,做的也很好 许多我们常用的函数接口名都是完全的英文单词,没有让人费解的缩写(我不喜欢 Rust 里的关键字 fn,也对 Go 中定义函数类型时类型在后 变量名在前的语法感到不适应<到底还是 C 先入为主啦>)。

做题的时候,总要在程序的前部进行 输入操作,我最常用到的是像:
while(~scanf("%d", &n));
while(scanf("%d", &n)!=EOF);
这样的语句,通过这样的语句来完成连续的输入操作。
其中,scanf()为C标准I/O中的格式化函数(同样,printf()亦是),在Linux中,其函数原型定义在/usr/include/stdio.h文件内: extern int scanf(const char *__restrict __format, ...); extern int printf (const char *__restrict format, ...);
除了scanf()、printf()这两个我们最常用到的格式化输入函数外,包括 打开和关闭文件的函数(fopen 和 fclose)、读和写字节的函数(fread 和 fwrite)、读和写字符串的函数(fgets 和 fputs)…… 构成了C的标准I/O库。


进程与线程


C 多进程与多线程


Python 多进程与多线程


​ 回到之前的Python多线程编程模型的讨论。我们知道,过多的系统调用会影响进程的执行效率,因为系统调用会使得进程经历:从用户态陷入内核态 –> 从内核态切换回用户态 这样的过程,而这样的过程需要操作系统执行上下文的切换,因此产生了开销(涉及了磁盘操作),影响了进程的执行效率。线程(此处为操作系统概念中的线程)作为操作系统进行资源调度的最小单位(在「线程」概念被提出并被普遍实现以后),共用其所属进程内的最后一级缓存,拥有自己独立的栈空间

FAQ


  1. 同步和异步的区别?

  2. 并发和并行的区别?

    • 这里讲到的并发特指「并发程序」,并行特指「并行程序」;
    • 并发程序是指在同一微时间段内可以被同时发起执行的程序;
    • 并行程序是指可以在并行的硬件上(多核/多路CPU、GPU)执行的并发程序;
    • 可以认为,并发程序代表了所有可以实现并发行为的程序,因此并发程序是并行程序的超集。
  3. 协程与非协程的区别?

  4. 阻塞与非阻塞的细节?

  5. 库函数调用和系统调用的区别?

    • 库函数调用属于过程调用,开销小(指不包含系统调用的库函数);

      系统调用需要内核空间栈帧的上下文切换,开销较大。

    • 对于所有的 ANSI C 编译器,C库函数是相同的;

      而系统调用随操作系统的不同而不同。