一个高级软件工程师面试被问的问题

2023-05-16

使用new和malloc如何解决内存碎片问题?


多进程间通信几种方式,你用过几种方式?
线程间通信,用过几种方式
分不同的场景,适合用哪种通信方式

内存管理,如果让你来实现。你会怎么去设计。内存池实现方案?
死锁的理解
如何避免死锁
内存管理,

程序在linux环境下运行崩溃了,如何查找问题?

void com_utils_t::dump_stack()
{
#ifdef __i386__
	int j, nptrs;
	char **strings;
	std::ostringstream oss;
	const int BACKTRACE_SIZE = 16;
	void *buffer[BACKTRACE_SIZE];
	
	nptrs = backtrace(buffer, BACKTRACE_SIZE);
	
	std::cout << "backtrace() returned " << nptrs << " addresses" << std::endl;
	
	strings = backtrace_symbols(buffer, nptrs);
	if (strings != NULL)
	{
		for (j = 0; j < nptrs; j++)
		{
			std::cout << "	[" << j << "] " << strings[j] << std::endl;
		}
		
		delete(strings);
		oss << "cat /proc/" << getpid() << "/maps";
		std::cout << oss.str() << std::endl;
		system((const char*)oss.str().c_str());
	}
	else
	{
		std::cout << "backtrace_symbols return error." << std::endl;
	}
#endif

	return;
}

程序运行时,内存溢出了,该如何解决
Linux关于系统调用
Linux内核
内核提供系统调用接口。
内存拷贝
内核态和用户态。
从用户态切换到内核态是怎么实现的?
用的软中断。
关于中断你再说一下?
内存缺页中断。

什么是缺页中断:

       进程线性地址空间里的页面不必常驻内存,在执行一条指令时,如果发现他要访问的页没有在内存中(存在位为0),那么停止该指令的执行,并产生一个页不存在异常,对应的故障处理程序可通过从外存加载加载该页到内存的方法来排除故障,之后,原先引起的异常的指令就可以继续执行,而不再产生异常。

页面调度算法:

       页式虚拟存储器实现的一个难点是设计页面调度(置换)算法,即将新页面调入内存时,如果内存中所有的物理页都已经分配出去,就要按某种策略来废弃某个页面,将其所占据的物理页释放出来,好的算法,让缺页率降低。常见的有先进先出调度算法,最近最少调度算法,最近最不常用调度算法。

缺页中断的计算:

         作业本身的程序编制方法。程序编制的方法不同,对缺页中断的次数有很大影响。

例如:有一个程序要将128×128的数组置初值“0”。现假定分给这个程序的主存块数只有一块,页面的尺寸为每页128个字,数组中的元素每一行存放在一页中,开始时第一页在主存。若程序如下编制:

    Var A: array[1..128] of array [1..128] of

integer;

 

for j := 1 to 128

 

do for i := 1 to128

 

do A[i][j]:=0

则每执行一次A[i][j] :=0就要产生一次缺页中断,于是总共要产生(128×128-1)次缺页中断。

如果重新编制这个程序如下:

    Var A: array[1..128] of

array[1..128] of

integer;

 

for i := 1 to128

 

do for j := 1 to128

 

do A[i][j] := 0

那么总共只产生(128-1)次缺页中断。

显然,虚拟存储器的效率与程序的局部化程度密切相关。程序的局部化有两种:时间局部化和空间局部化。

举例:

for(int i=0;i<1024;i++)

for(int j=0;j<1024;j++)

a[j][i]=0;

会产生1024*1024个缺页中断

for(int i=0;i<1024;i++)

for(int j=0;j<1024;j++)

a[i][j]=0;

会产生1024个缺页中断。
 

 


中断切换
大小端
网络编程时用到,不同操作系统
X86是小端
一个字节8
敲cd发生什么?回到家目录
C++新特性了解

 

使用new和malloc如何解决内存碎片问题?

通常我们习惯直接使用new、malloc等API申请分配内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。

高性能之内存池(频繁使用malloc和new会降低性能)

内存池(Memory Pool)是一种内存分配方式。通常我们习惯直接使用new、malloc等API申请分配内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率得到提升。

(1)针对特殊情况,例如需要频繁分配释放固定大小的内存对象时,不需要复杂的分配算法和多线程保护。也不需要维护内存空闲表的额外开销,从而获得较高的性能。

(2)由于开辟一定数量的连续内存空间作为内存池块,因而一定程度上提高了程序局部性,提升了程序性能。

(3)比较容易控制页边界对齐和内存字节对齐,没有内存碎片的问题。

