通用文件
dmesgTest.sh文件:查看内核的输出信息
while true
do
dmesg -c
sleep 1
done
Linux中创建一个如上文件。打开终端,进入当前目录,执行以下指令,后面驱动的输出信息都会在这边显示。
# chmod +x dmesgTest.sh
# ./dmesgTest.sh
Tips:第一次执行后会直接输出一大堆的信息,不是你做错了,这是正常的
Makefile文件:编译.c文件生成目标文件
obj-m:=globalmem.o
ifneq ($(arch), arm)
all:
make -C /usr/src/linux-headers-3.13.0-32-generic M=$(PWD) modules
else
CROSS=/opt/FriendlyARM/toolschain/4.5.1/bin/arm-linux-
CC=$(CROSS)gcc
LD=$(CROSS)ld
AR=$(CROSS)ar
AS=$(CROSS)as
all:
make -C /opt/FriendlyARM/mini210/linux/linux-3.0.8 M=$(PWD) modules
endif
clean:
rm -rf *.o *.ko *.mod.c *.mod.o *.order *.symvers
需要自行更改的有
1.第一行的globalmem,将其改为你所需要编译的文件的文件名后缀.o不能漏
2.第六行的内核编译器的版本号,需要改成自己虚拟机内核对应的版本号
查询版本号方式,在终端输入以下指令
cat /proc/version
Makefile的创建
终端进入到存放dmesgTest.sh的同级目录中,
使用gedit或vim指令创建Makefile文件,
并写入如上内容,
保存更改并退出。
关于Makefile的使用方法
1.直接编译目标文件(适用于Ubuntu调用)
# make
2.编译出适用于开发板的文件
# make arch=arm
3.清除编译生成的文件
# make clean
globalmem.c文件
测试点一:在内核空间中注册设备号[静态/动态]
代码如下:globalmem.c文件
# include <asm/io.h>
# include <asm/uaccess.h>
# include <linux/fs.h>
# include <linux/mm.h>
# include <linux/init.h>
# include <linux/cdev.h>
# include <linux/slab.h>
# include <linux/types.h>
# include <linux/errno.h>
# include <linux/sched.h>
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/device.h>
//#include <asm/system.h>
//#include <mach/regs-gpio.h> // 2.6.32
//#include <asm/arch/regs-gpio.h> // 2.6.12
/*---宏定义--*/
# define GLOBALMEM_MAJOR 201//定义GLOBALMEM_MAJOR的值为201;后面将其赋给主设备号变量
/*********************************输出开关****************************************/
# define PDEBUG 2//输出的调试开关
# ifdef PDEBUG//如果PDEBUG被定义了(或者说如果调试器被打开了)
# define DPRINTK printk//那么就把DPRINTK定义成printk
# else//如果没有打开
# define DPRINTK(...)//就把DPRINK(...)定义为空,也就是接下来不会有输出
# endif//接续判断语句
/*********************************************************************************/
/*--函数声明--*/
static int globalmem_init(void);//自定义的初始化函数 初始话
void globalmem_exit(void);//自定义的卸载函数
//关联模块初始函数
module_init(globalmem_init);//根据参数关联具体的初始化函数
//关联模块卸载函数
module_exit(globalmem_exit);
//写入模块协议
MODULE_LICENSE("GPL");
//写入模块作者
MODULE_AUTHOR("XYQ");
//设置模块版本号
MODULE_VERSION("XXX");
/*--全局变量--*/
dev_t dev;//存放设备号(备注:该为结构体变量,没有struct是因为在某个头文件中被typedef了)
static int globalmem_major;//存放主设备号
static int globalmem_init()
{
/*Step 1:在内核空间中注册设备号[静态/动态]*/
//定义变量
int result;//存放函数执行的结果
DPRINTK("Initial Start...\n");//提示进入初始化函数
//Step 1:注册设备号
globalmem_major = GLOBALMEM_MAJOR;//将宏定义好的数据赋值给 globalmem_major 变量
if(globalmem_major){
//静态[自定义的主设备号·非0]
//利用主设备号获取设备号
dev = MKDEV(globalmem_major,0);
//在内核空间中静态注册设备号
result = register_chrdev_region(dev,1,"Croe_Static_Xyq");
}else{
//动态[自定义的主设备号·为0]
result = alloc_chrdev_region(&dev,0,1,"Croe_Alloc_Xyq");
//从动态申请的设备中获取主设备号
globalmem_major = MAJOR(dev);
}
//根据函数的返回值判断设备号是否注册成功
if(result<0){
DPRINTK("Dev_Reg is failing...\n");
return -EINVAL;
}
DPRINTK("Initial Ended...\n");//提示结束初始化函数
return 0;
}
void globalmem_exit()
{
DPRINTK("Exit Start...\n");//提示进入卸载函数
unregister_chrdev_region(dev,1);//注销设备号
DPRINTK("Exit Ended...\n");//提示结束卸载函数
return;
}
/* 常见错误
* 1:输出的语句没有\n结尾会被送入缓冲区
* 2:动态分配设备号的函数第一个参数需要取地址,因为需对其进行更改
*
*/
测试指令:
Step 1: Linux下启动一个终端,进入存放dmesgTest.sh的同级目录,为其加上可执行的权限,然后执行以下指令,挂在旁边,到时候驱动的输出信息都会在这边显示
chmod +x dmesgTest.sh
./dmesgTest.sh
Step 2: Linux下再启动一个终端进入存放Makefile和globalmem.c文件的文件目录,执行make指令
make
Step 3: 如果有报错,一步步解决,如果创建成功可以利用ls
指令查看当前文件下有没有产生输出文件,比如.ko或者.order等文件
Step 4: 如果有生成输出文件说明编译成功了,接下来就要挂载驱动了
insmod globalmem.ko
Step 5: 这时候,刚刚运行./dmesgTest.sh
的终端应该会有提示信息输出,如下面的2,3行
root@xyq-machine:/mnt/hgfs/share/drivers# ./dmesgTest.sh
[12192.426088] Initial Start...
[12192.426091] Initial Ended...
Step 6:说明驱动初始化函数调用成功,接下来输入指令来查询内核空间是否成功注册设备号
cat /proc/devices
这时候会输出一长列的信息,分为两部分,我们主要在上面那部分,也就是Character devices:
字符设备驱动里,查找有没有你的设备名字,如果没有修改过我的代码应该有一行是201 Croe_Staic_Xyq
,这是静态申请的,代表成功了,若为动态申请那设备号就不一定的201,但名字是我设置过的Croe_Alloc_Xyq
。静态动态的差别主要在于初始化函数中的globalmem_major = GLOBALMEM_MAJOR;
有没有注释掉。
Step 7:上面都成功了的话需要移除驱动
rmmod globalmem.ko
Step 8:这时候,刚刚运行./dmesgTest.sh
的终端应该会有提示信息输出,如下
[12664.461616] Exit Start...
[12664.461620] Exit Ended...
然后可以在执行一次 Step 6 查看以下在内核中是否真的注销了设备号
Step 9:清除编译
make clean
这时候会清除所有的输出文件,一样的用ls查询是否成功就好就好
测试点二:对设备(devp)进行初始化
# include <asm/io.h>
# include <asm/uaccess.h>
# include <linux/fs.h>
# include <linux/mm.h>
# include <linux/init.h>
# include <linux/cdev.h>
# include <linux/slab.h>
# include <linux/types.h>
# include <linux/errno.h>
# include <linux/sched.h>
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/device.h>
//#include <asm/system.h>
//#include <mach/regs-gpio.h> // 2.6.32
//#include <asm/arch/regs-gpio.h> // 2.6.12
/*---宏定义--*/
# define SIZE 1024
# define GLOBALMEM_MAJOR 201//定义GLOBALMEM_MAJOR的值为201;后面将其赋给主设备号变量
/*********************************输出开关****************************************/
# define PDEBUG 2//输出的调试开关
# ifdef PDEBUG//如果PDEBUG被定义了(或者说如果调试器被打开了)
# define DPRINTK printk//那么就把DPRINTK定义成printk
# else//如果没有打开
# define DPRINTK(...)//就把DPRINK(...)定义为空,也就是接下来不会有输出
# endif//接续判断语句
/*********************************************************************************/
/*--设备结构体--*/
struct globalmem_dev{
struct cdev cdev;//设备(struct cdev该结构体类型是在头文件中声明的)
char mem[SIZE];//定义一个1024大小的数据缓冲区,SIZE在宏定义中被赋值为1024
};
/*--函数声明--*/
static int globalmem_init(void);//自定义的初始化函数 初始话
void globalmem_exit(void);//自定义的卸载函数
/*--全局变量--*/
dev_t dev;//存放设备号(备注:该为结构体变量,没有struct是因为在某个头文件中被typedef了)
static int globalmem_major;//存放主设备号
struct globalmem_dev *devp;//定义一个指针类型的设备结构体变量
//定义一个函数结构体变量 globalmem_fops 并对其中的成员进行赋值
static const struct file_operations globalmem_fops={
.owner = THIS_MODULE,//函数结构体变量的owner成员赋值为本模块(THIS_MODULE);
};
//Tips:该struct file_operations类型是头文件中定义好的 static,const只是修饰变量用的
//类比 static const int i = 0;
//关联模块初始函数
module_init(globalmem_init);//根据参数关联具体的初始化函数
//关联模块卸载函数
module_exit(globalmem_exit);
//写入模块协议
MODULE_LICENSE("GPL");
//写入模块作者
MODULE_AUTHOR("XYQ");
//设置模块版本号
MODULE_VERSION("XXX");
static int globalmem_init()
{
//定义变量
int result;//存放函数执行的结果
DPRINTK("Initial Start...\n");//提示进入初始化函数
//Step 1:注册设备号
globalmem_major = GLOBALMEM_MAJOR;//将宏定义好的数据赋值给 globalmem_major 变量
if(globalmem_major){
//静态[自定义的主设备号·非0]
//利用主设备号获取设备号
dev = MKDEV(globalmem_major,0);
//在内核空间中静态注册设备号
result = register_chrdev_region(dev,1,"Croe_Static_Xyq");
}else{
//动态[自定义的主设备号·为0]
result = alloc_chrdev_region(&dev,0,1,"Croe_Alloc_Xyq");
//从动态申请的设备中获取主设备号
globalmem_major = MAJOR(dev);
}
//根据函数的返回值判断设备号是否注册成功
if(result<0){
DPRINTK("Dev_Reg is failing...\n");
return -EINVAL;
}
//Step 2:对设备(devp)进行初始化 三步骤: 定义->注册->添加
//定义:在全局变量中对其进行定义,在这里只需要利用kmalloc函数对其开辟空间即可
devp = kmalloc(sizeof(struct globalmem_dev),GFP_KERNEL);//该函数原型的返回值类型为void *:无类型指针
if(devp<0){
//若失败返回-1
result = -EINVAL;//利用result存放错误信息
goto KMALLOC;//跳转到注销设备号的代码,释放设备号
}
memset(devp,0,sizeof(struct globalmem_dev));//该函数对参数一进行初始化
//注册:
cdev_init(&devp->cdev,&globalmem_fops);//注册设备cdev并关联 函数结构体 变量glcbalmem_fops
//添加:
cdev_add(&devp->cdev,dev,1);//设备地址 设备号 设备数量
DPRINTK("Initial Ended...\n");//提示结束初始化函数
return 0;
KMALLOC:
unregister_chrdev_region(dev,1);//注销设备号
return result;//返回错误信息
}
void globalmem_exit()
{
DPRINTK("Exit Start...\n");//提示进入卸载函数
cdev_del(&devp->cdev);//删除设备
kfree(devp);//释放设备空间
unregister_chrdev_region(dev,1);//注销设备号
DPRINTK("Exit Ended...\n");//提示结束卸载函数
return;
}
调试指令:
这一次的指令同测试点一,只要make以后不报错,和 测试点一 一样能挂载,能卸载就好.
因为测试点二做的是对设备的初始化,要到后面的读写等函数的运用,才能体现他的作用
测试点三:在用户空间中创建设备
# include <asm/io.h>
# include <asm/uaccess.h>
# include <linux/fs.h>
# include <linux/mm.h>
# include <linux/init.h>
# include <linux/cdev.h>
# include <linux/slab.h>
# include <linux/types.h>
# include <linux/errno.h>
# include <linux/sched.h>
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/device.h>
//#include <asm/system.h>
//#include <mach/regs-gpio.h> // 2.6.32
//#include <asm/arch/regs-gpio.h> // 2.6.12
/*---宏定义--*/
# define SIZE 1024
# define GLOBALMEM_MAJOR 201//定义GLOBALMEM_MAJOR的值为201;后面将其赋给主设备号变量
/*********************************输出开关****************************************/
# define PDEBUG 2//输出的调试开关
# ifdef PDEBUG//如果PDEBUG被定义了(或者说如果调试器被打开了)
# define DPRINTK printk//那么就把DPRINTK定义成printk
# else//如果没有打开
# define DPRINTK(...)//就把DPRINK(...)定义为空,也就是接下来不会有输出
# endif//接续判断语句
/*********************************************************************************/
/*--设备结构体--*/
struct globalmem_dev{
struct cdev cdev;//设备(struct cdev该结构体类型是在头文件中声明的)
char mem[SIZE];//定义一个1024大小的数据缓冲区,SIZE在宏定义中被赋值为1024
};
/*--函数声明--*/
static int globalmem_init(void);//自定义的初始化函数 初始话
void globalmem_exit(void);//自定义的卸载函数
/*--全局变量--*/
dev_t dev;//存放设备号(备注:该为结构体变量,没有struct是因为在某个头文件中被typedef了)
static int globalmem_major;//存放主设备号
struct globalmem_dev *devp;//定义一个指针类型的设备结构体变量
struct class *globalmem_class;//定义一个设备类指针
static const struct file_operations globalmem_fops={
.owner = THIS_MODULE,//函数结构体变量的owner成员赋值为本模块(THIS_MODULE);
};
//关联模块初始函数
module_init(globalmem_init);//根据参数关联具体的初始化函数
//关联模块卸载函数
module_exit(globalmem_exit);
//写入模块协议
MODULE_LICENSE("GPL");
//写入模块作者
MODULE_AUTHOR("XYQ");
//设置模块版本号
MODULE_VERSION("XXX");
static int globalmem_init()
{
//定义变量
int result;//存放函数执行的结果
DPRINTK("Initial Start...\n");//提示进入初始化函数
//Step 1:注册设备号
globalmem_major = GLOBALMEM_MAJOR;//将宏定义好的数据赋值给 globalmem_major 变量
if(globalmem_major){
//静态[自定义的主设备号·非0]
//利用主设备号获取设备号
dev = MKDEV(globalmem_major,0);
//在内核空间中静态注册设备号
result = register_chrdev_region(dev,1,"Croe_Static_Xyq");
}else{
//动态[自定义的主设备号·为0]
result = alloc_chrdev_region(&dev,0,1,"Croe_Alloc_Xyq");
//从动态申请的设备中获取主设备号
globalmem_major = MAJOR(dev);
}
//根据函数的返回值判断设备号是否注册成功
if(result<0){
DPRINTK("Dev_Reg is failing...\n");
return -EINVAL;
}
//Step 2:对设备(devp)进行初始化 三步骤: 定义->注册->添加
//定义:在全局变量中对其进行定义,在这里只需要利用kmalloc函数对其开辟空间即可
devp = kmalloc(sizeof(struct globalmem_dev),GFP_KERNEL);//该函数原型的返回值类型为void *:无类型指针
if(devp<0){
//若失败返回-1
result = -EINVAL;//利用result存放错误信息
goto KMALLOC;//跳转到注销设备号的代码,释放设备号
}
memset(devp,0,sizeof(struct globalmem_dev));//该函数对参数一进行初始化
//注册:
cdev_init(&devp->cdev,&globalmem_fops);//注册设备cdev并关联 函数结构体 变量glcbalmem_fops(在全局变量中对glcbalmem_fops有进行定义)
//添加:
cdev_add(&devp->cdev,dev,1);//设备地址 设备号 设备数量
//Step 3:在用户空间中创建设备 三步骤:创建设备类->创建设备节点
//创建设备类
globalmem_class = class_create(THIS_MODULE,"Xyq_class");//globalmem_class在全局变量处有定义
if(IS_ERR(globalmem_class)){
//IS_ERR函数判断设备类是否创建成功 成功:0
result = -EINVAL;//跳转到注销设备号的代码,释放设备号,并执行KMALLOC
goto CLASS;
}
//创建设备节点
if(device_create(globalmem_class,NULL,dev,NULL,"dev_xyq")==NULL){
DPRINTK("Create devices is failing...");
result = -EINVAL;
goto DEVICE;//跳转到销毁设备类的代码,并按序执行CLASS和KMALLOC
}
DPRINTK("Initial Ended...\n");//提示结束初始化函数
return 0;//结束初始化函数
DEVICE:
class_destroy(globalmem_class);
CLASS:
cdev_del(&devp->cdev);
kfree(devp);
KMALLOC:
unregister_chrdev_region(dev,1);//注销设备号
return result;//返回错误信息
}
void globalmem_exit()
{
DPRINTK("Exit Start...\n");//提示进入卸载函数
device_destroy(globalmem_class,dev);//销毁用户空间的设备节点
class_destroy(globalmem_class);//销毁设备类
cdev_del(&devp->cdev);//删除设备
kfree(devp);//释放设备空间
unregister_chrdev_region(dev,1);//注销设备号
DPRINTK("Exit Ended...\n");//提示结束卸载函数
return;
}
测试指令:
Step 1: ,然后执行以下指令,挂在旁边,到时候驱动的输出信息都会在这边显示(如果已经在执行了,就不需要再次执行)
./dmesgTest.sh
Step 2: Linux下再启动一个终端进入存放Makefile和globalmem.c文件的文件目录
make
Step 3: 如果有报错,一步步解决,如果创建成功可以利用 ls
指令查看当前文件下有没有多出输出文件,比如.ko或者.order等文件
Step 4: 如果有生成输出文件说明编译成功了,接下来就要挂载驱动了
insmod globalmem.ko
Step 5: 这时候,刚刚运行./dmesgTest.sh
的终端应该会有提示信息输出,如下面的2,3行
root@xyq-machine:/mnt/hgfs/share/drivers# ./dmesgTest.sh
[12192.426088] Initial Start...
[12192.426091] Initial Ended...
Step 6:说明驱动初始化函数调用成功,接下来输入指令来查询内核空间是否成功注册设备号
cat /proc/devices
这时候会输出一长列的信息,分为两部分,我们主要在上面那部分,也就是Character devices:
字符设备驱动里,查找有没有你的设备名字,如果没有修改过我的代码应该有一行是201 Croe_Staic_Xyq
,这是静态申请的,代表成功了,若为动态申请那设备号就不一定的201,但名字是我设置过的Croe_Alloc_Xyq
。静态动态的差别主要在于代码的81行有没有注释掉。
以下的步骤不同于上面
Step 7:设备号注册成功了之后要查看设备类有没有创建成功
ls /sys/class/
在列出的文件里查找有没有一个叫Xyq_class的设备类文件,如果有的话,说明设备类创建成功
Step 8:接着看一下用户空间中的设备节点是否创建成功
ls /dev
在列出的文件里查找有没有叫 dev_xyq 的设备节点,如果有则代表成功
Step 9:卸载驱动
rmmod globalmem.ko
接下去的步骤就是6,7,8步骤再执行一次,查看刚刚的那些存在的文件是不是都被卸载成功。
测试点四:打开设备文件
第一次写着文章,肯定有很多错误,欢迎大家指出,如果友友们需要的话,后面会继续写一些关于灯按键蜂鸣器等的驱动文件。