一、kmalloc
1 | |
- size:指定要分配的块的大小,单位是字节
- flags:指定分配内存时的控制方式;
该函数用于在内核空间中分配内存使用,它的返回速度快(除非被阻塞),并且对其分配的内存不进行任何初始化(清零)操作,分配的内存区域仍然保留有他原有的内容。
kmalloc申请得到的是物理内存,位于物理内存映射区,而且在物理地址上是连续的;但是kmalloc返回的内存地址却是虚拟地址(线性地址),返回的这个虚拟地址(线性地址)与真实的物理地址之间仅仅相差一个固定的偏移值;因此,kmalloc申请得到的物理内存块的首地址与其返回的虚拟地址之间存在着比较简单的转换关系;通过内核提供的函数virt_to_phys()可以实现该虚拟地址到真实的内核物理地址之间的转换。
kmalloc()函数用于小块内存的申请,最小可以申请的内存是32字节或64字节,最大可以申请的内存是128KB-16,其中,被减掉的16个字节用于存储页描述符结构;这些都依赖于体系架构所使用的页面大小;kmalloc申请的内存在物理地址上是连续的,这对于要进行DMA传输的设备来说,是非常重要的;
kmalloc()的内存分配是基于slab机制实现的,slab机制是为分配小内存而提供的一种高效的机制;但是slab机制也不是独立的,它本身也是在页分配器的基础上来划分更细粒度的内存供调用者使用;也就是说,系统先使用页分配器分配以页为最小单位的连续物理地址,然后,kmalloc()再在这个基础上根据调用者的需要进行切分的;另外,slab机制分配的内存在物理地址和虚拟地址(线性地址/逻辑地址)上都是连续的。
对于kmalloc()申请的内存,需要使用kfree函数来释放;
二、kzalloc
函数原型
1 | |
1)kzalloc()函数与 kmalloc()非常相似,参数及返回值一样
2)kzalloc()基于kmalloc实际上只是额外附加了 __GFP_ZERO 标志。所以它除了申请内核内存外,还会对申请到的内存内容清零
释放函数仍然为kfree
三、vmalloc
1 | |
vmalloc()函数也是用于申请内存的,但是它申请的内存是位于vmalloc_start到vmalloc_end之间的虚拟内存;它申请的内存在虚拟地址(线性地址/逻辑地址)上是连续的,但是并不要求在物理地址上连续,并且返回的地址与物理地址之间没有简单的转换关系。
vmalloc()函数适用于大块内存的申请环境中;但是它申请的内存不能直接用于DMA传输;因为DMA传输需要使用物理地址连续的内存块。
对于vmalloc()申请的内存,需要使用vfree()函数来释放。
备注:vmalloc是基于slab机制实现的。
GFP标志位
kmalloc、kzalloc、kcalloc、vmalloc、get_free_pages函数在调用时都有一个gfp_t类型的控制标记flags;这个标记用于控制申请内存时的内存分配控制方式;
1 | |
GFP的标记有两种:带双下划线前缀的和不带双下划线前缀的
不带双下划线前缀的GFP标志:
- GFP_ATOMIC:用于在中断上下文和进程上下文之外的其它代码中分配内存;从不睡眠;
- GFP_KERNEL:内核正常分配内存;可能睡眠;
- GFP_USER:用于为用户空间页分配内存;可能睡眠;
- GFP_HIGHUSER:如同GFP_USER,但它是从高端内存中申请;
- GFP_NOIO和GFP_NOFS:功能如同GFP_KERNEL,但是它俩增加限制到内核能做的来满足请求;GFP_NOFS分配不允许进行任何文件系统调用,而GFP_NOIO分配根本不允许进行任何IO初始化;它俩主要用于文件系统和虚拟内存代码,那里允许一个分配睡眠,但是递归的文件系统调用会是个坏主意;
带有双下划线前缀的GFP标志:
__GFP_DMA:这个标志要求分配的内存在能够进行DMA的内存区;平台依赖的;__GFP_HIGHMEM:这个标志指示分配的内存可以位于高端内存区;平台依赖的;__GFP_COLD:正常地,内存分配器尽力返回”缓冲热”的页—可能在处理器缓冲中找到的页;相反,这个标志请求一个”冷”页—在一段时间内没被使用的页;它对分配页做DMA读是很有用的,此时在处理器缓冲中出现是没用的;__GFP_NOWARN:这个标志用于分配内存时阻止内核发出警告,当一个分配请求无法满足时;__GFP_HIGH:这个标志标识了一个高优先级请求,它被允许来消耗甚至被内核保留给紧急状况的最后的内存页;__GFP_REPEAT:分配器的动作;当分配器有困难满足一个分配请求时,通过重复尝试的方式来”尽力尝试”,但是分配操作仍然有可能失败;__GFP_NOFAIL:分配器的动作;当分配器有困难满足一个分配请求时,这个标志告诉分配器不要失败,尽最大努力来满足分配请求;__GFP_NORETRY:分配器的动作;当分配器有困难满足一个分配请求时,这个标志告诉分配器立即放弃,不再做任何尝试;
通常,一个或多个带双下划线前缀的标记相或,即可得到对应的不带双下划线前缀的标记;
最常用的标记就是GFP_KERNEL,它的意思就是当前的这个分配代表运行在内核空间的进程而进行的;换句话说,这意味着调用函数是代表一个进程在执行一个系统调用;使用GFP_KERNEL标记,就意味着kmalloc能够使当前进程在少内存的情况下通过睡眠来等待一个内存页;因此,一个使用GFP_KERNEL的函数必须是可重入的,且不能在原子上下文中运行;当前进程睡眠,内核采取正确的动作来定位一些空闲的内存页,或者通过刷新缓存到磁盘或者交换出去一个用户进程的内存页;
如果一个内存分配动作发生在中断处理或内核定时器的上下文中时,当前进程就不能被设置为睡眠,也就不能再使用GFP_KERNEL标志了,此时应该使用GFP_ATOMIC标志来代替;正常地,内核试图保持一些空闲页以便来满足原子的分配;当使用GFP_ATOMIC标志时,kmalloc标志能够使用甚至最后一个空闲页;如果这最后一个空闲页不存在,那分配就会失败;
五、copy_to_user
用户一般访问内核,需要从用户态变为内核态,然后再访问内核,这么做的目的是防止用户随意篡改内核。在编写某个外设的驱动时,我们需要实现内核中的 read 和 write 函数,此时站在内核的角度,无法直接读取用户缓冲区 或者 无法直接向用户缓冲区写入。
因此,就需要用到 copy_to_user 和 copy_from_user 函数。
copy_to_user():完成内核空间到用户空间的复制
copy_from_user():完成用户空间到内核空间的复制
1 | |
第一个参数 to:目标用户空间的地址
第二个参数 from: 源内核空间地址。保存了要拷贝到用户空间的内容的地址
第三个参数 n:要拷贝的字节数
返回值:成功返回 0
六、copy_form_user
1 | |
第一个参数 to:目标内核空间的地址
第二个参数 from: 源用户空间地址。保存了用户要发送的数据,或者要拷贝到内核空间的内容的地址
第三个参数 n:要拷贝的字节数
返回值:成功返回 0