生命密码数字组合意义:[转载]Linux设备驱动程序学习(8)-分配内存 - Linux设备驱动程序 - Tekkaman Ninja

来源:百度文库 编辑:中财网 时间:2024/05/03 12:40:43

Tekkaman Ninja

Linux我的梦想,我的未来!专注linux内核和驱动!

本博客的原创文章的内容会不定期更新或修正错误!

转载文章都会注明出处,若有侵权,请即时同我联系,我一定马上删除!!

原创文章版权所有!如需转载,请注明出处: tekkman.cublog.cn ,谢谢合作!!!!!




Linux设备驱动程序学习(8)-分配内存

kmalloc 函数内幕

kmalloc 是一个功能强大且高速(除非被阻塞)的工具,所分配到的内存在物理内存中连续且保持原有的数据(不清零)。原型:

#include
void *kmalloc(size_t size, int flags);

size 参数

内核管理系统的物理内存,物理内存只能按页面进行分配。kmalloc 和典型的用户空间 malloc 在实际上有很大的差别,内核使用特殊的基于页的分配技术,以最佳的方式利用系统 RAM。Linux 处理内存分配的方法:创建一系列内存对象集合,每个集合内的内存块大小是固定。处理分配请求时,就直接在包含有足够大内存块的集合中传递一个整块给请求者。

必须注意的是:内核只能分配一些预定义的、固定大小的字节数组。kmalloc 能够处理的最小内存块是 32 或 64 字节(体系结构依赖),而内存块大小的上限随着体系和内核配置而变化。考虑到移植性,不应分配大于 128 KB的内存。若需多于几个 KB的内存块,最好使用其他方法。

flags 参数

内存分配最终总是调用 __get_free_pages 来进行实际的分配,这就是 GFP_ 前缀的由来。

所有标志都定义在 ,有符号代表常常使用的标志组合。

主要的标志常被称为分配优先级,包括:

最常用的标志,意思是这个分配代表运行在内核空间的进程进行。内核正常分配内存。当空闲内存较少时,可能进入休眠来等待一个页面。当前进程休眠时,内核会采取适当的动作来获取空闲页。所以使用 GFP_KERNEL 来分配内存的函数必须是可重入,且不能在原子上下文中运行。

内核通常会为原子性的分配预留一些空闲页。当当前进程不能被置为睡眠时,应使用 GFP_ATOMIC,这样kmalloc 甚至能够使用最后一个空闲页。如果连这最后一个空闲页也不存在,则分配返回失败。常用来从中断处理和进程上下文之外的其他代码中分配内存,从不睡眠。

用来为用户空间分配内存页,可能睡眠。

类似 GFP_USER,如果有高端内存,就从高端内存分配。

功能类似 GFP_KERNEL,但是为内核分配内存的工作增加了限制。具有GFP_NOFS 的分配不允许执行任何文件系统调用,而 GFP_NOIO 禁止任何 I/O 初始化。它们主要用在文件系统和虚拟内存代码。那里允许分配休眠,但不应发生递归的文件系统调。

Linux 内核把内存分为 3 个区段: 可用于DMA的内存(位于一个特别的地址范围的内存, 外设可以在这里进行 DMA 存取)、常规内存和高端内存(为了访问(相对)大量的内存而存在的一种机制)。目的是使每中计算机平台都必须知道如何将自己特定的内存范围归类到这三个区段中,而不是所有RAM都一样。

当要分配一个满足kmalloc要求的新页时, 内核会建立一个内存区段的列表以供搜索。若指定了 __GFP_DMA, 只有可用于DMA的内存区段被搜索;若没有指定特别的标志, 常规和 可用于DMA的内存区段都被搜索; 若 设置了 __GFP_HIGHMEM,所有的 3 个区段都被搜索(注意:kmalloc 不能分配高端内存)。

内存区段背后的机制在 mm/page_alloc.c 中实现, 且区段的初始化时平台相关的, 通常在 arch 目录树的 mm/init.c中。

有的标志用双下划线做前缀,他们可与上面标志“或”起来使用,以控制分配方式:

要求分配可用于DMA的内存。

分配的内存可以位于高端内存.

通常,分配器试图返回“缓存热(cache warm)”页面(可在处理器缓存中找到的页面)。 而这个标志请求一个尚未使用的“冷”页面。对于用作 DMA 读取的页面分配,可使用此标志。因为此时页面在处理器缓存中没多大帮助。

当一个分配无法满足,阻止内核发出警告(使用 printk )。

高优先级请求,允许为紧急状况消耗被内核保留的最后一些内存页。

告诉分配器当满足一个分配有困难时,如何动作。__GFP_REPEAT 表示努力再尝试一次,仍然可能失败; __GFP_NOFAIL 告诉分配器尽最大努力来满足要求,始终不返回失败,不推荐使用; __GFP_NORETRY 告知分配器如果无法满足请求,立即返回。

内核为驱动程序常常需要反复分配许多相同大小内存块的情况,增加了一些特殊的内存池,称为后备高速缓存(lookaside cache)。 设备驱动程序通常不会涉及后备高速缓存,但是也有例外:在 Linux 2.6 中 USB 和 SCSI 驱动。Linux 内核的高速缓存管理器有时称为“slab 分配器”,相关函数和类型在 中声明。slab 分配器实现的高速缓存具有 kmem_cache_t 类型。实现过程如下:

Linux 内核的高速缓存管理器有时称为“slab 分配器”,相关函数和类型在 中声明。slab 分配器实现的高速缓存具有 kmem_cache_t 类型。实现过程如下:

(1)创建一个新的后备高速缓存

kmem_cache_t *kmem_cache_create(const char *name,size_t size,size_t offset,
                 unsigned long flags,
       void (*constructor)(void *, kmem_cache_t *,unsigned long flags),

      void (*destructor)(void *, kmem_cache_t *, unsigned long flags));
/*创建一个可以容纳任意数目内存区域的、大小都相同的高速缓存对象*/

参数 一个指向 name 的指针,name和这个后备高速缓存相关联,功能是管理信息以便追踪问题;通常设置为被缓存的结构类型的名字,不能包含空格。

参数flags:控制分配方式的位掩码:

