操作系统面试题汇总
作者:互联网
进程与线程
进程和线程的区别
进程 | 线程 | |
---|---|---|
定义 | 资源调度的基本单位 | 程序执行的基本单位 |
切换 | 指令 + 资源(栈、寄存器、页表、文件句柄等) | 指令(线程栈、寄存器和 PC) |
通信 | 进程通信需要借助操作系统 | 线程可以读写进程数据段来进行通信 |
开销 | 切换虚拟空间,切换内核栈和硬件上下文,CPU高速缓存失效、页表切换 | 保存和设置少量寄存器内容 |
- 同一进程下线程共享堆、全局变量、静态变量、指针、引用、文件等,独自占有栈。
做一次简单的i = i + 1在计算机中并不是原子操作,涉及内存取数,计算和写入内存几个环节,而线程的切换有可能发生在上述任何一个环节中间,所以不同的操作顺序很有可能带来意想不到的结果。
将顺序执行的程序(暂时不考虑多进程)拆分成几个独立的逻辑流,这些逻辑流可以独立完成一些任务。
是否使用多进程、多线程,弄清以下两点非常重要:
- 线程之间有无先后访问顺序(依赖关系)
- 多个线程共享访问同一变量(同步互斥问题)
同一进程的多个线程共享进程的资源。每个线程除了标识线程的tid,还有自己独立的栈空间,线程彼此之间是无法访问其他线程栈上内容的。
线程作为处理机调度的最小单位,线程调度只需要保存线程栈、寄存器数据和 PC 即可,相比进程切换开销要小很多。
多进程和多线程区别
- 频繁修改:需要频繁创建和销毁的优先使用多线程
- 计算量:大量计算优先多线程,因为需要消耗大量 CPU 资源且切换频繁
- 相关性:任务间相关性比较强用多线程
- 多分布:可能扩展到多机分布的多进程,多核分布的多线程
进程对应的内存空间:
- 静态内存区:
- BSS区:存放程序中未初始化的全局变量的内存区域
- 数据区:存放程序中已初始化的全局变量的内存区域
- 代码区:存放执行代码的一块区域,通常只读,也有可能存放只读的常量变量,如字符串常量
- 堆:存放进程中动态分配的内存段(new、delete、malloc、free)
- 栈:用户存在程序临时创建的局部变量
全局的未初始化变量存放在 .bss 中,具体体现为一个占位符(不分配空间只记录数据所需的空间的大小),.bss 不占用可执行文件空间,由 OS 初始化(清零)。
C++ 程序对应部分:
- 堆
- 栈
- 静态区
- 常量区
- 代码区
父、子进程
父进程创建子进程之后,父、子进程除了 pid 外,其余部分几乎一样,共享全部数据,但不是同一数据块。子进程在读写数据时会通过写时复制机制将公共数据重新拷贝一份,在拷贝的数据上进行操作。
子进程想要运行自己的代码段,还可以通过调用 execv() 函数重新加载新的代码段,之后就和父进程独立开了。
Linux 进程控制
虚拟存储器为每个进程提供了独立占系统地址空间的假象。
对于32位进程的地址空间都是 4GB,用户态只能访问低 3GB 的地址空间,3GB~4GB 是内核态地址空间。
进程的调度实际就是内核选择相应的进程控制块(PCB)。
内核管理所有的 PCB,每一次进程调度就是一次上下文切换,其本质就是当前运行状态的切换,包括通用寄存器、浮点寄存器、状态寄存器、程序计数器、用户栈和内核数据结构(页表、进程表、文件表)等。
一次完整的 A、B 进程切换(A用户态 -> A内核态 -> B内核态 -> B用户态)
进程调度算法
- 先来先服务(fist-come first-serverd, FCFS)
- 短作业优先(shortest job first, SJF)
- 最短剩余时间优先(shortest remaining time next, SRTN)
- 时间片轮转:进程按 FCFS 原则排成一个队列,队首进程执行一个时间片后移至队尾。
- 优先级调度,防止低优先级的进程永远等不到调度,可随着时间推移增加等待进程的优先级。
- 多级反馈队列,为需要连续执行多个时间片的进程考虑,设置多个队列,每个队列时间片大小不一同,进程在第一个队列没执行完,会移到下一个队列。(时间片算法和优先级算法结合)
进程间通信
不同进程间的通信本质:进程之间可以看到一份公共资源,而提供这份资源的形式或者提供者不同,造成了通信方式不同。
- 匿名管道(内存文件):半双工的通信方式,数据只能单项流动,且只能在具有亲缘关系的进程中使用。
- 父进程创建管道,得到两个文件描述符指向管道的两端;
- 父进程 fork 出子进程,子进程也有两个文件描述符指向同一管道的两端;
- 父进程关闭 fd[0],子进程关闭 fd[1],即父进程关闭管道读端,子进程关闭管道写端(单向通信)。父进程可以写,子进程可以读,管道是用环形队列实现。
- 有名管道(FIFO 文件,借助文件系统):半双工通信方式,允许在没有亲缘关系的进程之间使用,管道是先进先出通信方式。
- 共享内存:映射一段能被其他进程所访问的内存,这段内存由一个进程创建,但多个进程都可以访问。共享内存最快方式是 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与信号量,配合使用来实现进程间的同步和通信。
- 消息队列:消息队列是有消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 套接字socket:适用于不同机器间进程通信,在本地也可作为两个进程通信的方式。
- 信号:用于通知接收进程某个事件已经发生,比如按下ctrl + C就是信号。
- 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,实现进程、线程的对临界区的同步及互斥访问。
进程间的关系
- 资源共享关系:系统中进程需要访问共同的资源,即当一个进程访问共享资源的时候,其他访问该资源的进程必须等待,这时需要互斥访问共享资源。
- 相互合作关系:系统中进程之间存在相互合作的关系,即当一个进程完成时,另一个进程才能开始,否则另一个进程不能开始,这是需要保证相互合作的进程在执行次序上要同步。
一个进程可以创建多少个线程
- 如果进程可用虚拟内存是2GB(31),默认线程栈大小1MB(20),理论上最多能创建 2048 个栈(11),如果要创建多于 2048 的话,需修改编辑器的设置。
- 一个进程可以创建的线程数由可用虚拟空间和线程的栈的大小共同决定,只要虚拟空间足够,那么新线程的建立就会成功。
进程同步机制
概念
- 临界资源:系统中资源在一段时间内只允许一个进程访问或使用,这种资源称之为临界资源。
- 临界区:多个并发的进程互斥访问或使用临界资源时,把每个进程访问临界资源的那段代码称为临界区。
- 进程同步:多个相关进程在执行次序上的协调,在一些关键点上需要相互等待或通信。通过临界区可以协调进程间的合作关系,这就是同步。
- 进程互斥:当一个进程进入临界区使用临界资源时,另一个进程必须等待。通过临界区可以协调程序间资源共享关系,就是进程互斥,进程互斥是同步的一种特例。
遵循原则
- 空闲让进
- 忙则等待
- 有限等待
- 让权等待:当进程不能进入自己的临界区时,应立即释放处理器,以免陷入“忙等”。
同步机制——锁
只能用于线程同步
同步机制——信号量
可用于进程同步,也可用于线程同步
进程同步的四种方法
- 临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
- 优点:保证同一时刻只有一个线程能访问数据的简单办法;
- 缺点:虽然临界区同步速度块,但只能是本进程内的线程,不可用来同步多进程的线程。
- 互斥量(Mutex):为协调共同对一个共享资源的单独访问而设计。跟临界区很相似,比临界区复杂,互斥对象只有一个,只有拥有互斥对象的线程才具有访问资源的权限。
- 优点:能应用到不同进程的不同线程之间实现对资源的安全共享;
- 缺点:1. 创建互斥量需要的资源更多 2. 互斥量一次只能一个用户来访问,对于多用户的查询不适用。
- 信号量:为控制一个具有有限数量资源而设计的,它允许多个线程在同一时刻访问同一资源。互斥量是信号量的特殊情况,信号量 = 1。
- 优点:适用于对Socket(套接字)程序中线程的同步。(例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。)
- 缺点:1. 信号量机制必须有公共内存,不能用于分布式操作系统; 2. 读写和维护复杂;3. 核心 P-V 分散在各用户程序中,不易控制和管理;
- 事件:用来通知线程有一些事件已发生,从而启动后继任务的开始
- 优点:事件对象通过通知操作的方式来保持线程的同步,并且可以实现不同进程中的线程同步操作。
- 临界区不是内核对象,只能用于进程内部的线程同步,是用户方式的同步。互斥、信号量是内核对象可以用于不同进程之间的线程同步(跨进程同步)。
- 互斥其实是信号量的一种特殊形式。互斥可以保证在某一时刻只有一个线程可以拥有临界资源。信号量可以保证在某一时刻有指定数目的线程可以拥有临界资源。
进程状态切换
- 新建态
- 就绪态,等待被调度
- 运行态
- 阻塞态,等待资源
- 终止态
- 就绪态和运行态可以相互转换,其它的都是单向转换。就绪状态的进程通过调度算法从而获得 CPU 时间,转为运行状态;而运行状态的进程,在分配给它的 CPU 时间片用完之后就会转为就绪状态,等待下一次调度。
- 阻塞状态是缺少需要的资源从而由运行状态转换而来,但是该资源不包括 CPU 时间,缺少 CPU 时间会从运行态转换为就绪态。
终端退出时,终端运行的进程会怎么样?
终端在退出时会发送 SIGHUP 给对应的 bash 进程,bash 进程收到这个信号后首先将它发给 session 下面的进程,如果程序没有对 SIGHUP 信号做特殊处理,那么进程就会随着终端关闭而退出。
如何让进程后台运行
- 命令后加 &,实际上,这是将命令放入到一个作业队列中了;
- ctrl + z 挂起进程,使用 jobs 查看序号,在使用 bg %序号 后台运行进程;
- nohup + &,将标准输出和标准错误缺省会被重定向到 nohup.out 文件中,忽略所有挂断(SIGHUP)信号
- 运行指令前面 + setsid,使其父进程编程init进程,不受HUP信号的影响
- 将 命令+ &放在()括号中,也可以是进程不受HUP信号的影响
怎么创建守护进程
Linux 守护进程是运行在后台的一种特殊进程,它独立于控制终端,并周期性地执行某种任务或等待处理某些发生的事件。
- 常见守护进程
- 系统日志进程 syslogd
- web 服务器 httpd
- 邮件服务器 sendmail
- 数据库服务器 mysqld
守护进程的父进程是 init 进程,它真正的父进程在 fork 出子进程后就先于子进程退出勒,所以它是一个由 init 继承的孤儿进程。
守护进程名称通常以 d 结尾
-
进程组:
- 每个进程属于一个进程组
- 每个进程组都有一个组号,该号等于进程组组长的 PID 号
- 一个进程只能为它自己或子进程设置进程组 ID 号
-
会话期
- 会话期 session 是有一个或多个进程组的集合
- setsid() 函数可以简历一个会话期
- 如果调用 setsid 的进程不是一个进程组的组长,此函数会创建一个新的会话期
- 此进程变成该对话期的首进程
- 此进程变成一个新进程组的组长进程。
- 此进程没有控制终端,如果在调用setsid前,该进程有控制终端,那么与该终端的联系被解除。 如果该进程是一个进程组的组长,此函数返回错误。
- 为了保证这一点,我们先调用fork()然后exit(),此时只有子进程在运行
-
创建守护进程所需要的步骤:
- 在父进程中执行 fork 并 exit 退出;
- 在子进程中调用 setsid 函数创建新的会话;
- 在子进程中调用 chdir 函数,让根目录 ”/” 成为子进程的工作目录;
- 在子进程中调用umask函数,设置进程的 umask 为0;
- 在子进程中关闭任何不需要的文件描述符
创建守护进程要点:
- 让程序后台执行。方法是调用 fork() 产生一个子进程,然后使父进程退出;
- 调用 setsid() 创建一个新会话。控制终端、登录会话和进程组通常是从父进程继承下来的,守护进程要摆脱它们影响,可以调用 setsid() 使进程成为一个会话组长。 setsid() 调用成功后,进程成为新的会话组长和进程组长,并与原来的登录会话、进程组和控制终端脱离。
- 禁止进程重新打开终端。以上步骤使进程成为一个无终端的会话组长,但它可以重新打开一个终端。
- 关闭不需要的文件描述符。子进程会从父进程继承打开的文件描述符,如不关闭,会造成资源浪费,进程所在的文件系统无法卸载而引发错误。
- 将当前目录更改为根目录。调用 chdir 函数,让根目录 ”/” 成为子进程的工作目录;
- 子进程从父进程继承的文件创建屏蔽字可能会拒绝某些许可权。为防止这一点,使用unmask(0)将屏蔽字清零。
- 处理 SIGCHLD 信号。对于服务器进程,在请求到来时往往生成子进程处理请求。如果子进程等待父进程捕获状态,则子进程将成为僵尸进程(zombie),从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN。这样,子进程结束时不会产生僵尸进程。
孤儿进程
如果父进程先退出,子进程还没有退出,那么子进程的父进程将变成 init 进程。(由 init 收养)
僵尸进程
子进程先退出,父进程没有退出,那么子进程必须等父进程捕获到子进程退出的状态才能真正退出,否则这时候子进程就成为勒僵尸进程。
设置僵尸进程的目的是维护子进程的信息,以便父进程在以后某个时候获取。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1(init进程)。继承这些子进程的init进程将清理它们(也就是说init进程将wait它们,从而去除它们的僵尸状态)。
如何避免僵尸进程
- 通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN),表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
- 父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。
- 如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
忽略SIGCHLD信号,这常用于并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。
为什么只能运行一个前台作业
shell分前后台来控制的不是进程而是作业(job)或者进程组(Process Group)。
一个前台作业可以由多个进程组成,一个后台也可以由多个进程组成,shell 可以运行一个前台作业和任意多个后台作业,这称为作业控制。
我们在前台新起了一个作业,shell 就被提到了后台,因此 shell 就没有办法再继续接受我们的指令并且解析运行了。 但是如果前台进程退出了,shell 就会有被提到前台来,就可以继续接受我们的命令并且解析运行。
作业与进程组的区别
如果作业中的某个进程有创建了子进程,则该子进程是不属于该作业的。 一旦作业运行结束,shell就把自己提到前台(子进程还存在,但是子进程不属于作业),如果原来的前台进程还存在(这个子进程还没有终止),他将自动变为后台进程组
会话
会话(Session)是一个或多个进程组的集合。一个会话可以有一个控制终端。在xshell或者WinSCP中打开一个窗口就是新建一个会话。
进程终止的几种方式
- main 函数自然返回,return
- 调用 exit 函数,C 库函数调用
- 调用 _exit 函数,属于系统调用
- 调用 abort 函数,程序异常终止,同时发送 GIGABRT 信号给调用进程
- 接受能导致进程终止的信号:ctrl+c,SIGINT
交换空间与虚拟内存的关系
- 交换空间
- Linux 中的交换空间(Swap space)在物理内存(RAM)被充满时被使用。如果系统需要更多的内存资源,而物理内存已经充满,内存中不活跃的页就会被移到交换空间去。交换空间的总大小应该相当于你的计算机内存的两倍和 32 MB这两个值中较大的一个,但是它不能超过 2048MB(2 GB)。
- 虚拟内存
- 虚拟内存是文件数据交叉链接的活动文件,即拿出一部分硬盘空间来充当内存使用。虚拟内存使用的是硬盘的空间。
存储管理
什么是快表
快表,又称联想寄存器(TLB) ,是一种高速缓冲存储器,用来存放当前访问的若干页表项,以加速地址变换的过程。与此对应,内存中的页表常称为慢表。
快表下地址转换过程
- CPU 给出逻辑地址,由硬件算出页号、页内偏移量;
- 检查页号合法性;
- 将页号与快表中页号匹配;
- 若命中,则直接从快表取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后访问该物理地址对应的内存单元。因此,若快秒命中,则只需要一次访存;
- 若没有命中,则需要再访问内存中的页表,找到对应页表项,得到内存块号,再将内存块号与页内偏移量拼接形成物理地址。
- 最后访问该物理地址对应的内存单元。
若没有命中,需要两次访存,并将页表存入快表。(FIFO、LRU、OPT)。
内存交换和覆盖的区别
交换是不同进程(或作业)之间进行,覆盖用于同一进程。
动态分区分配算法
-
首次适应算法
每次都从低地址开始查找,找到第一个能满足大小的空闲分区。(空闲分区链/空闲分区表)
-
邻近适应算法
首次适应算法每次从链头开始,容易导致低地址部分出现很多小的空闲区,并且从头开始开销也大,邻近适应算法从上次结束的位置开始检索。(实现:循环链表)
-
最佳适应算法
由于动态分区分配是一种连续分配方式,为了保证内存不浪费,为“大进程”留空间,优先选择适合当前需求的最小空闲区,但会造成许多碎片。(空闲分区按容量递增排列,空闲分区链/表)
-
最坏(最大)适应算法
每次分配时优先使用最大的连续空闲区,这样剩下的空闲区不会太小,方便使用。(实现:空闲分区按容量递减排列,空闲分区链/表)
实际上:首次适应最好,邻近会造成末尾分配空间分裂成小碎片,比首次要差,最佳导致大量碎片,最坏导致没有大的空间。
虚拟技术
虚拟技术把一个物理实体转换为多个逻辑实体。
主要有两种虚拟技术:时(时间)分复用技术和空(空间)分复用技术。
时分复用:多进程和多线程能在一个处理器上并发执行。并发(逻辑)、并行(物理)
虚拟内存使用空分复用技术,将物理内存抽象为地址空间,每个进程都有各自的地址空间,地址空间的页被隐射到物理内存,地址空间的页并不需要全部在物理内存中,当使用一个没有在物理内存的页时,执行页面置换算法,将页置换到内存中。
虚拟内存的目的
虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。
为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间,在程序的角度这个地址空间是连续的一段,但实际上这个地址空间被分割成多个块,每一块称为一页。
这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到不在物理内存中的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。这样程序不需要全部调入内存就可以运行,使有限内存运行大程序称为可能。
逻辑地址转换为物理地址的基本过程
可以借助进程的页表将逻辑地址转换为物理地址。
进程未执行时,页表的起始地址和页表长度会在进程控制块(PCB)中,当进程被调度时,会放到页表寄存器(PTR)中。
页面大小是2的整数幂 设页面大小为L,逻辑地址A到物理地址E的变换过程如下:
- 根据 PCB 逻辑地址计算出页号、页内偏移量
- 判断页号是否越界
- 查找页表,找到页号对应的页表项,确定页面
- 用内存块号和页内偏移量得到物理地址
- 访问目标内存单元
操作系统内存管理的工作
- 负责内存空间的分配与回收
- 从逻辑上对内存空间进行扩充
- 负责程序的逻辑地址与物理地址的转换
- 提供内存保护功能。保证各进程在各自存储空间内运行,互不干扰。
什么是内存?作用是什么?
执行 malloc 申请内存时,操作系统是怎么做的
从操作系统层面上看,malloc是通过两个系统调用来实现的: brk 和 mmap
- brk是将进程数据段(.data)的最高地址指针向高处移动,这一步可以扩大进程在运行时的堆大小;
- mmap是在进程的虚拟地址空间中寻找一块空闲的虚拟内存,这一步可以获得一块可以操作的堆内存。
通常,分配的内存小于128k时,使用brk调用来获得虚拟内存,大于128k时就使用mmap来获得虚拟内存。
进程先通过这两个系统调用获取或者扩大进程的虚拟内存,获得相应的虚拟地址,在访问这些虚拟地址的时候,通过缺页中断,让内核分配相应的物理内存,这样内存分配才算完成。
抖动、颠簸现象
刚刚换出的页面马上又要换入内存,刚刚换入的页面马上又要换出外存,这种频繁的页面调度行为称为抖动,或颠簸。产生抖动的主要原因是进程频繁访问的页面数目高于可用的物理块数(分配给进程的物理块不够)
为进程分配的物理块太少,会使进程发生抖动现象。为进程分配的物理块太多,又会降低系统整体的并发度,降低某些资源的利用率 为了研究为应该为每个进程分配多少个物理块,Denning 提出了进程工作集” 的概念
堆、栈分配效率比较
从两个方面考虑:
- 分配和释放:堆在分配和释放时都需要调用函数(malloc/free),比如分配时会去堆空间寻找足够大小的空间,而栈不需要这些;
- 访问时间:访问堆的一个具体单元,需要两次访问内存,第一次取得指针,第二次才是真正数据,而栈只需要访问一次。
- 堆的内容被操作系统交换到外存的概率比栈大,栈一般不会被交换出去。
常见的内存分配方式
- 从静态存储区分配。内存在编译时就分配好,在整个程序运行期间都存在,例如全局变量、static 变量;
- 在栈上创建。栈内存分配效率很高,但分配的内存容量有限;
- 从堆上创建,也叫动态内存分内,需要手动 malloc/new,自己负责 free/delete。
常见内存分配错误
- 分配未成功就使用。例如指针为空。
- 分配成功,但未初始化。如数组。
- 操作越过内存边界。
- 忘记释放内存,造成内存泄漏。
- 释放了内存却继续使用
- 程序调用过于复杂,不知道某对象是否已释放;
- return 语句写错,不要返回指向"栈内存"的指针或引用;
- free/delete 后,没有将指针设为 NULL,导致野指针。
发生内存交换时,进程的考虑
- 换出阻塞进程
- 换出优先级低的进程
- 考虑进程在内存的驻留时间
ASCII、Unicode和UTF-8编码的区别
- ASCII:127个字符,表示英文字母大小写、数字和一些符号,其他语言用 ASCII 编码表示字节不够。
- Unicode:每个国家语言的统一编码格式,通常连个字节表示一个字符,ASCII 是一个字节表示子一个字符。纯英文 Unicode 编码比 ASCII 编码存储空间多一倍。
- UTF-8:把 Unicode 编码转为可变长度编码。UTF-8编码将Unicode字符按数字大小编码为1-6个字节,英文字母被编码成一个字节,常用汉字被编码成三个字节,如果你编译的文本是纯英文的,那么用UTF-8就会非常节省空间,并且ASCII码也是UTF-8的一部分。
三者之间联系:
- 在计算机内存中,统一使用 Unicode 编码,当需要保存到硬盘或者需要传输的时候,就转换为 UTF-8 编码
- 用记事本编辑的时候,从文件读取的 UTF-8 字符被转换为 Unicode 字符到内存里,编辑完成后,保存的时候再把 Unicode 转换为 UTF-8 保存到文件。
- 浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器:
内存交换需要注意的关键点
- 交换需要备份存储,通常是快速磁盘,它必须足够大,并且提供对这些内存映像的直接访问。
- 为了有效使用CPU,需要每个进程的执行时间比交换时间长,而影响交换时间的主要是转移时间,转移时间与所交换的空间内存成正比。
- 如果换出进程,比如确保该进程的内存空间成正比。
- 交换空间通常作为磁盘的一整块,且独立于文件系统,因此使用就可能很快。
- 交换通常在有许多进程运行且内存空间吃紧时开始启动,而系统负荷降低就暂停。
- 普通交换使用不多,但交换的策略的某些变种在许多系统中(如UNIX系统)仍然发挥作用。
分段式分配和固定分区分配
分段式存储是按需分配,有外部碎片而无内部碎片
固定式分配是固定分配的方式
内部碎片和外部碎片
内部碎片:分配给某些进程的内存区域中有些部分没用上,常见于固定分配方式
外部碎片:内存中某些空闲区因为比较小,难以利用上,一般出现在内存动态分配方式中
如何消除碎片文件
外部碎片:通过紧凑技术消除,即操作系统不时对进程进行移动和整理,需要动态重定位寄存器的支持,且费时
通过内存交换。
死锁
几种典型的锁?
- 读写锁:多个同时进行读;写互斥;写优于读
- 互斥锁:一次只能一个线程拥有互斥锁,其他线程等待;抢占失败时进入睡眠,或者自旋一定阈值时间后睡眠,互斥锁是线程互斥的机制;
- 条件变量:常与互斥锁一起用,允许线程阻塞和等待另一个线程发送信号的方法弥补互斥锁的不足,条件变量是同步机制;
- 自旋锁:进线程无法取得锁时,会一直循环尝试获取锁,直到获取锁;不会引起调用者睡眠,所以自旋锁效率远高于互斥锁。
死锁产生原因
理论上认为死锁产生有以下四个必要条件,缺一不可:
- 互斥条件:进程对所需求的资源具有排他性;
- 不剥夺条件:资源未释放前,不能被强行夺走;
- 请求和保持条件:进程保持当前资源,同时能请求其他资源;
- 循环等待条件:存在进程资源循环等待链,链中进程获得的资源同时被下一个进程所请求。
死锁的解决方案
保持上锁顺序一致
处理方法
- 鸵鸟策略,忽略
- 死锁检测与死锁恢复
- 利用抢占恢复
- 利用回滚恢复
- 杀死进程恢复
- 死锁预防
- 破坏互斥条件
- 破坏请求和保持条件
- 破坏不剥夺条件
- 破坏循环请求等待
- 死锁避免
- 银行家算法
其他
外中断和异常的区别
- 外中断是 CPU 执行指令以外的中断,如 I/O 完成中断,时钟中断、控制台中断等。
- 异常是 CPU 执行指令的内部时间引起,如越界、溢出等。
程序从开始运行到结束的完整过程
- 预编译:处理源代码中以 # 开头的预编译指令,处理规则:
- 删除 #define,展开所有宏定义
- 处理所有条件预编译指定,如 #if,#endif,#ifdef, #elif, #else
- 处理 #include 指令,递归替换文件内容到指令位置
- 删除注释
- 保留 #pragma 指令
- 添加行号和文件标识,便于调试和错误输出
- 编译:预编译后文件进行词法分析、语法分析、语义分析及优化,生成汇编文件
- 词法分析:利用类似“有限状态机”算法,将源码输入到扫描机中,将字符序列分割成一系列记号;
- 语法分析:语法分析器对由扫描器产生的记号,进行语法分析,产生语法树;
- 语义分析:语法分析只是完成对表达式语法层面的分析,语义分析器则对表达式是否有意义进行判断,其分析的语义是静态语义;
- 目标代码生成:将中间代码转换成机器代码,汇编语言标识;
- 代码优化:寻找合适的寻址方式、使用位移来替换乘法、删除多余指令等。
- 汇编
将汇编代码转换成机器可以执行的机器码文件。经汇编后,产生 xxx.o、xxx.obj 文件 - 链接
将不同的目标文件进行链接,从而形成一个可执行的程序。链接分为静态链接和动态链接:- 静态链接:函数和数据被编译进一个二进制文件。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件。优点是速度快,在可执行程序中具备所有需要的东西;缺点的空间浪费,更新困难。
- 动态链接:把程序按模块拆分成相对独立部分,在运行时在链接在一起,形成完成过程。(静态链接是把所有模块链接成单独一个可执行文件)。优点:共享库、更新方便,缺点是性能损耗。
局部性原理
- 时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。(程序中存在大量循环)
- 空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。(数据连续存在,指令也顺序存放)
linux 中断和异常的区别
- 中断是由硬件设备产生的,而它们从物理上说就是电信号,之后通过中断控制器发给 CPU
- 异常是由 CPU 产生的,它会发给内核,要求内核处理异常。
相同点:
- 最后都是由CPU发送给内核,由内核去处理
- 处理程序的流程设计上是相似的
不同点:
- 产生源不相同,异常是由CPU产生的,而中断是由硬件设备产生的
- 内核需要根据是异常还是中断调用不同的处理程序
- 中断不是时钟同步的,这意味着中断可能随时到来;异常由于是CPU产生的,所以它是时钟同步的
- 当处理中断时,处于中断上下文中;处理异常时,处于进程上下文中
服务器高并发解决方案
- 应用数据与静态资源分离。即将静态资源(图片、视频、js 等)单独存储到静态资源服务器;
- 利用客户端缓存,页面尽可能用静态来实现,对于更新部分用 ajax 异步请求获取动态数据;
- 集群和分布式。集群是所有服务器功能相同,负载均衡。
- 反向代理。访问服务器时,通过别的服务器获取资源和结果返回给客户端。
标签:面试题,操作系统,访问,汇总,互斥,线程,内存,进程,资源 来源: https://www.cnblogs.com/cscshi/p/16409672.html