c++面经汇总(操作系统 互斥锁机制以及互斥锁和读写锁的区别、进程状态转换(动态就绪、静态就绪、动态阻塞、静态阻塞)软链接、硬链接、大端小端、用户态和内核态、死锁产生的必要条件、内存溢出和内存泄漏线
请你讲述一下互斥锁机制,以及互斥锁和读写锁的区别互斥锁用来保证在任何时刻,都只能有一个线程访问该对象,当获取锁操作失败时,线程会进入睡眠,等待锁释放时唤醒。读写锁:读写锁分为读锁和写锁,处于读的操作时,可以允许多个线程同时获得读操作,但是同一个时刻只能有一个线程可以获得写锁,当获得写锁失败时,线程进入睡眠状态,一直到写锁释放时被唤醒写锁会阻塞其他读写锁(当有一个线程获取到写锁时,读锁也不能被其他线
请你讲述一下互斥锁机制,以及互斥锁和读写锁的区别
- 互斥锁用来保证在任何时刻,都只能有一个线程访问该对象,当获取锁操作失败时,线程会进入睡眠,等待锁释放时唤醒。
- 读写锁:读写锁分为读锁和写锁,处于读的操作时,可以允许多个线程同时获得读操作,但是同一个时刻只能有一个线程可以获得写锁,当获得写锁失败时,线程进入睡眠状态,一直到写锁释放时被唤醒写锁会阻塞其他读写锁(当有一个线程获取到写锁时,读锁也不能被其他线程所获取)写锁优先于读(一旦有写者,后续的读者必须进行等待,唤醒时优先考虑写)非常适用于读数据频率远大于写数据的频率
- 互斥锁和读写锁的区别:
- 读写锁区分读者和写者,互斥锁不区分
- 互斥锁同一时间只允许一个线程访问该对象,无论读写,但是读写锁允许有多个读者同时读对象
- linux的四种锁机制
- 互斥锁:用于保证任何时刻都只能由一个线程访问该对象
- 读写锁:分为读锁和写锁,处于读锁,可以允许多个对象同时读,处于写锁,其他进行读写的线程都会失败,并且变为睡眠状态,一直到写锁被释放(写锁优先读锁进行)
- 自旋锁:自旋锁与互斥锁的区别就是,当某对象被占用的时候,其他线程不会陷入睡眠状态,而是在原地自旋,一直到锁被释放,这样就节省了从睡眠状态被唤醒的时间,如果加锁的时间很短,这样有利于节省时间,但是如果加锁的时间很长,就会造成cpu资源的浪费
- RCU (read - copy-update) 在修改数据时,首先需要读取数据(read),然后生成一个副本(copy),对副本就行修改,修改完成后,再将老数据更新成新的数据(update),使用RCU的时候,读者几乎不需要同步开销,也不用获取锁,直接读就可以了,但是对于写来说,需要使用锁机制并行其他写者的修改操作 ,因此开销非常大,如果是那种需要大量读,但是少量的写的话,RCU非常适合
请回答一下进程与线程的区别
- 进程是运行时对程序的封装,进程是操作系统进行资源的调度和分配的基本单位;而线程是进程的子任务,是CPU进行调度和分配的基本单位;
- 进程实现的是操作系统的并发;线程实现的是进程内部的并发
- 进程拥有属于自己的内存资源,而同一进程的线程则共用进程的资源,包括数据段、代码段等,但是线程拥有自己的线程栈
- 进程的通信复杂,包括了系统IPC、管道、套接字等,系统IPC又包括信号量、消息队列、共享内存等通讯方式;线程的通讯方式简单,因为同属于一个进程的不同线程是公用进程的资源
- 进程的创建与销毁对于操作系统来说,开销很大,需要分配内存空间、I/O设备等,销毁的时候还需要保存进程上下文,系统状态等,开销很大;但是线程在创建的时候,只需要保存少量的寄存器数据就可以了;
- 进程之间是相互独立的,如果某个进程挂掉,不会影响其他进程;线程之间存在联系,如果一个进程的所属线程挂掉了,那么其他县城也会挂掉,最终导致进程的死亡
请你说一说进程状态切换图 动态就绪、静态就绪、动态阻塞、静态阻塞
- 创建:进程正在被创建
- 就绪:进程创建成功后,被加入就绪队列中等待CPU调度
- 执行:CPU在就绪队列中选择队首的进程将其调入内存,分配CPU执行
- 阻塞:进程因为某种原因,等待设备而无法运行
- 终止:进程执行完毕
状态的切换:当某进程处于执行状态会转化为就绪状态(优先级别更高的任务达到就绪队列)、当处于阻塞状态转化为就绪状态(因为等待的事情发生,比如,读锁被释放)
动态就绪、静态就绪、动态阻塞、静态阻塞
由于多个进程会竞争资源,造成内存资源紧张,并且,如果此时没有就绪进程,那么处理机会空闲;由于I/O速度比处理机速度慢得多,可能会出现全部进程等待I/O;等情况
针对上面所述情况,提出了两种解决办法
- 交换技术:通过将一部分未开始的进程换到外存,腾出内存空间
- 虚拟存储技术、每个进程只能装入一部分程序和数据
在交换技术上,将内存暂时无法运行的进程,或者暂时用不到的数据和程序,换出到外存来腾出空间,把已经具备运行条件的进程(数据与程序)换入内存
通过上面的基础就可以很轻易的解释挂起状态:进程被挂起到外存
活动阻塞:进程在内存中,但是由于某种原因被阻塞
静止阻塞:进程在外存中,同时被某种原因阻塞了
活动就绪:进程在内存中,处于就绪状态(程序和数据都已经到达)只要分配CPU和调度就可以直接运行
静止就绪:进程在外存中,只要调入到内存,分配CPU和调度就可以运行
那么这也会出现几种状态的转换
活动就绪-静止就绪(内存不够,调入外存)
活动阻塞-静止阻塞)(内存不够,调入外存)
A* a = new A; a->i = 10;在内核中的内存分配上发生了什么?
- 首先要知道程序内存管理
一个程序由BSS段(未初始化区域:通常用来存放程序中未初始化的全局变量和静态变量的一块内存区域,属于静态分配,程序结束后资源由系统自动释放)、data段(存放程序中已经初始化的全局变量的一块区域,属于静态内存分配)、text(代码段:存放程序执行代码的一块内存区域,这块区域的大小在程序运行前就已经确定,并且内存区域属于只读,在代码段中,可能包含一些制度的常数变量)
bss段的大小从可执行文件中获取到,然后连接器得到这个大小的内存块,紧跟在数据段的后面,当这个内存进入程序的地址空间后,全部清零。
当可执行文件在执行的时候,又分出两个区域,堆区和栈区
栈区:由编译器自动进行申请和释放,存储一些函数的参数,局部变量等,当函数被调用的时候,函数的返回类型和调用的信息被存储到栈上,然后这个被调用的函数再给它的局部变量在栈上分配空间,当申请的栈的大小超过最大容量(由系统预先定义好)的时候,提示栈溢出;
堆区:用来动态分配内存的,通过程序员进行自主的申请与释放,由于频繁的申请和释放会导致内存的不连续,造成内存碎片;
解释语句
- 首先判断a是一个指针类型,那么系统会为其在栈上申请一个大小为4/8字节的空间,分配给a
- new A,在堆上申请一个类A大小的内存空间
- a = new A ;将指针a所指向的内存中填入为A申请到的空间地址,即现在a中是A的地址
- a->i ;在A中找到i在类A中的偏移量,得到a->i的地址,然后将其赋值为10;
静态变量什么时候初始化
全局变量、类的静态成员变量在main执行之前的静态初始化过程中分配内存并初始化;局部静态变量(一般为函数内的静态变量)在第一次使用时分配内存并初始化,同时,这里指的局部静态变量包含内置数据类型和自定义类型的对象;
**关于静态变量初始化的线程安全性 **
由于非局部静态变量一般在main执行之前的静态初始化过程中分配内存并且初始化,可以认为时线程安全的;但是局部静态变量在编译的时候,我们一般是在初始化语句的前面设置一个局部静态变量的表示来判断是否已经初始化,运行的时候,每次都需要判断,这个过程本身是不安全的
请你说一说用户态和内核态的区别
内核态从本质上看是一种软件——控制计算机的硬件资源,并且提供上层应用程序运行的环境;
由于一些操作需要在内核权限下才能执行,而我们平时的程序一般运行在用户态,这就涉及到了用户态切换到内核态的过程,比如malloc,它具体是使用brk系统调用来分配内存,当malloc调用brk的时候,就涉及到一次从用户态到内核态的切换,类似printf调用的就是write系统调用来输出字符串
那么什么时候会发生从用户态到内核态的转换呢?
- 当发生系统调用
- 当触发异常:如运行在用户态的程序,突然发生某些不可预知的异常,这个时候就会触发从用户态执行的进程转换到内核态执行相关的异常事件
- 外围设备的中断:当外围设备完成用户的请求操作,会向cpu发送中断信号,这个时候,cpu就会暂停执行下一条将要执行的指令,转而区执行中断信号对应的处理程序,如果先前执行的指令发生在用户态,那么就会发生从用户态到内核态的转换
两者的区别:
用户态和内核态是操作系统的两种运行级别,他俩最大的不同就是特权级的不同,用户态的特权级最低,内核态较高。运行在用户态的程序不能直接访问操作系统内核数据结构和程序;
请你回答一下软链接和硬链接的区别
- 为了解决文件共享问题,linux引入了软链接和硬链接;
- 两种链接除了解决了文件共享的问题,还带来了隐藏文件路径、增加权限安全及节省存储等好处;
- 若1个inode号对应多个文件名,则为硬链接,即硬链接就是同一个文件使用了不同的别名,通过In创建
- 若文件用户数据块中存放的内容是另一个文件的路径名指向,则该文件是软链接
- 软链接是一个普通文件,有自己独立的inode,但是其数据块内容比较特殊;
- 实际上两种链接都是指的文件别名
- 硬链接其实就是两个文件了,硬链接得到的文件名和原文件名指向的磁盘的inode节点是同一个,同时该inode中的引用计数会加1。删除原文件,也只是该inode节点的引用计数减1而已。
- 软链接得到的文件名存的是原文件的绝对路径,也即是原文件如果被删除,那么就会出现悬空指针,也即是此软链接得到的文件名失去了存在的意义。软链接相当于win中的快捷方式。
inode:
文件的字节数
文件拥有者的User ID
文件的Group ID
文件的读、写、执行权限
文件数据block的位置
文件引用数,即有多少文件名指向这个inode
文件的时间戳,共有三个:ctime指inode改变时间,mtime指文件内容上一次修改的时间,atime指文件最后一次访问的时间。
区别:
'mtime':当文件内容更改时,mtime才会更新
'ctime':更改文件任何属性,其都会更新
请问什么是大端小端以及如何判断大端小端
大端指的是低字节存放在高地址,而高字节存放在低地址;小端存储是指低字节存储在低地址,高字节存储在高地址,由于c中联合体变量从事从低地址存储,因此我们可以通过其来判断是小端存还是大端存
int func()
{
union test
{
int i;
char c;
}
test t;
t.i = 1;
return (t.c== 1);
}
因为联合体总是从低地址存储,如果是大端,则t.c为0x00,则t.c!=1,返回0,如果是小端,则t.c 为0x01则t.c == 1,返回1
如何设计server,使得能够接受多个客户端的请求;
通过多线程,线程池
内存溢出和内存泄漏
-
内存溢出:内存溢出是指程序申请内存时,没有足够的内存供申请者使用,内存溢出就是你要的内存空间超过了系统预先分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误
-
内存溢出的原因:
- 内存中加载的数据量过于庞大,比如一次从数据库中取出过多的数据
- 集合类中有对对象的引用,使用完后未清空
- 代码中存在死循环或者循环产生过多重复的对象实体
- 启动参数内存值设定的过小
-
内存泄漏:内存泄漏是指由于疏忽或者错误造成了程序未能释放掉不再使用的内存的情况,内存泄漏不是指内存在物理上的小时,而是引用程序分配某段内存之后,失去了对该段内存的控制,进而造成了内存的浪费
-
内存泄漏的分类:
- 堆内存泄漏,由于堆内存指的是程序运行中根据需要分配,通过malloc、realloc、new等从堆中分配的的一块内存,然后通过调用对应的free或者delete删掉,如果没有进行删除,那么对应的内存就不会再被使用,从而产生内存泄漏
- 没有将基类的析构函数定义为虚函数,当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确释放,因此造成内存泄漏
- 系统资源泄漏,主要是指系统分配的资源,比如Bitmap,handle,socket等没有使用相应的函数释放掉,导致系统资源的浪费,验证可导致系统效能的降低,系统运行不稳定;
你都用过什么线程模型
常用的线程模型:
- Future模型,该模型通常再使用的时候,需要结合Callable接口派和使用,Fature是把结果放在未来获取,如果当前主线程并不急于获取处理结果,允许子线程先处理一段事件,处理结束后把结果保存下来,当主线程需要使用的时候再向子线程索取;而Callable是类似与Runnable的接口,其中call方法类似于run方法,所不同的是run方法不能抛出受检异常,没有返回值,而call方法则可以抛出受检异常并可以设置返回值,两者的方法体都是线程执行过程
- 生产者消费者模型:其核心就是使用一个缓存来保存任务,开启一个/多个线程来生产任务,然后再开启一个/多个线程来从缓存中取出任务进行处理,这样的好处是,生产者不需要处理任务,将精力集中于生产任务,而消费者将精力集中于从缓存中取出任务进行处理;
- master - worker模型:master-worker模型类似于任务分发策略,开启一个master线程接收任务,然后再master中根据任务的具体情况进行分发给其他worker子进程,然后由子进程处理任务,如果需要返回结果,则worker处理结束之后,把处理结果返回给master;
就绪状态的进程再等待什么
被调度使用CPU的运行权
c++的锁你知道几种
互斥锁、条件变量、自旋锁、读写锁
请你说一说死锁产生的必要条件?
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已经获得的资源保持不放
- 不剥夺条件:进程已经获得的资源,再未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相连的循环等待资源关系
更多推荐
所有评论(0)