**********************************************************
文件夹:module_test(module_demo.c)
/************************* 必须有的部分 ********************/
// 所有模块都要用到的头文件
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
/*
* @brief 模块的初始化函数
* @return 0 模块加载成功
* !0 模块加载失败,返回错误码
* @notes __init 修饰模块加载函数,模块加载完成后,会释放模块加载函数占用的内存
*/
int __init module_demo_init(void)
{
extern int myint;
printk("module demo myint = %d\n", myint);
return 0;
}
/*
* @brief 模块的卸载函数
* @notes __exit 修饰模块卸载函数,静态加载模块,会释放模块卸载函数占用的内存
*/
void __exit module_demo_exit(void)
{
}
// 告诉内核模块的加载函数
module_init(module_demo_init);
// 告诉内核模块的卸载函数
module_exit(module_demo_exit);
// 告诉内核模块的许可
MODULE_LICENSE("GPL");
对应Makefile:
# KERNELRELEASE 变量装载的是内核版本,变量在内核顶层目录下的Makefile赋值
ifeq ($(KERNELRELEASE),)
# 内核源代码目录(模块将运行的内核的)
KERNELDIR ?= /usr/src/linux-headers-3.5.0-17-generic #(uname -r查看)
PWD := $(shell pwd)
# 调用内核顶层目录下的Makefile,是它调用本Makefile编译本模块
# -C 在做任何事情之前,进入指定目录
# M= 告诉内核顶层目录的Makefile,模块的位置
# modules 告诉内核顶层目录的Makefile 仅仅编译模块
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
# 清除编译出来的文件
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module* modules*
else
# 被内核顶层目录的Makefile调用,来编译模块
obj-m += module_test.o #模块重命名
module_test-objs := module_demo.o
endif
******************************************************************
文件夹module(module_demo.c)
/********************************** 必须有的部分 ****************************/
// 所有模块都要用到的头文件
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
/*
* @brief 模块的初始化函数
* @return 0 模块加载成功
* !0 模块加载失败,返回错误码
* @notes __init 修饰模块加载函数,模块加载完成后,会释放模块加载函数占用的内存
*/
int __init module_demo_init(void)
{
int i = 0;
extern short myshort;
printk("myshort = %d\n", myshort);
extern char *string;
printk("string = %s\n", string);
extern int array[10];
extern int num;
for (i = 0; i < num; i++) {
printk("array[%d]= %d\n", i, array[i]);
}
return 0;
}
/*
* @brief 模块的卸载函数
* @notes __exit 修饰模块卸载函数,静态加载模块,会释放模块卸载函数占用的内存
*/
void __exit module_demo_exit(void)
{
}
// 告诉内核模块的加载函数
module_init(module_demo_init);
// 告诉内核模块的卸载函数
module_exit(module_demo_exit);
// 告诉内核模块的许可
MODULE_LICENSE("GPL");
/********************************** 可选的部分 ****************************/
// 模块参数
// 在sys虚拟文件中查看参数值:cat /sys/module/module_demo/parameters/myint
// 加载模块命令
// insmod module_example.ko myshort=5 myint=6
short myshort = 0;
module_param(myshort, short, 0400);
MODULE_PARM_DESC(myshort, "myshort demo");
int myint = 0;
module_param(myint, int, 0400);
MODULE_PARM_DESC(myint, "myint demo");
long mylong = 0;
module_param(mylong, long, 0400);
MODULE_PARM_DESC(mylong, "mylong demo");
// 加载模块命令
// insmod module_example.ko string="hello"
char *string = 0;
module_param(string, charp, 0400);
MODULE_PARM_DESC(string, "string demo");
// 加载模块命令
// insmod module_example.ko array=1,2,3
int array[10] = {1, 2, 3};
int num;
module_param_array(array, int, &num, 0400);
// 导出符号到内核符号表
EXPORT_SYMBOL_GPL(myint);
EXPORT_SYMBOL(myshort);
// 作者
MODULE_AUTHOR("demo");
// 描述
MODULE_DESCRIPTION("A module demo");
// 重命名
MODULE_ALIAS("demo hello");
// 全大写的宏说明此宏跟编译运行无关,仅仅说明信息
/*
基础知识:
1:一个设备驱动可以只包含 <linux/sched.h> 并且引用当前进程.例如, 下面的语句打印了当前进程的进程 ID 和命令名称, 通过存取结构task_struct 中的某些字段.
printk(KERN_INFO "The process is \"%s\" (pid %i)\n", current->comm,current->pid);
存储于 current->comm 的命令名称是由当前进程执行的程序文件的基本名称( 截短到 15 个字符, 如果需要 ).
2:lsmod 通过读取 /proc/modules 虚拟文件工作. 当前加载的模块的信息也可在位于 /sys/module 的 sysfs 虚拟文件系统找到.
/**************************Makefile******************************/
KERN_DIR = /work/system/linux-2.6.30.4
all:
make -C $(KERN_DIR) M=`pwd` modules
# -C 指定进入指定的目录即KERN_DIR,是内核源代码目录,调用该顶层目录下的Makefile
# M=选项让该makefile在构造modules目标之前返回到模块源代码目录
clean: #然后modules目标指向obj-m变量中设定的模块
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += xxx.o
xxx-objs :=xxx1.o xxx2.o ..... #模块是由多个C文件构成
/***************************end*******************************/
/************************相关头文件*****************************/
#include <asm/irq.h> //IRQ中断相关头文件
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <mach/regs-gpio.h> //GPIO操作相关头文件
#include <asm/io.h>
#include <mach/hardware.h> //硬件平台相关头文件
#include <linux/kernel.h> //内核相关的头文件
#include <linux/module.h>
//所有模块都需要的头文件,此头文件包含了可装载模块需要的大量的符号和函数定义
#include <linux/init.h> //设备驱动初始化相关的头文件,包含该函数的目的是指定初始化和清除函数
#include <linux/mm.h> //内存管理相关的头文件
#include <linux/fs.h>
//文件系统相关的头文件,它是编写设备驱动程序必需的头文件,其中声明了许多重要的函数和数据结构
#include <linux/types.h> //设备类型定义
#include <linux/delay.h> //延时处理相关的头文件
#include <linux/moduleparam.h>
//大部分模块都包含的头文件,这样我们就可以在装载模块时向模块传递参数
#include <linux/slab.h>
#include <linux/errno.h> //错误处理
#include <linux/ioctl.h> //IO操作
#include <linux/cdev.h> //字符设备相关的头文件
#include <linux/string.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <asm/uaccess.h> //包含它,可以用它定义的用户空间和内核空间的数据传递
#include <asm/atomic.h>
#include <asm/unistd.h>
#include <linux/version.h> //版本相关
#include <linux/semaphore.h>
#include <asm/system.h>
#include <linux/device.h>
/***************************end*********************************/
/*******************模块相关的说明与许可证**************************/
MODULE_AUTHOR("xxxx"); //描述模块作者
MODULE_LICENSE("Dual BSD/GPL");//指定代码使用双重许可证
MODULE_VERSION("v1.0"); //模块版本
MODULE_DESCRIPTION("xxxx"); //说明模块用途
MODULE_ALIAS("xxx"); //模块别名
MODULE_DEVICE_TABLE(); //用来告诉内核空间模块所支持的设备
/***************************end*********************************/
/**********************模块加载与卸载******************************/
static int __init xxx_init(void) //初始化函数
{
//注意错误的处理,goto就比较有效
return 0;
}
static void __exit xxx_exit(void) //清除函数
{
}
module_init(xxx_init);//设备加载函数
module_exit(xxx_exit);//设备卸载函数
/***************************************************************/
当加载程序完成之后,__init标识符修饰的函数就会无效,所占用的内存就会被释放
__exit标识符也是一样(动态加载)
/***************************************************************/
/*
在注册设备时, 注册可能失败. 即便最简单的动作常常需要内存分配, 分配的内存可能不可用.
因此模块代码必须一直检查返回值,并且确认要求的操作实际上已经成功.
当模块的初始化出现错误后,模块必须自行撤销已注册的设施,错误恢复处理有时goto语句比较有效
/****************************end**********************************/
注:以上的学习总结选自韦东山群答疑助手:沈朝平的《Linux驱动程序学习笔记》!非常感谢!