原文链接:https://blog.csdn.net/waltonhuang/article/details/52141712#t27
目录
请你说一下进程与线程的概念,以及为什么要有进程线程,其中有什么区别,他们各自又是怎么同步的
请你说一说进程状态转换图,动态就绪,静态就绪,动态阻塞,静态阻塞
请问线程需要保存哪些上下文,SP、PC、EAX这些寄存器是干嘛用的
请你讲述一下互斥锁(mutex)机制,以及互斥锁和读写锁的区别
-4-ELF是什么?其大小与程序中全局变量的是否初始化有什么关系
-10-写一个C程序判断系统是32或64位、大端或小端字节序
-13-linux系统的各类同步机制。什么是死锁?如何避免死锁,定位死锁
请你说一下进程与线程的概念,以及为什么要有进程线程,其中有什么区别,他们各自又是怎么同步的
基本概念:
进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;
线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发;
- 线程是操作系统可识别的最小执行和调度单位。
- 每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。
- 每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源
区别:
- 1.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。
- 2.进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。)
- 3.进程是资源分配的最小单位,线程是CPU调度的最小单位;
- 4.系统开销: 由于在创建或撤消进程时,系统都要为之分配或回收资源,如内存空间、I/o设备等。因此,操作系统所付出的开销将显著地大于在创建或撤消线程时的开销。类似地,在进行进程切换时,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置。而线程切换只须保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。可见,进程切换的开销也远大于线程切换的开销。
- 5.通信:由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现,也变得比较容易。进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。在有的系统中,线程的切换、同步和通信都无须操作系统内核的干预
- 6.进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。
- 7.进程间不会相互影响 ;线程一个线程挂掉将导致整个进程挂掉
- 8.进程适应于多核、多机分布;线程适用于多核
进程间通信的方式:
进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字socket。
管道:
1.1 普通管道PIPE:
//1)它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端 //2)它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间) //3)它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。 // 但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
1.2 命名管道FIFO:
//1)FIFO可以在无关的进程之间交换数据 //2)FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
2. 系统IPC:
2.1 消息队列
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标记。 (消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点)具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息;
/* 特点: 1)消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。 2)消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。 3)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。 */
2.2 信号量semaphore
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
/* 特点: 1)信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。 2)信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。 3)每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。 4)支持信号量组。 */
2.3 信号signal
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
2.4 共享内存(Shared Memory)
它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等
/* 特点: 1)共享内存是最快的一种IPC,因为进程是直接对内存进行存取 2)因为多个进程可以同时操作,所以需要进行同步 3)信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问 */
3.套接字SOCKET:
线程间通信的方式:
- 临界区:通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问;
- 互斥量Synchronized/Lock:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问
- 信号量Semphare:为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目。
- 事件(信号),Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作
请你说一说进程状态转换图,动态就绪,静态就绪,动态阻塞,静态阻塞
请你说一说线程间的同步方式,最好说出具体的系统调用
信号量
信号量是一种特殊的变量,可用于线程同步。它只取自然数值,并且只支持两种操作:
P(SV):如果信号量SV大于0,将它减一;如果SV值为0,则挂起该线程。
V(SV):如果有其他进程因为等待SV而挂起,则唤醒,然后将SV+1;否则直接将SV+1。
/* 其系统调用为: sem_wait(sem_t *sem):以原子操作的方式将信号量减1,如果信号量值为0,则sem_wait将被阻塞,直到这个信号量具有非0值。 sem_post(sem_t *sem):以原子操作将信号量值+1。当信号量大于0时,其他正在调用sem_wait等待信号量的线程将被唤醒。 */
互斥量
互斥量又称互斥锁,主要用于线程互斥,不能保证按序访问,可以和条件锁一起实现同步。当进入临界区 时,需要获得互斥锁并且加锁;当离开临界区时,需要对互斥锁解锁,以唤醒其他等待该互斥锁的线程。其主要的系统调用如下:
/* pthread_mutex_init:初始化互斥锁 pthread_mutex_destroy:销毁互斥锁 pthread_mutex_lock:以原子操作的方式给一个互斥锁加锁,如果目标互斥锁已经被上锁,pthread_mutex_lock调用将阻塞,直到该互斥锁的占有者将其解锁。 pthread_mutex_unlock:以一个原子操作的方式给一个互斥锁解锁。 */
条件变量
条件变量,又称条件锁,用于在线程之间同步共享数据的值。条件变量提供一种线程间通信机制:当某个共享数据达到某个值时,唤醒等待这个共享数据的一个/多个线程。即,当某个共享变量等于某个值时,调用 signal/broadcast。此时操作共享变量时需要加锁。其主要的系统调用如下:
/* pthread_cond_init:初始化条件变量 pthread_cond_destroy:销毁条件变量 pthread_cond_signal:唤醒一个等待目标条件变量的线程。哪个线程被唤醒取决于调度策略和优先级。 pthread_cond_wait:等待目标条件变量。需要一个加锁的互斥锁确保操作的原子性。 该函数中在进入wait状态前首先进行解锁,然后接收到信号后会再加锁,保证该线程对共享资源正确访问。 */
请问线程需要保存哪些上下文,SP、PC、EAX这些寄存器是干嘛用的
线程在切换的过程中需要保存当前线程Id、线程状态、堆栈、寄存器状态等信息。其中寄存器主要包括SP PC EAX等寄存器,其主要功能如下:
/* SP:堆栈指针,指向当前栈的栈顶地址 PC:程序计数器,存储下一条将要执行的指令 EAX:累加寄存器,用于加法乘法的缺省寄存器 */
请你说一说Linux虚拟地址空间
为了防止不同进程同一时刻在物理内存中运行而对物理内存的争夺和践踏,采用了虚拟内存。
- 虚拟内存技术使得不同进程在运行过程中,它所看到的是自己独自占有了当前系统的4G内存。
- 所有进程共享同一物理内存,每个进程只把自己目前需要的虚拟内存空间映射并存储到物理内存上。
- 事实上,在每个进程创建加载时,内核只是为进程“创建”了虚拟内存的布局,具体就是初始化进程控制表中内存相关的链表,实际上并不立即就把虚拟内存对应位置的程序数据和代码(比如.text .data段)拷贝到物理内存中,只是建立好虚拟内存和磁盘文件之间的映射就好(叫做存储器映射),等到运行到对应的程序时,才会通过缺页异常,来拷贝数据。
- 还有进程运行过程中,要动态分配内存,比如malloc时,也只是分配了虚拟内存,即为这块虚拟内存对应的页表项做相应设置,当进程真正访问到此数据时,才引发缺页异常。
- 请求分页系统、请求分段系统和请求段页式系统都是针对虚拟内存的,通过请求实现内存与外存的信息置换。
虚拟内存的好处:
/* 1.扩大地址空间; 2.内存保护:每个进程运行在各自的虚拟内存地址空间,互相不能干扰对方。 虚存还对特定的内存地址提供写保护,可以防止代码或数据被恶意篡改。 3.公平内存分配。采用了虚存之后,每个进程都相当于有同样大小的虚存空间。 4.当进程通信时,可采用虚存共享的方式实现。 5.当不同的进程使用同样的代码时,比如库文件中的代码,物理内存中可以只存储一份这样的代码, 不同的进程只需要把自己的虚拟内存映射过去就可以了,节省内存 6.虚拟内存很适合在多道程序设计系统中使用,许多程序的片段同时保存在内存中。 当一个程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用。 在内存中可以保留多个进程,系统并发度提高 7.在程序需要分配连续的内存空间的时候,只需要在虚拟内存空间分配连续空间, 而不需要实际物理内存的连续空间,可以利用碎片 */
虚拟内存的代价:
/* 1.虚存的管理需要建立很多数据结构,这些数据结构要占用额外的内存 2.虚拟地址到物理地址的转换,增加了指令的执行时间。 3.页面的换入换出需要磁盘I/O,这是很耗时的 4.如果一页中只有一部分数据,会浪费内存。 */
请你说一说操作系统中的缺页中断
- malloc()和mmap()等内存分配函数,在分配时只是建立了进程虚拟地址空间,并没有分配虚拟内存对应的物理内存。当进程访问这些没有建立映射关系的虚拟内存时,处理器自动触发一个缺页异常。
- 缺页中断:在请求分页系统中,可以通过查询页表中的状态位来确定所要访问的页面是否存在于内存中。每当所要访问的页面不在内存,会产生一次缺页中断,此时操作系统会根据页表中的外存地址在外存中找到所缺的一页,将其调入内存。
/* 缺页本身是一种中断,与一般的中断一样,需要经过4个处理步骤: 1、保护CPU现场 2、分析中断原因 3、转入缺页中断处理程序进行处理 4、恢复CPU现场,继续执行 但是缺页中断是由于所要访问的页面不存在于内存时,由硬件所产生的一种特殊的中断,因此,与一般的中断存在区别: 1、在指令执行期间产生和处理缺页中断信号 2、一条指令在执行期间,可能产生多次缺页中断 3、缺页中断返回是,执行产生中断的一条指令,而一般的中断返回是,执行下一条指令。 */
请你说一说OS缺页置换算法
当访问一个内存中不存在的页,并且内存已满,则需要从内存中调出一个页或将数据送至磁盘对换区,替换一个页,这种现象叫做缺页置换。当前操作系统最常采用的缺页置换算法如下:
/* 先进先出(FIFO)算法:置换最先调入内存的页面,即置换在内存中驻留时间最久的页面。 按照进入内存的先后次序排列成队列,从队尾进入,从队首删除。 最近最少使用(LRU)算法: 置换最近一段时间以来最长时间未访问过的页面。 根据程序局部性原理,刚被访问的页面,可能马上又要被访问; 而较长时间内没有被访问的页面,可能最近不会被访问。 当前最常采用的就是LRU算法。 */
请你说一下虚拟内存置换的方式
比较常见的内存替换算法有:FIFO,LRU,LFU,LRU-K,2Q。
/* 1、FIFO(先进先出淘汰算法) 思想:最近刚访问的,将来访问的可能性比较大。 实现:使用一个队列,新加入的页面放入队尾,每次淘汰队首的页面,即最先进入的数据,最先被淘汰。 弊端:无法体现页面冷热信息 */
/* 2、LFU(最不经常访问淘汰算法) 思想:如果数据过去被访问多次,那么将来被访问的频率也更高。 实现:每个数据块一个引用计数,所有数据块按照引用计数排序,具有相同引用计数的数据块则按照时间排序。每次淘汰队尾数据块。 开销:排序开销。 弊端:缓存颠簸。 */
/* 3、LRU(最近最少使用替换算法) 思想:如果数据最近被访问过,那么将来被访问的几率也更高。 实现:使用一个栈,新页面或者命中的页面则将该页面移动到栈底,每次替换栈顶的缓存页面。 优点:LRU算法对热点数据命中率是很高的。 缺陷: 1)缓存颠簸,当缓存(1,2,3)满了,之后数据访问(0,3,2,1,0,3,2,1。。。)。 2)缓存污染,突然大量偶发性的数据访问,会让内存中存放大量冷数据。 */
/* 4、LRU-K(LRU-2、LRU-3) 思想: 最久未使用K次淘汰算法。 LRU-K中的K代表最近使用的次数,因此LRU可以认为是LRU-1。 LRU-K的主要目的是为了解决LRU算法“缓存污染”的问题,其核心思想是将“最近使用过1次”的判断标准扩展为“最近使用过K次”。 相比LRU,LRU-K需要多维护一个队列,用于记录所有缓存数据被访问的历史。 只有当数据的访问次数达到K次的时候,才将数据放入缓存。当需要淘汰数据时,LRU-K会淘汰第K次访问时间距当前时间最大的数据。 实现: 1)数据第一次被访问,加入到访问历史列表; 2)如果数据在访问历史列表里后没有达到K次访问,则按照一定规则(FIFO,LRU)淘汰; 3)当访问历史队列中的数据访问次数达到K次后,将数据索引从历史队列删除,将数据移到缓存队列中,并缓存此数据,缓存队列重新按照时间排序; 4)缓存数据队列中被再次访问后,重新排序; 5)需要淘汰数据时,淘汰缓存队列中排在末尾的数据,即:淘汰“倒数第K次访问离现在最久”的数据。 针对问题: LRU-K的主要目的是为了解决LRU算法“缓存污染”的问题,其核心思想是将“最近使用过1次”的判断标准扩展为“最近使用过K次”。 */
/* 5、2Q 类似LRU-2。使用一个FIFO队列和一个LRU队列。 实现: 1)新访问的数据插入到FIFO队列; 2)如果数据在FIFO队列中一直没有被再次访问,则最终按照FIFO规则淘汰; 3)如果数据在FIFO队列中被再次访问,则将数据移到LRU队列头部; 4)如果数据在LRU队列再次被访问,则将数据移到LRU队列头部; 5)LRU队列淘汰末尾的数据。 针对问题: LRU的缓存污染 弊端: 当FIFO容量为2时,访问负载是:ABCABCABC会退化为FIFO,用不到LRU。 */
请你说一说死锁发生的条件以及如何解决死锁
死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的下相互等待的现象。
死锁发生的四个必要条件如下:
/* 互斥条件: 进程对所分配到的资源不允许其他进程访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源; 请求和保持条件: 进程获得一定的资源后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但该进程不会释放自己已经占有的资源 不可剥夺条件: 进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用后自己释放 环路等待条件: 进程发生死锁后,必然存在一个进程-资源之间的环形链 */
解决死锁的方法即破坏上述四个条件之一,主要方法如下:
/* 资源一次性分配,从而剥夺请求和保持条件 可剥夺资源:即当进程新的资源未得到满足时,释放已占有的资源,从而破坏不可剥夺的条件 资源有序分配法:系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,释放则相反,从而破坏环路等待的条件 */
请你回答一下fork和vfork的区别
- 1. fork( )的子进程拷贝父进程的数据段和代码段;vfork( )的子进程与父进程共享数据段
- 2. fork( )的父子进程的执行次序不确定;vfork( )保证子进程先运行,在调用exec或exit之前与父进程数据是共享的,在它调用exec或exit之后父进程才可能被调度运行。
- 3. vfork( )保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
- 4.当需要改变共享数据段中变量的值,则拷贝父进程。
请你讲述一下互斥锁(mutex)机制,以及互斥锁和读写锁的区别
互斥锁:mutex,用于保证在任何时刻,都只能有一个线程访问该对象。
- 当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒。
读写锁:rwlock,分为读锁和写锁。
- 处于读操作时,可以允许多个线程同时获得读操作。但是同一时刻只能有一个线程可以获得写锁。其它获取写锁失败的线程都会进入睡眠状态,直到写锁释放时被唤醒。
- 注意:写锁会阻塞其它读写锁。当有一个线程获得写锁在写时,读锁也不能被其它线程获取;写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)。适用于读取数据的频率远远大于写数据的频率的场合。
/* 互斥锁和读写锁的区别: 1)读写锁区分读者和写者,而互斥锁不区分 2)互斥锁同一时间只允许一个线程访问该对象,无论读写;读写锁同一时间内只允许一个写者,但是允许多个读者同时读对象。 */
请你来说一下微内核与宏内核
宏内核:除了最基本的进程、线程管理、内存管理外,将文件系统,驱动,网络协议等都集成在内核里面,例如linux内核
- 优点:效率高。
- 缺点:稳定性差,开发过程中的bug经常会导致整个系统挂掉。
微内核:内核中只有最基本的调度、内存管理。驱动、文件系统等都是用户态的守护进程去实现的。
- 优点:稳定,驱动等的错误只会导致相应进程死掉,不会导致整个系统都崩溃
- 缺点:效率低。典型代表QNX,QNX的文件系统是跑在用户态的进程,称为resmgr的东西,是订阅发布机制,文件系统的错误只会导致这个守护进程挂掉。不过数据吞吐量就比较不乐观了。
请问怎么实现线程池
1.设置一个生产者消费者队列,作为临界资源
2.初始化n个线程,并让其运行起来,加锁去队列取任务运行
3.当任务队列为空的时候,所有线程阻塞
4.当生产者队列来了一个任务后,先对队列加锁,把任务挂在到队列上,然后使用条件变量去通知阻塞中的一个线程
请你来介绍一下5种IO模型
1.阻塞IO:
2.非阻塞IO:
3.信号驱动IO:
4.IO复用/多路转接IO:l
5.异步IO:
-1-Cache 和 Buffer的区别
buffer缓冲 cache是缓存。
写缓冲,读缓存。
简单点说,buffer是即将要被写入磁盘的,而cache是被从磁盘中读出来的。
缓冲(buffers)是根据磁盘的读写设计的,把分散的写操作集中进行,减少磁盘碎片和硬盘的反复寻道,从而提高系统性能。
linux有一个守护进程定期清空缓冲内容(即写入磁盘),也可以通过sync命令手动清空缓冲。
缓存(cached)是把读取过的数据保存起来,重新读取时若命中(找到需要的数据)就不要去读硬盘了,若没有命中就读硬盘。
其中的数据会根据读取频率进行组织,把最频繁读取的内容放在最容易找到的位置,把不再读的内容不断往后排,直至从中删除。
-2-虚拟内存文件映射mmap
mmap概念
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。
硬盘上文件的位置与进程逻辑地址空间 中一块大小相同的区域之间的一一对应,
用程序通过read,write,ioctl来访问硬件设备,它们都要经过两次的数据拷贝,(硬盘->内核->用户)
一次是用户空间和内核空间的数据拷贝,另外一次是内核空间和硬件之间的数据拷贝,
目的:
将硬件物理地址映射到用户虚拟地址空间,由2次数据拷贝变成1次数据拷贝!
特点:
实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,
即完成了对文件的操作而不必再调用read,write等系统调用函数。
相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
原理
mmap内存映射的实现过程,总的来说可以分为三个阶段:
- 应用进程启动映射,在进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址作为映射区域;
- 调用系统函数mmap,实现文件物理地址和进程虚拟地址的一一映射;
- 应用进程对映射区域访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝;
共享内存的使用实现原理
两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间
1、共享内存允许两个或更多进程共享一个给定的存储区,因为数据不需要再客户进程和服务进程之间复制。所以这是最快的一种ipc。
2、使用共享内存时需要注意:多个进程对共享内存的同步访问。
3、通常用信号量实现对共享内存的同步访问。
享内存段被映射进进程空间之后,存在于进程空间的什么位置?共享内存段最大限制是多少?
Linux进程地址空间 && 进程内存布局: 存在于 mmap区 (堆栈之间)
查看最大限制
$ sysctl kern.ipc.shmmax
$ cat /proc/sys/kernel/shmmax
在/proc/sys/kernel/目录下,记录着系统V共享内存的一下限制,如一个共享内存区的最大字节数shmmax,系统范围内最大共享内存区标识符数shmmni等,
-3-c++进程内存空间分布
-4-ELF是什么?其大小与程序中全局变量的是否初始化有什么关系
(注意未初始化的数据放在bss段)
nux ELF ELF = Executable and Linkable Format,可执行连接格式,扩展名为elf。
linux下用gcc编译出来的程序是ELF格式的32位可执行程序 利用readelf可以提取出可程序程序的信息
BSS存放的是未初始化的全局变量和静态变量,数据段存放的是初始化后的全局变量和静态变量。
-5-进程间通讯机制
1、无名管道(pipe)
2、有名管道(FIFO)
3、信号(signal)
4、消息队列
5、共享内存
6、信号量
7、套接字
-6-如何定位内存泄露? Valgrind
Valgrind:帮助程序员寻找程序里的bug和改进程序性能的工具集。擅长是发现内存的管理问题;
里边有若干工具,其中最重要的是Memcheck(内存检查)工具,用于检查内存的泄漏;
memcheck的基本功能,能发现如下的问题;
//a)使用未初始化的内存
//b)使用已经释放了的内存
//c)使用超过malloc()分配的内存
//d)对堆栈的非法访问
//e)申请的内存是否有释放*****
//f)malloc/free,new/delete申请和释放内存的匹配
//g)memcpy()内存拷贝函数中源指针和目标指针重叠;
内存泄漏检查示范
格式:
//valgrind --tool=memcheck 一些开关 可执行文件名
//--tool=memcheck :使用valgrind工具集中的memcheck工具
//--leak-check=full : 指的是完全full检查内存泄漏
//--show-reachable=yes :是显示内存泄漏的地点
//--trace-children = yes :是否跟入子进程
//--log-file=log.txt:讲调试信息输出到log.txt,不输出到屏幕
最终用的命令:
valgrind --tool=memcheck --leak-check=full --show-reachable=yes ./nginx
查看内存泄漏的三个地方:
//(1) 9 allocs, 8 frees 差值是1,就没泄漏,超过1就有泄漏
//(2)中间诸如: by 0x401363: CConfig::Load(char const*) (ngx_c_conf.cxx:77)和我们自己的源代码有关,就要注意;
//(3)LEAK SUMMARY:definitely lost: 1,100 bytes in 2 blocks
其他的方法还有:
一、重载new/delete操作符
重载new/delete操作符,用list或者map记录对内存的使用情况。new一次,保存一个节点,delete一次,就删除节点。
最后检测容器里是否还有节点,如果有节点就是有泄漏。也可以记录下哪一行代码分配的内存被泄漏。
类似的方法:在每次调用new时加个打印,每次调用delete时也加个打印。
二、查看进程maps表
在实际调试过程中,怀疑某处发生了内存泄漏,可以查看该进程的maps表,看进程的堆或mmap段的虚拟地址空间是否持续增加。如果是,说明可能发生了内存泄漏。如果mmap段虚拟地址空间持续增加,还可以看到各个段的虚拟地址空间的大小,从而可以确定是申请了多大的内存。
-7-动态链接和静态链接的区别
- 静态链接就是在生成exe的时候已经把代码链接进去程序中,因此程序比较大,但因为不需要动态链接所以效率较高。
- 动态链接是在程序运行的时候需要的时候再引入dll,生成的exe比较小,如果缺失dll,程序就无法运行了。
-8-32位系统一个进程最多多少堆内存
首先,32 位地址最多可以映射 4 GB 的内存。
win
windows原来规定操作系统占2GB,剩下2GB可以申请。
但是微软又允许在boot.init加入/3GB参数,于是可以申请3GB内存
Linux
32位操作系统每个进程4G虚拟内存,由上图可知:0x00000000-0xbfffffff,用户空间3G;0xc0000000-0xffffffff,内核空间1G
-9-系统如何将一个信号通知到进程?
内核给进程发送信号,是在进程所在的进程表项的信号域设置对应的信号的位。
进程检查信号的时机是:进程即将从内核态返回用户态时。如果进程睡眠了,要看睡眠能不能被中断,如果能被中断则唤醒。
进程有一个链表的数据结构,维护一个未决信号的链表。
信号在进程中注册,其实就是把该信号加入到这个未决信号链表当中。
可靠信号不管链表中是否已经有这个信号了,还是会加进去。
不可靠信号,如果链表中已经有这个信号了,就会忽略。
进程处理信号的时机:就是从内核态即将返回用户态度的时候。
执行用户自定义的信号处理函数的方法很巧妙。
把该函数的地址放在用户栈栈顶,进程从内核返回到用户态的时候,先弹出信号处理函数地址,于是就去执行信号处理函数了,然后再弹出,才是返回进入内核时的状态。
被屏蔽的信号,取消屏蔽后还会被检查。
-10-写一个C程序判断系统是32或64位、大端或小端字节序
一、判断系统是32位或64位
#include <stdio.h>
int main()
{
void *ptr = 0;
printf("%d\n", sizeof(ptr));
}
二、判断系统是大端还是小端
大端模式:字数据的高字节存储在低地址中,字数据的低字节存放在高地址中。big endian
小端模式:字数据的高字节存储在高地址中,字数据的低字节存放在低地址中。little endian
linux系统源码是怎么判断大小端的?
static union { char c[4]; unsigned long mylong; } endian_test = {{ 'l', '?', '?', 'b' } };
#define ENDIANNESS ((char)endian_test.mylong)
利用了union的性质和强制类型转换的性质。
这个union里面从低地址到高地址存放了'l', '?', '?', 'b'
。
- 如果是小端little endian,读取endian_test.mylong的顺序就是
'b', '?', '?', 'l'
(高字节->低字节)。强制类型转换成一个字节的char,指向了最低的字节,也就是l,以l表示little endian。- 如果是大端big endian,读取endian_test.mylong的顺序就是
'l', '?', '?', 'b'
(高字节->低字节)。强制类型转换成一个字节的char,指向了最低的地址,也就是b,以b表示big endian。
-11-信号:列出常见的信号,信号怎么处理?
SIGHUP 终端挂起。用户推出shell的时候,由该进程启动的所有进程都会收到这个信号,默认动作为终止进程。
SIGINT 键盘中断。 CTRL+C。用户终端向正在运行的程序发出这个信号,默认动作为终止进程。
SIGQUIT CTRL+D CTRL+\ 退出。 终止进程。
SIGFPE 计算错误时终止进程并产生core文件。
SIGKILL 强制退出。不做清理和暂存。
SIGALRM 定时器超时。
SIGTERM 与SIGKILL的不同是,可以被阻塞。程序在终止前可以保存文件和清理临时文件。
信号处理的相关动作
//(1)执行系统默认动作 ,绝大多数信号的默认动作是杀死你这个进程;
//(2)忽略此信号(但是不包括SIGKILL和SIGSTOP)
//kill -9 进程id,是一定能够把这个进程杀掉的;
//(3)捕捉该信号:我写个处理函数,信号来的时候,我就用处理函数来处理;(但是不包括SIGKILL和SIGSTOP)
-12-i++是否原子操作?并解释为什么?
原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断。
不是原子操作
1.i++分为三个阶段:
- 内存到寄存器
- 寄存器自增
- 写回内存
这三个阶段中间都可以被中断分离开.
2.++i首先要看编译器是怎么编译的
某些编译器比如VC在非优化版本中会编译为以下汇编代码:
_asm { moveax, dword ptr[i] inc eax mov dwordptr[i], eax }
这种情况下,必定不是原子操作,不加锁互斥是不行的。
假设加了优化参数,那么是否一定会编译为“inc dword ptr[i]”呢?答案是否定的,这要看编译器心情,如果++i的结果还要被使用的话,那么一定不会被编译为“inc dword ptr[i]”的形式。
那么假设如果编译成了“inc dword ptr[i]”,这是原子操作,是否就不需要加锁了呢?如果在单核机器上,不加锁不会有问题,但到了多核机器上,这个不加锁同样会带来严重后果,两个CPU可以同时执行inc指令,但是两个执行以后,却可能出现只自加了一次。
真正可以确保不“额外”加锁的汇编指令是“lock inc dword ptr[i]”,lock前缀可以暂时锁住总线,这时候其他CPU是无法访问相应数据的。但是目前没有任何一个编译器会将++int编译为这种形式。
-13-linux系统的各类同步机制。什么是死锁?如何避免死锁,定位死锁
-14-列举说明linux系统的各类异步机制
在IO方面,有异步IO。
在进程间通信方面,有信号。
-15-exit() _exit()的区别?
exit() 要调用
终止处理程序
和清除IO缓存
后再退出_exit()是“立即”终止程序。
-16-如何实现守护进程?
自己实现守护进程。要做以下步骤
1. fork()出子进程,父进程退出
2. 子进程创建新的session,调用setsid()
3. 子进程调用chdir,变换目录到/
4. 子进程设置文件创建掩膜
5. 子进程关闭不需要的文件描述符
-17-linux的内存管理机制是什么?
-18-linux的任务调度机制是什么?
首先,任务调度可以经过两个过程:选择算法(使用goodness函数计算权值,选取优先级高的)和上下文切换。
另外,任务调度可以分为主动调度(因为要等待输入而挂起等)和被动调度(被强占)
-19-标准库函数和系统调用的区别?
很多库函数是对系统调用的简单封装。
系统调用是跟操作系统相关的,可移植性不好。
标准库函数与操作系统无关,可移植性好。系统调用是内核空间和内核时间。
标准库函数是用户空间和用户时间。如果用户直接使用系统调用,需要在用户空间和内核空间之间切换,开销比较大。比如说读写文件,如果直接用系统调用,会因为反复的切换而开销大。使用库函数,可以利用缓冲区,等往缓冲区写完了,再系统调用一次性把数据写入到硬件媒介。
-20-mv/cp指令的底层实现的区别?
1、功能上的区别
mv:用户可以使用该命令为文件或目录重命名或将文件由一个目录移入另一个目录中。
cp: 该命令的功能是将给出的文件或目录拷贝到另一文件或目录中。
2、从inode角度来区分
mv:会将存储于indoe索引节点上的文件元信息也移动到新文件中。
cp: 只会复制文件数据,不会复制inode索引节点上的文件元信息。