今天起开始学习LINUX设备驱动,在此更新博客与大家分享学习的经验及收获。

很多人和我一样应该都是从应用开发,想着手开始驱动的学习,我现在也是在做嵌入式这方面的开发,但对于驱动的掌握也不太好,我现在手上有本《LINUX设备驱动程序》第三版的书,我通过这本书及网上资源的学习,尽量每天都把学习的内容及编写的代码贴出来,下面就开始了。

首先必须要有LINUX操作系统的环境,建议在虚拟机上装,硬盘分配的空间多一点,我现在用的是Ubantu10.4的,内核版本是2.6.32的版本。我硬盘分的大小是60G,不过现在发现已经不太够用,因为一个安卓的源代码都十几G了,反正如果空间够,尽量分配多点空间给虚拟机。安装时最好把所有的工具都安装上,以防要用的时候不能马上用上。

要想为内核构造模块,必须在自己的系统中配置并构造好内核树 驱动程序和用户程序可不一样,它是作为一个模块连接到内核模块来运行的,运行在内核空间里面。 所以要运行我们自己构造的模块,需要自己的系统已经配置好内核树,然后把目标模块和内核树连接起来运行。
     
    1、构建内核树:
     (1)查看自己的系统是否已经有了内核树。
如果在该目录下已存在build文件夹,则代表你已经有内核数了,如果没有,那也没事,那就得自己构建内核树了。
     (2)构建内核树:我虚拟机上已经有了,在网上我查到了如下构建内核树的步骤。

内核树构建过程

安装编译内核所需要的软件(也可不装,除非你要用 make menuconfig,用make oldconfig不要)

sudo apt-get install build-essential kernel-package libncurses5-dev fakeroot

下载内核源码

先查看linux内核版本:$uname -r

网上说用apt-cache search linux-source命令, 会列出一些可选源码包,对准你的内核版本号,选择“with Ubuntu patche”的那个

最后用apt-get install linux-source-2.6.35下载之。解压缩源码包,进入解压后的源码目录。

 可是我试了,搜不到,但是还是可以直接用上面的apt-get 命令下载的,但是我下载,也可以直接到这个网址下源代码,这里面有各个版本的内核,从1.0到2.6的,都有。

在编译之前我们需要Ubuntu原来内核的一个配置文件,这是我/usr/src目录下的文件预览:ls -al

drwxr-xr-x  4 root root     4096 2010-09-04 21:31 fglrx-8.723.1

drwxr-xr-x 24 root root     4096 2010-09-04 20:35 linux-headers-2.6.35-22

drwxr-xr-x  7 root root     4096 2010-09-04 20:35 linux-headers-2.6.35-22-generic

drwxr-xr-x 25 root root     4096 2010-09-16 21:39 linux-source-2.6.35

-rw-r--r--  1 root root 65846876 2010-09-01 22:41 linux-source-2.6.35.tar.bz2

现在我们需要/boot目录下的config-2.6.35-22-generic文件,我们把它拷贝到我们刚下好解压的目录,也就是linux-source-2.6.35

sudo cp /boot/config-2.6.35-22-generic /usr/src/linux-source-2.6.35/.config

接下来切换到root用户

sudo -i

cd /usr/src/linux-source-2.6.35

make menuconfig或者直接make oldconfig(无需拷贝.config)

终端会弹出一个配置界面

最后有两项:load a alternative kernel configuration... 

save a alternative configuration... 

选择load a kernel configuration保存,然后在选择save akernel configuration再保存退出,并退出配置环境。

 

接下来我们就要开始编译了。

#cd /usr/src/linux-source-2.6.35

#make

记住一定要是管理员帐号运行,这个过程很久,如果你的cpu是双核的可以在make后面加个参数,make -j4.

#make bzImage 执行结束后,可以看到在当前目录下生成了一个新的文件: vmlinux, 其属性为-rwxr-xr-x。 


2、构建完内核树,就可以开始驱动程序的编写及运行了。

以下通过实现打印Hello,world。来编写第一个驱动程序

hello.c
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");


static int hello_init(void)
{
	printk(KERN_ALERT "Hello, world\n");
	return 0;
}


static void hello_exit(void)
{
	printk(KERN_ALERT "Goodbye, cruel world\n");
}

module_init(hello_init);
module_exit(hello_exit);

Makefile
ifeq ($(KERNELRELEASE),)
    # Assume the source tree is where the running kernel was built
    # You should set KERNELDIR in the environment if it's elsewhere
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    # The current directory is passed to sub-makes as argument
    PWD := $(shell pwd)
modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean
else
    # called from kernel build system: just declare what our modules are
    obj-m := hello.o 
endif

以上为hello.c及Makefile文件,通过make即可得到hello.ko文件,该文件即为将要加载如内核的模块。

hello_init(void)函数将在模块被装载入内核时调用。

hello_exit(void)函数将在模块被移除时调用。

printk()函数类似于printf函数,用于信息打印。

Makefile看不懂的话,不急,先把程序Make下,接下来会专门把makefile的知识进行一次解读的,务必做到简单,每个人都会用,现在先跳过。

根据函数的功能,可以预期代码实现的效果为,当模块载入时,打印“Hello,world”
当模块被移除时,打印“Goodbye,cruel world”.
接下来让我们把hello.ko模块载入内核,在命令行中敲入insmod hello.ko,此动作将把hello.ko载入内核。
相似的rmmod则为移除。从字面上很容易理解两个操作的含义。以下为实际操作情况。

不对,那个美好的Hello,world并没有输出耶!
是不是程序有问题?那大家想想问题会是出在哪里呢?
很简单的代码,很容易想到,问题出在printk函数上。
让我们来看下代码:
	printk(KERN_ALERT "Hello, world\n");
是的,KERN_ALERT这个参数出现了问题。
接下来讲解下关于printk的简单知识,你就清楚为什么没打印了。
3、printk相关注意事项:
   printf和printk的一个不同的地方
  用printk,内核会根据日志级别,可能把消息打印到当前控制台上,这个控制台通常是一个字符模式的终端、一个串口打印机或是一个并口打印机。这些消息正常输出的前提是──日志输出级别小于console_loglevel(在内核中数字越小优先级越高)。
  没有指定日志级别的printk语句默认采用的级别是 DEFAULT_ MESSAGE_LOGLEVEL(这个默认级别一般为<4>,即与KERN_WARNING在一个级别上),其定义在linux26/kernel/printk.c中可以找到
  日志级别一共有8个级别,printk的日志级别定义如下(在include/linux/kernel.h中):
  #define KERN_EMERG    0
  #define KERN_ALERT     1
  #define KERN_CRIT       2
  #define KERN_ERR        3
  #define KERN_WARNING  4
  #define KERN_NOTICE    5
  #define KERN_INFO       6
  #define KERN_DEBUG     7
可以看到代码中我们用的是KERN_ALERT修改代码将printk语句改为:
printk(KERN_EMERG "Hello, world\n");
重新编译,便查看结果,就正确了。

      那刚刚的载入时,信息跑哪了,怎么无故失踪了?接下来看看一个地方,就恍然大悟了:

没错,刚刚的信息打印在这了。

第一个驱动程序就写到这边了,这博客,是我个人总结,督促学习的地方,也比较适合入门的人看,大牛勿喷,一起学习。
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