SLAB_HWCACHE_ALIGN  所有数据对象跟高速缓存行对齐,平台依赖,可能浪费内存。

其他标志详见 mm/slab.c。但是,通常这些标志在只在开发系统中通过内核配置选项被全局性地设置。

SLAB_HWCACHE_ALIGN  所有数据对象跟高速缓存行对齐,平台依赖,可能浪费内存。

其他标志详见 mm/slab.c。但是,通常这些标志在只在开发系统中通过内核配置选项被全局性地设置。

参数constructor 和 destructor 是可选函数(不能只有destructor,而没有constructor ),用来初始化新分配的对象和在内存被作为整体释放给系统之前“清理”对象。

constructor 函数在分配一组对象的内存时被调用,由于内存可能持有几个对象,所以可能被多次调用。同理,destructor不是立刻在一个对象被释放后调用,而可能在以后某个未知的时间中调用。 根据它们是否被传递 SLAB_CTOR_ATOMIC 标志( CTOR 是 constructor 的缩写),控制是否允许休眠。由于当被调用者是constructor函数时,slab 分配器会传递 SLAB_CTOR_CONSTRUCTOR 标志。为了方便,它们可通过检测这个标志以使用同一函数。

(2)通过调用 kmem_cache_alloc 从已创建的后备高速缓存中分配对象:

void *kmem_cache_alloc(kmem_cache_t *cache, int flags);
/*cache 参数是刚创建缓存,flags 是和kmalloc 的相同*/

(3)使用 kmem_cache_free释放一个对象:

void kmem_cache_free(kmem_cache_t *cache, const void *obj);

(4)当驱动用完这个后备高速缓存(通常在当模块被卸载时),释放缓存:

int kmem_cache_destroy(kmem_cache_t *cache);
/*只在从这个缓存中分配的所有的对象都已返时才成功。因此,应检查 kmem_cache_destroy 的返回值:失败指示模块存在内存泄漏*/

使用后备高速缓存的一个好处是内核会统计后备高速缓存的使用,统计情况可从 /proc/slabinfo 获得。

内存池

为了确保在内存分配不允许失败情况下成功分配内存,内核提供了称为内存池( "mempool" )的抽象,它其实是某种后备高速缓存。它为了紧急情况下的使用,尽力一直保持空闲内存。所以使用时必须注意: mempool 会分配一些内存块,使其空闲而不真正使用,所以容易消耗大量内存 。而且不要使用 mempool 处理可能失败的分配。应避免在驱动代码中使用 mempool。

内存池的类型为 mempool_t ,在 ,使用方法如下:

(1)创建 mempool :

mempool_t *mempool_create(int min_nr,

                           mempool_alloc_t *alloc_fn,
                           mempool_free_t *free_fn,
                           void *pool_data);
/*min_nr 参数是内存池应当一直保留的最小数量的分配对象*/

/*实际的分配和释放对象由 alloc_fn 和 free_fn 处理,原型:*/
typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data);
typedef void (mempool_free_t)(void *element, void *pool_data);
/*给 mempool_create 最后的参数 *pool_data 被传递给 alloc_fn 和 free_fn */

你可编写特殊用途的函数来处理 mempool 的内存分配,但通常只需使用 slab 分配器为你处理这个任务:mempool_alloc_slab 和 mempool_free_slab的原型和上述内存池分配原型匹配,并使用 kmem_cache_alloc 和 kmem_cache_free 处理内存的分配和释放。

典型的设置内存池的代码如下:

cache = kmem_cache_create(. . .);
pool = mempool_create(MY_POOL_MINIMUM,mempool_alloc_slab, mempool_free_slab, cache);

(2)创建内存池后,分配和释放对象:

void *mempool_alloc(mempool_t *pool, int gfp_mask);
void mempool_free(void *element, mempool_t *pool);

 在创建mempool时,分配函数将被调用多次来创建预先分配的对象。因此,对 mempool_alloc 的调用是试图用分配函数请求额外的对象,如果失败,则返回预先分配的对象(如果存在)。用 mempool_free 释放对象时,若预分配的对象数目小于最小量,就将它保留在池中,否则将它返回给系统。

可用一下函数重定义mempool预分配对象的数量:

int mempool_resize(mempool_t *pool, int new_min_nr, int gfp_mask);
/*若成功,内存池至少有 new_min_nr 个对象*/

(3)若不再需要内存池,则返回给系统:

void mempool_destroy(mempool_t *pool);
/*在销毁 mempool 之前,必须返回所有分配的对象,否则会产生 oops*/

get_free_page 和相关函数如果一个模块需要分配大块的内存,最好使用面向页的分配技术。

__get_free_page(unsigned int flags);
/*返回一个指向新页的指针, 未清零该页*/

get_zeroed_page(unsigned int flags);
/*类似于__get_free_page,但用零填充该页*/

__get_free_pages(unsigned int flags, unsigned int order);
/*分配是若干(物理连续的)页面并返回指向该内存区域的第一个字节的指针,该内存区域未清零*/

/*参数flags 与 kmalloc 的用法相同;
参数order 是请求或释放的页数以 2 为底的对数。若其值过大(没有这么大的连续区可用), 则分配失败*/

get_order 函数可以用来从一个整数参数 size(必须是 2 的幂) 中提取 order,函数也很简单:

/* Pure 2^n version of get_order */
static __inline__ __attribute_const__ int get_order(unsigned long size)
{
int order;

    size = (size - 1) >> (PAGE_SHIFT - 1);
    order = -1;
do {
        size >>= 1;
        order++;
} while (size);
return order;
}

使用方法举例在 中有。

通过/proc/buddyinfo 可以知道系统中每个内存区段上的每个 order 下可获得的数据块数目。

当程序不需要页面时,它可用下列函数之一来释放它们。

void free_page(unsigned long addr);
void free_pages(unsigned long addr, unsigned long order);

它们的关系是:

#define __get_free_page(gfp_mask) \
        __get_free_pages((gfp_mask),0)

若试图释放和你分配的数目不等的页面,会破坏内存映射关系,系统会出错。

注意:只要符合和 kmalloc 的相同规则, __get_free_pages 和其他的函数可以在任何时候调用。这些函数可能失败(特别当使用 GFP_ATOMIC 时),因此调用这些函数的程序必须提供分配失败的处理。

