linux内核编程Word文档下载推荐.docx
- 文档编号:20315408
- 上传时间:2023-01-21
- 格式:DOCX
- 页数:87
- 大小:71.17KB
linux内核编程Word文档下载推荐.docx
《linux内核编程Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《linux内核编程Word文档下载推荐.docx(87页珍藏版)》请在冰豆网上搜索。
罗马编程教科书上是以“Salut,Mundi”的程序开始的。
我不知道如果人们打破这个传统后会有什么后果,但我认为还是不要去发现这个后果比较安全。
一个内核模块至少包括两个函数:
init_module,在这个模块插入内核时调用;
cleanup_module,在模块被移出时调用。
典型情况下,init_module为内核中的某些东西注册一个句柄,或者把内核中的程序提换成它自己的代码(通常是进行一些工作以后再调用原来工作的代码)。
Clean_module模块要求撤销init_module进行的所有处理工作,使得模块可以被安全的卸载。
Exhello.c
/*hello.c
*Copyright(C)1998byOriPomerantz
*
*"
Hello,world"
-thekernelmoduleversion.
*/
/*Thenecessaryheaderfiles*/
/*Standardinkernelmodules*/
#include<
linux/kernel.h>
/*We'
redoingkernelwork*/
linux/module.h>
/*Specifically,amodule*/
/*DealwithCONFIG_MODVERSIONS*/
#ifCONFIG_MODVERSIONS==1
#defineMODVERSIONS
linux/modversions.h>
#endif
/*Initializethemodule*/
intinit_module()
{
printk("
Hello,world-thisisthekernelspeaking\n"
);
/*Ifwereturnanonzerovalue,itmeansthat
*init_modulefailedandthekernelmodule
*can'
tbeloaded*/
return0;
}
/*Cleanup-undidwhateverinit_moduledid*/
voidcleanup_module()
Shortisthelifeofakernelmodule\n"
1.1内核模块的编译文件
一个内核模块不是一个可以独立执行的文件,而是需要在运行时刻连接入内核的目标文件。
所以,它们需要用-c选项进行编译。
而且,所有的内核模块都必须包含特定的标志:
●__KERNEL__——这个标志告诉头文件此代码将在内核模块中运行,而不是作为用户进程。
●MODULE——这个标志告诉头文件要给出适当的内核模块的定义。
●LINUX——从技术上讲,这个标志不是必要的。
但是,如果你希望写一个比较正规的内核模块,在多个操作系统上编译,这个标志将会使你感到方便。
它可以允许你在独立于操作系统的部分进行常规的编译。
还有其它的一些可被选择包含标志,取决于编译模块是的选项。
如果你不能明确内核怎样被编译,可以在in/usr/include/linux/config.h中查到。
●__SMP__——对称多线程。
在内核被编译成支持对称多线程(尽管在一台处理机上运行)是必须定义。
如果是这样,还需要做一些别的事情(参见第12章)。
●CONFIG_MODVERSIONS——如果CONFIG_MODVERSIONS被激活,你需要在编译是定义它并且包含文件/usr/include/linux/modversions.h。
这可以有代码自动完成。
exMakefile
#Makefileforabasickernelmodule
CC=gcc
MODCFLAGS:
=-Wall-DMODULE-D__KERNEL__-DLINUX
hello.o:
hello.c/usr/include/linux/version.h
$(CC)$(MODCFLAGS)-chello.c
echoinsmodhello.ototurniton
echormmodhellototurnifoff
echo
echoXandkernelprogrammingdonotmix.
echoDotheinsmodandrmmodfromoutside
所以,并不是剩下的事情就是root(你没有把它编译成root,而是在边缘(注1.1)。
对吗?
),然后就在你的核心内容里插入或移出hello。
当你这样做的时候,要注意到你的新模块在/proc/modules里。
而且,编译文件不推荐从X下插入的原因是内核有一条需要用printk打印的消息,它把它送给了控制台。
如果你不使用X,它就送到了你使用的虚拟终端(你用Alt-F<
n>
选择的哪个)并且你可以看到。
相反的,如果你使用了X,就有两种可能性。
如果用xterm–C打开了一个控制台,输出将被送到哪里。
如果没有,输出将被送到虚拟终端7——被X“覆盖”的那个。
如果你的内核变得不稳定,你可以在没有X的情况下得到调试消息。
在X外,printk可以直接从内核中输出到控制台。
而如果在X里,printk输出到一个用户态的进程(xterm–C)。
当进程接收到CPU时间,它会将其送到X服务器进程。
然后,当X服务器进程接收到CPU时间,它将会显示,但是一个不稳定的内核意味着系统将会崩溃或重起,所以你不希望显示错误的消息,然后可能被解释给你什么发生了错误,但是超出了正确的时间。
1.2多文件内核模块
有些时候在几个源文件之间分出一个内核模块是很有意义的。
在这种情况下,你需要做下面的事情:
1.在除了一个以外的所有源文件中,增加一行#define__NO_VERSION__。
这是很重要的,因为module.h一般包括kernel_version的定义,这是一个全局变量,包含模块编译的内核版本。
如果你需要version.h,你需要把自己把它包含进去,因为如果有__NO_VERSION__的话module.h不会自动包含。
2.象通常一样编译源文件。
3.把所有目标文件联编成一个。
在X86下,用ld–melf_i386–r–o<
nameofmodule>
.o<
1stsourcefile>
这里给出一个这样的内核模块的例子。
exstart.c
/*start.c
*Copyright(C)1999byOriPomerantz
*Thisfileincludesjustthestartroutine
exstop.c
/*stop.c
-thekernelmoduleversion.This
*fileincludesjustthestoproutine.
#define__NO_VERSION__/*Thisisn'
t"
the"
file
*ofthekernelmodule*/
linux/version.h>
/*Notincludedby
*module.hbecause
*ofthe__NO_VERSION__*/
#Makefileforamultifilekernelmodule
start.ostop.o
ld-melf_i386-r-ohello.ostart.ostop.o
start.o:
start.c/usr/include/linux/version.h
$(CC)$(MODCFLAGS)-cstart.c
stop.o:
stop.c/usr/include/linux/version.h
$(CC)$(MODCFLAGS)-cstop.c
2.字符设备文件
那么,现在我们是原始级的内核程序员,我们知道如何写不做任何事情的内核模块。
我们为自己而骄傲并且高昂起头来。
但是不知何故我们感觉到缺了什么东西。
患有精神紧张症的模块不是那么有意义。
内核模块同进程对话有两种主要途径。
一种是通过设备文件(比如/dev目录中的文件),另一种是使用proc文件系统。
我们把一些东西写入内核的一个主要原因就是支持一些硬件设备,所以我们从设备文件开始。
设备文件的最初目的是允许进程同内核中的设备驱动通信,并且通过它们和物理设备通信(modem,终端,等等)。
这种方法的实现如下:
每个设备驱动都对应着一定类型的硬件设备,并且被赋予一个主码。
设备驱动的列表和它们的主码可以在in/proc/devices中找到。
每个设备驱动管理下的物理设备也被赋予一个从码。
无论这些设备是否真的安装,在/dev目录中都将有一个文件,称作设备文件,对应着每一个设备。
例如,如果你进行ls–l/dev/hd[ab]*操作,你将看见可能联结到某台机器上的所有的IDE硬盘分区。
注意它们都使用了同一个主码,3,但是从码却互不相同。
(声明:
这是在PC结构上的情况,我不知道在其他结构上运行的linux是否如此。
)
在系统安装时,所有设备文件在mknod命令下被创建。
它们必须创建在/dev目录下没有技术上的原因,只是一种使用上的便利。
如果是为测试目的而创建的设备文件,比如我们这里的练习,可能放在你编译内核模块的的目录下更加合适。
设备可以被分成两类:
字符设备和块设备。
它们的区别是块设备有一个用于请求的缓冲区,所以它们可以选择用什么样的顺序来响应它们。
这对于存储设备是非常重要的,读取相邻的扇区比互相远离的分区速度会快得多。
另一个区别是块设备只能按块(块大小对应不同设备而变化)接受输入和返回输出,而字符设备却按照它们能接受的最少字节块来接受输入。
大部分设备是字符设备,因为它们不需要这种类型的缓冲。
你可以通过观看ls-l命令的输出中的第一个字符而知道一个设备文件是块设备还是字符设备。
如果是b就是块设备,如果是c就是字符设备。
这个模块可以被分成两部分:
模块部分和设备及设备驱动部分。
Init_module函数调用module_register_chrdev在内核得块设备表里增加设备驱动。
同时返回该驱动所使用的主码。
Cleanup_module函数撤销设备的注册。
这些操作(注册和注销)是这两个函数的主要功能。
内核中的函数不是象进程一样自发运行的,而是通过系统调用,或硬件中断或者内核中的其它部分(只要是调用具体的函数)被进程调用的。
所以,当你向内和中增加代码时,你应该把它注册为具体某种事件的句柄,而当你把它删除的时候,你需要注销这个句柄。
设备驱动完全由四个设备_<
action〉函数构成,它们在希望通过有主码的设备文件实现一些操作时被调用。
内核调用它们的途径是通过file_operation结构Fops。
此结构在设备被注册是创建,它包含指向这四个函数的指针。
另一点我们需要记住的是,我们不能允许管理员随心所欲的删除内核模块。
这是因为如果设备文件是被进程打开的,那么我们删除内核模块的时候,要使用这些文件就会导致访问正常的函数(读/写)所在的内存位置。
如果幸运,那里不会有其他代码被装载,我们将得到一个恶性的错误信息。
如果不行,另一个内核模块会被装载到同一个位置,这将意味着会跳入内核中另一个程序的中间,结果将是不可预料的恶劣。
通常你不希望一个函数做什么事情的时候,会从那个函数返回一个错误码(一个负数)。
但这在cleanup_module中是不可能的,因为它是一个void型的函数。
一旦cleanup_module被调用,这个模块就死掉了。
然而有一个计数器记录着有多少个内核模块在使用这个模块,这个计数器称为索引计数器(/proc/modules中没行的最后一个数字)。
如果这个数字不是0,删除就会失败。
模块的索引计数器包含在变量mod_use_count_中。
有定义好的处理这个变量的宏(MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT),所以我们一般使用宏而不是直接使用变量mod_use_count_,这样在以后实现变化的时候会带来安全性。
exchardev.c
/*chardev.c
*Copyright(C)1998-1999byOriPomerantz
*Createacharacterdevice(readonly)
/*Forcharacterdevices*/
linux/fs.h>
/*Thecharacterdevice
*definitionsarehere*/
linux/wrapper.h>
/*Awrapperwhichdoes
*nexttonothingat
*atpresent,butmay
*helpforcompatibility
*withfutureversions
*ofLinux*/
/*In2.2.3/usr/include/linux/version.hincludes
*amacroforthis,but2.0.35doesn'
t-soIadd
*ithereifnecessary.*/
#ifndefKERNEL_VERSION
#defineKERNEL_VERSION(a,b,c)((a)*65536+(b)*256+(c))
#endif
/*Conditionalcompilation.LINUX_VERSION_CODEis
*thecode(asperKERNEL_VERSION)ofthisversion.*/
#ifLINUX_VERSION_CODE>
KERNEL_VERSION(2,2,0)
asm/uaccess.h>
/*forput_user*/
#defineSUCCESS0
/*DeviceDeclarations*****************************/
/*Thenameforourdevice,asitwillappear
*in/proc/devices*/
#defineDEVICE_NAME"
char_dev"
/*Themaximumlengthofthemessagefromthedevice*/
#defineBUF_LEN80
/*Isthedeviceopenrightnow?
Usedtoprevent
*concurentaccessintothesamedevice*/
staticintDevice_Open=0;
/*Themessagethedevicewillgivewhenasked*/
staticcharMessage[BUF_LEN];
/*Howfardidtheprocessreadingthemessage
*get?
Usefulifthemessageislargerthanthesize
*ofthebufferwegettofillindevice_read.*/
staticchar*Message_Ptr;
/*Thisfunctioniscalledwheneveraprocess
*attemptstoopenthedevicefile*/
staticintdevice_open(structinode*inode,
structfile*file)
staticintcounter=0;
#ifdefDEBUG
printk("
device_open(%p,%p)\n"
inode,file);
/*Thisishowyougettheminordevicenumberin
*caseyouhavemorethanonephysicaldeviceusing
*thedriver.*/
Device:
%d.%d\n"
inode->
i_rdev>
>
8,inode->
i_rdev&
0xFF);
/*Wedon'
twanttotalktotwoprocessesatthe
*sametime*/
if(Device_Open)
return-EBUSY;
/*Ifthiswasaprocess,wewouldhavehadto
*bemorecarefulhere.
*
*Inthecaseofprocesses,thedangerwouldbe
*thatoneprocessmighthavecheckDevice_Open
*andthenbereplacedbytheschedualerbyanother
*processwhichrunsthisfunction.Then,when
*thefirstprocesswasbackontheCPU,itwouldassume
*thedeviceisstillnotopen.
*However,Linuxguaranteesthataprocesswon'
t
*bereplacedwhileitisrunninginkernelcontext.
*InthecaseofSMP,oneCPUmightincrement
*Device_OpenwhileanotherCPUishere,rightafterthecheck.
*However,inversion2.0ofthekernelthisisnotaproblem
*becausethere'
salocktoguaranteeonlyoneCPUwill
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- linux 内核 编程