1、什么是可重入函数
可重入函数是指能够被多个线程“同时”调用的函数(线程安全),并且能够保证结果的正确性的函数。
在C语言中编写可重入函数时,尽量不使用全局变量和静态变量。如果使用了,则在线程中对这类变量访问时,要注意对其访问的互斥。一般,可以采用以下几种措施来保证函数的可重用:信号量机制、关调度机制、关中断机制。
需要注意的是,不要调用不可重入的函数。一旦调用,则会使得该函数也变得不可重入。一般驱动程序都是不可重入函数,因此在编写驱动程序时一定要注意重入问题。
2、线程相关
线程有时候被称为轻量级进程(LWP),是程序执行流中最小的单位。线程是进程中的实体,是被系统独立调度和分派的基本单位,不拥有自己的资源,只拥有一些必不可小的资源,可与进程中其他线程共享所拥有的资源。
一个线程独立 的部分包括线程ID,当前指针(PC),寄存器集合,堆栈,错误返回码,信号屏蔽码,线程优先级。
引入线程后的进程只作为除CPU外,系统资源的分配单元线程则作为处理器的分配单元。同一进程切换线程不会引起进程切换,但一个进程的切换到另一个进程的线程则会引起进程的切换。
3、临界区与互斥量的区别
1)临界区只能用来同步进程内的线程,而不可用来同步多个进程中的线程:互斥量,事件都可以被跨进程使用来同步数据操作。
2)临界区是非内核对象,只能用在用户态进行锁操作,速度快;互斥体是内核对象,在和心态进行锁操作,速度慢。
3)互斥体在Windows和Linux下都有,临界区只在Windows平台下可用。
4、地址重定位
当装入程序将可执行代码装入内存时,必须通过地址转换将逻辑地址转化为物理地址,这个过程称为地址重定位。
5、分段,分页,段页
基本分段存储管理:段内要求连续,段间不要求连续。段式系统中,段号和段内偏移必须由用户显示提供,在高级设计语言中,这个工作由编译程序完成,整个作业的地址空间是二维的。
基本分页系统中,逻辑地址的页号和页内偏移对用户是透明的。
段页式系统中,逻辑地址分为三个部分:段号,页号,页内偏移量。为实现地址变换,系统为每一个进程建立一张段表,而每个分段有一张页表。
6、如何减少频繁分配内存造成的内存碎片
内存池(Memory Pool)是一种内存分配方式。通常,我们使用new malloc 分配内存,但频繁使用会造成大量的内存碎片,并降低性能。内存池是在真正使用内存之前,先申请一定数量的,大小相等的(一般情况下)的内存留作备用,当新需求到来时,就从内存池中分配一部分内存块,若不够再继续申请新的内存,避免了内存碎片,提高了效率。
7、内存泄漏与缓冲区溢出
内存泄漏是指没有用相应方式释放已经不再使用的内存,导致该快内存无法被再次使用。
缓冲区溢出是指缓冲区数据位超过了缓冲区本身的容量。由于绝大多数程序都会假设数据长度总与分配空间相匹配,这就为缓冲区溢出埋下隐患。例如strcpy,sprintf等。缓冲区溢出是导致黑客型病毒横行的重要原因。
8、什么是线程安全
如果多线程程序运行结果是可预期的,而且与单线程运行结果一样,那么说明“线程安全”。
9、多线程同步和互斥有几种方法实现
临界区(critical section)、事件(event)、互斥量(mutex)、信号量(semaphores)、条件变量。临界区是效率最高的,因为不需要其他开销。
10、死锁的必要条件与处理
互斥条件,请求和保持,不可剥夺,循环等待
死锁处理方案:
1)预防死锁:通过设置某些条件去破坏产生死锁的一个或几个条件来防止死锁。但,由于限制条件往往太严格,导致资源利用率和系统吞吐率低。
2)避免死锁:不事先采取限制措施,而是在资源动态分配过程中,用某种方法防止系统进入不安全状态,从而避免死锁。银行家算法是常见避免死锁算法。
3)检测死锁,设置检测机构及时检测死锁,不事先采取任何措施
4)与检测死锁配套使用,将进程从死锁状态中解脱出来,常用的方法是撤销或挂起一些进程。
11、上下文切换
为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。
从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:
- 保存处理机上下文,包括程序计数器和其他寄存器。
- 更新PCB信息。
- 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。
- 选择另一个进程执行,并更新其PCB。
- 更新内存管理的数据结构。
- 恢复处理机上下文。
12、进程与线程的区别
进程是具有一定独立功能的程序关于某个数据集合的依次运行活动,它是操作系统资源分配的基本单位。线程又称为轻量级线程,是CPU调度和分配的基本单位。
二者区别主要有:
1、关系:一个线程必定属于一个进程,一个进程可以拥有多个线程。
2、资源:进程拥有自己的资源,一个进程的所有线程共享该进程的所有资源,包括打开的文件,创建的socket等。不同进程间相互独立。线程只拥有运行时必不可少的资源。
3、并发:线程间切换开销小,效率高,提高了并发性。
4、健壮:线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程崩溃将影响该进程内其他线程,多进程的程序要比多线程的程序健壮。
13、内核线程和用户线程的区别
根据操作系统内核是否可感知,可把线程分为内核线程和用户线程。
内核线程的建立和销毁都是由操作系统负责的,通过系统调用完成。在操作系统调用时,参考各进程内的线程情况作出调度决定。如果一个进程没有就绪态的线程,那么这个进程也不会调度占用CPU。
与内核线程相对的是用户线程,用户线程指不需要内核支持,而在用户程序中实现的线程,其不依赖于操作系统的核心,用户利用线程库创建,调度,同步,和管理线程。
引入用户线程的优点:
1、可以在不支持线程的操作系统中实现
2、创建、销毁、切换线程不需要经过用户态、内核态的切换,因此代价低,效率高
3、允许每个进程定制自己的调度算法,线程管理比较灵活
4、线程能利用的表空间和堆空间比内核级线程多
用户线程的缺点:
1、同一个进程只能有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起。
2、页面失效也会导致整个进程被挂起。
13、库函数与系统调用的区别
库函数调用是语言或应用程序的一部分,它是高层的,完全运行在用户空间,是为程序员提供实际事务操作的调用接口。
系统函数则是内核提供给应用程序的接口,属于系统的一部分。函数调用是语言或应用程序的一部分,而系统调用是操作系统的一部分。
库函数调用通常比行内展开的代码慢,因为它需要付出函数调用的开销。但,系统调用比库函数调用还慢得多,因为需要进行进行两次上下文环境切换。