操作系统总结(1)

进程与线程的概论区别

一句话概括:进程和线程都是一个时间段的描述,是CPU工作时间段的描述。

  • CPU执行任务的过程:先加载程序A的上下文,然后开始执行A,保存程序A的上下文,调入下一个要执行的程序B的程序上下文,然后开始执行B,保存程序B的上下文。。。。
  • 所以进程就是包括上下文切换的程序执行时间总和 = CPU加载上下文+CPU执行+CPU保存上下文。
  • 那么线程是什么呢?程序A分为a、b、c等多个块组合,CPU加载程序A上下文,开始执行程序A的a小段,然后执行A的b小段,然后再执行A的c小段,最后CPU保存A的上下文。
  • 这里a,b,c的执行是共享了A的上下文,CPU在执行的时候没有进行上下文切换的。这里的a,b,c就是线程,也就是说线程是共享了进程的上下文环境的更为细小的CPU时间段。

这里的上下文指什么?

一句话概括就是描述进程的信息。是当进程要切换时关于当前进程的寄存器内容以及内存页表的详细信息等内容,因为内核切换一个进程时,为了保证下次切回来能回到原状态,必须保存当前进程的所有状态,这里的所有状态就是上下文。

进程、线程的所有描述

  • 线程是进程里面的一部分,一个操作系统可能同时有几十到几百个进程在运行,每个进程里面可能又有几个到甚至上百个线程在运行。
  • 32位操作系统,进程总共有4G的内存空间可供寻址,即单个进程可占用的理论最大内存。
  • 每个进程自己的内存都是互相独立的。比如微信不可能读我网银的内存,不然钱都跑了。
  • 文件/网络句柄都是进程共享的,即不同的进程都能访问同一个文件或者网络端口。
  • 因为进程不共享内存,所以进程之间的通信,比较常见的就是通过TCP/IP端口来实现(Socket编程)。
  • 进程因为要分配这么多内存,所以开销大。
  • 绝大多数操作系统调度单位是线程、不是进程。

  • 线程含有“栈”或者称“调用堆栈”,主线程入口的main函数,会不断的进行函数调用,每次调用会把所有的参数和返回地址一层层的压到“栈”里面去,包括每个函数内部的局部变量也会放到这个“栈”里面。
  • 线程含有PC(ProgrammaCounter),里面放当前或者下一条指令的地址。所以我们操作系统真正运行的是一个一个线程,进程只是一个容器。PC这个指令放在内存中,PC的指针就是指向内存的。因此经常听到一个漏洞叫缓冲区溢出,比如输用户名的位置,黑客把用户名输的特别特别长,这个长度超出了我给用户分配的缓冲区,一直往后写写到了存储程序的那部分内存去了,黑客就通过这种方法植入代码,当然防止的方法是,一定要检查用户名长度。
  • TLS是各线程之间独立的内存空间,可存变量、数据,这些数据就是单个线程独有的。
  • 因为线程除了TLS其他是共享内存,因此线程间通信就是各自指针指向同一块内存。
  • 线程因为只需要分配一个“栈”,一个ProgrammaCounter,因此开销小。
  • 系统进行资源分配和调度的时候同时考虑线程和进程。
  • 线程是程序的多个顺序的流动态执行

寻址空间

操作系统怎么来寻址?就是给了一个地址,操作系统如何找到这个地址

寻址空间就是每一个进程里的指针可以取到的地址的范围。32位操作系统是4G

上图就是一个寻址过程

  • 我们有一个指针p,他所指向的内存不是物理内存,而是逻辑内存,只是在逻辑上存在,是进程独立的,每个进程都有自己的逻辑内存,根据操作系统是32位还是64位有不同的大小。
  • 接下来我们需要把逻辑内存和实际物理内存建立一个关系,我们就能通过逻辑内存找到物理内存了。
  • 但是逻辑内存对应的地址可能在物理内存里,也可能不在物理内存里,而是在硬盘上的虚拟内存里,因为物理内存空间有限,操作系统会在硬盘上开辟一个空间作为虚拟内存。
  • 如果在虚拟内存里,我们就要把虚拟内存放到物理内存中去。放的时候,这里是int,我们不可能只把4字节放过去,这样开销太大。所以虚拟内存有一个“分页”(下面还有分段、分段页)的概念,我们不是把p所指向的内存放过去,而是把p所指向的内存所在的分页整个放过去。分页根据操作系统配置可能有几KB也可能几MB。
  • 如果物理内存放不下分页,那么我们就要通过算法把物理内存中很久没有用的一块内存区域交换进虚拟内存
  • 在物理内存确保了p所在的数据后,就取出来放入寄存器里,就好了。
  • 因为存在“交换”,这就是为什么内存不够了,系统就很慢了,因为频繁交换分页。

分页、分段、分段页

进程间通信

能通信就一定是一种同步机制,这是显而易见的。

  • 文件。我写一个文件,你打开这个文件。
  • Signal:Signal是一个进程给另一个进程发的信号,是一个数字,代表一些特殊的含义。比如Linux中通过kill命令向另一个进程发Signal


  • 消息队列  就是一个进程向另一个进程发消息。
  • 管道(PIPE)、命名管道(FIFO)  管道PIPE   命名管道FIFO   非命名管道通常是单向的,只能用于有亲缘关系的进程通信;命名管道可能是单向的也可能是双向的,可以用于非亲缘关系进程通信。
  • 共享内存两个进程都同意我们共享某块内存,当然就可以共享。
  • 最重要的!!!Socket 。在机器上开一个端口作为服务器让客户连接,走TCP/UDP协议。这就是不同机器之间进程通信了。

各通信方式的应用场景

  • 管道、命名管道:适合非常短小、频率很高的消息,而且只能是两个进程之间。
  • 共享内存:适合非常庞大的、读写操作频率很高的数据(配合信号量使用),常见于多进程之间。
  • 消息队列不建议使用
  • 其他的考虑用Socket,在多进程、多线程、多模块所构成的今天最常见的分布式系统开发中,Socket是第一选择。

实际使用中,除非你有非常有说服力的理由,否则请用Socket。原因如下:

  • 虽然PIPE\FIFO理论上比Socket快,但现在计算机上真心不计较这么一点点速度损失的。你费劲纠结半天,不如我把socket设计好了,多插一块CPU来得更划算。
  • 另外,进程间通信的数据一般我们都会存入数据库,这样万一哪个进程挂掉了,也不至于丢失数据,从这个角度考虑,适用共享内存的场景就更少了。
  • 而且,PIPE和共享内存是不能跨网LAN的,虽然FIFO可以跨网,但代码可读性、易操作性、可移植性远不如Socket。
  • 最后,信号也如Socket,信号不能跨LAN,信息量极其有限。