内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。

(1)register_chrdev  比较老的内核注册的形式   早期的驱动
(2)register_chrdev_region/alloc_chrdev_region + cdev  新的驱动形式

区别:register_chrdev()函数是老版本里面的设备号注册函数,可以实现静态和动态注册两种方法,主要是通过给定的主设备号是否为0来进行区别,为0的时候为动态注册。register_chrdev_region以及alloc_chrdev_region就是将上述函数的静态和动态注册设备号进行了拆分的强化。

register_chrdev_region(dev_t first,unsigned int count,char *name)
First :要分配的设备编号范围的初始值, 这组连续设备号的起始设备号, 相当于register_chrdev()中主设备号
Count:连续编号范围.   是这组设备号的大小(也是次设备号的个数)
Name:编号相关联的设备名称. (/proc/devices); 本组设备的驱动名称

alloc_chrdev_region函数,来让内核自动给我们分配设备号

(1)register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。
(2)更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。
(3)自动分配的设备号,我们必须去知道他的主次设备号,否则后面没法去mknod创建他对应的设备文件。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

1:这个函数的第一个参数,是输出型参数,获得一个分配到的设备号。可以用MAJOR宏和MINOR宏,将主设备号和次设备号,提取打印出来,看是自动分配的是多少,方便我们在mknod创建设备文件时用到主设备号和次设备号。 mknod /dev/xxx c 主设备号 次设备号

2:第二个参数:次设备号的基准,从第几个次设备号开始分配。

3:第三个参数:次设备号的个数。

4:第四个参数:驱动的名字。

5:返回值:小于0,则错误,自动分配设备号错误。否则分配得到的设备号就被第一个参数带出来。

cdev介绍

cdev是一个结构体,里面的成员来共同帮助我们注册驱动到内核中,表达字符设备的,将这个struct cdev结构体进行填充,主要填充的内容就是

struct cdev {

struct kobject kobj;

struct module *owner;//填充时,值要为 THIS_MODULE,表示模块

const struct file_operations *ops;//这个file_operations结构体,注册驱动的关键,要填充成这个结构体变量

struct list_head list;

dev_t dev;//设备号,主设备号+次设备号

unsigned int count;//次设备号个数

};

file_operations这个结构体变量,让cdev中的ops成员的值为file_operations结构体变量的值。这个结构体会被cdev_add函数想内核注册

cdev结构体,可以用很多函数来操作他。

如:

cdev_alloc:让内核为这个结构体分配内存的

cdev_init:将struct cdev类型的结构体变量和file_operations结构体进行绑定的

cdev_add:向内核里面添加一个驱动,注册驱动

cdev_del:从内核中注销掉一个驱动。注销驱动

设备号

(1)dev_t类型(包括了主设备号和次设备号  不同的内核中定义不一样有的是16位次设备号和16位主设备号构成  有的是20为次设备号12位主设备号 )

(2)MKDEV、MAJOR、MINOR三个宏

MKDEV:  是用来将主设备号和次设备号,转换成一个主次设备号的。(设备号)

MAJOR:   从设备号里面提取出来主设备号的。

MINOR宏:从设备号中提取出来次设备号的。

register_chrdev_region的使用对比register_chrdev:
 // 模块安装函数
static int __init chrdev_init(void)
{
int retval; printk(KERN_INFO "chrdev_init helloworld init\n"); /*
// 在module_init宏调用的函数中去注册字符设备驱动
// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号
// 内核如果成功分配就会返回分配的主设备好;如果分配失败会返回负数
mymajor = register_chrdev(0, MYNAME, &test_fops);
if (mymajor < 0)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
*/ // 使用新的cdev接口来注册字符设备驱动
// 新的接口注册字符设备驱动需要2步 // 第1步:注册/分配主次设备号
mydev = MKDEV(MYMAJOR, );
retval = register_chrdev_region(mydev, MYCNT, MYNAME);//
//动态时如下直接改 同时将2526行去掉 其他都一样
//int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
if (retval) {
printk(KERN_ERR "Unable to register minors for %s\n", MYNAME);
return -EINVAL;
}
printk(KERN_INFO "register_chrdev_region success\n");
// 第2步:注册字符设备驱动
cdev_init(&test_cdev, &test_fops);
retval = cdev_add(&test_cdev, mydev, MYCNT);
if (retval) {
printk(KERN_ERR "Unable to cdev_add\n");
return -EINVAL;
}
printk(KERN_INFO "cdev_add success\n"); // 使用动态映射的方式来操作寄存器
if (!request_mem_region(GPJ0CON_PA, , "GPJ0CON"))
return -EINVAL;
if (!request_mem_region(GPJ0DAT_PA, , "GPJ0CON"))
return -EINVAL; pGPJ0CON = ioremap(GPJ0CON_PA, );
pGPJ0DAT = ioremap(GPJ0DAT_PA, ); *pGPJ0CON = 0x11111111;
*pGPJ0DAT = ((<<) | (<<) | (<<)); // 亮 return ;
} // 模块下载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n"); *pGPJ0DAT = ((<<) | (<<) | (<<)); // 解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
release_mem_region(GPJ0CON_PA, );
release_mem_region(GPJ0DAT_PA, ); /*
// 在module_exit宏调用的函数中去注销字符设备驱动
unregister_chrdev(mymajor, MYNAME);
*/ // 使用新的接口来注销字符设备驱动
// 注销分2步:
// 第一步真正注销字符设备驱动用cdev_del
cdev_del(&test_cdev);
// 第二步去注销申请的主次设备号
unregister_chrdev_region(mydev, MYCNT);
}

