操作系统面试八股文合集

目录

前述:本文初衷是为了总结本人在各大平台看到的面经,我会在本文持续更新我所遇到的一些C++面试问题,如有错误请一定指正我。

1.一个进程在读文件,另外一个进程在删除文件会发生什么?

2.什么是孤儿进程、僵尸进程?

它们的造成的问题和危害:

3.父进程怎么回收僵尸进程的资源

4.程序的内存布局是什么样的?(具体到每个区在内存中的顺序)

5.内存堆区和栈区的区别

6.虚拟内存和物理内存的区别

7.虚拟内存的大小有什么限制,举个例子

8.开发时如何避免内存泄漏?

9.内存置换在什么场景下使用LRU算法?LRU算法的实现方式

10.如果多进程同时读写一个文件,怎么保证其可见性?

11.用户态和内核态是什么?

12.Linux查看端口命令是什么?

13.Linux查看监控端口命令是什么?

14.解释动态链接和静态链接,各自的优势和使用场景

15.Linux子进程创建没有wait操作会发生什么?

16.redis有哪些底层数据结构?

17.内存映射是有名的还是匿名的?

18.讲一讲虚拟内存

19.怎么查看可执行程序依赖了哪些动态库?

20.怎么同时杀死多个进程?

21.虚拟地址是如何转化到物理地址的?页表的构成?

22.操作系统中的原子操作是如何实现的?

23.进程间通信的方式有哪些,他们的原理是什么?

24.线程之间的通信方式有哪些,他们的原理是什么?

25.时间片算是资源分配嘛?

26.在多核里,启动多个线程,时间片如果是资源的话,每个进程分配的时间片是固定的吗?为什么?

28.Linux中对于进程的保护机制有哪些,分别是什么?

29.进程和线程的区别是什么?

区别:

30.进程切换的流程有哪些

31.说明一下C++中的协程是什么?

32.页表是什么?


前述:本文初衷是为了总结本人在各大平台看到的面经,我会在本文持续更新我所遇到的一些C++面试问题,如有错误请一定指正我。

1.一个进程在读文件,另外一个进程在删除文件会发生什么?

Linux系统是通过link的数量来控制文件删除,只有当一个文件不存在任何任何link的时候,这个文件才会被删除。每个文件都有两个link计数器—i_count和i_nlink。i_count的意义是当前使用者的数量,i_link的意义就是介质连接的数量;或者可以理解为i_count是内存引用计数器,i_nlink是硬盘引用计数器。再换句话说,当文件被某个进程引用时,i_count就会增加,当创建文件的硬连接的时候i_nlink就会增加。

对于rm命令而言,就是减少i_nlink。这里就出现一个问题,如果一个文件正在被某个进程调用,而用户却执行rm命令把文件删除了,会出现什么后果呢?

当用户执行rm操作后,ls或者其他文件管理命令不再能够找到这个文件,但是进程却依然在继续正常执行,依然能够从文件中正确的读取内容。这是因为,rm操作只是将i_nlink置为0了;由于文件被进程引用的缘故,i_count不为0,所以系统没有真正删除这个文件,i_nlink是文件删除的充分条件,而i_count才是文件删除的必要文件。

拓展:猜想一下,如果一个进程在打开文件写日志的时候,手动或者另外一个进程将这个日志删除,会发生什么情况?

答:数据库并没有停掉。虽然文件被删除了,但是有一个进程已经打开了那个文件,所以那个文件中的写操作仍然会成功,数据仍然会提交。下面大家如何恢复那个删除文件。例:你删除了temp.log,执行lsof | grep temp.log,你应该能看到这样的输出:temp 2864 tcpdump 4w REG 253,0 0 671457 /root/temp.log (deleted),然后:cp/proc/2864/fd/4/root/temp.log

2.什么是孤儿进程、僵尸进程?

前言:在Uinx/Linux中,正常情况下,子进程是通过父进程创建的,子进程再创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程什么时候结束。当进程完成它的工作后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。

孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将会成为孤儿进程。孤儿进程将会被init进程(进程号为1)所收养,并且由init进程对他们完成状态收集工作。

僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程没有调用wait或者waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种叫僵死进程(僵尸进程)。

它们的造成的问题和危害:

