9、编写Red hat内核驱动程序前需要注意的
需要在usr/src/ 目录下建立指向linux内核的符号连接。若PC机的linux系统内核路径是/usr/src/linux-2.4.xxx,那么执行建立符号连接的命令是:
#ln -s /usr/src/linux-2.4.xxx /usr/src/linux
before make , please cd /up-techpxa270/kernel/linux-2.6.9/ directory and run " make zImage"
then go back to this directory ,run make
10、调试过程中利用printk跟踪
printk是一个很有用的函数,利用它可以实现从内核向Linux控制台的格式化输出。其用法与标准C的printf类似。在调试驱动程序时,依靠printk输出信息跟踪程序,也是很有效的方法。
与标准C的printf不同的是,printk支持分级输出。在include/linux/kernel.h中,为输出定义了如下8个级别:
#define KERN_EMERG "<0>" /* system is unusable,紧急情况 */
#define KERN_ALERT "<1>" /* action must be taken immediately,需要立即被注意到的 */
#define KERN_CRIT "<2>" /* critical conditions,临界情况 */
#define KERN_ERR "<3>" /* error conditions,错误 */
#define KERN_WARNING "<4>" /* warning conditions,警告 */
#define KERN_NOTICE "<5>" /* normal but significant condition,普通的,需要注意 */
#define KERN_INFO "<6>" /* informational,非正式的 */
#define KERN_DEBUG "<7>" /* debug-level messages,一般的调试信息 */
未指定优先级的默认级别定义在/kernel/printk.c中:
#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */
可以用这种方式,即使用:printk(KERN_INFO"your message/n"); 在指定的级别下输出。未使用输出级别定义的printk调用,则用DEFAULT_MESSAGE_LOGLEVEL作为默认的输出级别,它在kernel/printk.c中被定义为第4级的输出,即KERN_WARNING。在include/linux/kernel.h中,定义了宏console_loglevel.任何小于console_loglevel级别的输出,都会被显示在Linux控制台(console)上。console_loglevel的初始值为DEFAULT_CONSOLE_LOGLEVEL(在kernel/printk.c中被定义为7),而且该值可通过sys_syslog系统调用修改。如命令:klogd -C 4 就是利用sys_syslog系统调用把console_loglevel的值改成了4。
为了调试方便,建议在驱动程序中定义DPRINTK宏实现在调试过程中的跟踪。
其命令如下:#define DPRINTK(x... ) printk("s3c2410 - led: " ##x)
用法如下:DPRINTK("write: led=0x%x,count=%d/n",ledstatus,count);
DPRINTK("open/n");
DPRINTK("release/n");
11、编写Makefile文件需要注意的
编写hello.c文件
利用vi编辑器,我们键入下面的代码,并保存为hello.c文件。
/**
* hello.c
* ------Test for kernel module
*/
#i nclude <linux/init.h>
#i nclude <linux/module.h>
MODULE_LICENSE("MYGPL"); //(1)
static int hello_init(void) //(2)
{
printk(KERN_ALERT "Hello, world/n"); //(3)
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, cruel world/n");
}
module_init(hello_init); //(4)
module_exit(hello_exit); //(5)
对上面这段代码中有5个地方需要加以说明:
(1):本句代码可以不要,但不要的话,运行时会出现"hello: module license 'unspecified' taints kernel.",词典上对taints的解释是"感染,污点".
(2):我们可以看出,对模块内的函数一般需要加上static关键字进行修饰,这样可防止模块外访问该函数,这是应该养成的一个好习惯。
(3):printk函数相当于C标准库函数printf, KERN_ALERT是指的输出消息的优先级别,且KERN_ALERT优先级别最高。
(4)(5):模块初始化和模块退出时有专门的函数,我们只需要为这两个函数指定初始化和退出时的具体调用的函数名即可。
编写下面的makefile:
# Makefile for hello module
# If KERNELRELEASE is defined, we've been invoked from the
# kernel build system and can use its language.
ifneq($(KERNELRELEASE),)
obj-m := hello.o
# Otherwise we were called directly from the command
# line; invoke the kernel build system.
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) SUBDIRS=$(PWD) modules
endif
clean:
$(shell rm -f *.bak)
$(shell rm -f hello.o)
$(shell rm -f hello.ko)
$(shell rm -f hello.mod.c)
$(shell rm -f hello.mod.o)
对这段脚本需要说明下面几点:
(1)、将上面的脚本保存为Makefile,注意必须保存为M为大写的Makefile。这是因为编译的时候首先看环境变量KERNELRELEASE是否定义,如果没定义则调用Linux内核编译build脚本。该脚本会首先编译内核,其间会创建环境变量KERNELRELEASE,接着编译当前工作目录下的hello模块,此时会第二遍读取Makefile,再次判断环境变量KERNELRELEASE是否定义,已经定义的情况下开始编译hello模块。
如果将该脚本保存为小写m开头的makefile,编译时会出现这样的提示:
scripts/Makefile.build:13: /root/zhou/Makeifle: No such file or directory.
(2)、因为build脚本会首先判断有无必要重新编译内核,所以如果需要先重新编译内核的话,编译过程会运行一段时间。
(3)、在Makefile中行首缩进需要用Tab键,否则会出问题(这个问题曾经折磨我很长一段时间,后来改用Tab键缩进,问题一下子就得到了解决)。
(4)、在default部分,原书中原来的语句是:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
利用man make命令查看make的使用手册发现,对-m的说明:
-m These options are ignored for compatibility with other version of make.
即-m参数被忽略掉了,所以编译时会出现:
Nothing to be done for '/root/zhou'.
为了避免上述问题,可将M=$(PWD)改成SUBDIRS=$(PWD),即重新定义环境变量SUBDIRS,供编译时使用。
编译完成后,在当前工作目录下会多了几个文件:hello.o, hello.ko, hello.mod.c, hello.mod.o。其中hello.ko就是我们需要的。
另:在编译内核并安装时,系统会改变或新增哪些文件?
会在/boot/下增加新的内核文件,新的initrd和map文件,修改grub的配置
文件/boot/grub/grub.conf
运行
必须有root用户的权限,才能进行内核模块操作。
(1)、挂载模块
执行insmod hello.ko命令,即可挂载并运行hello模块。insmod意思是insert module。成功挂载上hello模块后,会输出:
Hello, world
(2)、卸载模块
执行rmmod hello命令(也可以执行rmmod hello.ko命令),即可卸载模块。rmmod意思是remove module。成功卸载模块后,会输出:
Goodbye, cruel world
在内核上成功挂载和卸载了hello模块。知道在执行rmmod hello命令后,hello模块已经卸载了。如果此时我们再执行一遍rmmod hello命令,则会出现类似下面的提示:
ERROR: Module hello does not exist in /proc/modules
从这句提示中,可以知道,挂载模块时,hello被放在了/proc/modules文件中,卸载模块时,从/proc/modules文件中删除了对应的记录行。为了验证这一点,利用insmod hello.ko命令重新挂载hello模块,然后,我们利用more /proc/modules命令来查看该文件的内容,可以看到在第一行就是我们刚挂上的hello模块:
hello 1152 0 - Live 0xca8c9000
关闭该文件。我们再利用rmmod hello命令卸载掉hello模块,然后我们再次利用more /proc/modules命令再次查看该文件,可以发现已经没有了这一行内容。其实每次执行lsmod命令,就是读取和显示/proc/modules文件中的内容。
12、设备文件和设备文件系统
在Linux系统中,字符设备和块设备是通过文件节点访问的。习惯上,这些设备文件存在于系统的/dev目录下。因为系统不是靠路径去关联设备文件和对应的驱动程序的,所以也可通过mknod命令把设备文件创建在其他位置。通过如下命令:ls -l /dev 可以列出系统的设备文件。
其中:对应的字母表示的是:字符设备文件(c),准予读取(r),准予写入(w),块设备文件(b),所有者(root),组(floppy);第一个数字对应的是主设备号,第二个数字对应的是次设备号。
Linux系统是依靠主次设备号来联系驱动程序和设备文件节点的(而不是设备文件的路径)。系统依靠主设备号标识不同的驱动程序,因此,在同一个系统中,一类设备的主设备号是惟一的。每一个Linux驱动程序都对应了一个主设备号。可以使用以下命令:cat /proc/devices 列出系统内核支持的设备驱动程序和对应的主设备号。
同一个驱动程序可以管理多个设备,它们依靠次设备号来区别。次设备号只在驱动程序内部使用,系统内核直接把次设备号传递给驱动程序,由驱动程序去管理。
对于字符设备和块设备,Linux内核对这些操作进行了统一 的抽象,把它们定义在结构体file_operations中。
Linux下的设备驱动程序通常放在/dev目录下。如果我们进入到/dev目录,然后执行ls -l|more命令,我们会发现下面几点:
(1)、以b开头的代表block device
(2)、以c开头的代表character device
(3)、以l开头的代表link文件
(4)、以d开头的代表directory
(5)、在last modified的前面有两个数字,用逗号隔开,分别是该设备的major number和minor number,即主设备号和次设备号。
例如:
brw-rw---- 1 root disk 34, 70 Dec 29 2005 hdh6
13、Linux驱动程序的编写细节点
嵌入式Linux操作系统下通过fd=open(DEVICE_NAME,O_RDER)把驱动程序和应用程序连接起来,便可以通过应用层程序操控硬件设备。
通常,程序员会通过一个判断语句来观察设备是否被正确打开,如果被正确打开会进行所要的操作,否则便会报错。实现方法如下面的代码所示:
if(fd==-1) {printf("open device %s error /n",DEVICE_NAME);}
else{//文件打开成功,进行具体的文件操作}
driver_register是2.6内核中提供的注册驱动程序的方法,其原型在drivers/base/driver.c中定义:
int driver_register(struct device_driver * drv)
它使得系统加载、检测、卸载驱动程序有一个统一的接口。
程序在最外一层的local_irq_restore调用才把中断打开,而__cli和__sti宏则不能嵌套调用。
14、嵌入式根文件系统
目录 习惯用法
bin:用户命令所在目录
dev:硬件设备文件及其它特殊文件
etc:系统配置文件,包括启动文件等
home:多用户主目录
lib:链接库文件目录
mnt:装配点,用于装配临时文件系统或其他的文件系统
opt:附加的软件套件目录
proc:虚拟文件系统,用来显示内核及进程信息
root:root 用户主目录
sbin:系统管理员命令目录
tmp:临时文件目录
usr:用户命令目录
var:监控程序和工具程序所存放的可变数据
对于用途单一的嵌入式系统,上边的一些用于多用户的目录可以省略,例如/home、/opt、/root目录等。而/bin、/dev、/etc、/lib、/proc、/sbin 和/usr 目录,是几乎每个系统必备的目录,也是不可或缺的目录。
15、查看/dev目录下的设备的主次设备号的命令
查看/dev目录下的设备的主次设备号使用如下的命令:ls -l /dev
在系统/dev下查看设备的命令:
#pwd
/dev
#ls -l