http://hi.baidu.com/wsdxs1234/item/5362c675ee32e1235d178943
我用的makefile
#
# Makefile for the led driver
#
ifneq ($(KERNELRELEASE),)
obj-m := led.o
else
export ARCH=arm
# 这里要写上arm交叉编译器的位置
export CROSS_COMPILE=/home/xlq/arm-linux-4.1.1/bin/arm-linux-
# 这里要写上内核树的位置,且内核必须已经编译过了
KDIR := /home/xlq/linux/src/preview-kit/linux
PWD := $(shell pwd)
#
# Include make variables (CC, etc...)
#
ASM := $(CROSS_COMPILE)as
LD := $(CROSS_COMPILE)ld
CC := $(CROSS_COMPILE)gcc
CPP := $(CROSS_COMPILE)c++
AR := $(CROSS_COMPILE)ar
STRIP := $(CROSS_COMPILE)strip
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump
EXTRA_CFLAGS += -O3
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
$(CROSS_COMPILE)strip --strip-unneeded *.ko
.PHONY: clean
clean:
rm -rf .*.swp .*.cmd *.o *.mod.c *.ko .tmp_versions *.symvers *.order
endif
知识汇总
1.有个前提,必须在能建立内核模块前解决。就是保证你有版本足够的新的编译器,模块工具,已经其他必要工具。
2.obj-m :=hello.o
表明有一个模块要从目标文件hello.o建立。在从目标文件建立后结果模块命名为hello.ke。
3.如果有一个模块名位module.ko,是来自2个源文件(file1.c和file2.c),正确的书写应该是:
obj-m :=module.o
module-objs :=file1.o file2.o
4.假设你的内核源码数位于~/kernel-2.6目录,用来建立你的模块的make命令会是:
make -C~/kernel-2.6 M='pwd' modules
这个命令开始是改变它的目录到用-C选项提供的目录下(就是说,内核源码目录)。在哪里发现内核的顶层makefile.
M=选项使makefile在试图建立模块目标前,回到你的模块源码的目录。这个目标,依次的,是指在obj-m变量中发现
的模块列表。
如下书写你的makefile:
# 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) M=$(PWD) modules
endif
这个 makefile 在一次典型的建立
中要被读 2 次. 当从命令行中调用这个 makefile , 它注意到 KERNELRELEASE 变量没有
设置. 它利用这样一个事实来定位内核源码目录, 即已安装模块目录中的符号连接指回内
核建立树. 如果你实际上没有运行你在为其而建立的内核, 你可以在命令行提供一个
KERNELDIR= 选项, 设置 KERNELDIR 环境变量, 或者重写 makefile 中设置 KERNELDIR 的
那一行. 一旦发现内核源码树, makefile 调用 default: 目标, 来运行第 2 个 make 命
令( 在 makefile 里参数化成 $(MAKE))象前面描述过的一样来调用内核建立系统. 在第 2
次读, makefile 设置 obj-m, 并且内核的 makefile 文件完成实际的建立模块工作.
快速参考
insmod
modprobe
rmmod
用户空间工具, 加载模块到运行中的内核以及去除它们.
#include <linux/init.h>
module_init(init_function);
module_exit(cleanup_function);
指定模块的初始化和清理函数的宏定义.
__init
__initdata
__exit
__exitdata
函数( __init 和 __exit )和数据 (__initdata 和 __exitdata)的标记, 只用在模
块初始化或者清理时间. 为初始化所标识的项可能会在初始化完成后丢弃; 退出的
项可能被丢弃如果内核没有配置模块卸载. 这些标记通过使相关的目标在可执行文
件的特定的 ELF 节里被替换来工作.
#include <linux/sched.h>
最重要的头文件中的一个. 这个文件包含很多驱动使用的内核 API 的定义, 包括睡
眠函数和许多变量声明.
struct task_struct *current;
当前进程.
current->pid
current->comm
进程 ID 和 当前进程的命令名.
obj-m
一个 makefile 符号, 内核建立系统用来决定当前目录下的哪个模块应当被建立.
/sys/module
/proc/modules
/sys/module 是一个 sysfs 目录层次, 包含当前加载模块的信息. /proc/moudles
是旧式的, 那种信息的单个文件版本. 其中的条目包含了模块名, 每个模块占用的
内存数量, 以及使用计数. 另外的字串追加到每行的末尾来指定标志, 对这个模块
当前是活动的.
vermagic.o
来自内核源码目录的目标文件, 描述一个模块为之建立的环境.
#include <linux/module.h>
必需的头文件. 它必须在一个模块源码中包含.
#include <linux/version.h>
头文件, 包含在建立的内核版本信息.
LINUX_VERSION_CODE
整型宏定义, 对 #ifdef 版本依赖有用.
EXPORT_SYMBOL (symbol);
EXPORT_SYMBOL_GPL (symbol);
宏定义, 用来输出一个符号给内核. 第 2 种形式输出没有版本信息, 第 3 种限制
输出给 GPL 许可的模块.
MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);
放置文档在目标文件的模块中.
module_init(init_function);
module_exit(exit_function);
宏定义, 声明一个模块的初始化和清理函数.
#include <linux/moduleparam.h>
module_param(variable, type, perm);
宏定义, 创建模块参数, 可以被用户在模块加载时调整( 或者在启动时间, 对于内
嵌代码). 类型可以是 bool, charp, int, invbool, short, ushort, uint, ulong,
或者 intarray.
#include <linux/kernel.h>
int printk(const char * fmt, ...);
内核代码的 printf 类似物.
#include <linux/types.h>
dev_t
dev_t 是用来在内核里代表设备号的类型.
int MAJOR(dev_t dev);
int MINOR(dev_t dev);
从设备编号中抽取主次编号的宏.
dev_t MKDEV(unsigned int major, unsigned int minor);
从主次编号来建立 dev_t 数据项的宏定义.
#include <linux/fs.h>
"文件系统"头文件是编写设备驱动需要的头文件. 许多重要的函数和数据结构在此
定义.
int register_chrdev_region(dev_t first, unsigned int count, char *name)
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char
*name)
void unregister_chrdev_region(dev_t first, unsigned int count);
允许驱动分配和释放设备编号的范围的函数. register_chrdev_region 应当用在事
先知道需要的主编号时; 对于动态分配, 使用 alloc_chrdev_region 代替.
int register_chrdev(unsigned int major, const char *name, struct
file_operations *fops);
老的( 2.6 之前) 字符设备注册函数. 它在 2.6 内核中被模拟, 但是不应当给新代
码使用. 如果主编号不是 0, 可以不变地用它; 否则一个动态编号被分配给这个设
备.
int unregister_chrdev(unsigned int major, const char *name);
恢复一个由 register_chrdev 所作的注册的函数. major 和 name 字符串必须包含
之前用来注册设备时同样的值.
struct file_operations;
struct file;
struct inode;
大部分设备驱动使用的 3 个重要数据结构. file_operations 结构持有一个字符驱
动的方法; struct file 代表一个打开的文件, struct inode 代表磁盘上的一个文
件.
#include <linux/cdev.h>
struct cdev *cdev_alloc(void);
void cdev_init(struct cdev *dev, struct file_operations *fops);
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
void cdev_del(struct cdev *dev);
cdev 结构管理的函数, 它代表内核中的字符设备.
#include <linux/kernel.h>
container_of(pointer, type, field);
一个传统宏定义, 可用来获取一个结构指针, 从它里面包含的某个其他结构的指针.
#include <asm/uaccess.h>
这个包含文件声明内核代码使用的函数来移动数据到和从用户空间.
unsigned long copy_from_user (void *to, const void *from, unsigned long count);
unsigned long copy_to_user (void *to, const void *from, unsigned long count);
在用户空间和内核空间拷贝数据.