Linux教程

操作系统常见面试题总结

本文主要是介绍操作系统常见面试题总结,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

操作系统常见面试题总结

    • 进程
      • 进程的五个状态
      • 什么时候进程会阻塞?
      • 进程间通信方式
      • 进程调度的方式
      • 进程调度策略
      • 一个Windows进程要包括哪些要素?
      • Linux系统进程的四大要素
    • 线程
      • 一个进程可以创建多少线程?和什么有关?
      • 线程间通信方式
      • 线程如何调度CPU?
      • 何时会发生线程切换?
      • 线程同步
      • 进程和线程的区别
      • 线程和协程的区别
      • 多进程和多线程的区别?
      • 为什么使用并发?
      • 为什么使用多线程?
      • 什么时候使用多线程?
      • 多线程可能会遇到哪些问题?
      • CAS无锁队列实现原理
    • 操作系统内存管理
      • 内存管理的功能
      • 常见的内存管理技术
        • 页式存储管理
        • 请求式分页存储管理
        • 段式存储管理
        • 段页式存储管理
    • 虚拟内存
      • 为什么要有虚拟内存
      • 虚拟内存的最大容量
      • 用户进程空间,内核进程空间
      • 虚拟内存的实现
      • 操作系统需要提供的功能
      • 缺页中断机制
      • 页面置换算法
      • 页面分配策略
      • 驻留集
      • 页面分配
      • 何时调入页面
      • 何处调入页面
      • 抖动现象
      • 工作集
    • 上下文切换
      • 进程上下文切换
        • 系统调用
        • 进程上下文切换跟系统调用又有什么区别呢?
        • 发生进程上下文切换的场景
      • 线程上下文切换
      • 中断上下文切换
    • 字节序(Linux,大小端)
      • 大端模式
      • 小端模式
      • 为什么有大小端模式之分?
      • 判断大小端
    • select、poll、epoll
      • Linux操作系统中基础的概念
      • Select 时间复杂度O(n)
      • Poll 时间复杂度O(n)
      • Epoll 时间复杂度O(1)
      • epoll更高效的原因
      • select, poll, epoll的区别
    • 简述ping命令的工作原理
    • 系统调用
      • 系统调用和库函数调用的区别?
      • 具体的执行过程
      • 使用库函数也会有系统调用的开销,为什么不直接使用系统调用呢?
    • 中断
    • Linux操作系统中常见命令
      • 查看进程命令
      • 修改文件命令
      • 查看IP地址命令
      • 查看文件命令
      • 文本搜索命令
      • glibc
      • 查看端口目前的状态?端口状态有哪些?
    • 单核CPU要不要考虑线程不安全情况?
    • 为什么++i和i++都是线程不安全的?
    • Linux系统中CPU占用率较高问题排查思路与解决方法

进程

进程:是指在操作系统中正在运行的一个应用程序,是系统资源调度和分配的基本单位。

进程的五个状态

  • 新建态:进程在创建时需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。如果资源无法满足,就无法被调度运行,即还没有加入到可执行进程组中,通常是进程控制块已经创建但是还没有加载到内存中的进程。
  • 就绪态:进程已经准备好,已分配到所需资源,只要分配到CPU就能够立即运行。
  • 运行态:进程处于就绪状态被调度后,进程进入执行状态。
  • 阻塞态(等待态):正在执行的进程由于某些事件(I/O请求,申请缓存区失败)而暂时无法运行,进程受到阻塞。在满足请求时进入就绪状态等待系统调用。
  • 退出态:操作系统从可执行进程组中释放出的进程,或由于自身或某种原因

什么时候进程会阻塞?

系统对服务提出请求(如启动I/O操作,访问文件系统),而进行阻塞;正在执行的进程需要从其他合作进程(网络新数据)获得的数据未到达,从而阻塞本进程(如等待网络新数据到达)。
在这里插入图片描述

进程间通信方式

管道;系统IPC(消息队列,信号量,信号,共享内存);套接字socket。

  • 管道( pipe ):是一种半双工的通信方式,数据只能单向流动,只能承载无格式字节流,而且只能在具有亲缘关系(通常指父子进程关系)的进程间使用。
  • 有名管道(namedpipe):半双工通信方式,允许无亲缘关系进程间的通信。
  • 消息队列(messagequeue):是消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  • 信号量(semophore):是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  • 信号(sinal):比较复杂的通信方式,用于通知接收进程某个事件已经发生。
    共享内存(shared memory):是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制(如信号量)配合使用,来实现进程间的同步和通信。
  • 套接字(socket ) :也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同设备及其之间的进程通信。

进程调度的方式

非抢占式调度和抢占式调度。
非抢占方式:当某一进程正在处理机上执行时,即使有某个更为重要或紧迫的进程进入就绪队列,该进程仍继续执行,直到其完成或发生某种事件而进入完成或阻塞状态时,才把处理机分配给更为重要或紧迫的进程。
抢占方式:当某一进程正在处理机上执行时,若有某个更为重要或紧迫的进程进入就绪队列,则立即暂停正在执行的进程,将处理机分配给这个更为重要或紧迫的进程。

进程调度策略

可以参考这篇文章: 进程调度策略.
先来先服务调度算法; 非抢占
短作业(进程)优先调度算法; 非抢占
最短剩余时间优先调度算法;抢占
高优先权优先调度算法;抢占
高响应比优先调度算法;抢占
时间片轮转法; 抢占
多级反馈队列调度算法。抢占

在这里插入图片描述

一个Windows进程要包括哪些要素?

由程序、数据和进程控制块(PCB)(堆栈?)组成。其中PCB通常记载进程的相关信息,包括:

  • 程序计数器:接着要运行的指令地址。
  • 进程状态:可以是new、ready、running、waiting或 blocked等。
  • CPU暂存器:如累加器、索引暂存器(Index register)、堆栈指针以及一般用途暂存器、状况代码等,主要用途在于中断时暂时存储数据,以便稍后继续利用;其数量及类因电脑架构有所差异。
  • CPU排班法:优先级、排班队列等指针以及其他参数。
  • 存储器管理:如标签页表等。
  • 会计信息:CPU与实际时间之使用数量、时限、账号、工作或进程号码。
  • 输入输出状态:配置进程使用I/O设备,如磁带机。

