linux设备驱动归纳总结(一):内核的相关基础概念
1. 内核与 linux 设备驱动的作用与关系
内核:用于管理软硬件资源,并提供运行环境。如分配 4G 虚拟空间等。
linux 设备驱动:是连接硬件和内核之间的桥梁。
linux 系统按个人理解可按下划分:
1). 应用层:包括 POSIX 接口, LIBC ,图形库等,用于给用户提供访问内核的接口。属于用户态 ARM 运行在用户模式 (usr) 或者系统模式 (sys) 下。
2). 内核层:应用程序调用相关接口后,会通过系统调用,执行 SWI 指令切换 ARM 的工作模式到超级用户(svc) 模式下,根据用户函数的要求执行相应的操作。
3). 硬件层:硬件设备,当用户需要操作硬件时,内核会根据驱动接口操作硬件设备
图结构如下:
举一个相对比较邪恶的类比:
在深圳的酒店经常会在门缝看到一些卡片,上面说可以通过打电话送货上门提供某中服务。
===============================================
2. 内核代码树介绍
linux-2.6.29
|- arch :包含和硬件体系结构相关的代码
|- block:硬盘调度算法,不是驱动
|- firmware:固件
|- Documentation:标准官方文档
|- dirver: linux 设备驱动
|- fs:内核所支持的文件体系
|- include:头文件。 linux/module.h linux/init.h常用库。
|- init:库文件代码, C 库函数在内核中的实现。
init/main.c ->start_kernel-> 内核执行第一条代码
|- ipc :进程件通信
|- mm:内存管理
|- kernel:内核核心部分,包括进程调度等
|- net:网络协议
|- sound:所有音频相关
其中,跟设备驱动有关并且经常查阅的文件夹有:
init
include : linux, asm-arm
drivers:
arch:
===============================================
3. 内核补丁:
补丁一般都是基于某个版本内核生成的,用于升级旧内核。
打补丁需要注意:
1. 对应版本的补丁只能用于对应版本的内核。
2. 如果在已打补丁的内核再打补丁,需要先卸载原来补丁。
打补丁的方法:
1. 制作补丁:
diff -Nur linux-2.6.30/ linux-2.6.30.1/ > linux-2.6.30.1.patch
//N 为新加的文件全部修改
//linux-2.6.30旧版本
//linux-2.6.30.1新版本
// 目标补丁
2. 打补丁:
cd linux-2.6.30// !!注意在原文件夹的目录中打补丁
patch -p1 < ../linux-2.6.30.1.patch//-p1 是忽略一级目录
3. 恢复:
cd linux-2.6.30// !!注意在原文件夹的目录中打补丁
patch -R < ../linux-2.6.30.1.patch// 撤销补丁
===============================================
4. 内核中的 Makefile :
对于内核, Makefile 分为 5 类:
Documentation/kbuild/makefiles.txt 描述如下:
50 The Makefiles have five parts:
51
52Makefile总Makefile,控制内核的编译
53.config内核配置文件,配置内核时生成,如make menuconfig后
54arch/$(ARCH)/Makefile对应体系结构的Makefile
55scripts/Makefile.*Makefile共用的规则,如图形配置界面。
56kbuild Makefiles各子目录下的Makefile,被上层的Makefile调用
简单来说,编译内核会执行以下事情。
1.make menuconfig
1.1 拷贝一个对应体系结构的配置文件到主目录下并改名为 .config ,这样就在 makemenuconfig生成的图形配置中已经有了一些默认的配置,减少用户的劳动量。
1.2 从内核顶层目录的 Makefile 决定编译的体系结构 (ARCH). 编译工具 (CROSS_COMPILE) 和需要进去编译的目录。
1.3 根据总 Makefile 的 ARCH 变量,进入相应体系结构的目录,读取 arch/$(ARCH)/Makefile, 决定对应的体系结构下还有哪些目录需要编译。
1.4 根据 arch/$(ARCH)/Makefile ,一个一个地递归进入指定的目录下调用该目录下的 makefile ,并根据目录下的 Kconfig 生成配置界面并由用户决定将该文件编译成模块还是编译进内核。
1.5 配置完毕后保存退出,会更改原来的 .config 的内容。
2.make
1.1 将生成的 .config 去掉注释,新建一份配置文件,文件名为 include/config/auto.conf 。
1.2 根据配置文件的要求,将需要编译的文件的各个子目录下生成一个 .o 或者 .a 文件,然后由总Makefile 指定的连接脚本 arch/$(ARCH)/kernel/vmlinux.lds 生成 vmlinux ,并通过压缩变成bzImage ,或者按要求在对应的子目录下编译成模块。
但是,具体是怎么生成配置文件的呢?
注:我使用的内核是被修改过的,可能有些地方和原内核不一样,如我内核里面 $(ARCH) 写成$(SRCARCH) 。还有在文件中的行数和原内核不一致,但这些不影响分析,搜索一下就出来了。
1. 在总 Makefile 中,根据以下语句进入需要编译的目录
470 # Objects we will link into vmlinux / subdirs we need to visit
471 init-y:= init/
472 drivers-y:= drivers/ sound/ firmware/
473 net-y:= net/
474 libs-y:= lib/
475 core-y:= usr/
476 endif # KBUILD_EXTMOD
639 core-y+= kernel/ mm/ fs/ ipc/ security/ crypto/ block/
#另外还有一个体系相关的arch目录
529 include $(srctree)/arch/$(SRCARCH)/Makefile
这样,就根据了体系结构决定了需要进去编译的目录了。
2. 在总 Makefile 中包含的目录还是不够的,内核还需要根据对应的 CPU 体系架构,决定还需要将哪些子目录将要编译进内核,在总 Makefile 中进去读取相应体系结构的 Makefile : arch/$(SRCARCH)/Makefile。
在总 Makefile 和体系架构下的 arch/(SRCARCH)/Makefile 中包含的子目录会根据该目录下的 Makefile的要求编译成模块还是编译进内核,当然也可以不编译。
如在arch/arm/Makefile下:
187 # If we have a machine-specific directory, then include it in the build.
188 core-y+= arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
189 core-y+= $(machdirs) $(platdirs)
190 core-$(CONFIG_FPE_NWFPE)+= arch/arm/nwfpe/
191 core-$(CONFIG_FPE_FASTFPE)+= $(FASTFPE_OBJ)
192 core-$(CONFIG_VFP)+= arch/arm/vfp/
193
194 drivers-$(CONFIG_OPROFILE)+= arch/arm/oprofile/
195
196 libs-y:= arch/arm/lib/ $(libs-y)
其中, y 表示编译成模块, m 表示编译进内核 ( 上面没有,因为默认情况下 ARM 全部编译进内核 ) ,但$(CONFIG_OPROFILE) 又是什么呢?
这些是根据用户在 make menuconfig 中设置后,生成的值赋给了 CONFIG_OPROFILE 。这是由各子目录下的 Kconfig 提供选项功用户选择并配置。如 arch/arm/Kconfig 。
另外有些配置会根据 arch/$(ARCH)/Kconfig 文件通过 Kconfig 的语法 source 读取各个包含的子目录Kconfig 来生成一个配置界面。每个 Makefile 目录下都有一个对应 Kconfig 文件,用于生成配置界面来给用户决定内核如何配置。
总结 Kconfig 的作用:
2.1. 在 make menuconfig 下可以配置选项 ;
2.2. 在 .config 中确定 CONFIG_XXX 的的值。
3. 只是读取以上的两个 Makefile 还是不够了,内核还会把包含的子目录一层一层的读取它里面的 Makefile和 Kconfig 。
假设我现在配置内核
1. 最简单的方法,直接修改子目录的 Makefile
如在我要取消 s3c2440 的时钟 ( 当然这是必须要开的,只是举例 ) 。
可以直接修改 arch/arm/mach-s3c2440/Makefile
12 obj-$(CONFIG_CPU_S3C2440)+= s3c2440.o dsc.o
13 obj-$(CONFIG_CPU_S3C2440)+= irq.o
14 obj-$(CONFIG_CPU_S3C2440)+= clock.o
15 obj-$(CONFIG_S3C2440_DMA)+= dma.o
将 obj-$(CONFIG_CPU_S3C2440)+= clock.o 改为
obj-+= clock.o
也可以编译成模块:
obj-m+= clock.o
2. 当然有更方便的通过图形界面, make menuconfig ,接下来我实现一下如何将一个选项加入到图形配置界面中。
2.1. 进入内核目录
cd linux-2.6.29
2.2. 在 driver 目录下模拟一个名为 test1 驱动的文件夹
mkdir driver/test1
2.3. 在 test1 目录下随便写个 C 文件
cd driver/test1
vim test1.c
1 void foo()
2 {
3 ;
4 }
2.4. 在目录下编写一个简单的 Makefile
vim Makefile
obj-$(CONFIG_TEST1) += test1.o
CONFIG_TEST1 是决定 test1 是否编译进内核或者编译成模块的。这就通过 Kconfig 由用户在make menuconfig 中选择。
2.5. 所以还要在目录下写一个 Kconfig
vim Kconfig
1 menu "test1 driver here"
2 config TEST1
3bool "xiaobai test1 driver"
4help
5This is test1
6 endmenu
说白了,就是在图形配置的 driver 下多了一个配置选项,用户配置后将 CONFIG_TEST1 的值存放在.config 中, Makefile 通过读取 .config 的去注释版 autoconf 读取到 CONFIG_TEST 的值,再进行编译。但是,以上几步还不能达到目的,因为虽然在总 Makefile 中已经包含了目录 driver, 但是 driver 目录的 Makefile 中并没有包含 test 目录。因此需要在 driver/Makefile 中添加:
2.6.vim driver/Makefile
再最后加上一句:
104 obj-$(CONFIG_OF)+= of/
105 obj-$(CONFIG_SSB)+= ssb/
106 obj-$(CONFIG_VIRTIO) += virtio/
107 obj-$(CONFIG_STAGING)+= staging/
108 obj-y+= platform/
109 obj-$(CONFIG_TEST1)+= test1///添加这句
虽然 Makefile 中已经包含了,但这样还是不行。因为当需要配置 ARM 时, ARM 结构下的 Kconfig并没有包含 test 的 Kconfig 。这样的话就不会出现在图形配置界面中,因此在 arch/arm/Kconfig 中添加语句。
2.7.vim arch/arm/Kconfig
1230 menu "Device Drivers"
1231
1232 source "drivers/base/Kconfig"
…..............................................
1328 source "drivers/staging/Kconfig"
1329
1330 source "drivers/test1/Kconfig"
1331
1332 endmenu
大功告成!
这样, make menuconfig 界面写的 Driver Devices 下就多了一个 "test1 driver here" 的目录,里面有一个配置选项 "xiaobai test1 driver" 。
Kconfig 文件的语法在 documentation/kbuild/kconfig-language.txt 文件中有详细的讲解,上面我只是简单实现了一下 , 但都是皮毛。
==============================================
5. 编译内核
编译内核很简单,只需要配置完毕后执行 make 命令,将指定的文件编译进内核
bzImage 或者编译成模块。
make = make bzImage + make modules
因此如果值编译内核,即只编译配置文件中 -y 选项,可以直接用命令
make bzImage
如果值编译模块,即只编译配置文件中的 -m 选项,可以之直接使用命令
make modules
模块可以编译当然也可以清除,使用命令
make modules clean
如果只想单独编译一个模块,可以使用命令
make M=drivers/test/ modules // 只单独编译 drivers/test 中的 .ko
make M=drivers/test/ modules clean// 清除
上面的是在内核目录下的操作,但当我写驱动时,我并不可能在内核目录下编
写,但我编译时却要依赖内核中的规则和 Makefile ,所以就有了以下的方法,
同时这也是一般的编写驱动时 Makefile 的格式。
指定内核 Makefile 并单独编译
make -C /root/linux-2.6.29 M=`pwd` module
make -C /root/linux-2.6.29 M=`pwd` module clean
//-C指定内核 Makefile 的路径,可以使用相对路径。
//-M指定要编译的文件的路径,同样课使用相对路径。
编译生成的模块可以指定存放的目录
make -C /root/linux-2.6.29 M=`pwd` modules_install INSTALL_MOD_PATH=/nfsroot
=======================================================================
以上都是个人理解,如内核的 Makefile 配置和编译时一个复杂的过程,我简单地描述了一下,并不保证一定正确。
错误地方,请指正。