2015年某光笔试

2015年某光笔试

今天参加笔试某光,突然发现有些知识点掌握的不够牢固,有些是含含糊糊,没有那么清晰,于是今天先记录一下,这个月补充一下。
想想笔试题目也是人家辛辛苦苦想出来的,不能这么容易透露。

1. 取偏移地址

x86机器,取结构体中某一数据成员的偏移地址,这在内核里经常遇到,也曾经写过总结。

#include<stdio.h>
typedef struct ext{
    int a;
    unsigned short b;
    char d;
    int c;
} ext_t;
int main(){
    printf("%d\n",(&((ext_t *)0)->c));
    return 0;
}

x86,32位机器,int类型4B对齐,printf打印出变量c对于结构体ext_t的偏移地址,输出结果是8。

2. malloc和kmalloc的比较

2.1 malloc和kmalloc的比较

内存泄露问题,原题目不很记得,对比malloc和kmalloc分配内存空间不释放后的结果,题目考察点应该是,malloc分配的是用户的内存空间,kmalloc分配的是内核的内存空间。

摘自:《linux内核设计与实现》第12.5章节

void *malloc(unsigned int size);
void *kmalloc(size_t size, gfp_t flags);
void *vmalloc(unsigned long size);
  1. vmalloc、kmalloc是用来分配内核空间内存的,malloc是用来分配用户空间的内存的;
  2. kmalloc分配的页在物理地址上是连续的(虚拟地址自然也是连续的), vmalloc只确保页在虚拟地址空间内是连续的,它通过分配非连续的物理内存块,再“修正”页表,把内存映射到逻辑地址空间是连续的区域内,malloc 返回的页在进程的虚拟地址空间内是连续的,但是并不保证在物理地址上是连续的;
    注意:调用malloc函数后OS并不会马上就分配实际的内存空间,只会返回一个虚拟地址,待用户要使用内存时,OS发出一个缺页中断,内存管理模块才会分配真正的物理内存。
  3. 内核中vmalloc,kmalloc分配都是以字节为单位。

2.2 slab缓存分配

slab机制,忘了哪家公司面试问到过。
了解slab机制前,请先看2.3 伙伴算法。

Linux针对大内存的物理地址分配,采用伙伴算法,如果是针对小于一个page的内存,频繁的分配和释放,有更加适宜的解决方案,如slab和kmem_cache。

2.2.2.1 slab分配的思想

伙伴算法按页分配内存,但页对内核来说太大了,为此,内核必须引入新的管理机制,这会给内核带来更大的开销,为了最小化这个额外的负担对系统性能的影响,该管理层的实现应该尽可能的紧凑,以便不要对处理器的高速缓存TLB带来特别的影响。同时,内核还必须保证内存利用的速度和效率。

Linux 所使用的 slab 分配器的基础是 Jeff Bonwick 为 SunOS 操作系统首次引入的一种算法。Jeff 的分配器是围绕对象缓存进行的。在内核中,会为有限的对象集(例如文件描述符和其他常见结构)分配大量内存。
Jeff 发现对内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间。因此他的结论是不应该将内存释放回一个全局的内存池,而是将内存保持为针对特定目的而初始化的状态。
例如,如果内存被分配给了一个互斥锁,那么只需在为互斥锁首次分配内存时执行一次互斥锁初始化函数(mutex_init)即可。后续的内存分配不需要执行这个初始化函数,因为从上次释放和调用析构之后,它已经处于所需的状态中了。

Linux slab 分配器使用了这种思想和其他一些思想来构建一个在空间和时间上都具有高效性的内存分配器。

2.2.2.2 slab分配器的设计

前言:
这部分还是《linux内核设计与实现》写的好。

分配和释放数据结构是所有内核中最普遍的操作之一。为了便于数据的频繁分配和回收,编程人员常常会用到空闲链表。空闲链表中包含可供使用的,已经分配好的数据结构块。当代码需要一个新的数据结构实例时,就可以从空闲链表中抓取一个,而不需要再去执行一些分配内存的代码,这样不仅高效而且使用简单。以后,当不需要这个数据结构的实例时,就把它放回空闲链表,而不是释放它。从这个意义上说,空闲链表相当于对象高速缓存——快速存储频繁使用的对象类型。

slab分配器扮演了通用数据结构缓存层的角色。一句话点出了slab分配器的作用:数据结构、缓存。

slab层把不同的对象划分为高速缓存组中,其中每个高速缓存组都存放不同类型的对象。每种对象类型对应一个高速缓存。例如,一个高速缓存用于存放进程描述符(task_struct结构),而另一个高速缓存存放索引节点对象(struct inode)。

然后,这些高速缓存又被划分为slab。slab由一个或多个物理上连续的页组成。每一个slab都包含一些对象成员,这里的对象是指被缓存的数据结构。每个缓存都处于三种状态之一:满、部分、空。

