别说爱就一个字,你数数你说过几次;
别说程序员好干,你看看你年薪几万!~
别说你爱的太真,伤的却太深——只要真心的投入就无悔、无怨恨
别说失去的太多,那是不懂得收获;别说爱了又错过,那是你没有去收获住那么硕果!~
请真心你现在所拥有的一切——守侯住属于你的爱情!FOR ever
Linux内核模块编程之字符设备文件
上一篇 / 下一篇 2008-02-13 16:45:35 / 个人分类:辞海
查看( 63 ) /
评论( 2 )
字符设备文件IXPUB技术博客.YS?"K+KW8{#yh
IXPUB技术博客 ba-C[ Yx*]hL
因此现在我们是大胆的内核程序员而且我们知道如何写什么也不做的内核模块。我们为自己而自豪并且可以高高的仰起我们的头。但是不知何故我们感到遗失了什么。令人紧张的模块并不是很有趣的。
'Z+q6I,Hv)N [P0
aAsw ~.V(S_a0 内核模块有两种主要的途径和进程对话。一种是通过设备文件 (像 /dev 目录中的文件), 另一种是使用 proc 文件系统。因为写内核中的某些东西的一个主要的原因是去支持某种硬件,所以我们从设备文件开始。IXPUB技术博客;I1b.QyxS1EK0[$f!n|
IXPUB技术博客^G4QD4VX)iD
设备文件的原始目的是允许进程和内核中的设备驱动程序通信以通过它们和物理设备通信 (调制解调器, 终端, 等等)。 这个办法的实现是像下面这样的。IXPUB技术博客:r)cj:_o9kJgI9Nv^
K*HJL D8k%`;Y0 每个设备驱动程序负责某种硬件,它被分配一个主设备号。驱动程序的列表和它们的主设备号可以在 /proc/devices找到。每一个物理设备由设备驱动程序控制且被分配一个次设备号。 /dev 目录被假设包含每一个这样的设备的被称之为设备文件的特殊文件,无论它是否被真正的安装在系统上。IXPUB技术博客3ASI(O%a
N9A8mZ qp0 例如,如果你 ls -l /dev/hd[ab]*,你将看到所有的可能被连接到系统上的IDE硬盘的分区。注意它们都使用相同的主设备号,3。但是次设备号彼此都不相同。IXPUB技术博客xQn |H;P
否认申明: 这是假设你正字使用 PC 架构的系统。我不知道基于其他架构的Linux设备的情况。.
6E+N+{ nx'Sv$HAZ#vw0
Jpj+V'Z`@0 当系统被安装,所有的那些设备文件被 mknod 命令创建。从技术上说没有必须将它们放在 /dev目录的原因,这只是一个有用的惯例。像练习所示的那样,当为了测试的目的而创建一个设备文件,将它放在你编译内核模块的那个目录也许更有意义。IXPUB技术博客J3a3I'w{#RKx ^K
IXPUB技术博客9a/_ZLa S Q
设备分为两种:字符设备和块设备。不同之处在于块设备对于请求有缓冲区,因此它们可以选择以什么顺序进行响应。对于存储设备而言这一点是很重要的,因为在读写连续的扇区时比远远的分离的扇区更快。另一个不同就是块设备只能以块为单位接受输入和返回输出(块的大小根据设备的不同而不同),而字符设备只能使用它们可能使用的或多或少的字节大小。大多数设备是字符设备,因为它们不需要这种缓冲而且不以固定块大小进行操作。你可以用ls -l区分一个设备文件是块设备还是字符设备.如果开头是“b”,那么它就是块设备;如果是“c”,那么就是字符设备。IXPUB技术博客1U Dn5ok9PQ.@
8j2g&L#B+K%N7NK u0 这个模块分为两部分:登记设备模块部分和设备驱动部分。 init_module 调用 module_register_chrdev 而将设备驱动程序加入内核的字符设备驱动程序表。它也返回该设备将使用的主设备号。 cleanup_module 注销该设备。
O:p&ki2g_6HF0
.}){Ei+G2A2f{0 这(登记什么和注销它)是那两种功能的常规功能。内核中的东西不想普通进程那样主动运行自己,而是由进程通过系统调用,或者由硬件通过中断,或者由内核的其他部分(简单的讲,由特殊的调用)进行调用。结果,当你向内核中加入代码,你被假设将之登记为某种句柄,而当你移除它时,你被假设出注销它。
4Zc:Iei/P}["W0
-JQX3RZz |K0f0 严格意义上讲,设备驱动程序由四个device_函数组成,当某人试图用我们的主设备号的设备文件做什么事时它被调用。内核是通过 file_operations结构知道要调用它们的,Fops, 在设备被登记时被给出,它包含那四个的。IXPUB技术博客9xrR%f/p
IXPUB技术博客iV-g8r2Ia.q
另一点我们在这必须记住的是我们不能允许内核模块在任何根感觉需要的时候被rmmod。原因是如果设备文件正被一个进程打开然后我们移除那个内核模块,这将使用那个文件,而这又将导致对那个适当的读写函数所在的内存区域的调用。如果幸运的话,没有其他的代码被加载到那儿,我们得到一个难看的错误消息。如果不幸运的话,另一个内核模块被加载到同一区域,这就意味着跳到内核中的另一个的中间,结果是不可预见的,但肯定不是什么好事。IXPUB技术博客3Bpn#r+C+G8q'@l
IXPUB技术博客P}t+kPV k3Q1{
通常,当你不允许什么事情发生,你会从被假设做这件事的返回一个用负数表示的错误代码。使用cleanup_module 是不可能的,因为它不返回任何值。一旦cleanup_module 被调用,模块即死亡了。然而,这儿有一个被称为引用计数器(在/proc/modules中的相应行的最后一个数字)的计数器计算有多少其他的内核模块正在使用该模块。如果该数字非零 rmmod 调用将失败。模块的引用计数器在变量mod_use_count_中. 因为有处理这个变量的宏定义 (MOD_INC_USE_COUNT and MOD_DEC_USE_COUNT),我们最好使用它们而不直接使用 mod_use_count_ ,这样,如果将来实现方法改变了我们会更安全。
Es)p;]s&j0范例 chardev.cIXPUB技术博客qP&ES/[!S)CBv
h4bo^S0/* chardev.c
h&psIs#l1I"Ng|0* Copyright (C) 1998-1999 by Ori Pomerantz
1Zm}w$e0*
[oj7y"Wz:Y0* 创建一个字符设备(只读)IXPUB技术博客%P"r Pd-i9ud
*/
L!I9@0N^ @]+a0IXPUB技术博客+a)F yi)W D _ U\
/* 必要的头文件 */
8z@$t!e#p;Z0
5lLZO7V)Y+E0/* 内核模块标准头文件 */
a \v~p0#include /* 内核工作 */
b#F x\+Y&FI8wJ*C!g,kf0#include /* 明确指定是模块 */IXPUB技术博客? j ~9Dp`
IXPUB技术博客r#}%`z#c$]?U
/* 处理 CONFIG_MODVERSIONS */
u6h*}K.?6@1P f0#if CONFIG_MODVERSIONS==1
h yjc J#e9r6C]q0#define MODVERSIONSIXPUB技术博客w?&K3^G)Vq.A
#include
#P(k,^ER+ibe0#endif
j6n a%V1[ e0IXPUB技术博客+_u7IQU
/* 对于字符设备 */
l-^r Wl0~bBr0#include /* 字符设备定义 */IXPUB技术博客`(U E L n v V
#include /* 目前对下一步没有任何用的包装IXPUB技术博客&r:t8`~\
* 但对未来的Linux 版本在兼容性上可能有帮助*/
Y gWG&`0t-e)n0IXPUB技术博客6twhT5uu+V\_e
/* 在2.2.3版内核 /usr/include/linux/version.h 包括这个宏,但是IXPUB技术博客O ]d'\b?O*b3P
* 在2.0.35中没有 - 因此我在这加入它以便需要 */
Y3c5Y ?"R4@0#ifndef KERNEL_VERSION
+I4s?^ H(K4~0_OW{0#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
0V7zabD8\3^$p5^0#endif
"Pp!k1cuEe~ KE7P0
]L RsL5n"g~0/* 条件编译。 LINUX_VERSION_CODE 是版本代号(经 KERNEL_VERSION) 。 */
oF&Y;? eFY(\](F ^0#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)IXPUB技术博客M(s4iB&A:N
IXPUB技术博客 ba-C[ Yx*]hL
因此现在我们是大胆的内核程序员而且我们知道如何写什么也不做的内核模块。我们为自己而自豪并且可以高高的仰起我们的头。但是不知何故我们感到遗失了什么。令人紧张的模块并不是很有趣的。
'Z+q6I,Hv)N [P0
aAsw ~.V(S_a0 内核模块有两种主要的途径和进程对话。一种是通过设备文件 (像 /dev 目录中的文件), 另一种是使用 proc 文件系统。因为写内核中的某些东西的一个主要的原因是去支持某种硬件,所以我们从设备文件开始。IXPUB技术博客;I1b.QyxS1EK0[$f!n|
IXPUB技术博客^G4QD4VX)iD
设备文件的原始目的是允许进程和内核中的设备驱动程序通信以通过它们和物理设备通信 (调制解调器, 终端, 等等)。 这个办法的实现是像下面这样的。IXPUB技术博客:r)cj:_o9kJgI9Nv^
K*HJL D8k%`;Y0 每个设备驱动程序负责某种硬件,它被分配一个主设备号。驱动程序的列表和它们的主设备号可以在 /proc/devices找到。每一个物理设备由设备驱动程序控制且被分配一个次设备号。 /dev 目录被假设包含每一个这样的设备的被称之为设备文件的特殊文件,无论它是否被真正的安装在系统上。IXPUB技术博客3ASI(O%a
N9A8mZ qp0 例如,如果你 ls -l /dev/hd[ab]*,你将看到所有的可能被连接到系统上的IDE硬盘的分区。注意它们都使用相同的主设备号,3。但是次设备号彼此都不相同。IXPUB技术博客xQn |H;P
否认申明: 这是假设你正字使用 PC 架构的系统。我不知道基于其他架构的Linux设备的情况。.
6E+N+{ nx'Sv$HAZ#vw0
Jpj+V'Z`@0 当系统被安装,所有的那些设备文件被 mknod 命令创建。从技术上说没有必须将它们放在 /dev目录的原因,这只是一个有用的惯例。像练习所示的那样,当为了测试的目的而创建一个设备文件,将它放在你编译内核模块的那个目录也许更有意义。IXPUB技术博客J3a3I'w{#RKx ^K
IXPUB技术博客9a/_ZLa S Q
设备分为两种:字符设备和块设备。不同之处在于块设备对于请求有缓冲区,因此它们可以选择以什么顺序进行响应。对于存储设备而言这一点是很重要的,因为在读写连续的扇区时比远远的分离的扇区更快。另一个不同就是块设备只能以块为单位接受输入和返回输出(块的大小根据设备的不同而不同),而字符设备只能使用它们可能使用的或多或少的字节大小。大多数设备是字符设备,因为它们不需要这种缓冲而且不以固定块大小进行操作。你可以用ls -l区分一个设备文件是块设备还是字符设备.如果开头是“b”,那么它就是块设备;如果是“c”,那么就是字符设备。IXPUB技术博客1U Dn5ok9PQ.@
8j2g&L#B+K%N7NK u0 这个模块分为两部分:登记设备模块部分和设备驱动部分。 init_module 调用 module_register_chrdev 而将设备驱动程序加入内核的字符设备驱动程序表。它也返回该设备将使用的主设备号。 cleanup_module 注销该设备。
O:p&ki2g_6HF0
.}){Ei+G2A2f{0 这(登记什么和注销它)是那两种功能的常规功能。内核中的东西不想普通进程那样主动运行自己,而是由进程通过系统调用,或者由硬件通过中断,或者由内核的其他部分(简单的讲,由特殊的调用)进行调用。结果,当你向内核中加入代码,你被假设将之登记为某种句柄,而当你移除它时,你被假设出注销它。
4Zc:Iei/P}["W0
-JQX3RZz |K0f0 严格意义上讲,设备驱动程序由四个device_函数组成,当某人试图用我们的主设备号的设备文件做什么事时它被调用。内核是通过 file_operations结构知道要调用它们的,Fops, 在设备被登记时被给出,它包含那四个的。IXPUB技术博客9xrR%f/p
IXPUB技术博客iV-g8r2Ia.q
另一点我们在这必须记住的是我们不能允许内核模块在任何根感觉需要的时候被rmmod。原因是如果设备文件正被一个进程打开然后我们移除那个内核模块,这将使用那个文件,而这又将导致对那个适当的读写函数所在的内存区域的调用。如果幸运的话,没有其他的代码被加载到那儿,我们得到一个难看的错误消息。如果不幸运的话,另一个内核模块被加载到同一区域,这就意味着跳到内核中的另一个的中间,结果是不可预见的,但肯定不是什么好事。IXPUB技术博客3Bpn#r+C+G8q'@l
IXPUB技术博客P}t+kPV k3Q1{
通常,当你不允许什么事情发生,你会从被假设做这件事的返回一个用负数表示的错误代码。使用cleanup_module 是不可能的,因为它不返回任何值。一旦cleanup_module 被调用,模块即死亡了。然而,这儿有一个被称为引用计数器(在/proc/modules中的相应行的最后一个数字)的计数器计算有多少其他的内核模块正在使用该模块。如果该数字非零 rmmod 调用将失败。模块的引用计数器在变量mod_use_count_中. 因为有处理这个变量的宏定义 (MOD_INC_USE_COUNT and MOD_DEC_USE_COUNT),我们最好使用它们而不直接使用 mod_use_count_ ,这样,如果将来实现方法改变了我们会更安全。
Es)p;]s&j0范例 chardev.cIXPUB技术博客qP&ES/[!S)CBv
h4bo^S0/* chardev.c
h&psIs#l1I"Ng|0* Copyright (C) 1998-1999 by Ori Pomerantz
1Zm}w$e0*
[oj7y"Wz:Y0* 创建一个字符设备(只读)IXPUB技术博客%P"r Pd-i9ud
*/
L!I9@0N^ @]+a0IXPUB技术博客+a)F yi)W D _ U\
/* 必要的头文件 */
8z@$t!e#p;Z0
5lLZO7V)Y+E0/* 内核模块标准头文件 */
a \v~p0#include /* 内核工作 */
b#F x\+Y&FI8wJ*C!g,kf0#include /* 明确指定是模块 */IXPUB技术博客? j ~9Dp`
IXPUB技术博客r#}%`z#c$]?U
/* 处理 CONFIG_MODVERSIONS */
u6h*}K.?6@1P f0#if CONFIG_MODVERSIONS==1
h yjc J#e9r6C]q0#define MODVERSIONSIXPUB技术博客w?&K3^G)Vq.A
#include
#P(k,^ER+ibe0#endif
j6n a%V1[ e0IXPUB技术博客+_u7IQU
/* 对于字符设备 */
l-^r Wl0~bBr0#include /* 字符设备定义 */IXPUB技术博客`(U E L n v V
#include /* 目前对下一步没有任何用的包装IXPUB技术博客&r:t8`~\
* 但对未来的Linux 版本在兼容性上可能有帮助*/
Y gWG&`0t-e)n0IXPUB技术博客6twhT5uu+V\_e
/* 在2.2.3版内核 /usr/include/linux/version.h 包括这个宏,但是IXPUB技术博客O ]d'\b?O*b3P
* 在2.0.35中没有 - 因此我在这加入它以便需要 */
Y3c5Y ?"R4@0#ifndef KERNEL_VERSION
+I4s?^ H(K4~0_OW{0#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
0V7zabD8\3^$p5^0#endif
"Pp!k1cuEe~ KE7P0
]L RsL5n"g~0/* 条件编译。 LINUX_VERSION_CODE 是版本代号(经 KERNEL_VERSION) 。 */
oF&Y;? eFY(\](F ^0#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)IXPUB技术博客M(s4iB&A:N