完整代码:

 #include <linux/module.h>        // module_init  module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#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>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h> #define MYMAJOR 200
#define MYCNT 1
#define MYNAME "testchar" #define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT #define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT) #define GPJ0CON_PA 0xe0200240
#define GPJ0DAT_PA 0xe0200244 unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT; int mymajor;
static dev_t mydev;
static struct cdev test_cdev; char kbuf[]; // 内核空间的buf static int test_chrdev_open(struct inode *inode, struct file *file)
{
// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。
printk(KERN_INFO "test_chrdev_open\n"); rGPJ0CON = 0x11111111;
rGPJ0DAT = ((<<) | (<<) | (<<)); // 亮 return ;
} static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n"); rGPJ0DAT = ((<<) | (<<) | (<<)); return ;
} ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -; 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 ;
} // 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
int ret = -; printk(KERN_INFO "test_chrdev_write\n"); // 使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间中的一个buf中
//memcpy(kbuf, ubuf); // 不行,因为2个不在一个地址空间中
memset(kbuf, , sizeof(kbuf));
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"); if (kbuf[] == '')
{
rGPJ0DAT = ((<<) | (<<) | (<<));
}
else if (kbuf[] == '')
{
rGPJ0DAT = ((<<) | (<<) | (<<));
} /*
// 真正的驱动中,数据从应用层复制到驱动中后,我们就要根据这个数据
// 去写硬件完成硬件的操作。所以这下面就应该是操作硬件的代码
if (!strcmp(kbuf, "on"))
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if (!strcmp(kbuf, "off"))
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
*/ return ;
} // 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可 .open = test_chrdev_open, // 将来应用open打开这个设备时实际调用的
.release = test_chrdev_release, // 就是这个.open对应的函数
.write = test_chrdev_write,
.read = test_chrdev_read,
}; // 模块安装函数
static int __init chrdev_init(void)
{
int retval; printk(KERN_INFO "chrdev_init helloworld init\n"); /*
// 在module_init宏调用的函数中去注册字符设备驱动
// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号
// 内核如果成功分配就会返回分配的主设备好;如果分配失败会返回负数
mymajor = register_chrdev(0, MYNAME, &test_fops);
if (mymajor < 0)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
*/ // 使用新的cdev接口来注册字符设备驱动
// 新的接口注册字符设备驱动需要2步 // 第1步:注册/分配主次设备号
mydev = MKDEV(MYMAJOR, );
retval = register_chrdev_region(mydev, MYCNT, MYNAME);
if (retval) {
printk(KERN_ERR "Unable to register minors for %s\n", MYNAME);
return -EINVAL;
}
printk(KERN_INFO "register_chrdev_region success\n");
// 第2步:注册字符设备驱动
cdev_init(&test_cdev, &test_fops);
retval = cdev_add(&test_cdev, mydev, MYCNT);
if (retval) {
printk(KERN_ERR "Unable to cdev_add\n");
return -EINVAL;
}
printk(KERN_INFO "cdev_add success\n"); // 使用动态映射的方式来操作寄存器
if (!request_mem_region(GPJ0CON_PA, , "GPJ0CON"))
return -EINVAL;
if (!request_mem_region(GPJ0DAT_PA, , "GPJ0CON"))
return -EINVAL; pGPJ0CON = ioremap(GPJ0CON_PA, );
pGPJ0DAT = ioremap(GPJ0DAT_PA, ); *pGPJ0CON = 0x11111111;
*pGPJ0DAT = ((<<) | (<<) | (<<)); // 亮 return ;
} // 模块下载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n"); *pGPJ0DAT = ((<<) | (<<) | (<<)); // 解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
release_mem_region(GPJ0CON_PA, );
release_mem_region(GPJ0DAT_PA, ); /*
// 在module_exit宏调用的函数中去注销字符设备驱动
unregister_chrdev(mymajor, MYNAME);
*/ // 使用新的接口来注销字符设备驱动
// 注销分2步:
// 第一步真正注销字符设备驱动用cdev_del
cdev_del(&test_cdev);
// 第二步去注销申请的主次设备号
unregister_chrdev_region(mydev, MYCNT);
} module_init(chrdev_init);
module_exit(chrdev_exit); // MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
 #include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h> #define FILE "/dev/test" // 刚才mknod创建的设备文件名 char buf[]; int main(void)
{
int fd = -;
int i = ; fd = open(FILE, O_RDWR);
if (fd < )
{
printf("open %s error.\n", FILE);
return -;
}
printf("open %s success..\n", FILE); /*
// 读写文件
write(fd, "on", 2);
sleep(2);
write(fd, "off", 3);
sleep(2);
write(fd, "on", 2);
sleep(2);
*/
/*
write(fd, "1", 1);
sleep(2);
write(fd, "0", 1);
sleep(2);
write(fd, "1", 1);
sleep(2);
*/
while ()
{
memset(buf, , sizeof(buf));
printf("请输入 on | off \n");
scanf("%s", buf);
if (!strcmp(buf, "on"))
{
write(fd, "", );
}
else if (!strcmp(buf, "off"))
{
write(fd, "", );
}
else if (!strcmp(buf, "flash"))
{
for (i=; i<; i++)
{
write(fd, "", );
sleep();
write(fd, "", );
sleep();
}
}
else if (!strcmp(buf, "quit"))
{
break;
}
} // 关闭文件
close(fd); return ;
}