(4)当需要分配管理的内存在100M一下的时候,采用内存池会节省大量的时间,否则会耗费更多的时间。

(5)内存池可以防止更多的内存碎片的产生

(6)更方便于管理内存

 

利用C/C++开发大型应用程序中,内存的管理与分配是一个需要认真考虑的部分。

本文描述了内存池设计原理并给出内存池的实现代码,代码支持Windows和Linux,多线程安全。

内存池设计过程中需要考虑好内存的分配与释放问题,其实也就是空间和时间的矛盾。

有的内存池设计得很巧妙,内存分配与需求相当,但是会浪费过多的时间去查找分配与释放,这就得不偿失;

实际使用中,我们更多的是关心内存分配的速度,而不是内存的使用效率。基于此,本文按照如下思想设计实现内存池。

主要包含三个结构:StiaticMemory, MemoryChunk和MemoryBlock,三者之间的关系如下图所示:

 

 

1.内存的分配:

(1)如果分配大小超过1024,直接采用malloc分配,分配的时候多分配sizeof(size_t)字节,用于保存该块的大小;

(2)否则根据分配大小,查找到容纳该大小的最小size的MemoryChunk;

(3)查找MemoryChunk的链表指针pList,找到空闲的MemoryBlock返回;

(4)如果pList为NULL,临时创建MemoryBlock返回;

(5)MemoryBlock头部包含两个成员,pChunk指向的所属的MemoryChunk对象,size表明大小,其后才是给用户使用的空间;

2.内存的释放:

(1)根据释放的指针,查找器size头部,即减去sizeof(size_t)字节,判断该块的大小;

(2)如果大小超过1024,直接free;

(3)否则交给MemoryChunk处理,而块的头部保存了该指针,因此直接利用该指针就可以收回该内存。

注意的问题:

上述设计的内存池通过冗余的头部来实现内存块的分配与释放,减少了内存池的操作时间,速度上要优于原始的malloc和free操作,同时减少了内存碎片的增加。

但是该设计中没有去验证释放的块冗余头部的正确性,因此故意释放不属于内存池中的块或者修改头部信息都会导致内存池操作失败,当然这些可以由程序员来控制。

此外,内存池中分配出去的内存块如果不主动释放,内存池没有保留信息,不会自动释放,但是在退出的时候会验证验证是否完全释放,其实这个在系统测试时候就可以检测出来,我想这个缺陷也是可以弥补的,在此提出,希望使用者注意。

下面贴上源码,如果对代码有任何建议或者发现存在的Bug,希望与我联系,共同学习交流,Tx。

 

 

多进程间通信几种方式,你用过几种方式?

答:管道、消息队列、信号量、共享内存、套接字

 

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

高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。

有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

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

信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

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

总结:

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。

IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

一、管道

管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。

1、特点:

它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。

它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。

它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

 

二、FIFO

FIFO,也称为命名管道,它是一种文件类型。

1、特点

FIFO可以在无关的进程之间交换数据,与无名管道不同。

 

FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

 

三、消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

1、特点

消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。

消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。

消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

 

四、信号量

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

1、特点

信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。

信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。

每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

支持信号量组。

五、共享内存

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。

1、特点

共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。

因为多个进程可以同时操作,所以需要进行同步。

信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

进程间的五种通信方式介绍

————————————————

线程间通信,用过几种方式

线程之间的通信方式有以下几种:

1、消息队列,是最常用的一种,也是最灵活的一种,通过自定义数据结构,可以传输复杂和简单的数据结构。

在Windows程序设计中,每一个线程都可以拥有自己的消息队列(UI线程默认自带消息队列和消息循环,工作线程需要手动实现消息循环),因此可以采用消息进行线程间通信sendMessage,postMessage。

定义消息#define WM_THREAD_SENDMSG=WM_USER+20;

添加消息函数声明afx_msg int OnTSendmsg();

添加消息映射ON_MESSAGE(WM_THREAD_SENDMSG,OnTSM)

添加OnTSM()的实现函数;

在线程函数中添加PostMessage消息Post函数

2、使用全局变量

进程中的线程间内存共享,这是比较常用的通信方式和交互方式。

注:定义全局变量时最好使用volatile来定义,以防编译器对此变量进行优化。

3、使用事件CEvent类实现线程间通信

Event对象有两种状态:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。

1)创建一个CEvent类的对象:CEvent threadStart;它默认处在未通信状态;

2)threadStart.SetEvent();使其处于通信状态;

