linux 模块编译步骤(详解)

上传人:沈*** 文档编号:155993125 上传时间:2022-09-25 格式:DOC 页数:15 大小:71KB
返回 下载 相关 举报
linux 模块编译步骤(详解)_第1页
第1页 / 共15页
linux 模块编译步骤(详解)_第2页
第2页 / 共15页
linux 模块编译步骤(详解)_第3页
第3页 / 共15页
点击查看更多>>
资源描述
Linux内核模块编程Linux内核模块编程是一个很重要的知识点。尤其是编写底层驱动程序时,一定会涉及到它。内核模块编程也是Tiger哥学习Linux时第一节课所接触的知识。由此可以看出它的important,也可以看出其实它很easy。一前言:1.什么是内核模块1内核模块是具有独立功能的程序。它可以被单独编译,但是不能单独运行,它的运行必须被链接到内核作为内核的一部分在内核空间中运行。2模块编程和内核版本密切相连,因为不同的内核版本中某些函数的函数名会有变化。因此模块编程也可以说是内核编程。3特点:模块本身不被编译进内核映像,从而控制了内核的大小;模块一旦被加载,就和内核中的其他部分完全一样。2.用户层编程和内核模块编程的区别应用程序内核模块程序使用函数libc库内核函数运行空间用户空间内核空间运行权限普通用户超级用户入口函数main()module_init出口函数exit()module_exit编译gccmakefile链接gccinsmod运行直接运行insmod调试gdbkdbug、kdb、kgdb二.说了这么多,那么怎么编写一个内核模块的程序呢?1.我们先来看两个最简单的函数实例,也是几乎所有程序员在学习一门新语言时都会编写的程序:输出hello world!现在我们分别用模块编程输出hello world!,和在用户层编程输出hello wrold!。通过这两个程序我们来分析下如何来编写一个内核模块程序。用户层编程:hello.c#includeint main(void)printf(hello world/n);内核编程: module.c#include #include #include MODULE_LICENSE(Dual BSD/GPL);static int hello_init(void)printk(KERN_ALERT hello,I am edsionte/n);return 0;static void hello_exit(void)printk(KERN_ALERT goodbye,kernel/n);module_init(hello_init);module_exit(hello_exit);/可选MODULE_AUTHOR(Tiger-John);MODULE_DESCRIPTION(This is a simple example!/n);MODULE_ALIAS(A simplest example);Tiger-John说明:1.相信只要是学过C语言的同学对第一个程序都是没有问题的。但是也许大家看了第二个程序就有些不明白了。可能有人会说:Tiger哥你没疯吧,怎么会把printf()这么简单的函数错写成了printk()呢。也有的人突然想起当年在大学学C编程时,老师告诉我们“一个C程序必须要有main()函数,并且系统会首先进入main()函数执行,那么你的程序怎么没有main()函数呢?没有main()函数程序是怎么执行的呢?可能也会有更仔细的人会发现:怎么两个程序头文件不一样呢?不是要用到输入和输出函数时,一定要用到这个头文件,你怎么没有呢?-Tiger哥很淡定的告诉大家其实第二个程序是正确的,现在我们就来看看到底如何来编写一个内核模块程序。2.内核模块编程的具体实现第一步:首先我们来看一下程序的头文件#include#include#include这三个头文件是编写内核模块程序所必须的3个头文件。Tiger-John说明:1由于内核编程和用户层编程所用的库函数不一样,所以它的头文件也和我们在用户层编写程序时所用的头文件也不一样。2我们在来看看在L inux中又是在那块存放它们的头文件a.内核头文件的位置:/usr/src/linux-2.6.x/include/b.用户层头文件的位置: /usr/include/现在我们就明白了。其实我们在编写内核模块程序时所用的头文件和系统函数都和用层 编程时所用的头文件和系统函数是 不同的。第二步:编写内核模块时必须要有的两个函数:1加载函数:static int init_fun(void)/初始化代码函数实例:static int hello_init(void)/不加void在调试时会出现报警printk(hello world!/n);return 0;2卸载函数无返回值static void cleaup_fun(void)/释放代码函数实例:static void hello_exit(void)/不加void会出现报警,若改为static int也会报错,因为出口函数是不能返会值的printk(bye,bye/n);在模块编程中必须要有上面这两个函数;Tiger-John补充:注册函数和卸载函数还有另一中写法:1模块加载函数static int _init init_fun(void)/初始化代码函数实例:static int _init hello_init(void)printk(hello tiger/n);return 0;2卸载函数无返回值static void _exit cleaup_fun(void)/释放代码函数实例:static void _exit exit(void)printk(bye bye!/n);Tiger-John补充:通过比较我们可以发现第二中函数的写法与第一中函数的写法主要不同就是加了_init和_exit前缀。(init和exit前面都是两个下划线)那么第二种方法比第一种有什么好处呢:_init和_exit是Linux内核的一个宏定义,使系统在初始化完成后释放该函数,并释放其所占内存。因此它的优点是显而易见的。所以建议大家啊在编写入口函数和出口函数时采用第二中方法。(1)在linux内核中,所有标示为_init的函数在连接的时候都放在.init.text这个区段内,此外,所有的_init函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些_init函数,并在初始化完成后释放init区段(包括.init.text,.initcall.init等)。(2)和_init一样,_exit也可以使对应函数在运行完成后自动回收内存。3现在我们来看一下printk()函数a.上面已经说了,我们在内核 编程时所用的库函数和在用户态下的是不一样的。printk是内核态信息打印函数,功能和比准C库的printf类似。printk还有信息打印级别。b.现在我们来看一下printk()函数的原型:int printk(const char *fmt, .)消息打印级别:fmt-消息级别:#define KERN_EMERG /*紧急事件消息,系统崩溃之前提示,表示系统不可用*/#define KERN_ALERT /*报告消息,表示必须立即采取措施*/#define KERN_CRIT /*临界条件,通常涉及严重的硬件或软件操作失败*/#define KERN_ERR /*错误条件,驱动程序常用KERN_ERR来报告硬件的错误*/#define KERN_WARNING /*警告条件,对可能出现问题的情况进行警告*/#define KERN_NOTICE /*正常但又重要的条件,用于提醒。常用于与安全相关的消息*/#define KERN_INFO /*提示信息,如驱动程序启动时,打印硬件信息*/#define KERN_DEBUG /*调试级别的消息*/Tiger-John说明:不同级别使用不同字符串表示,数字越小,级别越高。c.为什么内核态使用printk()函数,而在用户态使用printf()函数。printk()函数是直接使用了向终端写函数tty_write()。而printf()函数是调用write()系统调用函数向标准输出设备写。所以在用户态(如进程0)不能够直接使用printk()函数,而在内核态由于它已是特权级,所以无需系统调用来改变特权级,因而能够直接使用printk()函数。printk是内核输出,在终端是看不见的。我们可以看一下系统日志。但是我们可以使用命令:cat /var/log/messages,或者使用dmesg命令看一下输出的信息。第三步: 加载模块和卸载模块1module_init(hello_init)a.告诉内核你编写模块程序从那里开始执行。b.module_init()函数中的参数就是注册函数的函数名。2module_exit(hello_exit)a.告诉内核你编写模块程序从那里离开。b.module_exit()中的参数名就是卸载函数的函数名。Tiger-John说明:我们一般在注册函数里进行一些初始化比如申请内存空间注册设备号等 。那么我们就要在卸载函数进行释放我们所占有的资源。(1)若模块加载函数注册了XXX,则模块卸载函数应该注销XXX(2)若模块加载函数动态申请了内存,则模块卸载函数应该注销XXX(3)若模块加载函数申请了硬件资源(中断,DMA通道)的占用,则模块卸载函数应该释放这些硬件资源。(4)若模块加载函数开启了硬件,则卸载函数中一般要关闭硬件。第四步:许可权限的声明1函数实例:MODULE_LICENSE(Dual BSD/GPL);2此处可有可无,可以不加系统默认(但是会报警)模块声明描述内核模块的许可权限,如果不声明LICENSE,模块被加载时,将收到内核的警告。在Linux2.6内核中,可接受的LICENSE包括GPL,GPL v2,GPL and additional rights,Dual BSD/GPL,Dual MPL/GPL,Proprietary。第五部:模块的声明与描述(可加可不加)MODULE_AUTHOR(“author”);/作者MODULE_DESCRIPTION(“description”);/描述MODULE_VERSION(”version_string“);/版本MODULE_DEVICE_TABLE(“table_info”);/设备表对于USB,PCI等设备驱动,通常会创建一个MODULE_DEVICE_TABLEMODULE_ALIAS(”alternate_name“);/别名Tiger-John:总结经过以上五步(其实只要前四步)一个完整的模块编程就完成了。第六步:常用的模块编程命令:1在Linux系统中,使用lsmod命令可以获得系统中加载了的所有模块以及模块间的依赖关系2也可以用cat /proc/modules来查看加载模块信息3内核中已加载模块的信息也存在于/sys/module目录下,加载hello.ko后,内核中将包含/sys/module/hello目录,该目录下又包含一个refcnt文件和一个sections目录,在/sys/module/hello目录下运行tree -a可以看到他们之间的关系。4使用modinfo 命令可以获得模块的信息,包括模块的作者,模块的说明,某块所支持的参数以及vermagic.但是,前面我们已经说过了。内核编程和用户层编程它们之间的编译链接也不相同。那么我们 如何对模块程序进行编译,链接,运行呢?现在我么继续深入来学习Makefile文件的编写:三.make的使用以及Makefile的编写1.什么是Makefile,make1Makefile是一种脚本,这种脚本主要是用于多文件的编译2make程序可以维护具有相互依赖性的源文件,但某些文件发生改变时,它能自动识别出,并只对相应 文件进行自动编译2.Makefile的写法Makefile文件由五部分组成:显示规则 含规则 变量定义makefile指示符和注释一条Make的规则原型为:目标.:依赖.命令.makefile中可以使用Shell命令,例如pwd,uname简单的makefile文件:obj-m := hello.okernel_path=/usr/src/linux-headers-$(shell uname -r)all:make -C $(kernel_path) M=$(PWD) modulesclean:make -C $(kernel_path) M=$(PWD) cleanobj -m:= hello.o /产生hello模块的目标kernel_path /定义内核源文件目录all:make -C $(kernel_path) M=$(PWD) modules/生成内核模块参数为内核源代码目录以及模块所在目录clean:make -C $(kernel_path) M=$(PWD) clean/清除生成的模块文件以及中间文件Tiger-John说明:1在all和clean下面的一行,即make之前必须用Table符隔开,不能用空格隔开,否则编译错误。2其中-C后指定的是Linux内核源代码的目录,而M=后指定的是hello.c和Makefile所在的目录3.Makefile实例:1 obj-m:=module.o234 CURRENT_PATH :=$(shell pwd)5 VERSION_NUM :=$(shell uname -r)6 LINUX_PATH :=/usr/src/linux-headers-$(VERSION_NUM)78 all :9 make -C $(LINUX_PATH) M=$(CURRENT_PATH) modules10 clean :11 make -C $(LINUX_PATH) M=$(CURRENT_PATH) clean-经过上面的模块编程和Makefile的编程,我们就可以对我们的程序进行编译链接和运行了四.内核模块的操作过程1在控制台输入make进行编译链接2正确后在控制台输入sudo insmod module.ko(加载模块)3在控制台输入dmesg查看结果4在控制台输入rmmod tiger(卸载模块)5输入dmesg查看结果(或者用cat /var/log/messages查看系统日志文件)6make clean(去除中间生成的文件)-现在我们就总体来实践一下,来体验一下。编写内核模块程序的乐趣module.c1 #include2 #include3 #include4 MODULE_LICENSE(Dual BSD/GPL);56 static int _init hello_init(void)7 8 printk(Hello world/n);9 return 0;10 1112 static void _exit hello_exit(void)13 14 printk(Bye Corne/n);1516 17 module_init(hello_init);18 module_exit(hello_exit);Makefile1 obj-m:=module.o234 CURRENT_PATH :=$(shell pwd)5 VERSION_NUM :=$(shell uname -r)6 LINUX_PATH :=/usr/src/linux-headers-$(VERSION_NUM)78 all :9 make -C $(LINUX_PATH) M=$(CURRENT_PATH) modules10 clean :11 make -C $(LINUX_PATH) M=$(CURRENT_PATH) clean在终端输入makethinkubuntu:/work/moudule/mokua_biancheng$makemake -C /usr/src/linux-headers-2.6.32-25-generic M=/home/think/work/moudule/mokua_biancheng modulesmake1:正在进入目录/usr/src/linux-headers-2.6.32-25-generic Building modules, stage 2. MODPOST 1 modulesmake1:正在离开目录/usr/src/linux-headers-2.6.32-25-genericthinkubuntu:/work/moudule/mokua_biancheng$thinkubuntu:/work/moudule/mokua_biancheng$ sudo insmod module.kothinkubuntu:/work/moudule/mokua_biancheng$ dmesg19011.002597 Hello worldTiger-John:当程序没有错误时,当我们输入dmesg时就可以看到程序运行的结果了。
展开阅读全文
相关资源
相关搜索

最新文档


当前位置:首页 > 管理文书 > 施工组织


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!