僵尸进程:Uinx提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息,就可以得到。这种机制就是:在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留了一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait/waitpid来取时才释放。但这样就导致了问题,如果进程不调用wait/waitpid的话,那么保留的那段信息就不会释放,其进程号会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程。

孤儿进程:孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就会把孤儿进程的父进程设置为init,而init进程会循环地调用wait()它已经退出的子进程。这样一个孤儿进程结束了其生命周期的时候,init进程就会处理它的一切善后工作,因此它不会有什么危害。

后续拓展:任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没来得及处理,这是用ps命令就能看到子进程的状态为“Z”。如果父进程能够及时处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态,如果父进程在子进程结束之前退出,则子进程将由init接管。Init将会以父进程的身份对僵尸状态的子进程进行处理。

僵尸进程危害场景:当有一个进程它定期产生一个或多个子进程,这些子进程需要做的事情很少,昨完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是父进程只管产生新的子进程,至于子进程退出之后的事情,则一概不闻不问,这样系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为“Z”的进程,严格来说,僵死进程并不是问题的根源,罪魁祸首是产出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样僵死进程的问题就被解决了。

3.父进程怎么回收僵尸进程的资源

       1.改写父进程,在子进程死后要为其收尸。具体做法是接管SIGCHLD信号。子进程死后,会发送SIGCHLD信号(此信号在子进程结束时,父进程会收到这个信号)给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。(这是基于这样的原理:就算父进程没有调用wait,内核也会向它发送SIGCHLD消息,尽管默认的处理是忽略,如果想要访问这个消息,可以设置一个处理函数)

       2.终止父进程:父进程死后,僵尸进程成为孤儿进程,过继为1号进程init,init始终会负责清理僵尸进程,它所有的进程也跟着消失。

4.程序的内存布局是什么样的?(具体到每个区在内存中的顺序)

       代码段:存放程序执行的机器指令。通常情况下代码段是可共享的,使其可共享的目的是,对于频繁被执行的程序,只需要在内存中有一份副本即可。代码段是只读的,使其只读的原因是防止程序意外的修改它的指令。C程序的代码部分全部放在代码段,程序运行时由操作系统从程序映像中取出代码段,布局在程序的内存地址最低的区域,然后跳转到代码段的main函数开始运行函数,程序运行后由操作系统回收这段内存区域。

       已初始化数据段:用来存放C程序中的所有已赋值的全局和静态变量、对象、也包括字符串、数组等常量。但是基本类型的常量不包括其中,因为这些常量通常被编译成指令的一部分存放于代码段。程序运行时由操作系统从程序映像中取出data段,布局在程序内存地址较低的区域。程序结束后由操作系统回收这段内存区域。

       未初始化数据段:用来存放C程序中所有未赋值的全局和静态变量。

       栈:栈用于维护函数调用的上下文,离开了栈,函数调用就无法实现,栈通常在用户空间的最高地址处分配,通常有数兆字节的大小。

       堆:堆是用来容纳应用程序动态分配内存的内存区域,当程序使用malloc或者new分配内存的时候,得到的内存会来自堆里。堆通常存在栈的下方(低地址方向),在某些时候,堆也可能没有固定的统一存储区域。堆一般比栈大很多,可以有几十至百兆字节的容量。

       拓展:栈在程序中具有举足轻重的地位。最重要的是,栈保存了一个函数所需要保留的维护信息,这常被称为堆栈帧或活动记录,堆栈帧一般包含下面的几个方面:函数的返回地址和参数。临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。保存的上下文:包括在函数调用前后需要保持不变的寄存器。

       为什么需要堆:光有栈,对于面向过程的程序设计远远不够,因为栈上的数据返回的时候就会被释放掉,所以无法将数据传递到函数外部。而全局变量没有办法动态的产生,只能在编译的时候定义,有很多情况下缺乏表现力,在这种情况下,堆是一种唯一的选择。