cdev_alloc给定义出来的struct cdev类型的指针分配堆空间

cdev_alloc内部其实就是调用了内核中的kmalloc来进行给struct_cdev来分配堆内存的,内存大小就是这个struct cdev的大小。

例:如果我们定义了一个全局的 static struct cdev *pcdev; 我们就可以用 pcdev = cdev_alloc();来给这个pcdev分配堆内存空间。cdev_del(pcdev)时会释放掉这个申请到的堆内存,cdev_del函数内部是能知道你的struct cdev定义的对象是用的堆内存还是栈内存还是数据段内存的。这个函数cdev_del调用时,会先去看你有没有使用堆内存,如果有用到的话,会先去释放掉你用的堆内存,然后在注销掉你这个设备驱动。防止内存泄漏。注意如果struct cdev要用堆内存一定要用内核提供的这个cdev_alloc去分配堆内存,因为内部会做记录,这样在cdev_del注销掉这个驱动的时候,才会去释放掉那段堆内存。

使用cdev_alloc可以直接pcdev->ops = fops; 来进行绑定,就不需要cdev_init函数了,

代码:


static dev_t mydev;
//static struct cdev test_cdev;
static struct cdev *pcdev;

.........

// 第2步:注册字符设备驱动
pcdev = cdev_alloc(); // 给pcdev分配内存,指针实例化
//cdev_init( &test_cdev, &test_fops);//下面的代码可以讲该句替换了
pcdev->owner = THIS_MODULE;
pcdev->ops = &test_fops;

cdev_init源码:

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
cdev->kobj.ktype = &ktype_cdev_default;
kobject_init(&cdev->kobj);
cdev->ops = fops;
}

 cdev_init中的其他的操作在cdev_alloc中都有做了。

 

整理自

http://blog.csdn.net/tommy_wxie/article/details/7195471

http://blog.sina.com.cn/s/blog_14f1867f70102wlrj.html

http://edu.51cto.com/pack/view/id-529.html

http://blog.csdn.net/zqixiao_09/article/details/50839042(好贴)

