00-操作系统 基础
操作系统基础
什么是操作系统?
通过以下四点可以概括操作系统到底是什么:
- 操作系统(Operating System,简称 OS)是管理计算机硬件与软件资源的程序,是计算机的基石。
- 操作系统本质上是一个运行在计算机上的软件程序 ,主要用于管理计算机硬件和软件资源。举例:运行在你电脑上的所有应用程序都通过操作系统来调用系统内存以及磁盘等等硬件。
- 操作系统存在屏蔽了硬件层的复杂性。 操作系统就像是硬件使用的负责人,统筹着各种相关事项。
- 操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。 内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定性。
很多人容易把操作系统的内核(Kernel)和中央处理器(CPU,Central Processing Unit)弄混。你可以简单从下面两点来区别:
- 操作系统的内核(Kernel)属于操作系统层面,而 CPU 属于硬件。
- CPU 主要提供运算,处理各种指令的能力。内核(Kernel)主要负责系统管理比如内存管理,它屏蔽了对硬件的操作。
下图清晰说明了应用程序、内核、CPU 这三者的关系。
操作系统主要有哪些功能?
从资源管理的角度来看,操作系统有 6 大功能:
- 进程和线程的管理:进程的创建、撤销、阻塞、唤醒,进程间的通信等。进程控制、进程同步、进程通信、死锁处理、处理机调度等。
- 存储管理:内存的分配和管理、内存保护与共享、虚拟内存、外存(磁盘等)的分配和管理等。
- 文件管理:文件的读、写、创建及删除等。文件存储空间的管理、目录管理、文件读写管理和保护等。
- 设备管理:完成设备(输入输出设备和外部存储设备等)的请求或释放,以及设备启动等功能。完成用户的 I/O 请求,方便用户使用各种设备,并提高设备的利用率。主要包括缓冲管理、设备分配、设备处理、虛拟设备等。
- 网络管理:操作系统负责管理计算机网络的使用。网络是计算机系统中连接不同计算机的方式,操作系统需要管理计算机网络的配置、连接、通信和安全等,以提供高效可靠的网络服务。
- 安全管理:用户的身份认证、访问控制、文件加密等,以防止非法用户对系统资源的访问和操作。
常见的操作系统有哪些?
Windows
目前最流行的个人桌面操作系统,不做多的介绍,大家都清楚。界面简单易操作,软件生态非常好。
Unix
最早的多用户、多任务操作系统。后面崛起的 Linux 在很多方面都参考了 Unix。
目前这款操作系统已经逐渐逐渐退出操作系统的舞台。
Linux
Linux 是一套免费使用、开源的类 Unix 操作系统。 Linux 存在着许多不同的发行版本,但它们都使用了 Linux 内核 。
严格来讲,Linux 这个词本身只表示 Linux 内核,在 GNU/Linux 系统中,Linux 实际就是 Linux 内核,而该系统的其余部分主要是由 GNU 工程编写和提供的程序组成。单独的 Linux 内核并不能成为一个可以正常工作的操作系统。
很多人更倾向使用 “GNU/Linux” 一词来表达人们通常所说的 “Linux”。
Mac OS
苹果自家的操作系统,编程体验和 Linux 相当,但是界面、软件生态以及用户体验各方面都要比 Linux 操作系统更好。
用户态和内核态
什么是用户态和内核态?
根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:
- 用户态(User Mode): 用户态运行的进程可以直接读取用户程序的数据,拥有较低的权限。当应用程序需要执行某些需要特殊权限的操作,例如读写磁盘、网络通信等,就需要向操作系统发起系统调用请求,进入内核态。
- 内核态(Kernel Mode):内核态运行的进程几乎可以访问计算机的任何资源包括系统的内存空间、设备、驱动程序等,不受限制,拥有非常高的权限。当操作系统接收到进程的系统调用请求时,就会从用户态切换到内核态,执行相应的系统调用,并将结果返回给进程,最后再从内核态切换回用户态。
内核态相比用户态拥有更高的特权级别,因此能够执行更底层、更敏感的操作。不过,由于进入内核态需要付出较高的开销(需要进行一系列的上下文切换和权限检查),应该尽量减少进入内核态的次数,以提高系统的性能和稳定性。处于用户态的程序只能访问用户空间,而处于内核态的程序可以访问用户空间和内核空间。
为什么要有用户态和内核态?只有一个内核态不行么?
- 在 CPU 的所有指令中,有一些指令是比较危险的比如内存分配、设置时钟、IO 处理等,如果所有的程序都能使用这些指令的话,会对系统的正常运行造成灾难性地影响。因此,我们需要限制这些危险指令只能内核态运行。这些只能由操作系统内核态执行的指令也被叫做 特权指令 。
- 如果计算机系统中只有一个内核态,那么所有程序或进程都必须共享系统资源,例如内存、CPU、硬盘等,这将导致系统资源的竞争和冲突,从而影响系统性能和效率。并且,这样也会让系统的安全性降低,毕竟所有程序或进程都具有相同的特权级别和访问权限。
因此,同时具有用户态和内核态主要是为了保证计算机系统的安全性、稳定性和性能。
用户态和内核态是如何切换的?
用户态切换到内核态的 3 种方式:
- 系统调用(Trap):程序的执行一般是在用户态下执行的,但当程序需要使用操作系统提供的服务时,比如说打开某一设备、创建文件、读写文件(这些均属于系统调用)等,就需要向操作系统发出调用服务的请求,这就是系统调用。用户态进程 主动 要求切换到内核态的一种方式。系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现。
- 中断(Interrupt):当外围设备完成用户请求的操作后,会向 CPU 发出相应的中断信号,这时 CPU 会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
- 异常(Exception):当 CPU 在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
在系统的处理上,中断和异常类似,都是通过中断向量表来找到相应的处理程序进行处理。区别在于,中断来自处理器外部,不是由任何一条专门的指令造成,而异常是执行当前指令的结果。
用户态和核心态(内核态)之间的区别是什么呢?
权限不一样。
- 用户态的进程能存取它们自己的指令和数据,但不能存取内核指令和数据(或其他进程的指令和数据)。
- 内核态下的进程能够存取内核和用户地址某些机器指令和特权指令,在用户态下执行特权指令会引起错误。 在系统中内核并不是作为一个与用户进程平行的估计的进程的集合。
中断
所谓的中断就是在计算机执行程序的过程中,由于出现了某些特殊事情,使得 CPU 暂停对程序的执行,转而去执行处理这一事件的程序。等这些特殊事情处理完之后再回去执行之前的程序。 简单来说,对中断的理解就是对一些特殊事情的处理。中断一般分为三类:
- 由计算机硬件异常或故障引起的中断,称为 内部异常中断;
- 由程序中执行了引起中断的指令而造成的中断,称为 软中断(系统调用相关的中断);
- 由外部设备请求引起的中断,称为 外部中断。
与中断紧密相连的一个概念就是中断处理程序了。当中断发生的时候,系统需要去对中断进行处理,对这些中断的处理是由操作系统内核中的特定函数进行的,这些处理中断的特定的函数就是我们所说的中断处理程序。
另一个与中断紧密相连的概念就是中断的优先级。中断的优先级说明的是当一个中断正在被处理的时候,处理器能接受的中断的级别。中断的优先级也表明了中断需要被处理的紧急程度。每个中断都有一个对应的优先级,当处理器在处理某一中断的时候,只有比这个中断优先级高的中断可以被处理器接受并且被处理。 优先级比这个当前正在被处理的中断优先级要低的中断将会被忽略。
典型的中断优先级如下所示:
- 机器错误 > 时钟 > 磁盘 > 网络设备 > 终端 > 软件中断
系统调用
什么是系统调用?
我们运行的程序基本都是运行在用户态,如果我们调用操作系统提供的内核态级别的子功能咋办呢?那就需要系统调用了!
也就是说在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。
这些系统调用按功能大致可分为如下几类:
- 设备管理:完成设备(输入输出设备和外部存储设备等)的请求或释放,以及设备启动等功能。
- 文件管理:完成文件的读、写、创建及删除等功能。
- 进程管理:进程的创建、撤销、阻塞、唤醒,进程间的通信等功能。
- 内存管理:完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。
系统调用和普通库函数调用非常相似,只是系统调用由操作系统内核提供,运行于内核态,而普通的库函数调用由函数库或用户自己提供,运行于用户态。
总结:系统调用是应用程序与操作系统之间进行交互的一种方式,通过系统调用,应用程序可以访问操作系统底层资源例如文件、设备、网络等。
系统调用的过程了解吗?
系统调用的过程可以简单分为以下几个步骤:
- 用户态的程序发起系统调用,因为系统调用中涉及一些特权指令(只能由操作系统内核态执行的指令),用户态程序权限不足,因此会中断执行,也就是 Trap(Trap 是一种中断)。
- 发生中断后,当前 CPU 执行的程序会中断,跳转到中断处理程序。内核程序开始执行,也就是开始处理系统调用。
- 内核处理完成后,主动触发 Trap,这样会再次发生中断,切换回用户态工作。
Linux 的系统调用主要有
Task | Commands |
---|---|
进程控制 | fork(); exit(); wait(); |
进程通信 | pipe(); shmget(); mmap(); |
文件操作 | open(); read(); write(); |
设备操作 | ioctl(); read(); write(); |
信息维护 | getpid(); alarm(); sleep(); |
安全 | chmod(); umask(); chown(); |
系统调用与库函数的区别
- 系统调用(System call)是程序向系统内核请求服务的方式。 可以包括硬件相关的服务(例如,访问硬盘等),或者创建新进程,调度其他进程等。系统调用是程序和操作系统之间的重要接口。
- 库函数:把一些常用的函数编写完放到一个文件里,编写应用程序时调用,这是由第三方提供的,发生在用户地址空间。
- 在移植性方面,不同操作系统的系统调用一般是不同的,移植性差;而在所有的 ANSI C编译器版本中,C库函数是相同的。
- 在调用开销方面,系统调用需要在用户空间和内核环境间切换,开销较大;而库函数调用属于“过程调用”,开销较小。
基本特征
并发和并行
- 并发是指宏观上在一段时间内能同时运行多个程序,而并行则指同一时刻能运行多个指令。
- 并行需要硬件支持,如多流水线、多核处理器或者分布式计算系统。
- 操作系统通过引入进程和线程,使得程序能够并发运行。
共享
- 共享是指系统中的资源可以被多个并发进程共同使用。
- 有两种共享方式:互斥共享和同时共享。
- 互斥共享的资源称为临界资源,例如打印机等,在同一时间只允许一个进程访问,需要用同步机制来实现对临界资源的访问。
虚拟
- 虚拟技术把一个物理实体转换为多个逻辑实体。
- 主要有两种虚拟技术:时分复用技术和空分复用技术。
- 多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占有处理器,每次只执行一小个时间片并快速切换。
- 虚拟内存使用了空分复用技术,它将物理内存抽象为地址空间,每个进程都有各自的地址空间。地址空间的页被映射到物理内存,地址空间的页并不需要全部在物理内存中,当使用到一个没有在物理内存的页时,执行页面置换算法,将该页置换到内存中。
异步
- 异步指进程不是一次性执行完毕,而是走走停停,以不可知的速度向前推进。
同步与异步
同步的定义:是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么,这个进程将会一直等待下去,直到收到返回信息才继续执行下去。
特点:- 同步是阻塞模式。
- 同步是按顺序执行,执行完一个再执行下一个,需要等待,协调运行。
异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
特点:- 异步是非阻塞模式,无需等待。
- 异步是彼此独立,在等待某事件的过程中,继续做自己的事,不需要等待这一事件完成后再工作。线程是异步实现的一个方式。
同步与异步的优缺点
- 同步可以避免出现死锁,读脏数据的发生。一般共享某一资源的时候,如果每个人都有修改权限,同时修改一个文件,有可能使一个读取另一个人已经删除了内容,就会出错,同步就不会出错。但同步需要等待资源访问结束,浪费时间,效率低。
- 异步可以提高效率,但安全性较低。
一个程序从开始运行到结束的完整过程(四个过程)
- 预处理:条件编译,头文件包含,宏替换的处理,生成.i文件。
- 编译:将预处理后的文件转换成汇编语言,生成.s文件。
- 汇编:汇编变为目标代码(机器代码)生成.o的文件。
- 链接:连接目标代码,生成可执行程序。
内存池、进程池、线程池。(c++程序员必须掌握)
首先介绍一个概念“池化技术”。池化技术就是:提前保存大量的资源,以备不时之需以及重复使用。池化技术应用广泛,如内存池,线程池,连接池等等。内存池相关的内容,建议看看Apache、Nginx等开源web服务器的内存池实现。
由于在实际应用当做,分配内存、创建进程、线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作。因此,当程序中需要频繁的进行内存申请释放,进程、线程创建销毁等操作时,通常会使用内存池、进程池、线程池技术来提升程序的性能。
- 线程池:线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当需要一个开辟一个线程去做具体的工作时,就会唤醒线程池中的某一个睡眠线程,让它去做具体工作,当工作完成后,线程又处于睡眠状态,而不是将线程销毁。
- 进程池:与线程池同理。
- 内存池:内存池是指程序预先从操作系统申请一块足够大内存,此后,当程序中需要申请内存的时候,不是直接向操作系统申请,而是直接从内存池中获取;同理,当程序释放内存的时候,并不真正将内存返回给操作系统,而是返回内存池。当程序退出(或者特定时间)时,内存池才将之前申请的内存真正释放。
动态链接库与静态链接库的区别
静态库
- 静态库是一个外部函数与变量的集合体。静态库的文件内容,通常包含一堆程序员自定的变量与函数,其内容不像动态链接库那么复杂,在编译期间由编译器与链接器将它集成至应用程序内,并制作成目标文件以及可以独立运作的可执行文件。而这个可执行文件与编译可执行文件的程序,都是一种程序的静态创建(static build)。
动态库
- 静态库很方便,但是如果我们只是想用库中的某一个函数,却仍然得把所有的内容都链接进去,这个就会让程序变的越来越臃肿。一个更现代的方法则是使用共享库,避免了在文件中静态库的大量重复。
- 动态链接可以在首次载入的时候执行(load-time linking),这是 Linux 的标准做法,会由动态链接器ld-linux.so 完成,比方标准 C 库(libc.so) 通常就是动态链接的,这样所有的程序可以共享同一个库,而不用分别进行封装。
- 动态链接也可以在程序开始执行的时候完成(run-time linking),在 Linux 中使用 dlopen()接口来完成(会使用函数指针),通常用于分布式软件,高性能服务器上。而且共享库也可以在多个进程间共享。
- 链接使得我们可以用多个对象文件构造我们的程序。可以在程序的不同阶段进行(编译、载入、运行期间均可),理解链接可以帮助我们避免遇到奇怪的错误。
区别:
- 使用静态库的时候,静态链接库要参与编译,在生成执行文件之前的链接过程中,要将静态链接库的全部指令直接链接入可执行文件中。而动态库提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个.dll文件中,该dll包含一个或多个已被编译,链接并与使用它们的进程分开储存的函数。
- 静态库中不能再包含其他动态库或静态库,而在动态库中还可以再包含其他动态或者静态库。
- 静态库在编译的时候,就将库函数装载到程序中去了,而动态库函数必须在运行的时候才被装载,所以使用静态库速度快一些。