为了彻底搞清楚linux
和uClinux
的设备驱动,我觉得有必要找一份devices.txt
的copy
。那上面详细列出了0
至255
个linux
主设备号的分配情况,以及各种设备的相应次设备号。对于,我自己开发非标准的linux
设备非常有用。uClinux
的设备和linux
一样,因此这个文档同样适用于uClinux
。这个文档可以从下列站点:http://www.lanana.org/docs/device-list/
或者ftp://ftp.kernel.org/pub/linux/docs/device-list/
获取。作为开发的常用手册,这个文档有必要打印并保存下来。
我自己为我的开发板上的按键写了一个驱动,我想要像其他linux
标准设备那样在uClinux
启动起来时,在/dev
目录下有例如/dev/key
这样一个设备。但是开始我一直搞不清楚为什么在menuconfig
的char device
和input core
菜单中居然没有关于键盘的选项,但键盘驱动又是确实被编译进系统中了。后来,经过比较linux
和uClinux
后,我发现问题的关键不在于char/
目录下的config.in
文件,也不在于linux-2.4.x/arch/armnommu/Config.in
文件,而在于drivers/char/Makefile
文件。
简要说一下这个文件,它并不规定menuconfig
的char device
子菜单的构成,而是规定了drivers/char
这个目录下的文件的编译规则。通过在此文件中,加入一些条件判断,并根据条件判断为一些变量赋值,从而达到将特定设备编译进uClinux
的内核。
下面对Makefile
中的一些定义进行解释。
编译目标定义:
obj-$(CONFIG_TC) += tc.o
的语句是用来定义编译的目标,是子目录 Makefile
中最重要的部分。编译目标定义那些在本子目录(对于这个Makefile
来说,就是针对/linux-2.4.x/drivers/char
子目录)下,需要编译到 Linux
内核中的目标文件列表。为了只在用户选择了此功能后才编译,所有的目标定义都融合了对配置变量的判断。
前面说过,每个配置变量取值范围是:y
,n
,m
和空,obj-$(CONFIG_TC)
分别对应着 obj-y
,obj-n
,obj-m
,obj-
。如果 CONFIG_TC
配置为 y
,那么 tc.o
就进入了 obj-y
列表。obj-y
为包含到 Linux
内核 vmlinux
中的目标文件列表;obj-m
为编译成模块的目标文件列表;obj-n
和 obj-
中的文件列表被忽略。配置系统就根据这些列表的属性进行编译和链接。export-objs
中的目标文件都使用了 EXPORT_SYMBOL()
定义了公共的符号,以便可装载模块使用。在 tc.c
文件的最后部分,有 "EXPORT_SYMBOL(search_tc_card);"
,表明 tc.o
有符号输出。其中的“obj-y +=
”表示如果变量为真,那么就追加什么;而“obj-y
:=
”表示如果变量为真,那么就等于什么。
这里需要指出的是,对于编译目标的定义,存在着两种格式,分别是老式定义和新式定义。老式定义就是前面 Rules.make
使用的那些变量,新式定义就是 obj-y
,obj-m
,obj-n
和 obj-
。Linux
内核推荐使用新式定义,不过由于 Rules.make
不理解新式定义,需要在 Makefile
中的适配段将其转换成老式定义。
另外,需要指出的是linux
和uClinux
源码树中的Makefile
分为两类,一类是由autoconf
和automake
根据configure.in
模板自动生成,另一类是由开发者自己手工写的Makefile
。所以,对于前者来说,修改文件中的任何语句或判断条件,都是徒劳无功的。因为每次创建新的配置或修改以前的配置后,Makefile
就会被改写或重新生成。要想要通过这种办法达到自己满意的编译配置是不可能的。但后者就不同,由于不是由内核自动生成,因此,不论配置修改多少次,这个文件都是不会改变的。所以,我们可以通过修改Makefile
中的语句或判断条件,来达到构建我们自己的编译配置。这一点对添加自己的非标准设备非常有用。
适配段:
适配段的作用是将新式定义转换成老式定义。在下面的例子中,适配段就是将 obj-y
和 obj-m
转换成 Rules.make
能够理解的 L_TARGET
,L_OBJS
,LX_OBJS
,M_OBJS
,MX_OBJS
。
L_OBJS := $(sort $(filter-out $(export-objs), $(obj-y)))
定义了 L_OBJS
的生成方式:在 obj-y
的列表中过滤掉 export-objs
(tc.o
),然后排序并去除重复的文件名。这里使用到了GNU Make
的一些特殊功能,具体的含义可参考Make
的文档(info make
)。
显然,由于menuconfig
的图形配置菜单中根本没有关于键盘的选项,因此可以看到在obj-y
中并没有keyboard.o
。那么键盘驱动是怎么编译进char.o
的呢?
可以注意到:
KEYMAP
=defkeymap.o
KEYBD
=pc_keyb.o
CONSOLE =console.o
SERIAL
=serial.o
这四个变量都被赋值。它们分别表示键盘码和扫描码的映射表、键盘驱动、控制台驱动和串口驱动。上面是四个变量的默认值。
在下面这一段中,先判断变量ARCH
是否等于sh
,如果为真,那么变量为空;紧接着下一级逻辑判断中,判断变量CONFIG_SH_HP600
是否为真,如果为真,那么键盘映射表还是适用默认的映射表(defkeymap.o
),但键盘驱动就改为hp600_keyb.o
和scan_keyb.o
,控制台驱动不变。但是要注意串口在这一部分中,并没有加入判断语句中,因为串口驱动在下面用obj-$(CONFIG_68328_SERIAL) += 68328serial.o
这样的形势单独列出。
另外,需要特别指出的是,察看linux
或者uClinux
各种变量配置的情况,可以看
linux-2.4.x/include/linux/autoconf.h
、
uClinux-dist/config.in
以及
linux-2.4.x/config.in
这三个文件。
还有
ifeq
(
..
)表示假如等于
xxx
,那么
xxx
;
ifneq
(
..
)便是相反意思,表示假如不等于
xxx
,那么
xxx
;
ifdef
()表示如果
xxx
为真,那么
xxx
;
ifndef
()表示如果
xxx
为假,那么
xxx
。
ifeq ($(ARCH),sh)
如果ARCH=sh
,那么。。。
KEYMAP
=
KEYBD
=
CONSOLE =
ifeq ($(CONFIG_SH_HP600),y)
如果CONFIG_SH_HP600=y
,那么。。。
KEYMAP
= defkeymap.o
KEYBD
= scan_keyb.o hp600_keyb.o
CONSOLE = console.o
endif
endif
ifeq ($(ARCH),mips)
ifneq ($(CONFIG_PC_KEYB),y)
如果CONFIG_PC_KEYB
!=y
,那么。。。
KEYBD =
endif
endif
ifeq ($(ARCH),m68k)
ifdef CONFIG_AMIGA
如果CONFIG_AMIGA
为真(即变量为y
),那么。。。
KEYBD = amikeyb.o
else
ifndef CONFIG_MAC
如果CONFIG_AMIGA
为假,那么。。。
KEYBD =
endif
endif
SERIAL =
endif
下面这一段很重要。
ifndef CONFIG_SUN_KEYBOARD
如果变量为假,那么判断变量CONFIG_VT
obj-$(CONFIG_VT) += keyboard.o $(KEYMAP) $(KEYBD)
else
obj-$(CONFIG_PCI) += keyboard.o $(KEYMAP)
endif
结果就是CONFIG_SUN_KEYBOARD
(SUN
公司PC
键盘)通常为假(我们不会用到),那么CONFIG_VT
是虚拟控制台,通常我们要选择,就是CONFIG_VT=y
,那么追加keyboard.o
、$(KEYMAP)
和$(KEYBD)
。这两个变量在最前面有定义:defkeymap.o
和pc_keyb.o
。因此,标准键盘驱动被自动编译进char.o
中,进而被编译进内核中。
所以,我就可以根据这个道理将自己的按键驱动自动加入到内核中。下面是我加入的一段语句,完成自动将我自己的按键驱动编译进内核。
ifeq($(CONFIG_ARCH_S 3C44B0),y)
KEYBD = key.o
KEYMAP = mykeymap.o
CONSOLE = console.o
endif
这个Makefile
大致就解释到这儿。其实,不过是拿给自己看的罢了。