3)调用WaitForSingleObject()来监视CEvent对象

以上就是线程间通信方式有哪几种?的详细内容,更多请关注php中文网其它相关文章!

 

分不同的场景,适合用哪种通信方式

进程间的通信方式:

1.管道(pipe)及有名管道(named pipe):

管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。

2.信号(signal):

信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。

3.消息队列(message queue):

消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。

4.共享内存(shared memory):

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

5.信号量(semaphore):

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

6.套接字(socket);

这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。

线程之间的同步通信:

1.信号量二进制信号量互斥信号量整数型信号量记录型信号量

2.消息消息队列消息邮箱

3.事件event

互斥型信号量:必须是同一个任务申请,同一个任务释放,其他任务释放无效。同一个任务可以递归申请。(互斥信号量是二进制信号量的一个子集)

二进制信号量:一个任务申请成功后,可以由另一个任务释放。(与互斥信号量的区别)

整数型信号量:取值不局限于0和1,可以一个任务申请,另一个任务释放。(包含二进制信号量,二进制信号量是整数型信号量的子集)

 

二进制信号量实现任务互斥:

打印机资源只有一个,a bc三个任务共享,当a取得使用权后,为了防止其他任务错误地释放了信号量(二进制信号量允许其他任务释放),必须将打印机房的门关起来(进入临界段),用完后,释放信号量,再把门打开(出临界段),其他任务再进去打印。(而互斥型信号量由于必须由取得信号量的那个任务释放,故不会出现其他任务错误地释放了信号量的情况出现,故不需要有临界段。互斥型信号量是二进制信号量的子集。)

二进制信号量实现任务同步:

a任务一直等待信号量,b任务定时释放信号量,完成同步功能

记录型信号量(record semaphore):

每个信号量s除一个整数值value(计数)外,还有一个等待队列List,其中是阻塞在该信号量的各个线程的标识。当信号量被释放一个,值被加一后,系统自动从等待队列中唤醒一个等待中的线程,让其获得信号量,同时信号量再减一。

同步和互斥的区别:

当 有多个线程的时候,经常需要去同步这些线程以访问同一个数据或资源。例如,假设有一个程序,其中一个线程用于把文件读到内存,而另一个线程用于统计文件中 的字符数。当然,在把整个文件调入内存之前,统计它的计数是没有意义的。但是,由于每个操作都有自己的线程,操作系统会把两个线程当作是互不相干的任务分 别执行,这样就可能在没有把整个文件装入内存时统计字数。为解决此问题,你必须使两个线程同步工作。

所 谓互斥,是指散布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这 个程序片段后才可以运行。如果用对资源的访问来定义的话,互斥某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资 源的访问顺序,即访问是无序的

 

所谓同步,是指散步在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。如果用对资源的访问来定义的话,同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

内存管理,如果让你来实现。你会怎么去设计。内存池实现方案?

C/C++下内存管理是让几乎每一个程序员头疼的问题,分配足够的内存、追踪内存的分配、在不需要的时候释放内存——这个任务相当复杂。而直接使用系统调用malloc/free、new/delete进行内存分配和释放,有以下弊端:

内存池(memory pool)是代替直接调用malloc/free、new/delete进行内存管理的常用方法,当我们申请内存空间时,首先到我们的内存池中查找合适的内存块,而不是直接向操作系统申请,优势在于:

内存池设计

看到内存池好处这么多,是不是恨不能马上抛弃malloc/free,投奔内存池的怀抱呢?且慢,在我们自己动手实现内存池之前还需要明确以下几个问题: 

带着以上问题,我们来看以下一种内存池设计方案。

内存池实现方案一

这里下载该内存池实现的源码。

首先给出该方案的整体架构,如下:

 

图1.内存池架构图

结构中主要包含block、list 和pool这三个结构体,block结构包含指向实际内存空间的指针,前向和后向指针让block能够组成双向链表;list结构中free指针指向空闲 内存块组成的链表,used指针指向程序使用中的内存块组成的链表,size值为内存块的大小,list之间组成单向链表;pool结构记录list链表的头和尾。

内存跟踪策略

该方案中,在进行内存分配时,将多申请12个字节,即实际申请的内存大小为所需内存大小+12。在多申请的12个字节中,分别存放对应的list指针(4字节)、used指针(4字节)和校验码(4字节)。通过这样设定,我们很容易得到该块内存所在的list和block,校验码起到粗略检查是否出错的作用。该结构图示如下:

 

图2.内存块申请示意图

图中箭头指示的位置为内存块真正开始的位置。 