Linux系统进程的四大要素

  • 程序代码。代码不一定是进程专有,可以与其它进程共用。
  • 系统 堆栈空间,这是进程专用的。
  • 在内核中维护相应的 进程控制块。只有这样,该进程才能成为内核调度的基本单位,接受调度。并且,该结构也记录了进程所占用的各项资源。
  • 有独立的 存储空间,表明进程拥有专有的用户空间。

以上四条,缺一不可。若缺少第四条,那么就称其为“线程”。如果完全没有用户空间,称其为“内核线程”;如果是共享用户空间,则称其为“用户线程”。

线程

线程是指进程内独立执行某个任务的一个单元,是CPU调度和分配的基本单元。

一个进程可以创建多少线程?和什么有关?

理论上,一个进程可用虚拟空间是2G,默认情况下,线程的栈的大小是1MB,所以理论上最多只能创建2048个线程。
一个进程可以创建的线程数由可用虚拟空间和线程的栈的大小共同决定。

线程间通信方式

在windows系统中,线程间的通信一般采用四种方式:全局变量方式、消息传递方式、参数传递方式和线程同步法(信号量)。
消息队列,是最常用的一种,也是最灵活的一种,通过自定义数据结构,可以传输复杂和简单的数据结构。每一个线程都拥有自己的消息队列。 利用系统的提供的事件、信号等通知机制、使用同步锁和自定义数据结构等来实现。
全局变量/共享内存,进程中的线程间内存共享,这是比较常用的通信方式和交互方式。对于标准类型的全局变量,建议使用volatile修饰符,它告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。
使用事件CEvent类,Event对象有两种状态:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。
1)创建一个CEvent类的对象:CEvent threadStart;它默认处在未通信状态;
2)threadStart.SetEvent();使其处于通信状态;
3)调用WaitForSingleObject()来监视CEvent对象

线程如何调度CPU?

CPU是如何处理多线程的运行:分配时间段,每个线程运行一个时间段换另一个时间段,从整体看好像都在运行,其实同一时间只在运行一个线程。
调度规则:平均分配,平均分配每个线程占用的cpu的时间片;抢占式(按优先级),优先让优先级高的线程占用CPU。
饥饿:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。

何时会发生线程切换?

多线程的执行是通过cpu的时间片分配,每个线程会分配到一个时间片,循环执行这些线程,线程时间片消耗完了就会进入等待状态,直到分配到新的时间片,因为时间片的时间非常短,所以cpu不停的切换线程。或者有更高优先级的线程进入就绪队列。

上下文切换:就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。

线程同步

是指多线程通过特定的设置(如互斥量,事件对象,临界区)来控制线程之间的执行顺序(即所谓的同步),也可以说是在线程之间通过同步建立起执行顺序的关系。

线程互斥:是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。
(1)互斥量
互斥量是最简单的同步机制,即互斥锁。多个进程(线程)均可以访问到一个互斥量,通过对互斥量加锁,从而来保护一个临界区,防止其它进程(线程)同时进入临界区,保护临界资源互斥访问。互斥量是内核对象。
(2)条件变量/事件对象?
与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。条件变量适合多个进程(线程)等待同一事件发生,然后去干某事。
条件变量必须配合互斥量(锁)一起工作。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。
(3)读写锁
读写锁适合于使用在读操作多,写操作少的情况,比如数据库。读写锁读锁可以同时加很多,但是写锁是互斥的。当有进程或者线程要写时,必须等待所有的读进程或者线程都释放自己的读锁方可以写。
(4)信号量
它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。可以理解为带计数的条件变量。当信号量的值小于0时,工作进程或者线程就会阻塞,等待资源释放。信号量也是内核对象。

进程和线程的区别

线程具有许多传统进程所具有的特征,所以又称为轻型进程(Light-Weight Process)或进程元。相应地把传统进程称为重型进程(Heavy-Weight Process),传统进程相当于只有一个线程的任务。通常一个进程都拥有若干个线程,至少也有一个线程。
概念:进程是指在操作系统中正在运行的一个应用程序,是系统资源调度和分配的基本单位。线程是指进程内独立执行某个任务的一个单元,是CPU调度和分配的基本单元。一个线程只能属于一个进程, 而一个进程可以有多个线程, 但至少有一个线程。
独立性:同一进程中的多个线程独立性比不同进程间的独立性差很多。进程间不会相互影响;线程一个线程挂掉将导致整个进程挂掉。
拥有资源:每个进程都有独立的地址空间和资源。多个线程共享进程的内存,同一进程下多线程共享进程下的资源。线程仅拥有独立运行需要的资源,比如线程中的TCB。
系统开销:进程和线程的创建、撤销,系统都要为之分配和回收资源,比如内存空间、IO设备等。而线程切换只须保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。操作系统所付出的开销将显著地大于在创建或撤消线程时的开销。需要注意是同一个进程中的线程切换不会引起进程切换,但是不同进程中的线程切换,会导致进程切换。
通信:由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现,也变得比较容易。进程间通信 IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。在有的系统中,线程的切换、同步和通信都无须操作系统内核的干预。

线程和协程的区别

协程:是一种基于线程之上,但又比线程更加轻量级的存在,这种由程序员自己写程序来管理的轻量级线程叫做[ 用户空间线程 ],具有对内核来说不可见的特性。协程拥有自己的寄存器上下文和栈。如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。

  • 线程的切换由操作系统(内核)负责调度;协程由用户自己进行调度,因此减少了上下文切换,提高了效率。
  • 线程的默认Stack大小是1M,而协程更轻量,接近1K。因此可以在相同的内存中开启更多的协程。
  • 切换开销:线程涉及模式切换(从用户态切换到内核态)、16个寄存器、PC、SP…等寄存器的刷新等;协程只有三个寄存器的值修改 - PC / SP / DX。
  • 性能问题:线程资源占用太高,频繁创建销毁会带来严重的性能问题;协程资源占用小,不会带来严重的性能问题。
  • 数据同步:线程需要用锁等机制确保数据的一致性和可见性;协程不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