5.内存堆区和栈区的区别

       大小以及顺序差异:栈区是向低地址拓展的,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,大小是在进程分配时确定的,具体大小看编译器。堆区是向高地址分配的,是不连续的内存区域(这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的是动态分配的),因为会手动进行分配,会大一些,大小不固定。

       效率差异:栈区由系统自动分配释放,速度较快,程序员无法控制(系统将会为程序提供内存,否则将报异常提示栈溢出)。堆区是由new分配的内存,一般速度会比较慢,而且容易产生内存碎片,不过不及时进行释放会导致内存泄漏。

       数据结构方面:栈是有一块连续内存的线性表。堆是一种树形数据结构。

6.虚拟内存和物理内存的区别

       前景搭建:首先我们得知道物理内存其实就是插在计算机主板槽上的实际物理内存,CPU可以直接进行寻址,物理内存的容量是固定的,但是寻址空间却取决于cpu地址线条数,如32位机,则寻址空间为2^32=4G,所以最大支持4G的寻址空间,即使我们插入了16G的内存条,我们也只是拥有4G内存,假设没有虚拟内存机制,我们要运行一个程序就不得不把程序的全部装入内存中,然后运行,此时就会出现以下问题:如果有多个程序需要运行,但是内存不够了,就需要将其他程序暂时拷贝到程序中,然后将新的程序装入内存中运行,由于大量的数据装入装出,内存的使用效率会非常低下。由于程序都是直接访问物理内存的,所以一个程序可以修改其他进程的内存数据,甚至修改内核地址空间中的数据。因为内存是随机分配的,所以程序员的地址也是不正确的。

       区别:物理内存是我们在机器插槽上所安装的内存,而虚拟内存则是进程在创建加载的时候,自身感知获得了一个连续的内存地址空间,而实际上内核只是分配了一个逻辑上的虚拟内存空间,并且对虚拟内存和磁盘通过mmap做映射关系。

后续提及:等到程序真正运行时,需要某些数据,并且不在虚拟内存中才会触发缺页异常,进行数据拷贝(通俗一点就是虚拟内存是磁盘上面的一块空间,更通俗点就是将暂时不用的内存块信息写到磁盘中),这样物理内存得到了释放,这块内存可以用作其他目的,当需要用到原始内容时,这些信息会被重新从磁盘中读取到物理内存中。

7.虚拟内存的大小有什么限制,举个例子

       虚拟内存的最大容量由CPU的地址长度决定的。

       实际容量为内存和外存容量之和

8.开发时如何避免内存泄漏?

       养成良好的代码习惯,保证malloc/new和free/delete匹配

       尽早释放掉无用的引用,在引用推出作用域后设置为NULL

       尽量使用对象池来管理对象

       不要在经常调用的地方创建对象/变量,尤其忌讳在循环中,可以适当的使用容器来创建一组对象,不用的时候delete掉。

       在需要的时候将基类的析构函数定义为虚函数

       在需要的时候使用拷贝构造函数,避免野指针的情况。

       在释放一个数组的时候,使用delete[ ]

9.内存置换在什么场景下使用LRU算法?LRU算法的实现方式

       LRU算法是一种基于页面使用频率的置换算法,最被访问的,被访问的几率变大。最被访问的,当磁盘被写满时,会被清除。它的核心思想是选择最近未使用的页面进行置换。

       使用场景:Web缓存,数据库查询缓存等场景,即访问场景中通常具有较强的时间局部性。

       实现方式:可以通过维护一个页面队列来记录页面的访问顺序,并根据队列中页面的位置进行页面互换。

10.如果多进程同时读写一个文件,怎么保证其可见性?

  1. 文件锁(File Locking):使用文件锁机制可以确保同一时间只有一个进程可以对文件进行读写操作。进程在访问文件之前,先获取文件锁,其他进程需要等待锁释放后才能进行访问。这样可以避免多个进程同时读写导致的数据不一致问题。
  2. 同步机制(Synchronization):使用同步机制,如互斥锁(Mutex)或信号量(Semaphore),可以在进程之间建立互斥关系,确保同一时间只有一个进程可以访问文件。进程在访问文件之前,先获取同步对象的锁,其他进程需要等待锁释放后才能进行访问。
  3. 文件映射(File Mapping):使用文件映射技术可以将文件映射到进程的内存空间中,多个进程可以通过内存访问文件内容。通过使用同步机制来控制对内存的访问,可以保证多进程对文件的读写操作的可见性。
  4. 数据库或消息队列:将文件的读写操作交给数据库或消息队列来处理,这样可以通过数据库或消息队列的事务机制来保证数据的一致性和可见性。