从用户的角度,可感觉到的区别主要是速度提高和更好的内存利用率(因为没有内部的内存碎片)。但主要优势实际不是速度, 而是更有效的内存利用。 __get_free_page 函数的最大优势是获得的页完全属于调用者, 且理论上可以适当的设置页表将起合并成一个线性的区域。

alloc_pages 接口

struct page 是一个描述一个内存页的内部内核结构,定义在

Linux 页分配器的核心是称为 alloc_pages_node 的函数:

struct page *alloc_pages_node(int nid, unsigned int flags,
unsigned int order);

/*以下是这个函数的 2 个变体(是简单的宏):*/
struct page *alloc_pages(unsigned int flags, unsigned int order);
struct page *alloc_page(unsigned int flags);

/*他们的关系是:*/
#define alloc_pages(gfp_mask, order) \
        alloc_pages_node(numa_node_id(), gfp_mask, order)
#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)

参数nid 是要分配内存的 NUMA 节点 ID,
参数flags 是 GFP_ 分配标志,
参数order 是分配内存的大小.
返回值是一个指向第一个(可能返回多个页)page结构的指针, 失败时返回NULL。

alloc_pages 通过在当前 NUMA 节点分配内存( 它使用 numa_node_id 的返回值作为 nid 参数调用 alloc_pages_node)简化了alloc_pages_node调用。alloc_pages 省略了 order 参数而只分配单个页面。

释放分配的页:

void __free_page(struct page *page);
void __free_pages(struct page *page, unsigned int order);
void free_hot_page(struct page *page);
void free_cold_page(struct page *page);
/*若知道某个页中的内容是否驻留在处理器高速缓存中,可以使用 free_hot_page (对于驻留在缓存中的页) 或 free_cold_page(对于没有驻留在缓存中的页) 通知内核,帮助分配器优化内存使用*/

vmalloc 是一个基本的 Linux 内存分配机制,它在虚拟内存空间分配一块连续的内存区,尽管这些页在物理内存中不连续 (使用一个单独的 alloc_page 调用来获得每个页),但内核认为它们地址是连续的。 应当注意的是:vmalloc 在大部分情况下不推荐使用。因为在某些体系上留给 vmalloc 的地址空间相对小,且效率不高。函数原型如下:

#include
void *vmalloc(unsigned long size);
void vfree(void * addr);
void *ioremap(unsigned long offset, unsigned long size);
void iounmap(void * addr);


kmalloc 和 _get_free_pages 返回的内存地址也是虚拟地址,其实际值仍需 MMU 处理才能转为物理地址。vmalloc和它们在使用硬件上没有不同,不同是在内核如何执行分配任务上:kmalloc 和 __get_free_pages 使用的(虚拟)地址范围和物理内存是一对一映射的, 可能会偏移一个常量 PAGE_OFFSET 值,无需修改页表。

而vmalloc 和 ioremap 使用的地址范围完全是虚拟的,且每次分配都要通过适当地设置页表来建立(虚拟)内存区域。 vmalloc 可获得的地址在从 VMALLOC_START 到 VAMLLOC_END 的范围中,定义在 中。vmalloc 分配的地址只在处理器的 MMU 之上才有意义。当驱动需要真正的物理地址时,就不能使用 vmalloc。 调用 vmalloc 的正确场合是分配一个大的、只存在于软件中的、用于缓存的内存区域时。注意:vamlloc 比 __get_free_pages 要更多开销,因为它必须即获取内存又建立页表。因此, 调用 vmalloc 来分配仅仅一页是不值得的。vmalloc 的一个小的缺点在于它无法在原子上下文中使用。因为它内部使用 kmalloc(GFP_KERNEL) 来获取页表的存储空间,因此可能休眠。


ioremap 也要建立新页表,但它实际上不分配任何内存,其返回值是一个特殊的虚拟地址可用来访问特定的物理地址区域。
为了保持可移植性,不应当像访问内存指针一样直接访问由 ioremap 返回的地址,而应当始终使用 readb 和 其他 I/O 函数。

ioremap 和 vmalloc 是面向页的(它们会修改页表),重定位的或分配的空间都会被上调到最近的页边界。ioremap 通过将重映射的地址下调到页边界,并返回第一个重映射页内的偏移量来模拟一个非对齐的映射。

per-CPU 的变量

per-CPU 变量是一个有趣的 2.6 内核特性,定义在 中。当创建一个per-CPU变量,系统中每个处理器都会获得该变量的副本。其优点是对per-CPU变量的访问(几乎)不需要加锁,因为每个处理器都使用自己的副本。per-CPU 变量也可存在于它们各自的处理器缓存中,这就在频繁更新时带来了更好性能。

在编译时间创建一个per-CPU变量使用如下宏定义:

DEFINE_PER_CPU(type, name);
/*若变量( name)是一个数组,则必须包含类型的维数信息,例如一个有 3 个整数的per-CPU 数组创建如下: */
DEFINE_PER_CPU(int[3], my_percpu_array);

虽然操作per-CPU变量几乎不必使用锁定机制。 但是必须记住 2.6 内核是可抢占的,所以在修改一个per-CPU变量的临界区中可能被抢占。并且还要避免进程在对一个per-CPU变量访问时被移动到另一个处理器上运行。所以必须显式使用 get_cpu_var 宏来访问当前处理器的变量副本, 并在结束后调用 put_cpu_var。 对 get_cpu_var 的调用返回一个当前处理器变量版本的 lvalue ,并且禁止抢占。又因为返回的是lvalue,所以可被直接赋值或操作。例如:

get_cpu_var(sockets_in_use)++;
put_cpu_var(sockets_in_use);


当要访问另一个处理器的变量副本时, 使用: 当代码涉及到多处理器的per-CPU变量,就必须实现一个加锁机制来保证访问安全。动态分配per-CPU变量方法如下:

void *alloc_percpu(type);
void *__alloc_percpu(size_t size, size_t align);/*需要一个特定对齐的情况下调用*/
void free_percpu(void *per_cpu_var); /* 将per-CPU 变量返回给系统*/

