|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
在学习初期,你一定会遇到很多困难,或者说各种困难,所以你最好先将你linux中的重要内容备份,因为,在你学习的过程中,很可能将系统搞废(eg:源混乱等);
byValerieHenson
07/05/2007
(译者注:本文的例子是只能在linux的2.6内核下利用的,2.6以上的内核,译者没有做过实行,2.4是要修正make文件才干运转。)
原文出自:这里
译文来自:http://cocre.com/?p=566
自古以来,进修一门新编程言语的第一步就是写一个打印“helloworld”的程序。在本文中,我们将用一样的体例进修怎样编写一个复杂的linux内核模块和设备驱动程序。我将进修到怎样在内核形式下以三种分歧的体例来打印helloworld,这三种体例分离是:printk(),/proc文件,/dev下的设备文件。
筹办:安装内核模块的编译情况
一个内核模块kernelmodule是一段能被内核静态加载和卸载的内核代码,由于内核模块程序是内核的一个部分,而且和内核严密的交互,以是内核模块不成能离开内核编译情况,最少,它必要内核的头文件和用于加载的设置信息。编译内核模块一样必要相干的开辟工具,好比说编译器。为了简化,本文只扼要会商怎样在Debian、Fedora和其他以.tar.gz情势供应的原版linux内核下举行核模块的编译。在这类情形下,你必需依据你正在运转内核绝对应的内核源代码来编译你的内核模块kernelmodule(当你的内核模块一旦被装载到你内核中时,内核就将实行该模块的代码)
必需要注重内核源代码的地位,权限:内核程序一般在/usr/src/linux目次下,而且属主是root。现在,保举的体例是将内核程序放在一个非root用户的home目次下。本文中一切命令都运转在非root的用户下,只要在需要的时分,才利用sudo来取得一时的root权限。设置和利用sudo能够mansudo(8)visudo(8)和sudoers(5)。大概切换到root用户下实行相干的命令。不论甚么体例,你都必要root权限才干实行本文中的一些命令。
在Debian下编译内核模块的筹办
利用以下的命令安装和设置用于在Debian编译内核模块的module-assitant包
$sudoapt-getinstallmodule-assistant以此你就能够入手下手编译内核模块,你能够在《DebianLinuxKernelHandbook》这本书中找到对Debian内核相干义务的更深度的会商。
Fedora的kernel-devel包包括了你编译Fedora内核模块的一切需要内核头文件和工具。你能够经由过程以下命令失掉这个包。
$sudoyuminstallkernel-devel有了这个包,你就能够编译你的内核模块kernelmodules。关于Fedora编译内核模块的相干文档你能够从Fedorareleasenotes中找到。
一样平常Linux内核源代码和设置
(译者注,上面的编译很庞大,假如你的Linux不是下面的体系,你可使用REHLAS4体系,这个体系的内核就是2.6的内核,而且能够经由过程安装间接安装内核编译撑持情况,从而就省下了以下的步骤。并且上面的步骤对照庞大,倡议在假造机安装Linux举行实行。)
假如你选择利用一样平常的Linux内核源代吗,你必需,设置,编译,安装和重启的你编译内核。这个历程十分庞大,而且本文只会会商利用一样平常内核源代码的基础观点。
linux的出名的内核源代码在http://kernel.org上都能够找到。比来新公布的不乱版本的代码在首页上。下载全版本的源代码,不要下载补钉代码。比方,以后公布不乱版本在url:http://kernel.org/pub/linux/kernel/v2.6/linux-2.6.21.5.tar.bz2上。假如必要更疾速的下载,从htpp://kernel.org/mirrors上找到比来的镜像举行下载。最复杂取得源代码的体例是以断点续传的体例利用wget。现在的http很少产生中止,可是假如你鄙人载过程当中产生了中止,这个命令将匡助你持续下载剩下的部分。
$wget-chttp://kernel.org/pub/linux/kernel/v2.6/linux-2.6.21.5.tar.bz2解包内核源代码
$tarxjvflinux-<version>.tar.bz2如今你的内核源代码位于linux-/目次下。转到这个目次下,并设置它:
$cdlinux-<version>$makemenuconfig一些十分易用的编译方针maketargets供应了多种编译安装内核的情势:Debian包,RPM包,gzip后的tar文件等等,利用以下命令检察一切能够编译的方针情势
$makehelp一个能够事情在任何linux的方针是:(译者注:REHLAS4上没有tar-pkg这个方针,你能够任选一个rpm编译,编译完后再下层目次能够看到有一个linux-.tar.gz可使用)
$maketar-pkg当编译完成后,能够挪用以下命令安装你的内核
$sudotar-C/-xvflinux-<version>.tar在尺度地位创建的到内核源代码的链接
$sudoln-s<locationoftop-levelsourcedirectory>/lib/modules/"uname-r"/build如今已内核源代码已能够用于编译内核模块了,重启你的呆板以使得你依据新内核程序编译的内核能够被装载。
利用printk()函数打印”HelloWorld”
我们的第一个内核模块,我们将以一个在内核中利用函数printk()打印”Helloworld”的内核模块为入手下手。printk是内核中的printf函数。printk的输入打印在内核的动静缓存kernelmessagebuffer并拷贝到/var/log/messages(关于拷贝的变更依附于怎样设置syslogd)
下载hello_printk模块的tar包并解包:
$tarxzvfhello_printk.tar.gz这个包中包括两个文件:Makefile,内里包括怎样创立内核模块的指令和一个包括内核模块源代码的hello_printk.c文件。起首,我们将扼要的过一下这个Makefile文件。
obj-m:=hello_printk.oobj-m指出将要编译成的内核模块列表。.o格局文件会主动地有响应的.c文件天生(不必要显现的排列一切源代码文件)
KDIR:=/lib/modules/$(shelluname-r)/buildKDIR暗示是内核源代码的地位。在以后尺度情形是链接到包括着正在利用内查对应源代码的目次树地位。
PWD:=$(shellpwd)PWD唆使了以后事情目次而且是我们本人内核模块的源代码地位
default:$(MAKE)-C$(KDIR)M=$(PWD)modulesdefault是默许的编译毗连方针;即,make将默许实行本条划定规矩编译方针,除非程序员显现的指明编译其他方针。这里的的编译划定规矩的意义是,在包括内核源代码地位的中央举行make,然后之编译$(PWD)(以后)目次下的modules。这里同意我们利用一切界说在内核源代码树下的一切划定规矩来编译我们的内核模块。
如今我们来看看hello_printk.c这个文件
1.#include2.<linux/init.h>3.#include4.<linux/module.h>这里包括了内核供应的一切内核模块都必要的头文件。这个文件中包括了相似module_init()宏的界说,这个宏稍后我们将用到
1.staticint__init2.hello_init(void){3.printk("Hello,world!n");4.return0;5.}这是内核模块的初始化函数,这个函数在内核模块初始化被装载的时分挪用。__init关头字告知内核这个代码只会被运转一次,并且是在内核装载的时分。printk()函数这一即将打印一个”Hello,world”到内核动静缓存。printk参数的情势在年夜多半情形和printf(3)千篇一律。
1.module_init(hello_init);2.module_init()宏告知内核当内核模块第一次运转时哪个函数将被运转。任安在内核模块中其他部分城市遭到内核模块初始化函数的影响。
1.staticvoid__exit2.hello_exit(void){3.printk("Goodbye,world!n");4.}5.module_exit(hello_exit);一样地,加入函数也只在内核模块被卸载的时分会运转一次,module_exit()宏标示了加入函数。__exit关头字告知内核这段代码只在内核模块被卸载的时分运转一次。
1.MODULE_LICENSE("GPL");2.MODULE_AUTHOR("ValerieHensonval@nmt.edu");3.MODULE_DESCRIPTION("Hello,world!"minimalmodule");4.MODULE_VERSION("printk");5.MODULE_LICENSE()宏告知内核,内核模块代码在甚么样的license之下,这将影响主那些标记(函数和变量,等等)能够会见主内核。GPLv2下的模块(好像本例子中)能会见一切的标记。某些内核模块license将会伤害内核开源的特征,这些license唆使内核将装载一些非公然或不受信的代码。假如内核模块不利用MODULE_LICENSE()宏,就被假定为非GPLv2的,这会伤害内核的开源特征,而且年夜部分Linux内核开辟职员城市疏忽来自受损内核的bug呈报,由于他们没法会见一切的源代码,这使得调试变得加倍坚苦。剩下的MODULE_*()这些宏以尺度格局供应有效的标示该内核模块的信息(译者注:这里意义是,你必需利用GPLv2的license,不然你的驱动程序很有大概得不到Linux社区的开辟者的撑持:))
如今,入手下手编译和运转代码。转到响应的目次下,编译内核模块
$cdhello_printk$make接着,装载内核模块,利用insmod指令,而且经由过程dmesg来反省打印出的信息,dmesg是打印内核动静缓存的程序。
$sudoinsmod./hello_printk.ko$dmesg|tail你将从dmesg的屏幕输入中瞥见”Helloworld!”信息。如今卸载利用rmmod卸载内核模块,并反省加入信息。
$sudormmodhello_printk$dmesg|tail到此,你就乐成地完成了对内核模块的编译和安装!
利用/proc的Hello,World!
一种用户程序和内核通信最复杂和盛行的体例是经由过程利用/proc下文件体系举行通信。/proc是一个伪文件体系,从这里的文件读取的数据是由内核前往的数据,而且写进到这内里的数据将会被内核读取和处置。在利用/proc体例之前,所用用户和内核之间的通信都不能不利用体系挪用来完成。利用体系挪用意味着你将在要在查找已具有你必要的举动体例的体系挪用(一样平常不会呈现这类情形),大概创立一种新的体系挪用来满意你的需求(如许就请求对内核全局做修正,并增添体系挪用的数目,这是一般长短常欠好的做法),大概利用ioctl这个全能体系挪用,这就请求要创立一个新文件范例供ioctl操纵(这也长短常庞大并且bug对照多的体例,一样长短常烦琐的)。/proc供应了一个复杂的,无需界说的体例在用户空间和内核之间传送数据,这类体例不但能够满意内核利用,一样也供应充足的自在度给内核模块做他们必要做的事变。
为了满意我们的请求,我们必要当我们读在/proc下的某一个文件时将会前往一个“Helloworld!”。我们将利用/proc/hello_world这个文件。下载并解开helloproc这个gzip的tar包后,我们将起首来看一下hello_proc.c这个文件
1.#include<linux/init.h>2.#include<linux/module.h>3.#include<linux/proc_fs.h>此次,我们将增添一个proc_fs头文件,这个头文件包含驱动注册到/proc文件体系的撑持。当别的一个历程挪用read()时,下一个函数将会被挪用。这个函数的完成比一个完全的一般内核驱动的read体系挪用完成要复杂的多,由于我们仅做了让”Helloworld”这个字符串缓存被一次读完。
1.staticint2.hello_read_proc(char*buffer,char**start,off_toffset,3.intsize,int*eof,void*data)4.{这个函数的参数值得明白的注释一下。buffer是指向内核缓存的指针,我们将把read输入的内容写到这个buffer中。start参数多用更庞大的/proc文件;我们在这里将疏忽这个参数;而且我只明白的同意offset这个的值为0。size是指buffer中包括多字节数;我们必需反省这个参数已制止呈现内存越界的情形,eof参数一个EOF的简写,用于前往文件是不是已读到停止,而不必要经由过程挪用read前往0来判别文件是不是停止。这里我们不会商依托更庞大的/proc文件传输数据的办法。这个函数办法体排列以下:
01.char*hello_str="Hello,world!";02.intlen=strlen(hello_str);/*Don"tincludethenullbyte.*/03./**Weonlysupportreadingthewholestringatonce.*/04.if(size<len)05.return<-EINVAL;06./**Iffilepositionisnon-zero,thenassumethestringhas07.*beenreadandindicatethereisnomoredatatoberead.08.*/09.if(offset!=0)10.return0;11./**Weknowthebufferisbigenoughtoholdthestring.*/12.strcpy(buffer,hello_str);13./**SignalEOF.*/14.*eof=1;15.returnlen;16.}上面,我们需将内核模块在初始化函数注册在/proc子体系中。
01.staticint__init02.hello_init(void){03./*04.*Createanentryin/procnamed"hello_world"thatcalls05.*hello_read_proc()whenthefileisread.06.*/07.if(create_proc_read_entry("hello_world",0,08.NULL,hello_read_proc,NULL)==0){09.printk(KERN_ERR10."Unabletoregister"Hello,world!"procfilen");11.return-ENOMEM;12.}13.return0;14.}15.module_init(hello_init);当内核模块卸载时,必要在/proc移出注册的信息(假如我们不如许做的,当一个历程试图往会见/proc/hello_world,/proc文件体系将会试着实行一个已不存在的功效,如许将会招致内核溃散)
01.staticvoid__exit02.hello_exit(void){03.remove_proc_entry("hello_world",NULL);04.}05.module_exit(hello_exit);06.MODULE_LICENSE("GPL");07.MODULE_AUTHOR("ValerieHensonval@nmt.edu");08.MODULE_DESCRIPTION(""Hello,world!"minimalmodule");09.MODULE_VERSION("proc");上面我们将筹办编译和装载模组
cdhello_proc$make$sudoinsmod./hello_proc.ko如今,将会有一个称为/proc/hello_world的文件,而且读这个文件的,将会前往一个”Helloworld”字符串。
$cat/proc/hello_worldHello,world!你能够为为统一个驱动程序创立多个/proc文件,并增添响应写/proc文件的函数,创立包括多个/proc文件的目次,大概更多的其他操纵。假如要写比这个更庞大的驱动程序,可使用seq_file函数集来编写是更平安和简单的。关于这些更多的信息能够看《Driverporting:Theseq_fileinterface》
Hello,World!利用/dev/hello_world
如今我们将利用在/dev目次下的一个设备文件/dev/hello_world完成”Hello,world!”。追述之前的日子,设备文件是经由过程MAKEDEV剧本挪用mknod命令在/dev目次下发生的一个特定的文件,这个文件和设备是不是运转在改呆板上有关。到厥后设备文件利用了devfs,devfs在设备第一被会见的时分创立/dev文件,如许将会招致良多风趣的加锁成绩和屡次翻开设备文件的反省设备是不是存在的重试成绩。以后的/dev版本撑持被称为udev,由于他将在用户程序空间创立到/dev的标记毗连。当内核模块注册设备时,他们将呈现在sysfs文件体系中,并mount在/sys下。一个用户空间的程序,udev,注重到/sys下的改动将会依据在/etc/udev/下的一些划定规矩在/dev下创立相干的文件项。
下载helloworld内核模块的gzip的tar包,我们将入手下手先看一下hello_dev.c这个源文件。
1.#include<linux/fs.h>2.#include<linux/init.h>3.#include<linux/miscdevice.h>4.#include<linux/module.h>5.#include<asm/uaccess.h>正如我们看到的必需的头文件外,创立一个新设备还必要更多的内核头文件撑持。fs.sh包括一切文件操纵的布局,这些布局将由设备驱动程序来填值,并联系关系到我们相干的/dev文件。miscdevice.h头文件包括了对通用miscellaneous设备文件注册的撑持。asm/uaccess.h包括了测试我们是不是违反会见权限读写用户内存空间的函数。hello_read将在其他历程在/dev/hello挪用read()函数被挪用的是一个函数。他将输入”Helloworld!”到由read()传进的缓存。
01.staticssize_thello_read(structfile*file,char*buf,size_tcount,loff_t*ppos)02.{03.char*hello_str="Hello,world!n";04.intlen=strlen(hello_str);/*Don"tincludethenullbyte.*/05./**Weonlysupportreadingthewholestringatonce.*/06.if(count<len)07.return-EINVAL;08./*09.*Iffilepositionisnon-zero,thenassumethestringhas10.*beenreadandindicatethereisnomoredatatoberead.11.*/12.if(*ppos!=0)13.return0;14./*15.*Besidescopyingthestringtotheuserprovidedbuffer,16.*thisfunctionalsochecksthattheuserhaspermissionto17.*writetothebuffer,thatitismapped,etc.18.*/19.if(copy_to_user(buf,hello_str,len))20.return-EINVAL;21./*22.*Telltheuserhowmuchdatawewrote.23.*/24.*ppos=len;25.returnlen;26.}下一步,我们创立一个文件操纵布局fileoperationsstruct,并用这个布局来界说当文件被会见时实行甚么举措。在我们的例子中我们独一存眷的文件操纵就是read。
1.staticconststructfile_operationshello_fops={2..owner=THIS_MODULE,3..read=hello_read,4.};如今,我们将创立一个布局,这个布局包括有效于在内核注册一个通用miscellaneous驱动程序的信息。
01.staticstructmiscdevicehello_dev={02./*03.*Wedon"tcarewhatminornumberweendupwith,sotellthe04.*kerneltojustpickone.05.*/06.MISC_DYNAMIC_MINOR,07./*08.*Nameourselves/dev/hello.09.*/10."hello",11./*12.*Whatfunctionstocallwhenaprogramperformsfile13.*operationsonthedevice.14.*/15.&hello_fops16.};在一般情形下,我们在init中注册设备
01.staticint__init02.hello_init(void){03.intret;04./*05.*Createthe"hello"deviceinthe/sys/class/miscdirectory.06.*Udevwillautomaticallycreatethe/dev/hellodeviceusing07.*thedefaultrules.08.*/09.ret=misc_register(&hello_dev);10.if(ret)11.printk(KERN_ERR12."Unabletoregister"Hello,world!"miscdevicen");13.returnret;14.}15.module_init(hello_init);接下是在卸载时的加入函数
01.staticvoid__exit02.hello_exit(void){03.misc_deregister(&hello_dev);04.}05.module_exit(hello_exit);06.MODULE_LICENSE("GPL");07.MODULE_AUTHOR("ValerieHensonval@nmt.edu>");08.MODULE_DESCRIPTION(""Hello,world!"minimalmodule");09.MODULE_VERSION("dev");编译并加载模块:
$cdhello_dev$make$sudoinsmod./hello_dev.ko如今我们将有一个称为/dev/hello的设备文件,而且这个设备文件被root会见时将会发生一个”Hello,world!”
$sudocat/dev/helloHello,world!可是我们不克不及利用一般用户会见他:
$cat/dev/hellocat:/dev/hello:Permissiondenied$ls-l/dev/hellocrw-rw----1rootroot10,612007-06-2014:31/dev/hello这是有默许的udev划定规矩招致的,这个条规将标明当一个一般设备呈现时,他的名字将会是/dev/,而且默许的会见权限是0660(用户和组读写会见,其他用户没法会见)。我们在实在情形中大概会但愿创立一个被一般用户会见的设备驱动程序,而且给这个设备起一个响应的毗连名。为到达这个目标,我们将编写一条udev划定规矩。
udev划定规矩必需做两件事变:第一创立一个标记毗连,第二修正设备的会见权限。
上面这条划定规矩能够到达这个目标:
KERNEL=="hello",SYMLINK+="hello_world",MODE="0444"我们将具体的分化这条划定规矩,并注释每个部分。KERNEL==”hello”标示上面的的划定规矩将感化于/sys中设备名字”hello”的设备(==是对照符)。hello设备是我们经由过程挪用misc_register()并传送了一个包括设备名为”hello”的文件操纵布局file_operations为参数而到达的。你能够本人经由过程以下的命令在/sys下检察
$ls-d/sys/class/misc/hello//sys/class/misc/hello/SYMLINK+=”hello_world”的意义是在标记链接列表中增添(+=标记的意义着追加)一个hello_world,这个标记毗连在设备呈现时创立。在我们场景下,我们晓得我们的列表的中的只要这个标记毗连,可是其他设备驱动程序大概会存在多个分歧的标记毗连,因而利用将设备追到场到标记列表中,而不是掩盖列表将会是更好的理论中的做法。
MODE=”0444″的意义是原始的设备的会见权限是0444,这个权限同意用户,组,和其他用户能够会见。
一般,利用准确的操纵标记(==,+=,or=)长短常主要的,不然将会呈现不成预知的情形。
如今我们了解这个划定规矩是怎样事情的,让我们将其安装在/etc/udev目次下。udev划定规矩文件以和SystemV初始剧本目次定名的同种体例的目次下,/etc/udeve/rules.d这个目次,并以字母/数字的按次。和SystemV的初始化剧本一样,/etc/udev/rules.d下的目次一般标记毗连到真实的文件,经由过程利用标记毗连名,将使得划定规矩文件已准确的序次失掉实行。
利用以下的命令,拷贝hello.rules文件从/hello_dev目次到/etc/udev目次下,并创立逐一个开始被实行的划定规矩文件链接在/etc/udev/rules.d目次下。
$sudocphello.rules/etc/udev/$sudoln-s../hello.rules/etc/udev/rules.d/010_hello.rules如今我们从头装载驱动程序,并察看新的驱动程序项
$sudormmodhello_dev$sudoinsmod./hello_dev.ko$ls-l/dev/hello*cr--r--r--1rootroot10,612007-06-1921:21/dev/hellolrwxrwxrwx1rootroot52007-06-1921:21/dev/hello_world->hello最初,反省你可使用一般用户会见/dev/hello_world设备.
$cat/dev/hello_worldHello,world!$cat/dev/helloHello,world!更多编写udev划定规矩的信息能够在DanielDrake的文章Writingudevrules中找到。
买一本命令参考手册是必要的,遇到不知道怎么用的命令可以随时查询,这要比查man文档快.特别适合英语不好。 |
|