Linux杂项

本文详细介绍了如何在Linux内核模块中使用模块参数,包括模块参数的声明、使用和宏定义。此外,还深入探讨了内核中的内存管理技术,如内存池、bootmem分配器以及iomap的使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

 

 
引导模块时,可以向它传递参数。要使用模块参数加载模块,这样写:
insmod module.ko [param1=value param2=value ...]
为了使用这些参数的值,要在模块中声明变量来保存它们,并在所有函数之外的某个地方使用宏 MODULE_PARM(variable, type) 和  MODULE_PARM_DESC(variable, description) 来接收它们。 type参数应该是一个格式为  [min[-max]]{b,h,i,l,s} 字符串,其中 min 和 max 是数组的长度限度。如果两者都忽略了,则默认为 1。最后一个字符是类型说明符:
b byte
h short
i int
l long
s string



在用户态下编程可以通过main()的来传递命令行参数,而编写一个内核模块则通过module_param ()
module_param宏是Linux 2.6内核中新增的,该宏被定义在include/linux/moduleparam.h文件中,具体定义如下:
#define module_param(name, type, perm)
module_param_named(name, name, type, perm)

其中使用了 3 个参数:要传递的参数变量名, 变量的数据类型, 以及访问参数的权限。
module_param (name,type,perm);
module_param 使用了 3 个参数: 变量名, 它的类型, 以及一个权限掩码用来做一个辅助的 sysfs 入口(啥意思). 这个宏定义应当放在任何函数之外, 典型地是出现在源文件的前面.定义如:
static char *whom = "world";
static int howmany = 1;
module_param (howmany, int, S_IRUGO);
module_param (whom, charp, S_IRUGO);
模块参数支持许多类型:
bool
invbool
一个布尔型( true 或者 false)值(相关的变量应当是 int 类型). invbool 类型颠倒了值, 所以真值变成 false, 反之亦然.
charp
一个字符指针值. 内存为用户提供的字串分配, 指针因此设置.
int
long
short
uint
ulong
ushort
基本的变长整型值. 以 u 开头的是无符号值.

数组参数, 用逗号间隔的列表提供的值, 模块加载者也支持. 声明一个数组参数, 使用:

module_param _array(name,type,num,perm);
这里 name 是你的数组的名子(也是参数名),
type 是数组元素的类型,
num 是一个整型变量,
perm 是通常的权限值.
如果数组参数在加载时设置, num 被设置成提供的数的个数. 模块加载者拒绝比数组能放下的多的值.

perm参数的作用是什么?
最后的 module_param 字段是一个权限值; 你应当使用 中定义的值. 这个值控制谁可以存取这些模块参数在 sysfs 中的表示.如果 perm 被设为 0, 就根本没有 sysfs 项. 否则, 它出现在 /sys/module下面, 带有给定的权限. 使用 S_IRUGO 作为参数可以被所有人读取, 但是不能改变; S_IRUGO|S_IWUSR 允许 root 来改变参数. 注意, 如果一个参数被 sysfs 修改, 你的模块看到的参数值也改变了, 但是你的模块没有任何其他的通知. 你应当不要使模块参数可写, 除非你准备好检测这个改变并且因而作出反应.
perm参数的作用是什么?
最后的 module_param 字段是一个权限值,表示此参数在sysfs文件系统中所对应的文件节点的属性。你应当使用 中定义的值. 这个值控制谁可以存取这些模块参数在 sysfs 中的表示.当perm为0时,表示此参数不存在 sysfs文件系统下对应的文件节点。 否则, 模块被加载后,在/sys/module/ 目录下将出现以此模块名命名的目录, 带有给定的权限.。
权限在include/linux/stat.h中有定义
比如:
#define S_IRWXU 00700
#define S_IRUSR 00400
#define S_IWUSR 00200
#define S_IXUSR 00100
#define S_IRWXG 00070
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010
#define S_IRWXO 00007
#define S_IROTH 00004
#define S_IWOTH 00002
#define S_IXOTH 00001
使用 S_IRUGO 作为参数可以被所有人读取, 但是不能改变; S_IRUGO|S_IWUSR 允许 root 来改变参数. 注意, 如果一个参数被 sysfs 修改, 你的模块看到的参数值也改变了, 但是你的模块没有任何其他的通知. 你应当不要使模块参数可写, 除非你准备好检测这个改变并且因而作出反应.

通过宏MODULE_PARM_DESC()对参数进行说明:
static unsigned short size = 1;
module_param(size, ushort, 0644);
MODULE_PARM_DESC(size, “The size in inches of the fishing pole”
“connected to this computer.” );

 

 

 

module_param() module_param_array() 的作用就是让那些全局变量对insmod 可见,使模块装载时可重新赋值。