11.用户态和内核态是什么?

       用户态和内核态是操作系统的两种运行状态。

       内核态:处于内核态的CPU可以访问任意的数据,包括外围设备,比如网卡、硬盘等,处于内核态的CPU可以从一个程序切换到另外一个程序,并且占用CPU不会发生抢占情况,一般处于特权级0的状态我们称之为内核态。

       用户态:处于用户态的CPU只能受限的访问内存,并且不允许访问外围设备,用户态下的CPU不允许独占,也就是说CPU能够被其他程序获取。

       为什么要有内核态和用户态呢?:主要是堆访问能力的考量,计算机中有一些比较危险的操作,如设置时钟,内存清理,这些都需要在内核态下完成,如果随意进行危险操作,极容易导致系统崩坏。

12.Linux查看端口命令是什么?

       查看所有正在使用中的端口:netstat -anp

       查看某个特定的端口是否被使用:netstat -anp | grep:端口号

       查看某个特定进程占用的端口号:lsof -i: 端口号

  

13.Linux查看监控端口命令是什么?

       Netstat命令,-t:显示TCP端口,-u显示UDP端口,-n显示数字地址而不是解析主机,-l仅显示监听端口,-p显示监听端口对应进程的pid和名称,只有用户有root权限时才会显示信息。

14.解释动态链接和静态链接,各自的优势和使用场景

       链接:就是把其他的第三方库和自己源码生成的二进制目标文件融合在一起的过程,链接分为静态链接和动态链接两种。

       静态链接:在链接的时候把所有的依赖的第三方库都打包在一起,想用的时候直接使用就好了,这就导致了其实打包的东西很多,这导致最终的可执行文件非常大。同时程序的执行速度也很慢。一般情况下,静态链接的文件结尾都是.a结尾的。

       动态链接:不将所有的第三方库都打包到最终的可执行文件上,而是只记录了那些动态链接库,在运行时才将那些第三方库装载进来,也就是将磁盘上的程序和数据加载到内存上。

静态链接优势:生成的可执行文件不再需要任何静态库文件的支持就可以独立运行(可移植性强)。劣势:如果程序中多次调用库中的同一功能模块,则该模块代码势必会被复制很多次,生成的可执行文件中会包含多段完全相同的代码,导致代码冗余。和使用动态链接库生成的可执行文件相比,静态链接库生成的可执行文件的体积更大。

       动态链接优势:由于可执行文件中记录的是功能模块的地址,真正的实现代码会在程序运行时被载入内存,这意味着,即便功能模块被调用多次,使用的都是同一份实现代码(这也就是将动态链接称为共享库的原因)。劣势是:此方法生成的可执行文件无法独立运行,必须借助相应的库文件(可移植性差)。和使用静态链接生成可执行文件相比,动态链接库生成的可执行文件的体积更小,因为其内部不会被复制一堆冗余代码。

15.Linux子进程创建没有wait操作会发生什么?

一个进程使用fork创建子进程,如果子进程退出,而父进程没有调用wait或者waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,会造成僵死进程的情况。

16.redis有哪些底层数据结构?

       有五种基本数据类型:string(字符串),list(列表),hash(哈希),set(集合),

sort set(有序集合)。

       六种底层数据结构:简单动态字符串,双向链表,压缩列表,哈希表,调表和整数数组。

17.内存映射是有名的还是匿名的?

       内存映射可以是有名的,也可以是匿名的。文件映射(有名的),mmap系统调用将普通文件的一部分内容直接映射到调用进程的虚拟地址空间。一旦完成映射,就可以通过相应的内存区域中操作字节来访问文件内容。这种映射也被称为基于文件的映射。

       匿名映射:匿名映射没有对应的文件。这种映射的内存区域会被初始化为0,一个进程映射的内存可以与其他进程中的映射共享物理内存。所谓共享是指各个进程的页表条目指向RAM中的相同分页。

18.讲一讲虚拟内存

       从为什么要有虚拟内存,虚拟内存的优缺点这两个问题来回答,如下文;

