操作系统

摘自《程序员面试白皮书》

进程vs.线程

进程(process)与线程(thread)最大的区别是进程拥有自己的地址空间,某进程内的线程对于其他的进程不可见,即进程A不能通过传地址的方式直接读写进程B的存储区域。进程之间的通信需要通过进程间通信(Inter-process communication, IPC)。与之相对的,同一进程的各线程间可以直接通过传递地址或全局变量的方式传递信息。

此外,进程作为操作系统中拥有资源和独立调度的基本单位,可以拥有多个线程。通常操作系统中运行的一个程序就对应一个进程。在同一进程中,线程的切换不会引起进程切换。在不同的进程中进行线程切换,若从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换。相比进程切换,线程切换的开销要小很多。线程与进程相结合能够提高系统的运行效率。

线程可以分为两类: 一类是用户级线程(user level thread)。对于这类线程,有关线程管理的所有工作都由应用程序完成,内核意识不到线程的存在。在应用程序启动后,操作系统分配给该程序一个进程号,以及其对应的内存空间等资源。应用程序通常现在一个线程中运行,该线程被称为主线程。在其运行的某个时刻,可以通过调用线程库中的函数创建一个在相同进程中运行的新线程。用户级线程的好处是非常高效,不需要进入内核空间,但并发效率不高。

另一类是内核级线程(kernel level thread)。对于这类线程,有关线程管理的所有工作由内核完成,应用程序没有进行线程管理的代码,只能调用内核线程的接口。内核维护进程及其内部的每个线程,调度也有内核基于线程架构完成。内核级线程的好处是,内核可以将不同的线程更好地分配到不同的CPU,以实现真正的并行计算。

事实上,在现代操作系统中,往往使用组合方式实现多线程,即线程创建完全在用户空间中完成,并且一个应用程序中的多个用户级线程被映射到一些内核级线程上,相当于是一种折中方案。

上下文切换

对于单核单线程CPU而言,在某一时刻只能执行一条CPU指令。上下文切换(context switch)是一种将CPU资源从一个进程分配给另一个进程的机制。从用户角度看,计算机能够并行运行多个进程,这恰恰是操作系统通过快速上下文切换造成的结果。在切换的过程中,操作系统需要先存储当前进程的状态(包括内存空间的指针,当前执行完的指令等等),在读入下一个进程的状态,然后执行此进程。

系统调用

系统调用(system call)是程序向系统内核请求服务的方式。可以包括硬件相关的服务(访问硬盘等),或者创建进程,调度其他进程等。系统调用是程序和操作系统之间的重要接口。

Semaphore/Mutex

当用户创立了多个线程/进程时,如果不同线程/进程同时读写相同的内容,则可能造成读写错误,或者数据不一致。此时,需要通过加锁的方式,控制核心区域(critical section)的访问权限。对于semaphore而言,在初始化变量的时候可以控制允许多少个线程/进程同时访问一个critical section,其他的线程/进程会被堵塞,直到有人解锁。Mutex相当于只允许一个线程/进程访问的semaphore。此外,根据实际需要,人们还实现了一种读写锁(read-write lock),它允许同时存在多个reader,但任何时候之多只有一个writer,且不能和reader共存。

死锁

在引入锁的同时,我们遇到了一个新的问题:死锁(dead lock)。死锁是指两个或多个线程/进程之间相互阻塞,以至于任何一个都不能继续运行,因此也不能解锁其他线程/进程。例如,线程1占有锁A,并且尝试获取锁B;而线程2占有锁B,并尝试获取锁A。此时,两者相互阻塞,都无法继续运行。

总结产生死锁的四个条件(只有当四个条件同时满足时才会产生死锁):

  1. Mutual Exclusion — Only one process may use a resource at a time「资源互斥」
  2. Hold-and-Wait — Process holds resource while waiting for another
  3. No Preemption — Can’t take a resouce away from a process
  4. Circular Wait — The waiting processes form a cycle「循环等待」

生产者消费者

生产者消费者模型是一种常见的通信模型:生产者和消费者共享一个数据管道,生产者将数据写入buffer,消费者从另一头读取数据。对于数据管道,需要考虑为空和溢出的情况。同时,通常还需要将这部份共享内存用mutex加锁。在只有一个生产者消费者的情况下,可以设计无锁队列(lockfree queue),线程安全地直接读写数据。

进程间通信

在介绍进程的时候,我们提起过不能直接读写另一个进程的数据。两者之间的通信需要通过进程间通信(inter-process communication,IPC)进行。进程通信的方式通常遵循生产者消费者模型,需要实现数据交换和同步两大功能。

  1. Shared-memory + semaphore: 不同进程通过读写操作系统中特殊的共享内存进行数据交换,进程之间用semaphore实现同步。
  2. Message passing: 进程在操作系统内部注册一个端口,并检测有没有数据,其他进程直接写数据到该端口。该通信方式更加接近与网络通信方式。事实上,网络通信也是一种IPC,知识进程分布在不同机器上而已。

I/O

UNIX/Linux 程序在执行任何形式的 I/O 操作时,都是在读取或者写入一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数,它的背后可能是一个硬盘上的普通文件、FIFO、管道、终端、键盘、显示器,甚至是一个网络连接。

请注意,网络连接也是一个文件,它也有文件描述符!你必须理解这句话。

http://c.biancheng.net/view/2123.html