适用于被阻塞的,且需要大量并发的场景。 但不适用于大量计算的多线程,遇到此种情况,最好是用线程去解决。

多进程和多线程的区别?

多进程并发是指,存在多个单线程的进程,将应用程序分为多个。多进程一个很大的优点就是进程间非常独立,除了通信之外基本不会互相影响,即当一个进程崩溃时不会影响主进程和其他进程。

并发的另一个途径是单个进程中运行多个线程,每个线程可以相互独立运行,但是进程中的所有线程都享有共同的地址空间,并且线程间拥有不少共享数据。线程间的同步比较复杂,并且加锁之类的操作也有成本,会耗费一些资源;同时安全性问题也存在,一个线程挂掉,其他的也会同时挂掉。

为什么使用并发?

为了性能而使用并发:为了在硬件条件允许的情况下为了提高性能,在同一时刻干好几个任务,肯定比一个一个任务执行来的快。
为了划分关注点而使用并发:将任务拆解,将关注点划分开,易于管理。

为什么使用多线程?

多线程指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务。

  • 更高的运行效率——并行,和资源利用率;
  • 多线程是模块化的编程模型,能简化程序的结构,便于理解和维护;
  • 与进程相比,线程的创建和切换(多线程上下文切换的性能损耗:上下文切换(线程切换,进程切换,模式切换,地址空间切换)——中断处理(硬件中断,软件中断—线程被挂起);多任务处理(每个程序都有相应的处理时间片);用户态切换)开销更小;
  • 通信方便。

什么时候使用多线程?

系统接受实现多用户多请求的高并发时,通过多线程来实现。
大任务处理起来比较耗时,这时候可以采用多个线程并行 加快处理。

多线程可能会遇到哪些问题?

  • 线程安全问题(可见性、原子性)。多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果达不到预期。解决方案:互斥锁。
  • 活跃性问题(死锁、活锁、饥饿)。活跃性是指某件正确的事情终究会发生,当某个操作无法继续进行下去的时候,就会发生活跃性问题。
  • 性能问题(创建线程开销、线程上下文切换)。减少上下文切换的方法有:无锁并发编程,CAS算法。

CAS无锁队列实现原理

CAS是解决多线程并行情况下使用锁造成性能损耗问题的一种机制。无锁队列的内部实现实际也是原子操作,可以避免多线程调用出现的不可预知的情况。
CAS主要有三个操作数,当前值A、内存值V和要更改的新值B。当当前值A跟内存值V相等,那么就将内存值V改成B;当 当前值A和内存值V不想等的话,要么就重试,要么放弃更新B。实际就是多线程操作的时候,不加锁,多线程操作了共享的资源之后,在实际修改的时候判断是否修改成功。

操作系统内存管理

内存一直是计算机系统中宝贵而又紧俏的资源,内存能否被有效、合理地使用,将直接影响到操作系统的性能。

内存管理的目的:一是方便用户使用;二是提高存储器的利用率。

内存管理的功能

  • 内存空间的分配与回收:由操作系统完成主存储器空间的分配和管理,使程序员摆脱存储分配的麻烦,提高编程效率。
  • 地址转换:在多道程序环境下,程序中的逻辑地址与内存中的物理地址不可能一致,因此存储管理必须提供地址变换功能,把逻辑地址转换成相应的物理地址。
  • 存储保护和存储共享:保证各道作业在各自的存储空间内运行,互不干扰;同时充分利用内存空间,共享内存中存放的信息。
  • 内存空间的扩充:利用虚拟存储技术或自动覆盖技术,从逻辑上扩充内存。

常见的内存管理技术

页式存储管理

页式存储管理将程序逻辑地址空间划分为固定大小的页(page),而物理内存划分为同样大小的页框(page frame)。为方便地址转换,页面大小应是2的整数幂。每一个作业有一个页表,用来记录各个页在内存中所对应的块(页框)。

优点:没有外碎片,每个内碎片不超过页的大小。
缺点:程序全部装入内存,要有硬件支持。如地址变换、缺页中断的产生和选择淘汰页面等都要求有相应的硬件支持。增加了机器成本和系统开销。

地址结构包含两部分:前一部分为页号P,后一部分为页内偏移量W。其中页号与页内偏移量所占多少位,与页面的大小和主存的最大容量有关。
每页大小为4KB,主存大小为4GB。则地址长度为32 位,其中011位为页内地址,即:1231位为页号,地址空间最多允许有2^20页。
地址变换机制:若页表全部放在内存中,则存取一个数据或一条指令至少要访问两次内存:一次是访问页表,确定所存取的数据或指令的物理地址,第二次才根据该地址存取数据或指令。显然,这种方法比通常执行指令的速度慢了一半。

局部性原理:
时间局部性:如果执行了程序中的某条指令, 那么不久之后这条指令很有可能再次执行; 如果某个数据被访问过, 不久之后该数据很可能再次被访问。
空间局部性:一旦程序访问了某个存储单元, 在不久之后, 其附近的存储单元也很有可能被访问到。

为此,在地址变换机构中增设了一个具有并行查找能力的高速缓冲存储器——快表,又称联想寄存器(TLB),用来存放当前访问的若干页表项,以加速地址变换的过程。而内存中的页表常称为慢表。
在这里插入图片描述

请求式分页存储管理

请求分页系统建立在基本分页系统基础之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。在请求分页系统中,只要求将当前需要的一部分页面装入内存,便可以启动作业运行。在作业执行过程中,当所要访问的页面不在内存时,再通过调页功能将其调入,同时还可以通过置换功能将暂时不用的页面换出到外存上,以便腾出内存空间。