19.怎么查看可执行程序依赖了哪些动态库?

       1.使用objdump命令:查看依赖的库:objdump -x xxx.so | grep NEEDED查看可执行程序依赖的库:objdump -x ./路径 | grep NEDDED

       2.使用readdlf命令:查看依赖的库:readelf -a xxx.so | grep “Shared”,查看可执行程序依赖的库:readelf -a ./路径 | grep “Shared”,查看依赖的库:readelf -d xxx.so或readelf -d ./路径。查看静态库有哪些.o文件:readelf -d xxx.a

       3.使用ldd命令:查看依赖的库:ldd xxx.so,查看可执行程序依赖的库:ldd ./路径

       4.在服务端查看哪些进程在使用某一个so:lsof ***.so

20.怎么同时杀死多个进程?

       根据单个进程号杀死单个进程:kill 2153,强制杀死进程号为2153的进程:kill -9 2153,

杀死多个进程(空格隔开进程号),kill xxx xxx xxx;

21.虚拟地址是如何转化到物理地址的?页表的构成?

       页表的构成如32问,虚拟地址到物理地址的转换是通过操作系统的内存管理单元(MMU)来完成的。MMU负责将虚拟地址映射到物理地址。在使用虚拟内存的系统中,每个进程都有自己的虚拟地址空间,这个空间被划分为多个页面(通常是4KB大小)。每个页面都有一个对应的页表项,页表项记录了虚拟内存和物理页面之间的映射关系。当程序当问一个虚拟地址时,MMU会根据页表查找对应的物理页面。如果该虚拟页面已经在物理内存中了,则MMU将虚拟地址转换为物理地址,并将访问请求发送到物理内存。如果虚拟页面不在物理内存中,则发生缺页异常,操作系统会将对应的物理页面加载到内存中,并更新页表。总结:虚拟内存到物理地址的转换是通过页表来实现的,MMU根据页表将虚拟地址映射到物理地址。这种转换机制使得每个进程都可以拥有自己的虚拟地址空间,从而实现了内存的隔离和保护。

22.操作系统中的原子操作是如何实现的?

       处理器实现原子操作:使用基于对缓存加锁或者总线加锁的方式来实现多处理器之间的原子操作。

              1.使用总线锁保证原子性:如果多个处理器同时对共享变量进行读写改操作,那么共享变量就会被多个处理器同时进行操作,这样读改写操作就不是原子的,操作完之后共享变量的值会和期望的不一致。想要保证操作是原子的,就必须保证CPU1读改写共享变量的时候,CPU2不能操作缓存了该共享变量内存地址的缓存,是使用处理器提供的一个LOCK#信号,当一个处理器在总线输出此信号时,其他处理器的请求将被阻塞住,那么该处理器的请求可以独占共享内存。

              2.使用缓存锁保证原子性。在同一时刻我们只需保证对某个内存地址的操作是原子性即可,但总线锁定把CPU和内存之间的通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销较大,目前处理器在某些场合下使用缓存锁定代替总线锁定进行优化。缓存锁是指内存区域如果被缓存在处理器的缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上声明LOCK#信号,而是修改内存地址并允许他的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据,当其它处理器回写已被锁定的缓存行的数据时,会使缓存行无效。但是有两种情况下处理器不会使用缓存锁定:1.当数据不能被缓存在处理器内部,或操作的数据跨多个缓存行时,则处理器会调用总线锁定。2.有些处理器不支持缓存锁定,某些处理器就算锁定的内存区域在处理器的缓存行中也会调用总线锁定。