内存申请和释放策略

申请:根据所申请内存的大小,遍历list链表,查看是否存在相匹配的size;

存在匹配size:查看free时候为NULL

free为NULL:使用malloc/new申请内存,并将其置于used所指链表的尾部

free不为NULL:将free所指链表的头结点移除,放置于used所指链表的尾部

不存在匹配size:新建list,使用malloc/new申请内存,并将其置于该list的used所指链表尾部

返回内存空间指针

释放:根据内存跟踪策略,获取list指针和used指针,将其从used指针所指的链表中删除,放置于free指针所指向的链表

对方案一的分析

对照“内存池设计”一节中提出的问题,我们的方案一有以下特点:

 

结合分析,可以得出该方案应用场景如下:程序所申请的内存块大小比较固定(比如只申请/释放1024bytes或2048bytes的内存),申请和释放的频率基本保持一致(因申请多而释放少会占用过多内存,最终导致系统崩溃)。

 

这篇文章讲解了内存管理的基本知识,以一个简单的内存池实现例子作为敲门砖,引领大家认识内存池,下一篇为内存池进阶文章,讲解apache服务器中内存池的实现方法。

死锁的理解

如何避免死锁

造成死锁的原因有如下几条(需同时满足条件):

1、互斥条件:任务使用的资源中至少有一个是不能共享的,资源的使用和释放方法都使用了synchronized关键字修饰

2、至少有一个任务它必须持有一个资源并且这个任务正在等待获取另一个当前正在被别的任务持有的资源

3、资源不能被项目抢占,任务必须把资源释放当做普通事件,资源只能被释放后才能被其他任务获取到

4、必须有循环等待,这时一个任务等待其他任务释放资源,其他任务又在等待另一个任务释放资源,且直到最后有一个任务在等待第一个任务释放资源,使得大家都被锁住,就造成了死锁

那如何避免死锁的情况发生:

1、加锁顺序,线程按照一定的顺序枷锁

2、加锁时限,线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并主动释放自己已经占有的锁,可以考虑使用lock.trylock(timeout)来代替使用内部锁机制

3、避免一个线程同时获取多个锁

4、避免一个线程在锁内同时占用多个资源

5、对于数据库锁,加锁和解锁必须在一个数据库连接里,否则有可能出现解锁失败的情况

一、什么是死锁

死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。例如,在某一个计算机系统中只有一台打印机和一台输入 设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程P2 所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。

回到顶部

二、死锁产生的原因

1. 系统资源的竞争

系统资源的竞争导致系统资源不足,以及资源分配不当,导致死锁。

2. 进程运行推进顺序不合适

进程在运行过程中,请求和释放资源的顺序不当,会导致死锁。

回到顶部

三、死锁的四个必要条件

互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。

循环等待条件: 若干进程间形成首尾相接循环等待资源的关系

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

 

回到顶部

四、 死锁的避免与预防

1. 死锁避免

死锁避免的基本思想:系统对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配,这是一种保证系统不进入死锁状态的动态策略。 

如果操作系统能保证所有进程在有限时间内得到需要的全部资源,则系统处于安全状态否则系统是不安全的。

 下面我们来通过一个例子对安全状态和不安全状态进行更深的了解 

如上图所示系统处于安全状态,系统剩余3个资源,可以把其中的2个分配给P3,此时P3已经获得了所有的资源,执行完毕后还能还给系统4个资源,此时系统剩余5个资源所以满足(P2所需的资源不超过系统当前剩余量与P3当前占有资源量之和),同理P1也可以在P2执行完毕后获得自己需要的资源。 

如果P1提出再申请一个资源的要求,系统从剩余的资源中分配一个给进程P1,此时系统剩余2个资源,新的状态图如下:那么是否仍是安全序列呢那我们来分析一下 

 

系统当前剩余2个资源,分配给P3后P3执行完毕还给系统4个资源,但是P2需要5个资源,P1需要6个资源,他们都无法获得资源执行完成,因此找不到一个安全序列。此时系统转到了不安全状态。

 

2. 死锁预防

我们可以通过破坏死锁产生的4个必要条件来 预防死锁,由于资源互斥是资源使用的固有特性是无法改变的。

产生死锁的原因主要是:

(1) 因为系统资源不足。

(2) 进程运行推进的顺序不合适。

(3) 资源分配不当等。

如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则

就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。

产生死锁的四个必要条件:

(1) 互斥条件:一个资源每次只能被一个进程使用。