/*访问动态分配的per-CPU变量通过 per_cpu_ptr 来完成,这个宏返回一个指向给定 cpu_id 版本的per_cpu_var变量的指针。若操作当前处理器版本的per-CPU变量,必须保证不能被切换出那个处理器:*/
per_cpu_ptr(void *per_cpu_var, int cpu_id);

/*通常使用 get_cpu 来阻止在使用per-CPU变量时被抢占,典型代码如下:*/

int cpu;
cpu = get_cpu()
ptr = per_cpu_ptr(per_cpu_var, cpu);
/* work with ptr */
put_cpu();

/*当使用编译时的per-CPU 变量, get_cpu_var 和 put_cpu_var 宏将处理这些细节。动态per-CPU变量需要更明确的保护*/


per-CPU变量可以导出给模块, 但必须使用一个特殊的宏版本:

EXPORT_PER_CPU_SYMBOL(per_cpu_var);
EXPORT_PER_CPU_SYMBOL_GPL(per_cpu_var);


/*要在模块中访问这样一个变量,声明如下:*/
DECLARE_PER_CPU(type, name);

注意:在某些体系架构上,per-CPU变量的使用是受地址空间有限的。若在代码中创建per-CPU变量, 应当尽量保持变量较小.在引导时获得专用缓冲区若真的需要大块连续的内存作缓冲区,最好的方法是在引导时来请求内存来分配。在引导时分配是获得大量连续内存页(避开 __get_free_pages 对缓冲大小和固定颗粒双重限制)的唯一方法。一个模块无法在引导时分配内存,只有直接连接到内核的驱动才可以。 而且这对普通用户不是一个灵活的选择,因为这个机制只对连接到内核映象中的代码才可用。要安装或替换使用这种分配方法的设备驱动,只能通过重新编译内核并且重启计算机。
当内核被引导, 它可以访问系统种所有可用物理内存,接着通过调用子系统的初始化函数, 允许初始化代码通过减少留给常规系统操作使用的 RAM 数量来分配私有内存缓冲给自己。在引导时获得专用缓冲区要通过调用下面函数进行:

#include
/*分配不在页面边界上对齐的内存区*/
void *alloc_bootmem(unsigned long size);
void *alloc_bootmem_low(unsigned long size); /*分配非高端内存。希望分配到用于DMA操作的内存可能需要,因为高端内存不总是支持DMA*/

/*分配整个页*/
void *alloc_bootmem_pages(unsigned long size);
void *alloc_bootmem_low_pages(unsigned long size);/*分配非高端内存*/

/*很少在启动时释放分配的内存,但肯定不能在之后取回它。注意:以这个方式释放的部分页不返回给系统*/
void free_bootmem(unsigned long addr, unsigned long size);


更多细节请看内核源码中 Documentation/kbuild 下的文件。
ARM9开发板实验这几个实验我都没有用LDD3原配的代码,而是用以前的ioctl_and_lseek的代码改的。(1)scullc 模块试验(2)scullp 模块试验(3)scullv 模块试验 实验现象:

[Tekkaman2440@SBC2440V4]#insmod /lib/modules/scullc.ko
[Tekkaman2440@SBC2440V4]#cat /proc/devices
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  7 vcs
 10 misc
 13 input
 14 sound
 81 video4linux
 89 i2c
 90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
204 s3c2410_serial
252 scullc
253 usb_endpoint
254 rtc

Block devices:
  1 ramdisk
256 rfd
  7 loop
 31 mtdblock
 93 nftl
 96 inftl
179 mmc
[Tekkaman2440@SBC2440V4]#mknod -m 666 scullc c 252 0
[Tekkaman2440@SBC2440V4]#ls -l /tmp/test -rw-r--r-- 1 root root 2070 Nov 24 2007 /

