Ubuntu下HelloWorld驱动实现全过程
今天是⼀个值得纪念的⽇⼦,我也不知道我花了多长时间才编译成功!在编写这个驱动的过程中,真的体会到驱动编程不是那么简单的~,现在有点⼩⼩的激动,真的,很不容易啊~我把整个过程在重复⼀遍吧!
在编写这个驱动的过程中参考了⽹上的很多⽂档,最终的结果是弄的我头都晕了,每个⼈写的都不⼀样,其实我现在还有⼀些概念不是很清楚。
1:到底丫的什么是内核源码树?
2:为什么要编译内核源码树?
1:内核源码树我现在的理解就是整个linux内核源代码,它是编译驱动的前提。系统默认情况下是没有的,
内核源码树是要⾃⼰下载的。
2:驱动最终以*.ko的形式⽣成,insmod的本质就是将ko⽂件与运⾏的内核进⾏链接的过程。类似于编译helloworld的链接过程。
链接必然需要先进⾏编译,以便确定所需的外部符号(EXPORT_SYMBOLS)是否存在,因为有些符号(函数或全局变量)在
内核中。在驱动中如果使⽤到这些符号,必须预留⼀个位置,insmod时进⼀步确定这些符号的具体位置(符号绑定)。
如果内核都没有编译过,怎么知道这些符号有没有编⼊内核中。
驱动程序简介
驱动程序作为系统内核的⼀部分,它⼯作在核⼼态,⽽应⽤程序⼯作在⽤户态。也就是说,不能直接通过指针,把⽤户空间的数据地址传递给内核(因为MMU映射的地址根本不⼀样)。要想在应⽤程序和驱动程序之间传递数据(指针),就需要经过转换。把⽤户态“看到”的空间地址转换成内核态可访问的地址。Linux系统提供了⼀系列⽅便的函数实现这种转换,如get_user、put_user、copy_from_user、
copy_to_user等,它们⾃⼰负责访问权限的检查,使⽤时,不需要关系更多的问题。
Linux内核把驱动程序划分为3种类型:字符设备、块设备和⽹络设备。
字符设备和块设备可以像⽂件⼀样被访问。它们的主要区别不在于能否seek,⽽是在于系统对于这两种类型设备的管理⽅式。应⽤程序对于字符设备的每⼀个I/O操作,都会直接传递给系统内核对应的驱动程序;⽽应⽤程序对于块设备的操作,要经过系统的缓冲区管理,间接传递给驱动程序处理。块设备的这种管理⽅式是为存储提供优化的;⽽字符设备的管理⽅式是为操作提供优化的。
⾄于⽹络设备,它在Linux系统中是⼀类⽐较特殊的设备它不像字符设备或块设备那样通过对应的设备⽂件节点去访问,内核也不再通过read和write等调⽤去访问⽹络设备。Linux的⽹络系统主要是基于BSD UNIX的套接字机制,在系统和驱动程序之间有专门的数据结构进⾏数据传输,系统⽀持对数据发送和数据接收缓存,提供流量控制机制,提供更多的协议⽀持。
在linux系统中,驱动程序都做成模块的形式,也就是module。简单的说,⼀个模块提供⼀个功能,这些模块是可以按照需要随时装⼊内核空间和从内核空间卸载的。因此,内核模块是为了给内核动态增减功能⽽设计的,并不仅仅是限于驱动程序。
因为内核模块需要加载到内核空间,所以其程序的编写与⼀般应⽤程序不同,在⾥⾯再也不到类似main()这样的⼊⼝函数。
Hello World驱动实现全过程
1:⾸先要查看⾃⼰系统是使⽤的内核版本
www.linuxidc @linux:~$ uname -r
3.2.0-34-generic-pae //我⽤的是ubuntu12.04版本的,⽐较⾼
如果系统⾃动安装了源码。在/usr/src⽬录下⾯会有对应的使⽤的版本,我那个版本没有,我是⾃⼰下载的
www.linuxidc @linux:/usr/src$ ls
linux-headers-3.2.0-34 linux-source-3.2.0(我下载的)
linux-headers-3.2.0-34-generic-pae
如果没有源码,查看⼀下可以下载的源码包(不要使⽤超级⽤户使⽤此命令,他会提⽰没有这个命令)
#apt-cache search linux-source
www.linuxidc @linux:/usr/src$ apt-cache search linux-source
linux-source - Linux kernel source with Ubuntu patches
linux-source-3.2.0 - Linux kernel source for version 3.2.0 with Ubuntu patches
2:然后下载linux-source-3.2.0
#sudo apt-get install linux-source-3.2.0
下载完成之后,会在/usr/src下,⽂件名为linux-source-3.2.0.tar.bz2这个压缩包,解压之后就可以得到整个源代码# sudo tar jxvf linux-source-3.2.0.tar.bz2
解压之后会⽣成⼀个新的⽬录/usr/src/linux-source-3.2.0,现在所有的源代码都在⾥⾯
3:现在开始配置内核,有三种选择选择⽅式1:make oldconfig 2:make menuconfig 3:make xconfig
我选择的是最快的配置原版的配置⽅法
#sudo make oldconfig
(如果配置的过程中出现问题,原因是因为你没有下载配置环境的那个依赖的库⽂件,可以⾃⼰下载)
配置完成之后就要开始对内核进⾏编译了
4:编译内核
#sudo make
这个过程很漫长,建议去做做别别的事情吧~,我⼤概花了⼀个多⼩时,吃完晚饭回来就ok了
#sudo make zImage // 或者 make bzImage
编译内核的结果最终出现了⼏个错误,但最终对那个Hello World 没有造成影响
执⾏结束之后,会在当前⽬录下⾯⽣成⼀个新的⽂件:vmlinux
5:然后就是编译模块了
#sudo make modules
6:安装模块
#sudo make modules_install
执⾏结束之后,会在/lib/modules下⽣成⼀个新的⽬录/lib/modules/3.2.31/
在随后的编译模块⽂件时,需要⽤到这个路径下的build⽬录。到这⼀步,内核就编译完成了
好了,下了很长时间的Hello World 程序现在终于可以派上⽤场了
我的Hello World在/home/xiongyao/下⾯
//hello.c
#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
linux内核设计与实现 pdfprintk(KERN_EMERG "Hello ,Linux Driver!\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_EMERG "Hello Diver Exit !\n");
}
module_init(hello_init);
module_exit(hello_exit);
Makefile ⽂件
obj-m := hello.o
KERNELDIR := /lib/modules/3.2.31/build
PWD :=$(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
.PHONY :clean
clean:
rm -rf *.o *ko
make -C $(KERNELDIR) M=$(PWD) modules
这句话的意思: -C是改变make的执⾏⽬录,$(KERNELDIR)是你本机的kernel的路径地址,
M=$(PWD) modules这个是使makefile在试图⽣成模块之前,回到模块源码⽬录,说的在直接点就是把该makefile同⽬录下的 modules 给到M,执⾏make指令。
obj-m :=hello.o⽬标⽂件依赖hello.o
最后关键的⼀步到了,所有准备就绪,进⼊makefile的那个⽬录
www.linuxidc @linux:~$ sudo make
make -C /lib/modules/3.2.31/build M=/home/xiongyao
make[1]: 正在进⼊⽬录 `/usr/src/linux-source-3.2.0/linux-source-3.2.0'
LD /home/xiongyao/built-in.o
CC [M] /home/xiongyao/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/d.o
LD [M] /home/xiongyao/hello.ko
make[1]:正在离开⽬录 `/usr/src/linux-source-3.2.0/linux-source-3.2.0'
上⾯证明已经成功了
然后加载模块
#sudo insmod ./hello.ko
本应该会在终端显⽰hello,world 但是终端什么也没有显⽰(以后在去解决)
查看加载模块
#sudo lsmod
⾥⾯已经⽣成了hello
hello 2560 0
^_^,已经加载上了
删除模块
#sudo rmmod hello
那么程序的输出到底在哪⾥呢?在⽹上看到,如果不出现在终端,则会下进syslog中
#cat /var/log/syslog |grep world
#Hello,world
#Goodbye,linux world
现在所有⼯作全部完成了,希望做这个Hello World 是我进⼊linux驱动的第⼀步~,相信你们也能编译的!成功的编译成功的。注意事项
程序中的MODULE_LICENSE("GPL")⽤于声明模块的许可证。
也可以如下编译:
gcc -c -I /usr/src/linux-2.4/include/ hello.c
运⾏:
insmod hello.o
<1>在gcc编译选项中增加-c
<2>在gcc编译选项中定义两个宏:-DMODULE -D__KERENL__
或直接在源⽂件中定义这两个宏:
#define MODULE
#define __KERNEL__
<3>在源⽂件中包括module.h⽂件:
#include "linux/module.h"
<4>假定你现在运⾏的内核的源码⽬录绝对路径是MyKernelSrcPath,在gcc编译时增加选项:
-I $MyKernelSrcPath/include (如-I /usr/src/linux/include)
<5>某些时候⽤insmod -f能够成功加载,但需谨慎使⽤。
<6>如果看不到⽤printk打印的信息,可以⽤dmesg命令看。
<7>打印消息受级别的限制,消息级别可以通过printk设置,如:
printk(" 《n》something"); /* 其中0<=n<=7 */
假设控制台的消息级别为m, 当n
这样⼀⽅⾯可以提⾼要打印消息本⾝的级别(数字越⼩级别越⾼),
另⼀⽅⾯可以改变控制台的消息级别(可从1到8),如改为8可⽤以下命令:
# echo "8" > /proc/sys/kernel/printk
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论