页面置换算法:

  • 最佳算法(OPT算法),用来评价其他算法,使用缺页中断率:
    f = F / A(其中F为作业失败访问的次数,A为作业总的访问次数)
  • 先进先出算法(FIFO算法),淘汰在内存驻留时间最长的页面。
  • 最近最久未使用淘汰算法(LRU算法),淘汰最久没有被使用的页面。
  • 最不常使用淘汰算法(LFU算法),淘汰一段时间内访问次数最少的页面。

段式存储管理

段式存储管理要求每个作业的地址空间按照程序自身的逻辑划分为若干段,每个段都有一个唯一的内部段号,每段从0开始编址。以段为单位进行分配, 每个段在内存中占据连续空间, 但各段之间可以不相邻。

优点:可以分别编写和编译;可以针对不同类型的段采取不同的保护;可以以段为单位来进行共享,包括通过动态链接进行代码共享。
缺点:会产生碎片。

逻辑地址由段号S(16-31)与段内偏移量W(0-15)两部分组成。段号和段内偏移量都为16位,则一个作业最多可有2^16=65536个段,最大段长为64KB。
程序分为多个段, 各个段离散地装入内存, 为了保证程序能正常运行, 就必须能从物理内存中找到各个逻辑段的存放位置。为此, 需为每个进程建立一张段映射表, 简称"段表"。每个段对应一个段表项, 记录着该段在内存中的起始位置 (基址) 和 段长。各个段表项的长度是相同的, 因此和页号一样, 段号是"隐含"的, 不占据存储空间。

分段和分页的对比:

  • 页是信息的物理单位, 分页的主要目的是为了实现离散分配, 提高内存利用率。分页仅仅是系统管理上的需要, 完全是系统行为, 对用户是不可见的。
    段是信息的逻辑单位, 分段的主要目的是更好地满足用户需求。一个段通常包含着一组属于一个逻辑模块的信息, 分段对用户是可见的, 用户编程时需要显式地给出段名。
  • 页的大小固定且由系统决定;段的长度不固定, 取决于用户编写的程序。
  • 分页的用户进程地址空间是一维的, 分段的用户进程是二维的。
  • 分段比分页更容易实现信息的共享和保护。
    在这里插入图片描述

段页式存储管理

在段页式存储中,每个分段又被分成若干个固定大小的页。

优点:没有外碎片,每个内碎片不超过页的大小,能提高存储空间的利用率。可以分别编写和编译;可以针对不同类型的段采取不同的保护;可以以段为单位来进行共享,包括通过动态链接进行代码共享。有利于实现程序的动态连接
缺点:由于管理软件的增加,复杂性和开销也增加。另外需要的硬件以及占用的内存也有所增加,使得执行速度下降。存在系统抖动的危险。

逻辑地址由段号S(16-35)、段内页号P(12-15)与段内偏移量W(0-11)三部分组成。段号的位数决定了每个进程最多可以分为几个段;页号位数决定了每个段最大有多少页;页内偏移量决定了页面大小和内存块的大小。
"分段"对用户是可见的, 而将各段"分页"对用户是不可见的, 系统会根据段内地址自动划分页号和段内偏移量, 因此段页式管理的地址结构是"二维"的。
每一个进程对应一个段表, 每一个段又对应一个页表, 因此一个进程可能对应多个页表。

查找过程:

  • 由逻辑地址得到段号, 页号, 页内偏移
  • 段号与段表寄存器的段长度比较, 检查是否越界
  • 由段表起始地址, 段号找到对应段表项— (一次访存)
  • 根据段表中记录的页表长度, 检查页号是否越界
  • 由段表中的页表地址, 页号得到查询页表, 找到相应页表项— (二次访存)
  • 由页面存放的内存块号, 页内偏移得到最终的物理地址
  • 访问目标单元— (三次访存)
    在这里插入图片描述

虚拟内存

虚拟内存:在程序装入时, 将程序中很快会用到的部分装入内存, 暂时用不到的部分留在外存, 就可以让程序开始执行。在程序执行过程中, 当所访问的信息不在内存时, 由操作系统负责将所需信息由外存调入内存, 然后继续执行程序。内存空间不够时, 操作系统负责将内存中暂时用不到的信息换出到外存。在用户看来, 就有一个比实际内存大很多的内存, 这就叫虚拟内存。

为什么要有虚拟内存

在早期的计算机中,是没有虚拟内存的概念的。我们要运行一个程序,会把程序全部装入内存,然后运行。当运行多个程序时,经常会出现以下问题:

  • 进程地址空间不隔离,没有权限保护。由于程序都是直接访问物理内存,所以一个进程可以修改其他进程的内存数据,甚至修改内核地址空间中的数据。
  • 内存使用效率低。内存空间不足时,要将其他程序暂时拷贝到硬盘,然后将新的程序装入内存运行。由于大量的数据装入装出,内存使用效率会十分低下。
  • 程序运行的地址不确定。因为内存地址是随机分配的,所以程序运行的地址也是不确定的。

虚拟内存的最大容量

虚拟内存的最大容量是由计算机的地址结构 (CPU的寻址范围) 确定的, 虚拟内存的实际容量 = min(内存容量+外存容量, CPU寻址范围)
在这里插入图片描述

用户进程空间,内核进程空间

在这里插入图片描述

虚拟内存的实现

  • 请求分页存储管理
  • 请求分段存储管理
  • 请求段页式存储管理

操作系统需要提供的功能

  • 请求调页 (段)
  • 页面置换 (段置换)

缺页中断机制

在请求分页操作系统中, 每当要访问的页面不在内存时, 便产生一个缺页中断, 然后由操作系统的缺页中断处理程序处理中断。此时缺页的进程阻塞, 放入阻塞队列, 调页完成后再将其唤醒, 放回就绪队列。
如果内存中有空闲块, 则为进程分配一个空闲块, 将所缺页面装入该块, 并修改页表中相应的页表项。
如果内存中没有空闲块, 则由页面置换算法选择一个页面淘汰, 若该页面在内存期间被修改过, 则要将其写回外存, 未修改过的页面不用写回外存。
缺页中断是因为当前执行的指令想要访问目标页面未调入内存而产生的, 因此属于内中断。

在这里插入图片描述

