参考文献

进程

  • (有时候也称做任务)是指一个程序运行的实例.在 Linux 系统中,线程就是能并行运行并且与他们的父进程(创建他们的进程)共享同一地址空间(一段内存区域)和其他资源的轻量级的进程

IPC(Inter-Process Communication)

  • 管道(Pipe)或者具名管道(Named Pipe): 管道类似于两个进程间的桥梁,可通过管道在进程间传递少量的字符流或字节流.普通管道只用于有亲缘关系进程(由一个进程启动的另外一个进程)间的通信,具名管道摆脱了普通管道没有名字的限制,除具有管道所有的功能外,它还允许无亲缘关系进程间的通信.管道典型的应用就是命令行中的|操作符
  • 信号(Signal): 信号用于通知目标进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程自身
  • 信号量(Semaphore): 信号量用于两个进程之间同步协作手段,它相当于操作系统提供的一个特殊变量,程序可以在上面进行wait()notify()操作.
  • 消息队列(Message Queue): 以上三种方式只适合传递传递少量信息,POSIX 标准中定义了消息队列用于进程间数据量较多的通信.进程可以向队列添加消息,被赋予读权限的进程则可以从队列消费消息.消息队列克服了信号承载信息量少,管道只能用于无格式字节流以及缓冲区大小受限等缺点,但实时性相对受限.
  • 共享内存(Shared Memory): 允许多个进程访问同一块公共的内存空间,这是效率最高的进程间通信形式.原本每个进程的内存地址空间都是相互隔离的,但操作系统提供了让进程主动创建、映射、分离、控制某一块内存的程序接口.当一块内存被多进程共享时,各个进程往往会与其它通信机制,譬如信号量结合使用,来达到进程间同步及互斥的协调操作.
  • 套接字接口(Socket): 消息队列和共享内存只适合单机多进程间的通信,套接字接口是更为普适的进程间通信机制,可用于不同机器之间的进程通信.套接字(Socket)起初是由 UNIX 系统的 BSD 分支开发出来的,现在已经移植到所有主流的操作系统上.出于效率考虑,当仅限于本机进程间通信时,套接字接口是被优化过的,不会经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等操作,只是简单地将应用层数据从一个进程拷贝到另一个进程,这种进程间通信方式有个专名的名称: UNIX Domain Socket,又叫做IPC Socket.

Copy-on-Write

什么是Copy-on-Write

  • 写时复制(Copy-on-write,COW),有时也称为隐式共享(implicit sharing).COW将复制操作推迟到第一次写入时进行: 在创建一个新副本时,不会立即复制资源,而是共享原始副本的资源;当修改时再执行复制操作.通过这种方式共享资源,可以显著减少创建副本时的开销,以及节省资源;同时,资源修改操作会增加少量开销.
  • 以下引用至维基百科

写时复制(Copy-on-write)的主要用途是在操作系统中,在fork()系统调用的实现中共享运行多个进程的计算机的物理内存.通常,新进程不修改任何内存,并立即执行一个新进程,完全替换地址空间.如果在fork期间复制所有旧进程的内存,然后立即丢弃该副本,将浪费处理器时间和内存.

通过将内存的某些页面标记为只读并记录对该页的引用数量,可以使用页表有效地实现写时复制.当数据写入这些页时,操作系统内核会拦截写尝试,并分配一个新的物理页,用写时拷贝数据初始化,不过如果只有一个引用,可以跳过分配.然后,内核用新的(可写的)页更新页表,减少引用的数量,并执行写操作.新的分配确保一个进程的内存更改在另一个进程中是不可见的.

写时复制技术可以扩展为支持有效的内存分配,方法是将物理内存的一页填满0.当分配内存时,返回的所有页都指向零页,并且都标记为写时复制.这样,在写入数据之前,不会为进程分配物理内存,从而允许进程保留比物理内存更多的虚拟内存,并在虚拟地址空间耗尽的风险下稀疏地使用内存.组合算法类似于请求分页.

Linux 内核的同页合并功能中也使用了写时复制页面.

为什么需要Copy-on-write

  • 当通过 fork() 来创建一个子进程时,操作系统需要将父进程虚拟内存空间中的大部分内容全部复制到子进程中(主要是数据段、堆、栈;代码段共享).这个操作不仅非常耗时,而且会浪费大量物理内存.特别是如果程序在进程复制后立刻使用 exec 加载新程序,那么负面效应会更严重,相当于之前进行的复制操作是完全多余的.
  • 因此引入了写时复制技术.内核不会复制进程的整个地址空间,而是只复制其页表,fork 之后的父子进程的地址空间指向同样的物理内存页.
  • 但是不同进程的内存空间应当是私有的.假如所有进程都只读取其内存页,那么就可以继续共享物理内存中的同一个副本;然而只要有一个进程试图写入共享区域的某个页面,那么就会为这个进程创建该页面的一个新副本.
  • 写时复制技术将内存页的复制延迟到第一次写入时,更重要的是,在很多情况下不需要复制.这节省了大量时间,充分使用了稀有的物理内存.

Copy-on-write实现原理

  • fork() 之后,内核会把父进程的所有内存页都标记为只读.一旦其中一个进程尝试写入某个内存页,就会触发一个保护故障(缺页异常),此时会陷入内核.
  • 内核将拦截写入,并为尝试写入的进程创建这个页面的一个新副本,恢复这个页面的可写权限,然后重新执行这个写操作,这时就可以正常执行了.
  • 内核会保留每个内存页面的引用数.每次复制某个页面后,该页面的引用数减少一;如果该页面只有一个引用,就可以跳过分配,直接修改.
  • 这种分配过程对于进程来说是透明的,能够确保一个进程的内存更改在另一进程中不可见.

Copy-on-write优缺点

  • 优点: 减少不必要的资源分配,节省宝贵的物理内存.
  • 缺点: 如果在子进程存在期间发生了大量写操作,那么会频繁地产生页面错误,不断陷入内核,复制页面.这反而会降低效率

线程上下文切换

  • 巧妙地利用了时间片轮转的方式, CPU 给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务,任务的状态保存及再加载, 这段过程就叫做上下文切换.时间片轮转的方式使多个任务在同一颗 CPU 上执行变成了可能.

上下文

  • 是指某一时间点CPU寄存器和程序计数器的内容.

CPU寄存器

  • 是 CPU 内部的数量较少但是速度很快的内存(与之对应的是CPU外部相对较慢的 RAM 主内存).寄存器通过对常用值(通常是运算的中间值)的快速访问来提高计算机程序运行的速度.

程序计数器

  • 是一个专用的寄存器,用于表明指令序列中 CPU 正在执行的位置,存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置,具体依赖于特定的系统.

上下文切换的活动

  1. 挂起一个进程,将这个进程在CPU 中的状态(上下文)存储于内存中的某处.
  2. 在内存中检索下一个进程的上下文并将其在CPU 的寄存器中恢复.
  3. 跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程在程序中.

引起线程上下文切换的原因

  • 当前执行任务的时间片用完之后,系统CPU 正常调度下一个任务;
  1. 当前执行任务碰到 IO 阻塞,调度器将此任务挂起,继续下一任务;
  2. 多个任务抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续下一任务;
  3. 用户代码挂起当前任务,让出 CPU 时间;
  4. 硬件中断