module_param_array() 宏的第三个参数用来记录用户insmod 时提供的给这个数组的元素个数,NULL 表示不关心用户提供的个数

module_param() module_param_array() 最后一个参数权限值不能包含让普通用户也有写权限,否则编译报错。这点可参考linux/moduleparam.h __module_param_call() 宏的定义。

字符串数组中的字符串似乎不能包含逗号,否则一个字符串会被解析成两个 

module_param_named(name, parameter name, type, permission)
总共四个参数
1。第一个参数是在insmod的时候用的(insmod对应的参数变量名), insdome a.ko nohwcrypt=1
2.  第二个参数是在程序中定义的变量名
3. 第三个参数是变量的类型
4。 第四个参数是权限

 

内核抢占

           与其他大部分Unix变体和其他大部分的操作系统不同, Linux完整地支持内核抢占。在不支持内核抢占的内核中,内核代码可以一直执行,到它完成为止。也就是说,调度程序没有办法在一个内核级的任务正在执行的时候重新调度 -- 内核中的各任务是协作方式调度的,不具备抢占性。在2.6版的内核中,内核引人了抢占能力;现在,只要重新调度是安全的,那么内核就可以在任何时间抢占正在执行的任务。
    那么,什么时候重新调度才是安全的呢?只要没有持有锁,内核就可以进行抢占。锁是非抢占区域的标志。由于内核是支持SMP的,所以,如果没有持有锁,那么正在执行的代码就是可重新导入的,也就是可以抢占的。
    为了支持内核抢占所作的第一处变动就是每个进程的thread_info引入了preempt_count(thread_info.preempt_count)计数器。该计数器初始值为0,每当使用锁的时候数值加1,释放锁的时候数值减1。当数值为0的时候,内核就可执行抢占。从中断返回内核空间的时候,内核会检查need_resched和preempt_count的值。如果need_resched被设置,并且preempt_count为0的话,这说明有一个更为重要的任务需要执行并且可以安全地抢占,此时,调度程序就会调度(抢占当前进程)。如果preempt_count不为0,说明当前任务持有锁,所以抢占是不安全的。这时,就会像通常那样直接从中断返回当前执行进程。如果当前进程所持有的所有的锁都被释放了。那么preempt_count就会重新为0。此时,释放锁的代码会检查need_resched是否被设置。如果是的话,就会调用调度程序。有些内核代码需要允许或禁止内核抢占。
    如果内核中的进程被阻塞了,或它显式地调用了schedule(),内核抢占也会显式地发生。这种形式的内核代码从来都是受支持的,因为根本无需额外的逻辑来保证内核可以安全地发生被抢占。如果代码显式的调用了schedule(),那么它应该清楚自己是可以安全地被抢占的。
    内核抢占发生在:
    (1) 当"从中断处理程序"正在执行,且返回内核空间之前
    (2) 内核代码再一次具有可抢占性的时候
    (3) 如果内核中的任务显式的调用schedule()
    (4) 如果内核中的任务阻塞(这同样也会导致调用schedule())

 

persistent  ram :the content of this area is survive aftrer a restart .

/sys/module/ramoops/parameters/   for dump parameters

 /dev/mem  for dump data 

 

ioremap()

(1)I/O映射方式(I/O-mapped) 典型地,如X86处理器为外设专门实现了一个单独的地址空间,称为"I/O地址空间"或者"I/O端口空间",CPU通过专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元。

(2)内存映射方式(Memory-mapped)RISC指令系统的CPU(如ARM、PowerPC等)通常只实现一个物理地址空间,外设I/O端口成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。

一般来说,在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。Linux在io.h头文件中声明了函数ioremap(),用来将I/O内存资源的物理地址映射到核心虚地址空间(3GB-4GB)中,原型如下:

void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags); iounmap函数用于取消ioremap()所做的映射,原型如下:

void iounmap(void * addr); 这两个函数都是实现在mm/ioremap.c文件中。

 

ioremap和vmalloc一样,都会返回vmalloc区的虚拟地址,但是ioremap并不会分配任何物理内存,ioremap也需要建立新的页表来访问物理页面。

ioremap获取的虚拟地址空间需要使用iounmap来释放。

http://blog.csdn.net/kickxxx/article/details/6620532

ioremap主要用来映射设备的物理内存到内核的虚拟地址空间,这样,内核就可以通过这些虚拟地址空间访问设备内存。

使用ioremap需要注意几点:

1. 尽量不要直接访问这些虚拟地址,使用专用的io操作函数可以增加移植性,当然有些特殊情况,比如android pmem是对物理内存进行的ioremap,所以可以直接访问内存

2. 有些情况下,可能不想让系统cache发挥作用,这时要使用ioremap_nocache进行映射

Memory Pool