页面置换算法

  • 最佳置换算法OPT:每次选择淘汰的页面是以后永不使用或者在最长时间内不会使用的页面, 保证最低的缺页率。但是操作系统无法预判页面访问序列, 这种算法是无法实现的。
  • 先进先出置换算法 FIFO:每次淘汰的页面是最早进入内存的页面。实现 : 将调入内存的页面根据调入的先后顺序排成一个队列, 需要置换页面的时候选择队首的页面。实现简单, 算法性能差, 不适应进程实际运行时的规律。
  • 最近最久未使用置换算法 LRU:当需要淘汰一个页面的时候, 选择现有页面中t值最大的, 即最近最久未使用的页面。需要淘汰页面时, 逆向检查此时在内存中的几个页面号, 最后一个出现的页号就是需要被淘汰的。特点 : 性能好, 但实现起来需要专门的硬件支持, 算法开销大。
  • 时钟置换算法 ( CLOCK ):简单的时钟置换算法仅考虑到了一个页面最近是否被访问过。但是事实上, 如果被淘汰的页面没有被修改过, 就不需要执行I/O操作写回外存。只有被淘汰的页面被修改过时, 才需要写回外存。因此, 除了考虑一个页面最近有没有被访问过之外, 操作系统还应该考虑页面有没有被修改过。在其他条件都相同时, 应该优先淘汰没有修改过的页面, 避免I/O操作, 这就是改进型的时钟置换算法的思想。利用 (访问位, 修改位) 的形式表示各页面状态。特点 : 开销小, 性能也不错。

页面分配策略

在这里插入图片描述

驻留集

请求分页存储管理器中给进程分配的物理块的集合。(系统给进程分配了n各物理块 ----的另一种表述 : 驻留集大小为n)。
采用虚拟存储技术的系统中, 驻留集的大小一般小于进程的总大小。如果驻留集太小, 会导致缺页频繁, 系统要花大量的时间来处理缺页, 实际用于进程推进的时间很少。如果驻留集太大, 会导致多道程序并发度下降, 资源利用率降低。

页面分配

固定分配全局置换:系统为每个进程分配一定数量的物理块,在整个运行期间都不改变。
可变分配全局置换:只要缺页就给进程分配新物理块。
可变分配局部置换:根据发生缺页的频率来动态增加或减少进程的物理块。
在这里插入图片描述

何时调入页面

在这里插入图片描述
在这里插入图片描述

何处调入页面

文件区用于调入不会被修改的数据, 对换区用用于调入可能被修改的数据。
在这里插入图片描述

抖动现象

在这里插入图片描述

工作集

一般来说 驻留集 的大小不能小于 工作集 的大小, 否则进程运行过程中将频繁缺页。
在这里插入图片描述

上下文切换

CPU 寄存器和程序计数器就是 CPU 上下文,因为它们都是 CPU 在运行任何任务前,必须的依赖环境。
CPU 寄存器是 CPU 内置的容量小、但速度极快的内存。
程序计数器则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。

上下文切换:就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。而这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行。
CPU 上下文切换的类型:进程、线程、中断上下文切换。

进程上下文切换

Linux 按照特权等级,把进程的运行空间分为内核空间和用户空间,分别对应着下图中, CPU 特权等级的 Ring 0 和 Ring 3。
内核空间(Ring 0)具有最高权限,可以直接访问所有资源;
用户空间(Ring 3)只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问这些特权资源。
在这里插入图片描述
进程既可以在用户空间运行,又可以在内核空间中运行。进程在用户空间运行时,被称为进程的用户态;而陷入内核空间的时候,被称为进程的内核态。

下图中灰色部分即为进程上下文切换:
在这里插入图片描述

系统调用

从用户态到内核态的转变,需要通过系统调用来完成。比如,当我们读写文件内容时,就需要多次系统调用来完成:首先调用 open() 打开文件,然后调用 read() 读取文件内容,并调用 write() 将内容写到标准输出,最后再调用 close() 关闭文件。

在这个过程中就发生了 CPU 上下文切换,整个过程是这样的:
1、保存 CPU 寄存器里原来用户态的指令位
2、为了执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置。
3、跳转到内核态运行内核任务。
4、当系统调用结束后,CPU 寄存器需要恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。
一次系统调用的过程,其实是发生了两次 CPU 上下文切换。(用户态-内核态-用户态)。

系统调用过程中,并不会涉及到虚拟内存等进程用户态的资源,也不会切换进程。这跟进程上下文切换是不一样的:进程上下文切换,是指从一个进程切换到另一个进程运行;而系统调用过程中一直是同一个进程在运行。系统调用过程通常称为特权模式切换,而不是上下文切换。系统调用属于同进程内的 CPU 上下文切换。系统调用过程中,CPU 的上下文切换还是无法避免的。

进程上下文切换跟系统调用又有什么区别呢?

首先,进程是由内核来管理和调度的,进程的切换只能发生在内核态。所以,进程的上下文不仅包括了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的资源。
因此,进程的上下文切换就比系统调用时多了一步:在保存内核态资源(当前进程的内核状态和 CPU 寄存器)之前,需要先把该进程的用户态资源(虚拟内存、栈、全局变量等)保存下来;而加载了下一进程的内核态后,还需要刷新进程的虚拟内存和用户栈。但保存上下文和恢复上下文的过程并不是“免费”的,需要内核在 CPU 上运行才能完成。

发生进程上下文切换的场景

  • 为了保证所有进程可以得到公平调度,CPU 时间被划分为一段段的时间片,这些时间片再被轮流分配给各个进程。这样,当某个进程的时间片耗尽了,该进程就会被系统挂起,切换到其它正在等待 CPU 的进程运行。
  • 进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行。
  • 当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度。
  • 在抢占式策略中,当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行。
  • 发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序。

线程上下文切换

线程与进程最大的区别在于:线程是CPU调度和分配的基本单位,而进程则是系统进行资源调度和分配的基本单位。所谓内核中的任务调度,实际上的调度对象是线程;而进程只是给线程提供了虚拟内存、全局变量等资源。