(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之

一不满足,就不会发生死锁。

死锁的解除与预防:

理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和

解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确

定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态

的情况下占用资源。因此,对资源的分配要给予合理的规划。

 

内存管理,

程序在linux环境下运行崩溃了,如何查找问题?

void com_utils_t::dump_stack()
{
#ifdef __i386__
        int j, nptrs;
        char **strings;
        std::ostringstream oss;
        const int BACKTRACE_SIZE = 16;
        void *buffer[BACKTRACE_SIZE];
        
        nptrs = backtrace(buffer, BACKTRACE_SIZE);
        
        std::cout << "backtrace() returned " << nptrs << " addresses" << std::endl;
        
        strings = backtrace_symbols(buffer, nptrs);
        if (strings != NULL)
        {
                for (j = 0; j < nptrs; j++)
                {
                        std::cout << "        [" << j << "] " << strings[j] << std::endl;
                }
                
                delete(strings);
                oss << "cat /proc/" << getpid() << "/maps";
                std::cout << oss.str() << std::endl;
                system((const char*)oss.str().c_str());
        }
        else
        {
                std::cout << "backtrace_symbols return error." << std::endl;
        }
#endif

return;
}

 

内存异常经常导致程序出现莫名其妙的错误,往往很难查证,本文介绍在linux下的各种常见内存异常的查证工具和方法。

1 访问空指针/未初始化指针/重复释放内存

对于像访问空指针、未初始化指针(非法地址),重复释放内存等内存异常,linux默认会抛异常。

比如下面代码有空指针访问,编译运行后会coredump

int main()
{
    int *p=0;
    *p=6;
    return 0;
}

对于此类问题,我们只要在gcc编译程序时加入-g选项,同时在运行时能够生成coredump文件,利用gdb就可以定位到具体的问题代码行。

1.1 开启coredump

**ulimit -c  unlimited** //unlimited表示不限制coredump文件大小,也可指定一个具体值来限制文件最大长度。

1.2 定制core文件名

默认的coredump文件名为core,如果想自己定制core文件名,可以运行如下命令:

**echo "./core-%e-%p-%t" > /proc/sys/kernel/core_pattern** 

可以在core_pattern模板中使用变量还很多,见下面的列表:

%% 单个%字符

%p 所dump进程的进程ID

%u 所dump进程的实际用户ID

%g 所dump进程的实际组ID

%s 导致本次core dump的信号

%t core dump的时间 (由1970年1月1日计起的秒数)

%h 主机名

%e 程序文件名

1.3 使用gdb定位代码行

通过gdb即可定位出错代码行

root@ubuntu:/home/zte/test# gcc null.cc -g
root@ubuntu:/home/zte/test# ./a.out 
Segmentation fault (core dumped)
root@ubuntu:/home/zte/test# gdb a.out core
.......
Core was generated by `./null'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00000000004004fd in main () at null.cc:4
4    *p=6;

2、函数栈溢出

局部变量的写越界可能会破坏函数栈导致程序出现各种异常行为,但是OS默认不会在越界的第一现场coredump,因此导致问题查证非常困难。

幸运的是我们可以通过gcc的编译选项-fstack-protector 和 -fstack-protector-all在函数栈被破坏的函数返回时抛异常,从而可以很方便地定位问题所在函数。

代码示例

int main()
{
    int a=5;
    int *p=&a;
    p[3]=6;
    return 0;
}

 

上面代码会破坏函数栈,如果我们用gcc直接编译运行,不会抛异常。但是加了编译参数-fstack-protector 和 -fstack-protector-all后,再运行就会抛异常。下面是具体命令执行结果。

root@ubuntu:/home/zte/test# gcc t.c
root@ubuntu:/home/zte/test# ./a.out 
root@ubuntu:/home/zte/test# gcc t.c -fstack-protector -fstack-protector-all -g
root@ubuntu:/home/zte/test# ./a.out 
*** stack smashing detected ***: ./a.out terminated
Aborted (core dumped)
```
可以进一步用gdb的bt命令定位出问题的函数。
```
root@ubuntu:/home/zte/test# gdb a.out core
。。。。。。。。
Core was generated by `./a.out'.
Program terminated with signal SIGABRT, Aborted.
#0  0x00007f6bcfab5c37 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
56../nptl/sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0  0x00007f6bcfab5c37 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#1  0x00007f6bcfab9028 in __GI_abort () at abort.c:89
#2  0x00007f6bcfaf22a4 in __libc_message (do_abort=do_abort@entry=1, fmt=fmt@entry=0x7f6bcfc01d70 "*** %s ***: %s terminated\n")
    at ../sysdeps/posix/libc_fatal.c:175
#3  0x00007f6bcfb8d83c in __GI___fortify_fail (msg=<optimized out>, msg@entry=0x7f6bcfc01d58 "stack smashing detected")
    at fortify_fail.c:38
#4  0x00007f6bcfb8d7e0 in __stack_chk_fail () at stack_chk_fail.c:28
#5  0x00000000004005aa in main () at t.c:7
(gdb) q

 

3 越界读写动态分配内存/读写已释放动态分配内存

动态分配内存读写越界、读写已释放动态分配内存系统往往不会抛异常,我们可以使用electric-fence来使得读写越界内存/已释放内存后立刻抛异常,加速问题定位。

3.1 安装Electric fence

sudo apt-get install electric-fence

3.2 使用Electric fence

下面是越界写代码

#include <stdlib.h>
int main()
{
    int *p = (int*)malloc(sizeof(int));
    p[1] = 6;
    return 0;
}

 

如果使用gcc直接编译运行,不会抛异常。

我们可以加上参数 -lefence -g编译后运行,就会抛异常。通过gdb的bt打印即可定位到问题代码行。

root@ubuntu:/home/zte/test# gcc malloc_read_free.cc -lefence -g
root@ubuntu:/home/zte/test# ./a.out 
  Electric Fence 2.2 Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com>
Segmentation fault (core dumped)autogen.sh

 

4 内存泄漏

C/C++程序经常被内存泄漏问题困扰,本文介绍使用gperftools来快速定位内存泄漏问题。

4.1 安装gperftools工具

4.1.1 安装automake

sudo apt-get install automake

4.1.2 编译安装libunwind

从https://github.com/libunwind/libunwind/releases下载最新版本的libunwind源码包

解压到/usr/local/src目录

cd 解压源码目录

./autogen.sh

./configure

make -j6

make install

4.1.3 编译安装gperftools

从https://github.com/gperftools/gperftools/releases下载最新版本的gperftools源码包

解压到/usr/local/src目录

cd 解压源码目录

./autogen.sh

./configure

make -j6

make install

4.2 内存泄漏检测

下面是一段简单的内存泄漏源码

int main()
{
    int *p = (int*)malloc(sizeof(int));
    return 0;
}

编译代码、运行工具检察内存泄漏,注意设置下

root@ubuntu:/home/zte/# gcc leak.cc -g
root@ubuntu:/home/zte/# env HEAPCHECK=normal LD_PRELOAD=/usr/local/lib/libtcmalloc.so ./a.out 
WARNING: Perftools heap leak checker is active -- Performance may suffer
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 4 bytes in 1 objects
The 1 largest leaks:
*** WARNING: Cannot convert addresses to symbols in output below.
*** Reason: Cannot run 'pprof' (is PPROF_PATH set correctly?)
*** If you cannot fix this, try running pprof directly.
Leak of 4 bytes in 1 objects allocated from:
@ 40053f 
@ 7f334da06f45 
@ 400469 
If the preceding stack traces are not enough to find the leaks, try running THIS shell command:
pprof ./a.out "/tmp/a.out.8497._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gv
If you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1
If the leak report occurs in a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=false, it might help find leaks more repeatabl
Exiting with error code (instead of crashing) because of whole-program memory leaks

上面的关键的输入信息是:

Leak of 4 bytes in 1 objects allocated from:

@ 40053f   //内存分配的指令地址

@ 7f334da06f45 

@ 400469 

由于工具没有直接输出问题代码行,我们通过反汇编来定位代码行:

objdump -S a.out  //反汇编程序

截取汇编代码如下:

int main()
{
  40052d:55                   push   %rbp
  40052e:48 89 e5             mov    %rsp,%rbp
  400531:48 83 ec 10          sub    $0x10,%rsp
    int *p = (int*)malloc(sizeof(int));
  400535:bf 04 00 00 00       mov    $0x4,%edi
  40053a:e8 f1 fe ff ff       callq  400430 <malloc@plt>
  40053f:48 89 45 f8          mov    %rax,-0x8(%rbp)
    return 0;
  400543:b8 00 00 00 00       mov    $0x0,%eax

我们注意到40053f就是对应代码行int *p = (int*)malloc(sizeof(int));

至此,内存泄漏的元凶被揪出来了,呵呵。

程序运行时,内存溢出了,该如何解决?

内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能提供的最大内存。

   引起内存溢出的原因有很多种,常见的有以下几种:

1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;

2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;

3.代码中存在死循环或循环产生过多重复的对象实体;

4.使用的第三方软件中的BUG;

5.启动参数内存值设定的过小;

内存溢出的解决方案:

      第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)

第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。

第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。

重点排查以下几点:

1.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。

2.检查代码中是否有死循环或递归调用。 

3.检查是否有大循环重复产生新对象实体。 

4.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中   数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。 

5.检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

第四步,使用内存查看工具动态查看内存使用情况

 

 

Linux关于系统调用

Linux内核

内核提供系统调用接口。

内存拷贝

内核态和用户态。

从用户态切换到内核态是怎么实现的?

用的软中断。

关于中断你再说一下?

内存缺页中断。

 

中断切换

大小端

网络编程时用到,不同操作系统

网络字节序是大端对齐

X86平台是小端对齐

一个字节8

敲cd发生什么?回到家目录

C++新特性了解

  • 破坏“不可剥夺”条件:一个进程不能获得所需要的全部资源时便处于等待状态,等待期间他占有的资源将被隐式的释放重新加入到 系统的资源列表中,可以被其他的进程使用,而等待的进程只有重新获得自己原有的资源以及新申请的资源才可以重新启动,执行。
  • 破坏”请求与保持条件“:第一种方法静态分配即每个进程在开始执行时就申请他所需要的全部资源。第二种是动态分配即每个进程在申请所需要的资源时他本身不占用系统资源。
  • 破坏“循环等待”条件:采用资源有序分配其基本思想是将系统中的所有资源顺序编号,将紧缺的,稀少的采用较大的编号,在申请资源时必须按照编号的顺序进行,一个进程只有获得较小编号的进程才能申请较大编号的进程。
  • 安全状态是指:如果系统存在 由所有的安全序列{P1,P2,…Pn},则系统处于安全状态。一个进程序列是安全的,如果对其中每一个进程Pi(i >=1 && i <= n)他以后尚需要的资源不超过系统当前剩余资源量与所有进程Pj(j < i)当前占有资源量之和,系统处于安全状态则不会发生死锁。
  • 不安全状态:如果不存在任何一个安全序列,则系统处于不安全状态。他们之间的对对应关系如下图所示:
  • 程序启动后内存池并没有内存块,到程序真正进行内存申请和释放的时候才接管内存块管理;
  • 该内存池对到来的申请,对申请大小并不做限制,其为每个size值创建链表进行内存管理;
  • 该方案没有提供限定内存池大小的功能
  • 内存池的空间如何获得?是程序启动时分配一大块空间还是程序运行中按需求分配?
  • 内存池对到来的内存申请,有没有大小的限制?如果有,最小可申请的内存块为多大,最大的呢?
  • 如何合理设计内存块结构,方便我们进行内存的申请、追踪和释放呢?
  • 内存池占用越多空间,相对应其他程序能使用的内存就越少,是否要设定内存池空间的上限?设定为多少合适呢?
  • 比malloc/free进行内存申请/释放的方式快
  • 不会产生或很少产生堆碎片
  • 可避免内存泄漏
  • 调用malloc/new,系统需要根据“最先匹配”、“最优匹配”或其他算法在内存空闲块表中查找一块空闲内存,调用free/delete,系统可能需要合并空闲内存块,这些会产生额外开销
  • 频繁使用时会产生大量内存碎片,从而降低程序运行效率
  • 容易造成内存泄漏
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一个高级软件工程师面试被问的问题 的相关文章

随机推荐

  • NR V2X新总结

    https mp weixin qq com s a7t9fwiJOc2LCCQsqeIvUw 图片不显示 xff0c 可以看链接 自从2018年6月RAN 80全会上启动了NR V2X技术研究后 xff0c 3GPP工作组针对NR V2X
  • ADAS是什么?

    先进驾驶辅助系统 xff08 Advanced Driver Assistant System xff09 xff0c 简称ADAS xff0c 是利用安装于车上的各式各样的传感器 xff0c 在第一时间收集车内外的环境数据 xff0c 进
  • 为什么要序列化,如何序列化和反序列化?

    当两个进程在进行远程通信时 xff0c 彼此可以发送各种类型的数据 无论是何种类型的数据 xff0c 都会以二进制序列的形式在网络上传送 发送方需要把这个对象转换为字节序列 xff0c 才能在网络上传送 xff1b 接收方则需要把字节序列再
  • AD/ADAS 自动驾驶领域相关书籍整理和推荐

    本文整理了最近各方面收集的有关ADAS 智能 无人驾驶 xff08 Intelligent Driverless Driving xff09 领域的书籍资料 xff0c 这些书中不乏比较具有前瞻性的五星级书籍 xff0c 也包括技术性相关的
  • 什么是JSON?

    我有点懒 xff0c 大家耐心看图 xff0c 哈哈
  • 关于C-V2X 你需要知道的十件事

    蜂窝车联网 xff0c 通信正持续获得生态系统的支持 xff0c 将成为对汽车安全和未来自动驾驶至关重要的一项技术 在整个汽车和科技行业也都能看到C V2X技术的发展势头 举例来说 xff0c 5G汽车联盟 xff08 5GAA xff09
  • C++中的四种强制转换 dynamic_case,const_cast,static_case,reinterprer_case的不同

    使用标准C 43 43 的类型转换符 xff1a static cast dynamic cast reinterpret cast 和const cast 1 static cast 用法 xff1a static cast lt typ
  • V2X高通的布局

  • 5W2H工作法,使工作更有条理,生活更好梳理

    发明者用五个以W开头的英语单词和两个以H开头的英语单词进行设问 xff0c 发现解决问题的线索 xff0c 寻找发明思路 xff0c 进行设计构思 xff0c 从而搞出新的发明项目 xff0c 这就叫做5W2H法 xff08 1 xff09
  • C 可变参数

    有时 xff0c 您可能会碰到这样的情况 xff0c 您希望函数带有可变数量的参数 xff0c 而不是预定义数量的参数 C 语言为这种情况提供了一个解决方案 xff0c 它允许您定义一个函数 xff0c 能根据具体的需求接受可变数量的参数
  • 给初学者:3个月学会机器学习 ||附完整路径+资源

    感觉本科学的三门数学课 xff0c 不是无用的鸡肋了 xff0c 可是我已经都还给老师了 http www sohu com a 225511837 99905135 https www jianshu com p 27124019c69b
  • 车路协调场景与演进与V2X SDK技术解析

    车路协调场景与演进与V2X SDK技术解析 xff1a 回看链接 https apposcmf8kb5033 h5 xiaoeknow com content page eyJ0eXBlIjoiMiIsInJlc291cmNlX3R5cGU
  • 新的开始之Win7、CentOS 6.4 双系统 硬盘安装

    目的 xff1a 在已经有Win7的操作系统上安装CentOS6 4的32位操作系统 本博客结合了以下的博客 http blog csdn net markho365 article details 8969591 http www cnb
  • 详解protobuf-从原理到使用

    这里写的少 xff0c 后面再补充 https www jianshu com p 419efe983cb2
  • signal(SIGCHLD, SIG_IGN)和signal(SIGPIPE, SIG_IGN);

    这个链接写的比较好 xff1a https yq aliyun com articles 42215 signal SIGCHLD SIG IGN 因为并发服务器常常fork很多子进程 xff0c 子进程终结之后需要服务器进程去wait清理
  • Linux c 网络socket编程

    网络编程 xff0c 一定离不开套接字 xff1b 那什么是套接字呢 xff1f 在Linux下 xff0c 所有的I O操作都是通过读写文件描述符而产生的 xff0c 文件描述符是一个和打开的文件相关联的整数 xff0c 这个文件并不只包
  • Linux c 下socket编程全面

    网络的Socket数据传输是一种特殊的I O xff0c Socket也是一种文件描述符 Socket也具有一个类似于打开文件的函数调用Socket xff0c 该函数返回一个整型的Socket描述符 xff0c 随后的连接建立 数据传输等
  • CANoe与金溢的obu can连接的环境问题 Cifconfig can0 up 失败 设置波特率失败

    今天搭建了CANoe与金溢的obu can连接的环境问题 遇到了一个让人不解的问题 can0起不来 xff0c 于是怀疑波特率不匹配 xff0c 使用调不了 Linux 设置波特率 ifconfig can0 down 关闭CAN0 ip
  • V2X-Locate方案,解决隧道内自动车辆定位问题

    2019年3月连网自驾车辆 Connected and Autonomous Vehicles xff0c CAV 通讯技术厂商CohdaWireless于挪威B rum市新建隧道 长达1 3英里 2 2公里 内采用V2X Locate方案
  • 一个高级软件工程师面试被问的问题

    使用new和malloc如何解决内存碎片问题 xff1f 多进程间通信几种方式 xff0c 你用过几种方式 xff1f 线程间通信 xff0c 用过几种方式 分不同的场景 xff0c 适合用哪种通信方式 内存管理 xff0c 如果让你来实现