驱动注册的两种方式(一)——file_operations结构体

来源:互联网 发布:java sip协议开发 编辑:程序博客网 时间:2024/05/19 18:16

使用file_operations结构体进行字符驱动设备的注册/注销:

#include <linux/module.h>//module_init()&module_exit()#include <linux/init.h>//__init()&__exit()#include <linux/fs.h>//register_chrdev() & unregister_chrdev()#include <asm/uaccess.h>#include <mach/regs-gpio.h>#include <mach/gpio-bank.h>// arch/arm/mach-s5pv210/include/mach/gpio-bank.h#include <linux/string.h>//strcmp()#include <linux/io.h>//#include <asm/io.h>#include <linux/ioport.h>#include <linux/cdev.h>//cdev_init()#include <linux/device.h>//class_create() & device_create() & device_destroy() & calss_destroy()//#define MYMAJOR  200#define MYCNT 1#define MYNAME "testchar"//静态映射LED寄存器地址(虚拟地址映射)#define GPJ0CONS5PV210_GPJ0CON#define GPJ0DATS5PV210_GPJ0DAT#define rGPJ0CON*((volatile unsigned int *)GPJ0CON)#define rGPJ0DAT*((volatile unsigned int *)GPJ0DAT)//动态映射寄存器地址(物理地址映射)#define GPJ0CON_PA 0XE0200240#define GPJ0DAT_PA 0XE0200244unsigned int *pGPJ0CON;unsigned int *pGPJ0DAT;//int mymajor;static dev_t mydev;//static struct cdev test_cdev;static struct cdev *pcdev;    //alloc_init()函数static struct class *test_class;struct device *dev;char kbuf[100];static int test_chrdev_open(struct inode *inode, struct file *file){//打开该设备的硬件操作代码rGPJ0CON = 0x11111111;// led初始化,也就是把GPJ0CON中设置为输出模式rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// 亮printk(KERN_INFO "test_chrdev_open.\n");return 0;}static int test_chrdev_release(struct inode *inode, struct file *file){printk(KERN_INFO "test_chrdev_release.\n");rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));//灭灯return 0;}static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos){int ret = -1;printk(KERN_INFO "test_chrdev_read.\n");ret = copy_to_user(ubuf, kbuf, count);//内核->用户if(ret){printk(KERN_ERR "copy_to_user fail\n");return -EINVAL;}printk(KERN_INFO "copy_to_user success....\n");return 0;}
//写函数的本质是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file,const char __user *ubuf,size_t count, loff_t *ppos){int ret = -1;printk(KERN_INFO "test_chrdev_write.\n");//使用该函数将应用层中传过来的ubuf中的数据拷贝到驱动空间中的kbuf//memcpy(kbuf, ubuf);//不行,因为两者不在同一个地址空间中ret = copy_from_user(kbuf, ubuf, count);//用户->内核if(ret){printk(KERN_ERR "copy_from_user fail...\n");return -EINVAL;}printk(KERN_INFO "copy_from_user success....\n");//根据驱动中接收的kbuf中的数据进行硬件操控if( kbuf[0] == '1') {rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));}else if(kbuf[0] == '0'){rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));}return 0;}//自定义file_operations结构体,并且填充static const struct file_operations test_fops = {.owner= THIS_MODULE,//该成员根本不是一个操作,他是一个指向拥有这个结构的模块的指针,该成员用来在它的操作还在被使用时阻止模块被卸载,几乎在所有的时间中,它被简单初始化为THIS_MODULE.open= test_chrdev_open,//打开相关设备的函数指针.release= test_chrdev_release,//关闭相关设备的函数指针.read= test_chrdev_read,.write= test_chrdev_write,};//模块安装函数static int __init chrdev_init(void){int retval;printk(KERN_INFO "chrdev_init.\n");//使用新的接口cdev来注册设备驱动,分两步://第一步:注册/分配主次设备号retval = alloc_chrdev_region(&mydev, 0, MYCNT, MYNAME);//返回所分配的设备号if (retval < 0) {printk(KERN_ERR "Unable to alloc_chrdev_region minors for %s\n", MYNAME);goto flag1;}printk(KERN_INFO "alloc_chrdev_region success...\n");printk(KERN_INFO "major = %d, minor = %d\n", MAJOR(mydev), MINOR(mydev));//第二步:注册字符设备驱动//cdev_init(pcdev, &test_fops);pcdev = cdev_alloc();//给pcdev分配内存,使指针实例化pcdev->owner = THIS_MODULE;pcdev->ops = &test_fops;retval = cdev_add(pcdev, mydev, MYCNT);//添加一个字符设备驱动if (retval) {printk(KERN_ERR "Unable to get cdev_add \n");goto flag2;}printk(KERN_INFO "cdev_add success...\n");//注册字符设备驱动完成后,添加设备类的操作,以让内核帮我们发信息//给udev,让udev自动创建和删除设备文件test_class = class_create(THIS_MODULE, "amber_class");//为设备创建一个classif (IS_ERR(test_class)) return -EINVAL;//最后一个参数字符串,就是我们将来要在/dev目录下创建的设备文件的名字//所以我们这里要的文件名是/dev/test//device_create(test_class, NULL, mydev, NULL, "test"); dev = device_create(test_class, NULL, mydev, NULL, "test111");//创建class对应的设备if (IS_ERR(dev)) return -EINVAL;// 使用动态映射的方式来操作寄存器if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")) //request_mem_region向内核申请需要映射的内存资源goto flag3;if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON")) goto flag3;pGPJ0CON = ioremap(GPJ0CON_PA, 4);//flag4pGPJ0DAT= ioremap(GPJ0DAT_PA, 4);//真正用来实现映射,传给他物理地址他给你映射返回一个虚拟地址*pGPJ0CON = 0x11111111;*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// 亮return 0;//缺少这句会报错:insmod: can't insert 'module_test.ko': File exists//flag4:release_mem_region(GPJ0CON_PA, 4);release_mem_region(GPJ0DAT_PA, 4);flag3:cdev_del(pcdev);flag2:unregister_chrdev_region(mydev, MYCNT);flag1:return -EINVAL;//return 0;}//模块卸载函数static void __exit chrdev_exit(void){printk(KERN_INFO "chrdev_exit.\n");*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// 解除映射iounmap(pGPJ0CON);//解除物理地址与虚拟地址的映射iounmap(pGPJ0DAT);release_mem_region(GPJ0CON_PA, 4);//归还向内核申请的内存资源release_mem_region(GPJ0DAT_PA, 4);/*unregister_chrdev(mymajor, MYNAME);*/device_destroy(test_class, mydev);//设备销毁class_destroy(test_class);//class销毁//使用新的接口注销字符设备驱动,分两步://第一步:真正注销字符设备驱动cdev_del(pcdev);//第二步:注销申请到的主次设备号unregister_chrdev_region(mydev, MYCNT);}module_init(chrdev_init);module_exit(chrdev_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息MODULE_LICENSE("GPL");// 描述模块的许可证MODULE_AUTHOR("amber");// 描述模块的作者MODULE_DESCRIPTION("module test");// 描述模块的介绍信息MODULE_ALIAS("alias xxx");// 描述模块的别名信息
相关问题分析:
1、
cdev_alloc() & cdev_init()