所以,对于线程和进程,可以这么理解: - 当进程只有一个线程时,可以认为进程就等于线程。 - 当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源。这些资源在上下文切换时是不需要修改的。 - 另外,线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的。

前后两个线程属于不同进程时,因为资源不共享,所以切换过程就跟进程上下文切换是一样。前后两个线程属于同一个进程时,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据(如栈、寄存器)等不共享的数据。

发生线程上下文切换的场景:

  • 自发性上下文切换。指线程由于自身因素导致的切出。通过调用下列方法会导致自发性上下文切换:Thread.sleep();Object.wait();Thread.yeild();Thread.join();LockSupport.park()等。
  • 非自发性上下文切换。指线程由于线程调度器的原因被迫切出。切出线程的时间片用完;有一个比切出线程优先级更高的线程需要被运行。

中断上下文切换

为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件。而在打断其他进程时,就需要将进程当前的状态保存下来,这样在中断结束后,进程仍然可以从原来的状态恢复运行。

跟进程上下文不同,中断上下文切换并不涉及到进程的用户态。所以,即便中断过程打断了一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。中断上下文,其实只包括内核态中断服务程序执行所必需的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等。

对同一个 CPU 来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进程上下文切换同时发生。同样道理,由于中断会打断正常进程的调度和执行,所以大部分中断处理程序都短小精悍,以便尽可能快的执行结束。

另外,跟进程上下文切换一样,中断上下文切换也需要消耗 CPU,切换次数过多也会耗费大量的 CPU,甚至严重降低系统的整体性能。所以,当发现中断次数过多时,就需要注意去排查它是否会给你的系统带来严重的性能问题。

字节序(Linux,大小端)

字节序:指多字节数据在计算机内存中存储或网络传输时各字节的存储顺序。
网络字节序定义:收到的第一个字节被当作高位看待,这就要求发送端发送的第一个字节应当是高位。而在发送端发送数据时,发送的第一个字节是该数字在内存中起始地址对应的字节。可见多字节数值在发送前,在内存中数值应该以大端法存放。

大端模式

是指数据的低位(就是权值较小的后面那几位)保存在内存的高地址中,而数据的高位,保存在内存的低地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放。

小端模式

是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。

为什么有大小端模式之分?

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

判断大小端

Linux中判断大小端的一种方法:常用的方法有使用联合体和指针法。

static union 
{
char c[4]; 
unsignedlong mylong; 
} endian_test = {{'l','?','?','b' } };
#define ENDIANNESS ((char)endian_test.mylong)
if(ENDIANNESS == ‘b’)
cout << ”It’s big endian.” << endl;
	else
		cout << ”It’s little endian.” << endl;

select、poll、epoll

I/O多路复用(multiplexing)的本质是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。即通过I/O多路复用让一个进程同时为多个客户端端提供服务。本质上是同步I/O。
Linux API 提供了三种 I/O 复用方式:select、poll 和 epoll。
Unix四种I/O模型:阻塞I/O,非阻塞I/O,I/O多路复用,异步I/O 。

Linux操作系统中基础的概念

  • 用户空间 / 内核空间:

现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。

  • 进程切换:

为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。任何进程都是在操作系统内核的支持下运行的,与内核紧密相关,且进程切换是非常耗费资源的。

  • 进程阻塞:

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。进程进入阻塞状态不占用CPU资源。

  • 文件描述符:

是一个用于表述指向文件的引用的抽象化概念。文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。

  • 缓存I/O:

缓存I/O又称为标准I/O,大多数文件系统的默认I/O操作都是缓存I/O。在Linux的缓存I/O机制中,操作系统会将I/O的数据缓存在文件系统的页缓存中,即数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

Select 时间复杂度O(n)

select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。select()的机制中提供一种fd_set的数据结构,实际上是一个long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是Socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据I/O状态修改fd_set的内容,由此来通知执行了select()的进程哪一Socket或文件可读。

从流程上来看,使用select函数进行I/O请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。

使用select以后最大的优势是:用户可以在一个线程内同时处理多个socket的I/O请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个I/O请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

存在的问题:

  • 每次调用select,都需要把fd_set集合从用户态拷贝到内核态,如果fd_set集合很大时,那这个开销也很大;
  • 每次调用select都需要在内核遍历传递进来的所有fd_set,如果fd_set集合很大时,那这个开销也很大;
  • 为了减少数据拷贝带来的性能损坏,内核对被监控的fd_set集合大小做了限制,并且这个是通过宏控制的,大小不可改变(32位机限制为1024,64位2048)。

Poll 时间复杂度O(n)

poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。也就是说,poll只解决了上面的问题3,并没有解决问题1,2的性能开销问题。
poll改变了文件描述符集合的描述方式(链表),使用了pollfd结构而不是select的fd_set结构,使得poll支持的文件描述符集合限制远大于select的1024。

Epoll 时间复杂度O(1)

epoll的核心是3个API:epoll_create;epoll_ctl;epoll_wait。核心数据结构是:1个红黑树和1个链表。

epoll在Linux2.6内核正式提出,是基于事件驱动的I/O方式,相对于select来说,epoll没有描述符个数限制,使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用I/O接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核I/O事件异步唤醒而加入Ready队列的描述符集合就行了。
epoll除了提供select/poll那种I/O事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存I/O状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

水平触发(LT):默认工作模式,即当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件;下次调用epoll_wait时,会再次通知此事件
边缘触发(ET):当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。(直到你做了某些操作导致该描述符变成未就绪状态了,也就是说边缘触发只在状态由未就绪变为就绪时通知一次)。ET模式很大程度上减少了epoll事件的触发次数,因此效率比LT模式下高。

