字符设备驱动文件初始化与卸载(s5pv210/tiny210)

本文档提供了一步步的指南,讲解如何在Linux环境下进行设备驱动的开发。首先,通过dmesgTest.sh脚本实时查看内核输出信息。接着,介绍了Makefile文件的创建和使用,用于编译.c源文件生成内核模块。内容涵盖动态和静态注册设备号,以及设备的初始化。然后,展示了如何在用户空间创建设备节点,包括设备类和设备节点的创建与销毁。整个过程结合具体代码实例,便于理解和实践。
摘要由CSDN通过智能技术生成
通用文件
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步骤再执行一次,查看刚刚的那些存在的文件是不是都被卸载成功。

测试点四:打开设备文件
第一次写着文章,肯定有很多错误,欢迎大家指出,如果友友们需要的话,后面会继续写一些关于灯按键蜂鸣器等的驱动文件。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值