struct cdev *cdev_alloc(void)  {      struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);      if (p) {          INIT_LIST_HEAD(&p->list);          kobject_init(&p->kobj, &ktype_cdev_dynamic);      }      return p;  }
 //通过cdev_alloc的代码可以看出,它主要完成了空间的申请和简单的初始化操作;
void cdev_init(struct cdev *cdev, const struct file_operations *fops)  {      memset(cdev, 0, sizeof *cdev);      INIT_LIST_HEAD(&cdev->list);      kobject_init(&cdev->kobj, &ktype_cdev_default);      cdev->ops = fops;  }  
//通过cdev_init的代码可以看出,主要是对空间起到一个清零作用并较之cdev_alloc多了一个ops的赋值操作。


【注意】:kzalloc后的空间是不需要再执行memset的,因为它本身就包含了这个操作。而memset一般作用在已经存在的空间上

由此分析:

cdev_alloc函数针对于需要空间申请的操作,而cdev_init针对于不需要空间申请的操作;因此如果你定义的是一个指针,那么只需要使用cdev_alloc函数并在其后做一个ops的赋值操作就可以了;如果你定义的是一个结构体而非指针,那么只需要使用cdev_init函数就可以了

看到有些代码在定义一个指针后使用了cdev_alloc函数,紧接着又使用了cdev_init函数,这个过程不会出现错误,但只是做了一些重复的无用工作,其实完全可以不需要的。

2、注册/注销驱动必须使用倒影式

内核中很多函数中包含了很多个操作,这些操作每一步都有可能出错,而且出错后后面的步骤就没有进行下去的必要性了。

解决方案:倒影式机制,(先注册,后注销)