epoll更高效的原因

  • select和poll的动作基本一致,只是poll采用链表来进行文件描述符的存储,而select采用fd标注位来存放,所以select会受到最大连接数的限制,而poll不会。
  • select、poll、epoll虽然都会返回就绪的文件描述符数量。但是select和poll并不会明确指出是哪些文件描述符就绪,而epoll会。造成的区别就是,系统调用返回后,调用select和poll的程序需要遍历监听的整个文件描述符找到是谁处于就绪,而epoll则直接处理即可。
  • select、poll都需要将有关文件描述符的数据结构拷贝进内核,最后再拷贝出来。而epoll创建的有关文件描述符的数据结构本身就存于内核态中。
  • select、poll采用轮询的方式来检查文件描述符是否处于就绪态,而epoll采用回调机制。造成的结果就是,随着fd的增加,select和poll的效率会线性降低,而epoll不会受到太大影响,除非活跃的socket很多。
  • epoll边缘触发模式效率高,系统不会有大量不关心的就绪文件描述符。

epoll是Linux目前大规模网络并发程序开发的首选模型。在绝大多数情况下性能远超select和poll。目前流行的高性能web服务器Nginx正是依赖于epoll提供的高效网络套接字轮询服务。但在并发连接不高的情况下,多线程+阻塞I/O方式可能性能更好。

select, poll, epoll的区别

在这里插入图片描述

  • 支持一个进程所能打开的最大连接数:select:单个进程所能打开的最大连接数有FD_SETSIZE宏定义。poll本质上和select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的。epoll无上限。
  • FD剧增后带来的IO效率问题:select和poll:因为每次调用时都会对连接进行线性遍历,所以随着FD的增加会造成遍历速度慢的“性能线性下降问题”。 epoll:因为epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的性能线性下降的问题。
  • 消息传递方式:select和poll:内核需要将消息传递到用户空间,都需要内核拷贝动作。epoll通过内核和用户空间共享一块内存来实现的。

简述ping命令的工作原理

ping用于确定本地主机是否能与另一台主机成功交换(发送与接收)数据包,再根据返回的信息,就可以推断TCP/IP参数是否设置正确,以及运行是否正常、网络是否通畅等。原理:利用网络上机器IP地址的唯一性,给目标IP地址发送一个数据包,要求对方返回一个同样大小的数据包来确定两台机器是否连接相通,时延是多少。

要监测主机A(192.168.0.1)和主机B(192.168.0.2)之间网络是否可达,那么在主机A上输入命令:ping 192.168.0.2。此时,ping命令会在主机A上构建一个 ICMP的请求数据包(数据包里的内容后面再详述),然后 ICMP协议会将这个数据包以及目标IP(192.168.0.2)等信息一同交给IP层协议。IP层协议得到这些信息后,将源地址(即本机IP)、目标地址(即目标IP:192.168.0.2)、再加上一些其它的控制信息,构建成一个IP数据包。 IP数据包构建完成后,还需要加上MAC地址,因此,还需要通过ARP映射表找出目标IP所对应的MAC地址。当拿到了目标主机的MAC地址和本机MAC后,一并交给数据链路层,组装成一个数据帧,依据以太网的介质访问规则,将它们传送出去。

当主机B收到这个数据帧之后,会首先检查它的目标MAC地址是不是本机,如果是就接收下来处理,否则丢弃。接收之后会检查这个数据帧,将数据帧中的IP数据包取出来,交给本机的IP层协议,然后IP层协议检查完之后,再将ICMP数据包取出来交给ICMP协议处理,当这一步也处理完成之后,就会构建一个ICMP应答数据包,回发给主机A。

在一定的时间内,如果主机A收到了应答包,则说明它与主机B之间网络可达;如果没有收到,则说明网络不可达。除了监测是否可达以外,还可以利用应答时间和发起时间之间的差值,计算出数据包的延迟耗时。

系统调用

操作系统实现的所有系统调用所构成的集合即程序接口或应用编程接口(Application Programming Interface,API)。是应用程序同系统之间的接口。Linux系统调用,包含了大部分常用系统调用和由系统调用派生出的的函数。

系统调用执行过程:用户在程序中使用系统调用,给出系统调用名和函数后,即产生一条相应的陷入(异常)指令,通过陷入处理机制调用服务,引起处理机中断,然后保护处理机现场,取系统调用功能号并寻找子程序入口,通过入口地址表来调用系统子程序,然后返回用户程序继续执行。

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

系统调用是最底层的应用,是面向硬件的。而库函数的调用是面向开发的,相当于应用程序的API(即预先定义好的函数)接口;
各个操作系统的系统调用是不同的,因此系统调用一般是没有跨操作系统的可移植性;而库函数的移植性良好(c库在Windows和Linux环境下都可以操作);
库函数属于过程调用,调用开销小;系统调用需要在用户空间和内核上下文环境切换,开销较大;
库函数调用函数库中的一段程序,这段程序最终还是通过系统调用来实现的;系统调用 调用的是系统内核的服务。

具体的执行过程

系统调用是发生在内核空间的,所以如果在用户空间使用系统调用进行文件操作,必然会引起用户空间到内核空间切换的开销。(系统调用是一个很费时的操作)事实上,即使在用户空间使用库函数对文件进行操作,因为文件总是存在于存储介质上,因此,读写操作都是对硬件(存储器)的操作,所以肯定会引起系统调用,也就是说,库函数对文件的操作是通过系统调用来实现的,(即库函数封装了系统调用的很多细节)。例如C库函数的fopen()就是通过系统调用open ()来实现的。

使用库函数也会有系统调用的开销,为什么不直接使用系统调用呢?

文件的读写操作通常是大量的数据(大量是底层实现而言),这时,使用库函数可以大大减少系统调用的次数。这一结果源于缓冲区技术,在内核空间和用户空间,对文件操作都使用了缓冲区,例如用fwrite()写文件,都是先将内容写到用户空间缓冲区,当用户空间缓冲去写满或者写操作结束时,才将用户缓冲区的内容写到内核缓冲区,同样的道理,当内核缓冲区写满或者写结束时才将内核缓冲区的内容写到文件对应的硬件媒介上。

中断

中断是指CPU对系统发生的某个事件做出的一种反应,CPU暂停正在执行的程序,保存现场后自动去执行相应的处理程序,处理完该事件后再返回中断处继续执行原来的程序。中断是多程序并发执行的前提条件。用户态到核心态的转换就是通过中断机制实现的,并且中断是唯一途径。