作为一个例子,让我们考察一下inode结构,该结构是磁盘索引节点在内存中的体现。这些数据结构会频繁地创建和释放。因此,struct inode就由inode_cachep高速缓存进行分配。这种高速缓存由一个或多个slab组成——由多个slab组成的可能性较大。每个slab包含尽可能多的struct inode对象。当内核请求分配一个新的inode结构时,内核就从部分满的slab或空slab中返回一个指向已分配但未使用的结构的指针,当内核使用完inode对象后,slab分配器将该对象标记为空闲。下图显示了高速缓存、slab以及对象之间的关系。
这里写图片描述

参考:
[1]《linux内核设计与实现》12.6 slab层
[2] https://www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/
[3] http://guojing.me/linux-kernel-architecture/posts/slab/

2.3 伙伴算法

记忆中应该是看过伙伴算法的,现在想想都忘了,过去的又怎么能过去。
伙伴算法是一个经典的内存分配算法,linux内核采用伙伴算法来管理物理内存。我这里主要介绍算法思想,实现请看参考[1]。

2.3.1 为什么会有伙伴算法?

伙伴算法是用来解决什么问题的,解决外部碎片的问题。

在实际应用中,经常需要分配一组连续的页框,而频繁地申请和释放不同大小的连续页框,必然导致在已分配页框的内存块中分散了许多小块的空闲页框。这样,即使这些页框是空闲的,其他需要分配连续页框的应用也很难得到满足。为了避免出现这种情况,Linux内核中引入了伙伴系统算法(buddy system)。

2.3.2 伙伴算法过程

把所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块。最大可以申请1024个连续页框,对应4MB大小的连续内存。每个页框块的第一个页框的物理地址是该块大小的整数倍。假设要申请一个256个页框的块,先从256个页框的链表中查找空闲块,如果没有,就去512个页框的链表中找,找到了则将页框块分为2个256个页框的块,一个分配给应用,另外一个移到256个页框的链表中。如果512个页框的链表中仍没有空闲块,继续向1024个页框的链表查找,如果仍然没有,则返回错误。
页框块在释放时,会主动将两个连续的页框块合并为一个较大的页框块。

2.3.3 伙伴算法优缺点

优点:
 较好的解决外部碎片问题

 当需要分配若干个内存页面时,用于DMA的内存页面必须连续,伙伴算法很好的满足了这个要求

 只要请求的块不超过512个页面(2K),内核就尽量分配连续的页面。

 针对大内存分配设计。

 缺点:

  1. 合并的要求太过严格,只能是满足伙伴关系的块才能合并,比如第1块和第2块就不能合并。

  2. 碎片问题:一个连续的内存中仅仅一个页面被占用,导致整块内存区都不具备合并的条件

  3. 浪费问题:伙伴算法只能分配2的幂次方内存区,当需要8K(2页)时,好说,当需要9K时,那就需要分配16K(4页)的内存空间,但是实际只用到9K空间,多余的7K空间就被浪费掉。

  4. 算法的效率问题: 伙伴算法涉及了比较多的计算还有链表和位图的操作,开销还是比较大的,如果每次2^n大小的伙伴块就会合并到2^(n+1)的链表队列中,那么2^n大小链表中的块就会因为合并操作而减少,但系统随后立即有可能又有对该大小块的需求,为此必须再从2^(n+1)大小的链表中拆分,这样的合并又立即拆分的过程是无效率的。

参考:
[1] 伙伴算法的极简实现:http://coolshell.cn/articles/10427.html
[2] http://blog.csdn.net/hs794502825/article/details/7887915
[3] http://www.cnblogs.com/cherishui/p/4246133.html
slab机制,快速申请和释放内存?
mmap机制,介绍?
buddy算法?
http://dongxicheng.org/os/linux-memory-management-basic/

3. C中继承、属性、多态的实现

C中继承的实现:
struct base {
xxx;
};
struct derived {
struct base dad;
yyy;
};
注意:派生类结构体中一定要把父类类型的成员放在第一个;
C中属性就是结构体变量;
C中多态的实现通过函数指针,核心思想是回调函数。

#include <stdio.h>
typedef struct nice {
    void (*print)();
} nc;
void printa() {
    printf("a\n");
}
void printb() {
    printf("b\n");
}
int main()
{
    nc test;
    test.print = printa;
    test.print();
    test.print = printb;
    test.print();
    return 0;
}

4. 双向链表的基本操作

插入、删除、合并两个链表的操作

5. 生产者消费者问题

这个问题,某T某M都考过,总之是出现频率很高的题目。
题目大概就这样了,很正常的题目

猜你喜欢

转载自blog.csdn.net/zxc995293774/article/details/49284513
今日推荐