register_chrdev_region/alloc_chrdev_region和cdev注册字符设备驱动的更多相关文章

  1. 29.使用register_chrdev_region()系列来注册字符设备

    1.之前注册字符设备用的如下函数注册字符设备驱动: register_chrdev(unsigned int major, const char *name,const struct file_ope ...

  2. Linux字符设备驱动实现

    Linux字符设备驱动实现 要求 编写一个字符设备驱动,并利用对字符设备的同步操作,设计实现一个聊天程序.可以有一个读,一个写进程共享该字符设备,进行聊天:也可以由多个读和多个写进程共享该字符设备,进 ...

  3. Linux字符设备驱动结构(一)--cdev结构体、设备号相关知识机械【转】

    本文转载自:http://blog.csdn.net/zqixiao_09/article/details/50839042 一.字符设备基础知识 1.设备驱动分类 linux系统将设备分为3类:字符 ...

  4. 字符设备驱动1:新的方式添加cdev + 在open函数中将文件私有数据指向设备结构体

    本例中,驱动入口处,使用cdev_add添加驱动,这点也可与字符设备驱动0:一个简单但完整的字符设备驱动程序对比一下. 另外主要讲xx_open实现文件私有数据指向设备结构体. 引子: 偶然看到,在j ...

  5. Linux 驱动框架---cdev字符设备驱动和misc杂项设备驱动

    字符设备 Linux中设备常见分类是字符设备,块设备.网络设备,其中字符设备也是Linux驱动中最常用的设备类型.因此开发Linux设备驱动肯定是要先学习一下字符设备的抽象的.在内核中使用struct ...

  6. 字符设备驱动[深入]:linux cdev详解

    linux cdev详解  http://blog.chinaunix.net/uid-24517893-id-161446.html 用cdev_add添加字符设备驱动: //linux2.6中用c ...

  7. Linux字符设备驱动框架

    字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...

  8. Linux驱动设计——字符设备驱动(一)

    Linux字符设别驱动结构 cdev结构体 struct cdev { struct kobject kobj; struct module *owner; const struct file_ope ...

  9. 【转】linux设备驱动程序之简单字符设备驱动

    原文网址:http://www.cnblogs.com/geneil/archive/2011/12/03/2272869.html 一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用 ...

随机推荐

  1. java方法参数

    Java程序设计语言总是采用值调用.也就是说,方法得到的是所有参数的一个拷贝,特别是方法不能修改传递给它的任何参数变量的内容. 基本类型参数 1)X被初始化为percent值的一个拷贝: 2)X被乘以 ...

  2. jQuery核心之那些有效的方法

    jQuery提供了一些很有效的方法,这些方法都在$命名空间之下,对常规的编码很有帮助,完整的api详见:utilities documentation on api.jquery.com $.trim ...

  3. Nbimer族助手 部分控件不能用的解决方法(转)

    用户提出的问题现象: 我两天笔记本安装的都是win7 SP1系统,一台为64为一台为32位,网络环境是移动宽带通过D-Link路由器实现无线局域网,DHPC自动分配IP地址.每次打开IE或者Chrom ...

  4. Deep Learning 9_深度学习UFLDL教程:linear decoder_exercise(斯坦福大学深度学习教程)

    前言 实验内容:Exercise:Learning color features with Sparse Autoencoders.即:利用线性解码器,从100000张8*8的RGB图像块中提取颜色特 ...

  5. Nine-Patch图片

    Nine-Patch图片以.9.png结尾,用作背景图片时,可使背景随着内容拉伸(缩小)而拉伸(缩小). 如何将普通图片制作为Nine-Patch图片: 在Android sdk目录下有一个tools ...

  6. 如何myEclipse修改工程项目的运行环境和编译环境

    修改工程运行环境(开发环境)JRE 右击工程名-----选择properties----选择对话框左侧的java build path----查看使用的JRE 选择Library选项卡中的,选中使用中 ...

  7. SQL Server 用户名密码查看

    因为SQL Server是默认使用Windows身份验证的,很多时间就会慢慢忘记掉原来设置的密码,那么怎么重新设置用户名密码呢 这里以SQL Server2013为例,先以windows身份验证登陆进 ...

  8. EaseType缓动函数

    http://sol.gfxile.net/interpolation/   一篇很详细的图文

  9. C# 技巧(1) C# 转换时间戳

    经常发现很多地方使用一个时间戳表示时间.比如: 1370838759  表示 2013年6月10日 12:32:39. 我们就需要一个工具,方便地转换这种时间格式 什么是时间戳? 时间戳, 又叫Uni ...

  10. WCF初探-12:WCF客户端异常处理

    前言: 当我们打开WCF基础客户端通道(无论是通过显式打开还是通过调用操作自动打开).使用客户端或通道对象调用操作,或关闭基础客户端通道时,都会在客户端应用程序中出现异常.而我们知道WCF是基于网络的 ...