23.进程间通信的方式有哪些,他们的原理是什么?

       管道即有名管道:管道是一种半双工通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程间亲缘关系是指父子进程关系。有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

       信号:信号量是一个计数器,可以用来控制多个进程对共享资源的访问,它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源,因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

       消息队列:消息队列是消息的链接表,它克服了1.2种通信方式中信号量有限的缺点,具有写权限的进程可以按照一定的规则向消息队列中添加新消息;对消息队列中有读权限的进程则可以从消息队列中读取信息。其基本思想是:根据“生产者-消费者”原理,利用内存中共用消息缓冲区实现进程之间的信息交换。内存中开辟了若干消息缓冲区,用以存放信息,每当一个进程向另一个进程发送信息时,便会申请一个消息缓冲区,并把已准备好的消息送到缓冲区,然后把该消息缓冲区插入到接收进程的消息队列中,最后通知进程接收,进程接收后发来的通知后,从本进程的消息队列中摘下一消息缓冲区,取出所需信息,然后把消息缓冲区不定期给系统,系统负责管理共用消息以及消息的传递。一个进程可以给若干个进程发送消息,反之,一个进程可以接收不同进程发来的消息,显然,进程中关于消息队列的操作是临界区,当发送进程正往接收进程的消息队列中添加一条信息时,接收进程不能同时从该消息队列中导出信息,反之也一样。

       共享内存:可以说是最有用的进程间通信方式,它使得多个进程可以同时访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量。

       信号量:主要作为进程之间及同一种进程的不同线程之间的同步和互斥手段。

       套接字:套接口也是一种进程间通信机制,与其他机制不同的是,它可用于不同进程间通信。

24.线程之间的通信方式有哪些,他们的原理是什么?

       有全局变量,互斥量,信号量,事件,临界区。

       实现原理:

1.全局变量:使用全局变量,对该变量加volatile关键字,让程序每一次都从内存中读取这个值,而不是因编译器优化从缓存的地方读取。

2.互斥量:在功能上与临界区域很相似,线程拥有互斥量后就进入临界区状态,一次只能有一个线程拥有该互斥量。

3.信号量:它是最具有历史的同步机制,是解决生产者消费者问题的关键要素。通过以恶搞计数器来代表目前可用资源数。

4.事件:用事件(Event)来同步线程是最具弹性的了。一个事件有两种状态:激发状态和未激发状态。也称有信号状态和无信号状态。事件又分两种类型:手动重置事件和自动重置事件。手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。自动重置事件被设置为激发状态后,会唤醒“一个”等待中的线程,然后自动恢复为未激发状态。所以用自动重置事件来同步两个线程比较理想。

5.临界区

25.时间片算是资源分配嘛?

       时间片可以被视为一种资源分配的方式。

在计算机操作系统中,时间片(time slice)是指操作系统将处理器的使用时间划分为一段段固定长度的时间片段,每个时间片段被分配给不同的任务或进程。在每个时间片段结束时,操作系统会切换到下一个任务,以保证每个任务都有机会执行。这种时间片的分配方式被称为时间片轮转调度(round-robin scheduling)。

时间片的分配类似于资源分配,因为处理器的使用时间是计算机系统中的一种有限资源。操作系统需要合理地将处理器时间分配给正在运行的任务,以确保每个任务都能得到充分的处理器时间,并避免某个任务长时间占用处理器而导致其他任务得不到执行。

时间片轮转调度算法以公平地分配处理器时间为目标,它可以确保每个任务都获得相同长度的时间片,从而避免某个任务长时间霸占处理器。这种公平性有助于提高系统的响应性和用户体验,特别是在多任务环境下。

26.在多核里,启动多个线程,时间片如果是资源的话,每个进程分配的时间片是固定的吗?为什么?

       在多核处理器上,每个核心都可以独立地执行任务。当启动多个线程或进程时,在时间片轮转调度算法中,每个进程或线程仍然会被分配固定长度的时间片来执行。每个核心按照时间片的顺序执行任务,直到时间片用尽或任务完成。

时间片的长度通常是预先设定的,并且对于所有的线程或进程是相同的。这是因为采用相同长度的时间片可以确保公平性和均衡性,每个线程或进程都有相同的机会获得处理器资源。

如果每个线程或进程被分配不同长度的时间片,可能会导致一些线程或进程获得更多的处理器时间,而其他线程或进程得到的时间较少。这可能导致系统不公平,某些任务可能无法及时完成,而其他任务则有过多的时间片可以使用,影响整体性能和响应速度。

通过为每个线程或进程分配固定长度的时间片,操作系统可以更好地控制任务的执行顺序,确保每个任务都能得到适当的处理器时间,并在核心之间进行公平切换。这有助于提高系统的负载均衡和响应性能。

28.Linux中对于进程的保护机制有哪些,分别是什么?

用户级进程进程监控工具:Linux系统中提供了who,ps、top等多种查看进程信息的系统调用,通过这些系统调用我们可以清晰的了解到进程的运行状态和存活情况,对进程进行监控和保护。