3、手动创建 ——>自动创建 字符设备驱动

手动创建:老接口分析

register_chrdev
__register_chrdev
__register_chrdev_region
cdev_alloc
cdev_add

手动创建过程:

装载:先insmod装载设备,再使用mknod创建设备文件【mknod /dev/test c 250 0】

卸载:先rmmod卸载设备,再删除mknod创建的设备文件


自动创建:新接口分析

register_chrdev_region
__register_chrdev_region

alloc_chrdev_region
__register_chrdev_region

自动创建过程:

装载:直接insmod,装载设备的同时创建文件

卸载:直接rmmod,卸载设备的同时删除创建的文件 

udev(嵌入式中用的是mdev)
(1)什么是udev?应用层的一个应用程序
(2)内核驱动和应用层udev之间有一套信息传输机制(netlink协议)
(3)应用层启用udev,内核驱动中使用相应接口
(4)驱动注册和注销时信息会被传给udev,由udev在应用层进行设备文件的创建和删除

内核驱动设备类相关函数
(1)class_create//创建类
(2)device_create//创建设备

(2)device_destroy//销毁设备
(1)calss_destroy//销毁类

4、相关函数 & 结构体分析

注册字符设备驱动新接口:

0——驱动向内核注册自己的函数register_chrdev()
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)

unsigned int major,主设备号
const char *name,设备名称 
const struct file_operations *fops: 
向函数内部传参(将自己写的file_operations传给register_chrdev()函数,在该函数内部完成自己编写的file_operations结构体的注册),因为file_operations是输入型参数,故前面要加const修饰
*******************************************************
常见错误定义文件目录:linux/asm-generic/errno-base.h
头文件包含:#include <linux/errno.h>
cat /pro/devices 查看当前设备
**********************************************************
1——新接口与老接口
(1)老接口:register_chrdev:绑定file_operations结构体和主次设备号(可自动分配也可自己设定)
(2)新接口:register_chrdev_region/alloc_chrdev_region + cdev
register_chrdev_region:指定设备号,让内核直接分配
alloc_chrdev_region + cdev:自动分配设备号
cdev:字符驱动设备的注册


2——cdev结构体介绍:
包含于 kernel/linux/cdev.h文件中
struct cdev {
struct kobject kobj;
struct module *owner;//用来将其与我们的模块挂钩
const struct file_operations *ops;//file_operations 结构体变量
struct list_head list;
dev_t dev; //设备号 = 主设备号 + 次设备号 dev_t:代表设备号
unsigned int count;//计数,(记录read或write的次数)
};
3——相关函数:cdev_alloc、cdev_init、cdev_add、cdev_del
cdev_alloc :给cdev结构体申请内存空间
cdev_init :初始化
cdev_add :添加一个设备驱动
cdev_del : 注销一个设备驱动

4——设备号
(1)主设备号和次设备号
(2)dev_t类型
(3)MKDEV、MAJOR、MINOR三个宏
MKDEV :将主设备号和次设备号构成设备号
MAJOR :提取主设备号
MINOR :提取次设备号
5——编程实践
(1)使用register_chrdev_region + cdev_init + cdev_add进行字符设备驱动注册,分两步:
//第一步:分配主次设备号
mydev = MKDEV(MYMAJOR, 0); //获取设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name)//绑定file_operations结构体和主次设备号
{
return 0;
}
dev_t from :起始设备号 = 主设备号 + 次设备号
unsigned count:次设备号数量
const char *name:设备名称
//第二步:注册字符设备驱动
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
struct cdev *cdev//字符驱动设备,(函数指针)
const struct file_operations *fops//file_operations结构体(函数指针)


int cdev_add(struct cdev *p, dev_t dev, unsigned count)
struct cdev *p//字符驱动设备,(函数指针)
dev_t dev //获取的设备号


6——注销设备驱动
// 第一步真正注销字符设备驱动用cdev_del
void cdev_del(struct cdev *p); //struct cdev *p字符驱动设备,(函数指针)
// 第二步去注销申请的主次设备号
unregister_chrdev_region(USB_DEVICE_DEV, USB_DEVICE_MAX);
USB_DEVICE_DEV//获取的设备号 
USB_DEVICE_MAX//次设备号数量


7——//自动分配设备号alloc_chrdev_region + cdev
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
dev_t *dev //输出型参数——也就是系统自动分配的主设备号
unsigned baseminor //次设备号的基准
unsigned count //次设备号数量
const char *name //


阅读全文
0 0
原创粉丝点击