终于开始写驱动程序,首先说些题外话吧。
我们学习嵌入式,学习了bootloader kernel filesystem
进行过内核的移植,驱动的移植,根文件系统的制作,可是这些都与编程能力无关。
而我们学习的造诣,我们的能力,更多的是体现在编程能力上,所以以后的学习还要多敲代码,多看书。
首先来贴一段代码:hello.c
#include<linux/module.h>
#include<linux/kernel.h>
int insmod_module(void)
{
printk("Hello,World\n");
return 0;
}
void rmmod_module(void)
{
printk("Goodbye\n]");
}
module_init(insmod_module);
module_exit(rmmod_module);
这是一个最简单的字符型驱动模型
可以分为四部分
//驱动程序所必需要的包含文件
//设备的功能接口函数与数据结构体
主要是:open()
reda()
write()
ioctrl() 还有file_operation
//加载驱动的入口点 module_init()
//卸载设备驱动的入口点module_exit()
Makefile
KERNELDIR ?= /home/linux/linux-2.6.22.6
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
接下来我们在说一下,makefile
将前面声明的路径替换到后面的命令(注意后面的命令前面的不是空格,是TAB)
引用自:http://www.embedu.org/Column/Column310.htm
KERNELDIR ?= /home/linux/linux-2.6.22.6,这句是对KERNELDIR进行赋值,这个变量是后面我们用到的指代内核源码目录用的。
PWD := $(shell pwd),这句是对PWD变量进行赋值,作用是将$(shell pwd)的返回结果既求得当前目录的路径赋值给PWD,这个变量我们在后面指代我们要编译的驱动程序所在的位置。
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
这句是Makefile的规则:这里的$(MAKE)就相当于make,-C 选项的作用是指将当前工作目录转移到你所指定的位置。“M=”选项的作用是,当用户需要以某个内核为基础编译一个外部模块的话,需要在make modules 命令中加入“M=dir”,程序会自动到你所指定的dir目录中查找模块源码,将其编译,生成KO文件。
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
这个命令是模块的安装,在Makefile中搜索“lib\/modules”可以看到下面的语句,通过阅读你不难找到这个“MODLIB”的用处,它是用来指定安装路径的,而变量“INSTALL_MOD_PATH”往往为空。
MODLIB = $(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)
Export MODLIB
.PHONY: modules modules_install clean
这句话是的作用是保证modules,modules_install,clean这三个命令能正常完成。.PHONY 这是一个特殊目标名称,还有其它的,如:
.SUFFIXES,.DEFAULT,.PRECIOUS,.INTERMEDIATE,.SECONDARY,.SECONDEXPANSION,.DELETE_ON_ERROR,.IGNORE .LOW_RESOLUTION_TIME .SILENT .EXPORT_ALL_VARIABLES .NOTPARALLEL
它们的具体用法可以参考GNU手册中的Special Built-in Target Names章节。
.PHONY目标的具体意思是如果在Makefile的工作目录中有名如:modules,modules_install,clean等文件时命令会出错。它是防止这出错的方式。
编译成功后会得到很多个文件,其中有我们所要的hello.ko
通过 insmod 可以加载模块到我们的系统上。
insmod hello.ko
按道理我们应该的得到一行打印信息:Hello,World;可是我的系统上并没有这个信息
接着执行lsmod
确实找到了我们编译出来的模块hello.ko
然后卸载 rmmod hello.ko 可是也没有打印信息。这个不大明白是怎么回事。
有待解决。
解决方法:将所有的printk改成: printk("Hello,World\n");
printk(KERN_EMERG "Hello,World\n");
今天在网上google了一下找到了相关的资料。
printk与printf类似只是一个运行在内核态,一个运行在用户态。
二者的作用相同,不同点也是很明显的。
用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
通过读写/proc/sys/kernel/printk文件可读取和修改控制台的日志级别。查看这个文件的方法如下:
#cat /proc/sys/kernel/printk
# 8 4 1 7
上面显示的4个数据分别对应控制台日志级别、默认的消息日志级别、最低的控制台日志级别和默认的控制台日志级别
可用下面的命令设置当前日志级别:
# echo 8 > /proc/sys/kernel/printk
这样所有级别<8,(0-7)的消息都可以显示在控制台上.