29.进程和线程的区别是什么?

线程:是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程不自己拥有系统资源,只拥有一点在运行中必不可少的资源,与所在进程中的其他线程共享进程所有的全部资源,可并发执行。

进程:是一个具有独立功能的程序在一个数据集合上依次动态执行的过程

区别:

内存占用方面:进程占用内存多,切换复杂,CPU利用率低,线程占用内存少,切换简单,CPU占用率高

销毁切换方面:进程创建销毁切换复杂,线程创建销毁切换简单,速度很快

编程调试方面:进程编程调试简单,线程编程复杂,调试复杂

可靠性:进程间不会互相影响,而线程一个线程挂掉将导致整个进程挂掉

分布式:进程使用于多核多机分布式,拓展机器容易,线程适用于多核分布式

30.进程切换的流程有哪些

切换新的页表,然后使用新的虚拟地址,切换内核栈,加入新的内容,硬件上下文切换

31.说明一下C++中的协程是什么?

是一种用户态的轻量级线程,协程的调度完全由用户控制。它拥有自己的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,上下文切换非常快。

32.页表是什么?

       页表是一种数据结构,存放着各个虚拟页的状态,是否映射,是否缓存,进程要知道哪些内存地址上的数据在物理内存上,哪些不在,还有在物理内存上的哪里需要用页表来记录。页表的每一个表项分为两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存页的地址(如果在的话)。

       作用:当进程访问到某个虚拟地址,去查看页表的时候,如果发现对应的数据不在物理内存中,如果物理地址内存已经满了,没有空地方了,那就找一个页覆盖,当然如果被覆盖的页曾经被修改过,需先将此页写回磁盘中。

       页表的状态:如果页表的有效地址为1,那么说明虚拟地址存储的内容存储在物理页中。

如果页表的有效位置为0,那么说明虚拟存储的内容没有存储在物理页中,发生了缺页异常,需要调用处理掉缺页异常。

       工作原理:1.我们CPU想访问虚拟地址所在的虚拟页,根据页表,找出页表中对应的值。判断有效位,如果有效位为1,则DRMA缓存命中,根据物理页号,找到物理页中的内容,返回。

       2.若有效位为0,参数缺页异常,调用内核缺页异常处理程序。内核会选择一个物理页作为牺牲页,将该页的内容刷新到磁盘空间中,然后把该页映射的磁盘文件缓存到该物理页上,然后页中对应的值的有效位变成1,第二部分存储上可以对应物理内存页的地址的内容。

       3.缺页异常处理完毕后,返回中断前的指令,重新执行,此时缓存命中,执行1

       4.将找到的内容映射到高速缓存中,CPU从高速缓存中获取该值,结束

33.Linux虚拟内存的机制:

       Linux把虚拟内存划分为区域的集合,一个区域包括连续的多个页,区域的数据结构图如上:

       内核为每个进程维护了一个单独的任务结果task_struct

       Task_struct当中的struct mm_struct* mm指针,指向了mm_struct,该结构描述虚拟内存的运行状态。

       Mm_struct结构体中的pgd指针指向进程的一级页面的基地址,mmap指针,指向vm_area_struct链表。

       Vm_area_struct 描述area的结构,vm_start表示area的开始位置,vm_end表示area的结束位置,vm_port表示area内的读写权限,vm_flags表示area内的页表是进程私有还是共享的,vm_next指向的是下一个area节点。

34.虚拟内存机制的优点:

1.既然每个进程的内存空间都是一致而且固定的(32位平台下都是4G),所以链接器在链接可执行文件时,可以设定内存地址,而不用去管这些数据最终实际内存地址,这交给内核来完成映射关系。

2.当不同的进程使用同一段代码时,比如库文件的代码,在物理内存中可以只存储一份这样的代码,不同进程只要将自己的虚拟内存映射过去就好了,这样可以节省物理内存。

3.在程序需要分配连续空间的时候,只需要在虚拟内存分配连续空间,而不需要物理内存时连续的,实际上,往往物理内存都是断断续续的内存碎片。这样就可以有效地利用我们的物理内存。

猜你喜欢

转载自blog.csdn.net/songbijian/article/details/132570427
今日推荐