内存池是一种 内存分配方式。 通常我们习惯直接使用new、malloc等API申请分配内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的 内存碎片并进而降低性能。内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是尽量避免了 内存碎片,使得 内存分配效率得到提升。在 内核中有不少地方 内存分配不允许失败. 作为一个在这些情况下确保分配的方式, 内核开发者创建了一个已知为内存池(或者是 "mempool" )的抽象. 一个内存池真实地只是一类后备缓存, 它尽力一直保持一个空闲内存列表给紧急时使用.如果需要, 你可编写特殊用途的函数来处理 mempool 的 内存分配. 常常, 但是, 你只需要使 内核  slab 分配器为你处理这个任务. 有 2 个函数 ( 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); 一旦已创建了内存池, 可以分配和释放对象,使用:void *mempool_alloc(mempool_t *pool, int gfp_mask); void mempool_free(void *element, mempool_t *pool); 当内存池创建了, 分配函数将被调用足够的次数来创建一个预先分配的 对象池. 因此, 对 mempool_alloc 的调用试图从分配函数请求额外的对象; 如果那个分配失败, 一个预先分配的对象(如果有剩下的)被返回. 当一个对象被用 mempool_free 释放, 它保留在池中, 如果对齐预分配的对象数目小于最小量; 否则, 它将被返回给系统.一个 mempool 可被重新定大小, 使用:int mempool_resize(mempool_t *pool, int new_min_nr, int gfp_mask); 这个调用, 如果成功, 调整内存池的大小至少有 new_min_nr 个对象. 如果你不再需要一个内存池, 返回给系统使用:void mempool_destroy(mempool_t *pool); 你编写返回所有的分配的对象, 在销毁 mempool 之前, 否则会产生一个内核 oops.如果你考虑在你的驱动中使用一个 mempool, 请记住一件事: mempools 分配一块内存在一个 链表中, 对任何真实的使用是空闲和无用的. 容易使用 mempools 消耗大量的内存. 在几乎每个情况下, 首选的可选项是不使用 mempool 并且代替以简单处理分配失败的可能性. 如果你的驱动有任何方法以不危害到系统完整性的方式来响应一个分配失败, 就这样做. 驱动代码中的 mempools 的使用应当少.

Bootmem

在系统启动时,内存的伙伴系统/slab算法还没有初始化之前,系统也需要来作内存管理,分配一些核心数据结构,bootmem分配器就实现了该功能,它用于在启动阶段早期分配内存。Bootmem分配器使用位图来管理页,位图数量和系统的物理内存也数量是相同的。当页被使用时,就标记为1,否则为0表示空闲页。由于该分配器管理机制比较简单,并没有考虑性能和通用性,所以在伙伴系统完成初始化之后,bootmem分配器就要交出管理权,然后销毁掉。

Oom killer: 当系统内存不足时,会根据当前进程的内存使用状况以及oom score来Kill掉某个进程。

Low memorykiller: 会周期性的检查系统内存状况,当系统内存小于其设定的返回时,根据 oom score和当前rss占用内存情况来杀掉进程。

 

mmap详解

 

 

mmap函数是unix/linux下的系统调用。将IOMEM映射到了一个设备文件,访问这个设备文件就等同于访问了这段IOMEM。mmap系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。mmap并不分配空间, 只是将文件映射到调用进程的地址空间里, 然后你就可以用memcpy等操作写文件, 而不用write()了.写完后用msync()同步一下, 你所写的内容就保存到文件里了. 不过这种方式没办法增加文件的长度, 因为要映射的长度在调用mmap()的时候就决定了.

 

1.栈 - 由编译器自动分配释放
2.堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收
3.全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。- 程序结束释放
4.另外还有一个专门放常量的地方。- 程序结束释放
在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态区),在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内定义的static表示只在该函数体内有效。另外,函数中的"adgfdf"这样的字符串存放在常量区。

 

Semaphore

 

Semaphore分为单值和多值两种,前者只能被一个线程获得,后者可以被若干个线程获得。
以一个停车场是运作为例。为了简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆不受阻碍的进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入一辆,如果又离开两辆,则又可以放入两辆,如此往复。
在这个 停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是 信号量的作用。
更进一步, 信号量的特性如下:信号量是一个非负整数(车位数),所有通过它的线程(车辆)都会将该整数减一(通过它当然是为了使用资源),当该整数值为零时,所有试图通过它的线程都将处于等待状态。在 信号量上我们定义两种操作: Wait(等待) 和 Release(释放)。 当一个线程调用Wait(等待)操作时,它要么通过然后将 信号量减一,要么一直等下去,直到信号量大于一或超时。Release(释放)实际上是在 信号量上执行加操作,对应于车辆离开停车场,该操作之所以叫做“释放”是因为加操作实际上是释放了由信号量守护的资源。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值