[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullc
[Tekkaman2440@SBC2440V4]#cat /proc/slabinfo
slabinfo - version: 2.1 (statistics)
# name : tunables : slabdata : globalstat : cpustat
scullc 1 1 4020 1 1 : tunables 24 12 0 : slabdata 1 1 0 : globalstat 1 1 1 0 0 0 0 0 0 : cpustat 0 1 0 0
......
[Tekkaman2440@SBC2440V4]#cat /proc/scullcseq

Device 0: qset 1000, q 4000, sz 2070
  item at c3edca00, qset at c3efe000
       0: c3f56028
[Tekkaman2440@SBC2440V4]#/tmp/scullc_test
open scull !
scull_quantum=10 scull_qset=4
close scull !

[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullc
[Tekkaman2440@SBC2440V4]#cat /proc/slabinfo
slabinfo - version: 2.1 (statistics)
# name : tunables : slabdata : globalstat : cpustat
scullc 207 207 4020 1 1 : tunables 24 12 0 : slabdata 207 207 0 : globalstat 208 207 208 1 0 0 0 0 0 : cpustat 0 208 1 0
......
[Tekkaman2440@SBC2440V4]#cat /proc/scullcmem

Device 0: qset 4, q 10, sz 2070
  item at c3edc930, qset at c3edc8c8
  item at c3edc894, qset at c3edc860
  item at c3edc82c, qset at c3edc7f8
  item at c3edc7c4, qset at c3edc790
  item at c3edc75c, qset at c3edc178
  item at c3edc624, qset at c3edc4b8
  item at c3edc9cc, qset at c3edc998
  item at c3edc4ec, qset at c3edc964
  item at c3edc484, qset at c3edccd8
  item at c3edcca4, qset at c3edcc70
  item at c3edcc3c, qset at c3edcc08
  item at c3edcbd4, qset at c3edcba0
  item at c3edcb6c, qset at c3edcb38
  item at c3edcb04, qset at c3edcad0
  item at c3edca9c, qset at c3edca68
  item at c3edca34, qset at c3edca00
  item at c3edc8fc, qset at c3edcfb0
  item at c3edcf7c, qset at c3edcf48
  item at c3edcf14, qset at c3edcee0
  item at c3edceac, qset at c3edce78
  item at c3edce44, qset at c3edce10
  item at c3edcddc, qset at c3edcda8
  item at c3edcd74, qset at c3edcd40
  item at c3edcd0c, qset at c388a450
  item at c388a41c, qset at c388a3e8
  item at c388a3b4, qset at c388a380
  item at c388a34c, qset at c388a318
  item at c388a2e4, qset at c388a2b0
  item at c388a27c, qset at c388a248
  item at c388a214, qset at c388a1e0
  item at c388a1ac, qset at c388a178
  item at c388a144, qset at c388a790
  item at c388a75c, qset at c388a728
  item at c388a6f4, qset at c388a6c0
  item at c388a68c, qset at c388a658
  item at c388a624, qset at c388a5f0
  item at c388a5bc, qset at c388a588
  item at c388a554, qset at c388a520
  item at c388a4ec, qset at c388a4b8
  item at c388a484, qset at c388aad0
  item at c388aa9c, qset at c388aa68
  item at c388aa34, qset at c388aa00
  item at c388a9cc, qset at c388a998
  item at c388a964, qset at c388a930
  item at c388a8fc, qset at c388a8c8
  item at c388a894, qset at c388a860
  item at c388a82c, qset at c388a7f8
  item at c388a7c4, qset at c388ae10
  item at c388addc, qset at c388ada8
  item at c388ad74, qset at c388ad40
  item at c388ad0c, qset at c388acd8
  item at c388aca4, qset at c388ac70
       0: c38fb028
       1: c38fc028
       2: c38fd028
[Tekkaman2440@SBC2440V4]#insmod /lib/modules/scullp.ko
[Tekkaman2440@SBC2440V4]#cat /proc/devices
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  7 vcs
 10 misc
 13 input
 14 sound
 81 video4linux
 89 i2c
 90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
204 s3c2410_serial
251 scullp
252 scullc
253 usb_endpoint
254 rtc

Block devices:
  1 ramdisk
256 rfd
  7 loop
 31 mtdblock
 93 nftl
 96 inftl
179 mmc
[Tekkaman2440@SBC2440V4]#mknod -m 666 /dev/scullp c 251 0
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullp
[Tekkaman2440@SBC2440V4]#cat /proc/scullpseq

Device 0: qset 1000, q 4000, sz 2070
  item at c3edc964, qset at c3eff000
       0: c3f21000
[Tekkaman2440@SBC2440V4]#/tmp/scullp_test
open scull !
scull_quantum=1000 scull_qset=2
close scull !

[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullp
[Tekkaman2440@SBC2440V4]#cat /proc/scullpseq

Device 0: qset 2, q 1000, sz 2070
  item at c3edcc70, qset at c3edcca4
  item at c3edc484, qset at c3edcc3c
       0: c383f000
[Tekkaman2440@SBC2440V4]#insmod /lib/modules/scullv.ko
[Tekkaman2440@SBC2440V4]#cat /proc/devices
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  7 vcs
 10 misc
 13 input
 14 sound
 81 video4linux
 89 i2c
 90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
204 s3c2410_serial
250 scullv
251 scullp
252 scullc
253 usb_endpoint
254 rtc

Block devices:
  1 ramdisk
256 rfd
  7 loop
 31 mtdblock
 93 nftl
 96 inftl
179 mmc
[Tekkaman2440@SBC2440V4]#mknod -m 666 /dev/scullv c 250 0
[Tekkaman2440@SBC2440V4]#cat /tmp/test > /dev/scullv
[Tekkaman2440@SBC2440V4]#cat /proc/scullvseq

Device 0: qset 1000, q 4000, sz 2070
  item at c3edc7c4, qset at c389d000
       0: c485e000
[Tekkaman2440@SBC2440V4]#cat /proc/scullpseq

Device 0: qset 2, q 1000, sz 2070
  item at c3edcc70, qset at c3edcca4
  item at c3edc484, qset at c3edcc3c
       0: c383f000

从上面的数据可以看出: 在s3c2440 (ARM9)体系上, vmalloc返回的虚拟地址就在用于映射物理内存的地址上。

发表于: 2007-11-22,修改于: 2007-11-27 00:08,已浏览3146次,有评论0条 推荐 投诉

网友评论
好文章,我是从你的第一章开始看的,今天9号看到你又有新内容,学不少东西,请坚持下去,加油啊
很不错啊,我几乎每天都来学习你的经验,感觉收益非浅,加油啊!
我觉得最好的方式就是有人交流^_^我很想与你交流,我的QQ32313055
我学arm一年多了,可是水平比阁下差远了。看完文章,收获良多,非常感谢!
向Tekkaman  Ninja致敬,你的学习态度和学习方式都值得我们借鉴!

圣诞快乐!
让我学到了很多东西,
很好的文章啊,我正在学习驱动开发,刚刚入门,谢谢啊


write error! code=-1 
write ok! code=21 
read ok! code=20 
[0]=0 [1]=0 [2]=1 [3]=2 [4]=3
[5]=4 [6]=5 [7]=6 [8]=7 [9]=8
[10]=9 [11]=10 [12]=11 [13]=12 [14]=13
[15]=14 [16]=15 [17]=16 [18]=17 [19]=18

我改了一下makefile在pc上运行是这个结果


所有的源码都在友善之臂SBC2440V4上反复测试过(内核为2.6.22.2)。所以基本上只要改Makefile就好(除了硬件相关的部分)。如果有问题可以将详细的情况发邮件给我,有空我会回复的。



写的真的很好
我们老师就让写一个字符设备驱动程序,我都是通过在你的网页上学会的所有的东西。
赞一个,总结的很好

曾经研究过ldd3,没有认真写下读书笔记,以后还得好好再看下了
请问怎么使用drivers/spi/atmel_spi.c提供的驱动呢?在/dev下没有相应的设备节点,怎么才能在用户空间访问设备?如果自己写一个驱动注册一个spi驱动可以使用其中哪些函数接口?诚请帮忙
今天看了你的文章很感慨,希望能共同学习。有个问题想问,
请问当insmod scull.ko scull_quantum=6 scull_qset=2
数据已经溢出了,为什么20个数据还能写进去。谢谢了


这个和scull的数据结构有关,你认真看看《Linux设备驱动程序学习(1)-字符设备驱动程序》,中的图,你可能对scull的数据结构还没有理解透,scull_quantum代表了一个quantum的字节数,scull_qset代表一个qset包含几个quantum,但是还有qset没有限制,也就是说我写20个字节进去,一个scull中有2个qset。


总结的太好了。
有一个问题想请教一下。看模块中定义了一个char *whom = "world"
这是一个字符串常量,如果按照标准C的话,指针指向的是一个常量字符串才对,其中的内容不能修改。比如whom[2]='a';这样会有段错误,为什么在内核中这样操作是合法的呢? 


我觉得你这样是定义一个字符串变量,所以可以修改。我学硬件的,对c研究还不深,不知见解是否正确?


 lz的经验和态度值得我们学习,向你致敬!
很不错,尤其是有源码和测试程序。
你好!刚接触linux驱动,看到你这里的设备模型分析,很不错
帮了我的大忙!
我有一个问题,对于nand flash,作为一个块设备,应用程序如何去访问它,对它进行擦除、读写等操作?
因为它不象字符设备那样有可供调用的系统调用


块设备我暂时还没看,见谅


我也想知道块设备的一些知识,老是来你这儿看,希望能得到一些帮助,呵呵


块设备我还没看啊,苦于没时间和没板做实验


问一个问题:
你上文提到的测试程序是不是相当于linux系统的应用程序,实现对驱动的系统调用?
如果是这样,那么测试程序应该放在什么路径下,用什么编译器进行编译,因为我看到你有写测试程序的makefile,是不是也要用交叉编译器对它进行编译阿,可是这样的话,生成的目标代码是arm格式的,不能在pc上直接执行。
麻烦你帮我解答一下这个疑问。
刚刚接触linux,问得问题比较愚蠢,见笑了



测试程序是应用程序,在板上运行
要交叉编译


总结得很好   就像上完课了要复习  
很好的学习总结,受益匪浅!
[root@(none) scullc]# insmod scullc.ko
Using scullc.ko
nfs: server 222.31.45.9 not responding, still trying
nfs: server 222.31.45.9 not responding, still trying
nfs: server 222.31.45.9 not responding, still trying
我按照傅大哥写的模块,一装载的时候就出错了........
里面的网络没问题,就是装载了模块后出问题的........
傅大哥知道怎么回事吗???????

我是刚接触linux的。今天看到你的总接非常高兴,做的太详细了。。。
我想问一下,是否可以讲解一下container_of是怎么用的。。
谢谢!
关于楼主描述的:
最关键的“syslog(8,NULL,level)”语句我不理解,没有找到相关资料。但是通过在ARM9板上的实验表明:程序是ok的!我用Hello world模块做了实验,现象和书上的一致。
我查阅了一下,应该这么理解:
原型是:int syslog(int type, char *bufp, int len);
根据type来采取对内核ring buffer的操作,并设置consele_loglevel的值为len。
type参数有以下几种值:
       /*
        * Commands to sys_syslog:
        *
        *      0 -- Close the log.  Currently a NOP.
        *      1 -- Open the log. Currently a NOP.
        *      2 -- Read from the log.
        *      3 -- Read up to the last 4k of messages in the ring buffer.
        *      4 -- Read and clear last 4k of messages in the ring buffer
        *      5 -- Clear ring buffer.
        *      6 -- Disable printk’s to console
        *      7 -- Enable printk’s to console
        *      8 -- Set level of messages printed to console
        *      9 -- Return number of unread characters in the log buffer
        */
返回值的描述:
       In case of error, -1 is returned, and errno is set. Otherwise,
       for  type  equal  to 2, 3 or 4, syslog() returns the number of
       bytes read, and otherwise 0.
不妥之处请指教,谢谢。
文章写的很好,刚开始学驱动,有很多地方都不是很懂,很想跟你学习啊,想请教一下学习linux设备驱动的方法。
问一个很弱的问题,楼主的源码文件是如何存储在cublog里面的,敬请告知,不胜感激

write code=6
write code=6
write code=6
write code=2
read code=6
read code=6
read code=6
read code=2
[0]=0 [1]=1 [2]=2 [3]=3 [4]=4
[5]=5 [6]=6 [7]=7 [8]=8 [9]=9
[10]=10 [11]=11 [12]=12 [13]=13 [14]=14
[15]=15 [16]=16 [17]=17 [18]=18 [19]=19

 看你的测试程序中 输出语句 要么是 work error! code=..   或者 work ok! code=..    上面的代码输出 work  code=...   既没有error! 也没有ok! 什么原因啊?
在字符驱动那张图 scull_device所指的链表中 Scull_qset 这种结构的个数如何确定呢。

insmod scull_debug.ko scull_nr_devs=1 scull_quantum=6 scull_qset=2后
我测试的结果是:
write error! code=6
write error! code=6
write error! code=6
write ok! code=2
read error! code=6
read error! code=6
read error! code=6
read ok! code=2
[0]=0 [1]=1 [2]=2 [3]=3 [4]=4
[5]=5 [6]=6 [7]=7 [8]=8 [9]=9
[10]=10 [11]=11 [12]=12 [13]=13 [14]=14
[15]=15 [16]=16 [17]=17 [18]=18 [19]=19
请问如何解释啊。

从楼主这里学到不少东西
void kobject_del(struct kobject *kobj); /*是 kobject_del 和 kobject_put 的结合*/写错了
编译的时候出现
[root@localhost scull]# make modules
make -C /home/alu/mywork/systems/linux-2.6.22.6  M=/home/soso/ldd3/scull   modules
make[1]: Entering directory `/home/alu/mywork/systems/linux-2.6.22.6'
  CC [M]  /home/soso/ldd3/scull/scull_t1.o
cc1: invalid option `little-endian'
cc1: invalid option `arm'
cc1: invalid option `apcs'
cc1: invalid option `no-sched-prolog'
cc1: invalid option `apcs-32'
cc1: invalid option `no-thumb-interwork'
cc1: invalid option `tune=arm9tdmi'
cc1: invalid option `alignment-traps'
cc1: unrecognized option `-Wdeclaration-after-statement'
make[2]: *** [/home/soso/ldd3/scull/scull_t1.o] 错误 1
make[1]: *** [_module_/home/soso/ldd3/scull] 错误 2
make[1]: Leaving directory `/home/alu/mywork/systems/linux-2.6.22.6'
make: *** [modules] 错误 2


请教大哥  这个大概可能是什么问题啊  那么多参数无效  

内核版本2.6.22.6 
arm-linux-gcc -v
gcc version 3.3.6
"include/linux/moduleparam.h文件中有关于module_param_array宏的说明,这里第三个参数的作用是:
*nump is set to number they actually specified

所以这里的TNparam_nr并不是限制输入数组参数的数目,而是实际数据的大小。
可以在上面的示例代码中加一句:
printk(KERN_ALERT "TNparam_nr=%d\n",TNparam_nr);
得到的结果会是你实际输入的数组元素个数。

BTW:笔记写得这么详细,我都懒得写了,直接收藏你的笔记了:-)


感谢兄台指点!!!!!!!


新学驱动开发   跟着ldd3看不懂  来支持下你   
    /* Then follow the list */
    while (n--) {
        if (!qs->next) {
            qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
            if (qs->next == NULL)
                return NULL;  /* Never mind */
            memset(qs->next, 0, sizeof(struct scull_qset));
        }
        qs = qs->next;
        continue;
    }
    return qs;
}
continue;有什么作用


正如注释所说的: 是不断的跟随双向链表到底!


 网友: 本站网友  时间:2009-04-12 09:43:54 IP地址:159.226.139.★ 
 
 
     /* Then follow the list */
    while (n--) {
        if (!qs->next) {
            qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
            if (qs->next == NULL)
                return NULL;  /* Never mind */
            memset(qs->next, 0, sizeof(struct scull_qset));
        }
        qs = qs->next;
        continue;
    }
    return qs;
}
continue;有什么作用

Blog作者的回复:
正如注释所说的: 是不断的跟随双向链表到底!
 
去掉continue;依然可以继续循环,continue是多余的??? 
 

 Tekkaman:
            你好,首先向你的工作致敬,我很希望和你一起探讨内核题,
对于 文中提到“我这个实验除了对参数的改变进行实验外,我的一个重要的目的是测试“ module_param_array(TNparam , int , &TNparam_nr , S_IRUGO);”中&TNparam_nr对输入参数数目的限制作用。经过我的实验,表明&TNparam_nr并没有对输入参数的数目起到限制作用。真正起到限制作用的是“static int TNparam[] = {1,2,3,4};”本身定义的大小,我将程序进行修改:”
  我作了测试,当insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1,5,时,系统报错
“insmod:error inserting 'paramadd.ko":-1 Invalid parameters
说明&TNparam_nr有对输入参数的数目起到限制作用,
我用的是Fedora8
博主你是我见过的最好的博主了!永远支持你!
man syslog  如下:

NAME
       syslog,  klogctl  -  read  and/or clear kernel message ring buffer; set
       console_loglevel

SYNOPSIS
       int syslog(int type, char *bufp, int len);
                       /* No wrapper provided in glibc */

       /* The glibc interface */
       #include 

       int klogctl(int type, char *bufp, int len);

DESCRIPTION
       If you need the libc function syslog()  (which  talks  to  syslogd(8)),
       then look at syslog(3).  The system call of this name is about control‐
       ling the kernel printk()  buffer,  and  the  glibc  version  is  called
       klogctl().

       The type argument determines the action taken by this function.

       Quoting from kernel/printk.c:
       /*
        * Commands to sys_syslog:
        *
        *      0 -- Close the log.  Currently a NOP.
        *      1 -- Open the log. Currently a NOP.
        *      2 -- Read from the log.
        *      3 -- Read all messages remaining in the ring buffer.
        *      4 -- Read and clear all messages remaining in the ring buffer
        *      5 -- Clear ring buffer.
        *      6 -- Disable printk to console
        *      7 -- Enable printk to console
        *      8 -- Set level of messages printed to console
        *      9 -- Return number of unread characters in the log buffer
        *     10 -- Return size of the log buffer
        */

       Only  command types 3 and 10 are allowed to non-root processes.  Type 9
       was added in 2.4.10; type 10 in 2.6.6.

   The kernel log buffer
       The kernel has a cyclic buffer of length LOG_BUF_LEN in which  messages
       given  as arguments to the kernel function printk() are stored (regard‐
       less of their loglevel).  In early kernels, LOG_BUF_LEN had  the  value
       4096;  from  kernel  1.3.54,  it  was  8192; from kernel 2.1.113 it was
       16384; since 2.4.23/2.6 the value is a kernel configuration option.  In
       recent kernels the size can be queried with command type 10.

       The  call  syslog(2,buf,len)  waits  until  this  kernel  log buffer is
       nonempty, and then reads at most len bytes into  the  buffer  buf.   It
       returns  the  number  of bytes read.  Bytes read from the log disappear
       from the log buffer: the information can only be read  once.   This  is
       the  function  executed  by  the  kernel  when  a  user  program  reads
       /proc/kmsg.

       The call syslog(3,buf,len) will read the last len bytes  from  the  log
       buffer  (non-destructively),  but  will  not read more than was written
       into the buffer since the last "clear ring buffer" command (which  does
       not clear the buffer at all).  It returns the number of bytes read.

       The  call  syslog(4,buf,len) does precisely the same, but also executes
       the "clear ring buffer" command.

       The call syslog(5,dummy,dummy) executes just the  "clear  ring  buffer"
       command.  (In each call where buf or len is shown as "dummy", the value
       of the argument is ignored by the call.)

       The call syslog(6,dummy,dummy) sets the console log level  to  minimum,
       so that no messages are printed to the console.

       The  call  syslog(7,dummy,dummy) sets the console log level to default,
       so that messages are printed to the console.

       The call syslog(8,dummy,level) sets the console  log  level  to  level,
       which must be an integer between 1 and 8 (inclusive).  See the loglevel
       section for details.

       The call syslog(9,dummy,dummy) returns the number  of  bytes  currently
       available to be read on the kernel log buffer.

       The  call  syslog(10,dummy,dummy)  returns the total size of the kernel
       log buffer.

   The loglevel
       The kernel routine printk() will only print a message on  the  console,
       if  it  has  a  loglevel  less  than  the  value  of  the variable con‐
       sole_loglevel.  This variable  initially  has  the  value  DEFAULT_CON‐
       SOLE_LOGLEVEL (7), but is set to 10 if the kernel command line contains
       the word "debug", and to 15 in case of a kernel fault (the  10  and  15
       are just silly, and equivalent to 8).  This variable is set (to a value
       in the range 1-8) by the call syslog(8,dummy,value).   The  calls  sys‐
       log(type,dummy,dummy)  with  type  equal to 6 or 7, set it to 1 (kernel
       panics only) or 7 (all except debugging messages), respectively.

       Every text line in a message has  its  own  loglevel.   This  level  is
       DEFAULT_MESSAGE_LOGLEVEL  - 1 (6) unless the line starts with  where
       d is a digit in the range 1-7, in which case the level is d.  The  con‐
       ventional  meaning  of  the  loglevel is defined in  as
       follows:

       #define KERN_EMERG    "<0>"  /* system is unusable               */
       #define KERN_ALERT    "<1>"  /* action must be taken immediately */
       #define KERN_CRIT     "<2>"  /* critical conditions              */
       #define KERN_ERR      "<3>"  /* error conditions                 */
       #define KERN_WARNING  "<4>"  /* warning conditions               */
       #define KERN_NOTICE   "<5>"  /* normal but significant condition */
       #define KERN_INFO     "<6>"  /* informational                    */
       #define KERN_DEBUG    "<7>"  /* debug-level messages             */

RETURN VALUE
       For type equal to 2, 3, or 4, a successful call to syslog() returns the
       number of bytes read.  For type 9, syslog() returns the number of bytes
       currently available to be read on the kernel log buffer.  For type  10,
       syslog() returns the total size  of  the  kernel log buffer.  For other
       values of type, 0 is returned on success.

       In case of error, -1 is returned, and errno  is  set  to  indicate  the
       error.

ERRORS
       EINVAL Bad  parameters  (e.g., bad type; or for type 2, 3, or 4, buf is
              NULL, or len is less than zero; or for type 8, the level is out‐
              side the range 1 to 8).

       EPERM  An attempt was made to change console_loglevel or clear the ker‐
              nel message ring buffer by a process  without  root  permissions
              (more precisely: without the CAP_SYS_ADMIN capability).

       ERESTARTSYS
              System  call  was  interrupted  by  a  signal; nothing was read.
              (This can be seen only during a trace.)

CONFORMING TO
       This system call is Linux-specific and should not be used  in  programs
       intended to be portable.

NOTES
       From  the  very start people noted that it is unfortunate that a system
       call and a library routine of the same name are entirely different ani‐
       mals.   In  libc4  and  libc5  the  number  of this call was defined by
       SYS_klog.  In glibc 2.0 the syscall is baptized klogctl().

SEE ALSO
       syslog(3)

COLOPHON
       This page is part of release 2.79 of the Linux  man-pages  project.   A
       description  of  the project, and information about reporting bugs, can
       be found at http://www.kernel.org/doc/man-pages/.


您好,我是为向您求教才注册的。我现在想以动态分配设备号的方式加载驱动,并建立设备节点。由于是新手,所以好多地方不是很明白。总之,我希望能在驱动加载上就自动建立设备文件节点,方法1:在网上看到好像说是在驱动中用class之类的可以达到这个效果;方法2:希望通过读取/proc/dev得到的消息来建立设备文件节点,但没写过shell脚本,且不知道怎样让它自动运行。希望得到您的提示。谢谢


要自动创建节点的话必须实现:
1、文件系统中mdev或udev
2、驱动中实现class(或是用偷懒点的办法:直接注册misc类)。


刚才申请的帐号居然登录不上,所以只能匿名了,郁闷……
博主,请问一下你的hello模块没什么没写Kconfig文件,我看有的书上说添加模块必须写kconfig文件啊····?请指教,谢谢!


我是在内核外编译模块,不用Kconfig


请问楼主你的程序哪里来的,书上都不全呀?


我的程序是根据《LDD3》的程序修改、增删而成的。 


请问测试程序read(sculltest , &buffer2[20-i] , i),write(sculltest , &buffer1[20-i] , i)两个函数与驱动是怎么挂钩,是怎么样一个运行机制?谢谢。
哎……驱动层与用户层的,代码上的联系是怎么样的?说实话,这个驱动我看完了都不知道它能做什么?更不知道测试程序应该怎么写...惭愧啊


所以在学驱动前一定要学一点应用层的东西,写一些应用层的小软件试试,不如驱动是学不下去的。


请问你当初在应用层是如何学习的?谢谢指点。
我看到你也是硬件出身,很期待你对于应用层开发入门的建议。

我是初学者,我按照你提供的程序输入make modules 输出提示:missing separator. Stop.
不管读,还是写,都休眠,然后就卡在那了,动也动不了,大概是什么原因
博主好久没发 新的关于驱动的啦 ,终于来啦 哈哈,激动
博主:
      我是初学者,写2440的linux驱动时会用到一些例如s3c2410_gpio_setpin()的函数,这些函数的定义在内核的哪个目录下呢?有没有一些系统的函数用法说明啊?

博主:
请教个问题,你在驱动程序IO_irq.c中的函数为int IO_irq_ioctl(struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg)
而你在应用程序IO_irq_test.c中调用的if ((code=ioctl( IO_irqtest , IO_IRQ_1  , NULL ) ) < 0) ,没有参数struct inode *inode,能解释下吗?谢谢
哈哈,最近也在学ldd3,每学完一章就来你这儿看一下你的笔记,实在是写得太好了。
我刚学习linux驱动,请问以下问题怎么解决呢?
[root@localhost LinuxDriver]# insmod ./hello.o
./hello.o: kernel-module version mismatch
        ./hello.o was compiled for kernel version 2.4.20
        while this kernel is version 2.4.20-8.


你编译模块使用的内核版本和运行模块的内核版本不同,建议统一下。
或者,好像在内核配置上可以关闭这个检查。


喜欢注释  感谢一个。。。。
牛人,兄弟你太有才了,前途无量!
总结的不错....转了....谢谢!
拜读美文,醍醐灌顶,如梦初醒。