中断的处理过程:
(1)执行完每个指令后,CPU都要检查当前是否有外部(未响应)中断信号。
(2) 如果检测到外部中断信号,则需要保护被中断进程的CPU环境(如程序状态字PSW、程序计数器、各种通用寄存器)。
(3) 根据中断信号类型转入相应的中断处理程序,进行中断处理。
(4) 恢复进程的CPU环境并退出中断,返回原进程继续往下执行。

Linux操作系统中常见命令

查看进程命令

(1)ps命令:查看静态的进程统计信息。
ps aux命令:以简单列表的形式显示进程信息。
ps -elf命令:以长格式显示系统中的进程信息。
(2)top命令:查看进程的动态信息(3s刷新一次),类似于任务管理器。
(3)pgrep命令:查询进程信息。结合 “-l” 选项可同时输出对应的进程名;结合 “-U” 选项查询指定用户的进程。
(4)pstree命令:查看进程树。
pstree -aup 命令:显示各进程对应的完整命令、用户名、PID 号等信息。

修改文件命令

(1)vim命令
vim filename命令:打开vim并创建名为filename的文件。
命令模式:使用 Vim 编辑文件时,默认处于命令模式。在此模式下,可以使用上、下、左、右键或者 k、j、h、l 命令进行光标移动,还可以对文件内容进行复制、粘贴、替换、删除等操作。
输入模式:在输入模式下可以对文件执行写操作,类似在 Windows 的文档中输入内容。进入输入模式的方法是输入 i、a、o 等插入命令,编写完成后按 Esc 键即可返回命令模式。
编辑模式:要保存、查找或者替换一些内容等,就需要进入编辑模式。编辑模式的进入方法为:在命令模式下按":“键,Vim 窗口的左下方会出现一个”:“符号,这时就可以输入相关的指令进行操作了。指令执行后会自动返回命令模式。
保存和退出:字母"w”:保存不退出;字母"q":不保存退出;字符"!":强制性操作;
(2)echo命令
echo “字符串”,功能:在屏幕上打印 字符串。
echo ‘hello linux’ >> /data/hello.txt :单行内容追加到文件结尾。
一个大于号>,是覆盖重定向,会清除文件里的所有以前数据,增加新数据。
两个大于号>>,是追加重定向,文件结尾加入内容,不会删除已有文件内容。

查看IP地址命令

linux的ip命令和ifconfig类似,但前者功能更强大,并旨在取代后者。
查看IP:ifconfig -a命令;ip addr命令
修改IP:vim /etc/sysconfig/network-scripts/ifcfg-eth0

查看文件命令

cat:由第一行开始显示内容,并将所有内容输出;
tac:从最后一行倒序显示内容,并将所有内容输出;
more:根据窗口大小,一页一页的现实文件内容;
less:和more类似,但其优点可以往前翻页,而且进行可以搜索字符;
head:只显示头几行;
tail:只显示最后几行。

文本搜索命令

grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。

glibc

glibc是GNU发布的libc库,即c运行库。glibc是linux系统中最底层的api,几乎其它任何运行库都会依赖于glibc。glibc除了封装linux操作系统所提供的系统服务外,它本身也提供了许多其它一些必要功能服务的实现。

查看端口目前的状态?端口状态有哪些?

Netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多播成员 (Multicast Memberships) 等等。
常见参数:
-a (all)显示所有选项,默认不显示LISTEN相关
-t (tcp)仅显示tcp相关选项
-u (udp)仅显示udp相关选项
-n 拒绝显示别名,能显示数字的全部转化成数字。
-l 仅列出有在 Listen (监听) 的服务状态

-p 显示建立相关链接的程序名
-r 显示路由信息,路由表
-e 显示扩展信息,例如uid等
-s 按各个协议进行统计
-c 每隔一个固定时间,执行该netstat命令。
提示:LISTEN和LISTENING的状态只有用-a或者-l才能看到

单核CPU要不要考虑线程不安全情况?

单核CPU仍然存在线程切换,且不能保证调度的顺序性和任务的原子性,因此仍然存在线程不安全问题。

为什么++i和i++都是线程不安全的?

i++过程:将i值取出放到寄存器 —> 将寄存器中的值返回 —> 寄存器中的值加1 —> 使用寄存器值修改i的值。
++i过程:将i值取出放到寄存器 —> 寄存器中的值加1 —> 将寄存器中的值返回并修改i的值。
如果线程1在执行第一条代码的时候,线程2访问i变量,这个时候,i的值还没有变化,还是原来的值,所以是不安全的。

Linux系统中CPU占用率较高问题排查思路与解决方法

如果CPU 持续跑高,则会影响业务系统的正常运行,带来企业损失。

  • 使用 top 查看进程纬度的 CPU 负载:

方法一:
第一步:使用 top 命令,然后按shift+p按照CPU排序,找到占用CPU过高的进程的pid。
第二步:使用 top -H -p [进程id] 找到进程中消耗资源最高的线程的id;
第三步:使用 echo ‘obase=16;[线程id]’ | bc或者printf “%x\n” [线程id] 将线程id转换为16进制(字母要小写);bc是linux的计算器命令;
第四步:执行 jstack [进程id] |grep -A 10 [线程id的16进制]” 查看线程状态信息。

方法二:
第一步:使用 top 命令,然后按shift+p按照CPU排序,找到占用CPU过高的进程;
第二步:使用 ps -mp pid -o THREAD,tid,time | sort -rn 获取线程信息,并找到占用CPU高的线程;
第三步:使用 echo ‘obase=16;[线程id]’ | bc或者printf “%x\n” [线程id] 将需要的线程ID转换为16进制格式;
第四步:使用 jstack pid |grep tid -A 30 [线程id的16进制] 打印线程的堆栈信息。

  • 使用vmstat查看系统维度的CPU负载:

vmstat -n 1 # -n 1 表示结果一秒刷新一次。

这篇关于操作系统常见面试题总结的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!