字符设备驱动1:新的方式添加cdev + 在open函数中将文件私有数据指向设备结构体
本例中,驱动入口处,使用cdev_add添加驱动,这点也可与字符设备驱动0:一个简单但完整的字符设备驱动程序对比一下。
另外主要讲xx_open实现文件私有数据指向设备结构体。
引子:
偶然看到,在jz2440韦东山写的一个led驱动中,open函数仅对硬件做了初始化(每次open之后默认打开led灯,这不是我期望的),而且没有将文件私有数据指向设备结构体。
<1>基于此,在测试过程中,我试着不实现驱动的xx_open函数,在app中,仍使用open(filename, O_RDWR)打开设备并随后对其进行ioctl以及read等操作,发现可以正常操作led灯。见文中的实例。
说明在驱动中不实现xx_open,在应用程序里面还是可以通过open打开该设备。
实现“dev = container_of(inode->i_cdev , struct xx_dev ,cdev); filp->private_data = dev ; ” 这样的代码,主要是增加了一层封装和注册,便于结构化代码。
<2>而作为对比,在《linux设备驱动开发详解》中的一个驱动,实现了open,并将文件私有数据指向设备结构体。
int xx_open(struct inode inode ,struct file* filp)
{
struct xx_dev *dev;
dev = container_of(inode->i_cdev , struct xx_dev ,cdev);
filp->private_data = dev ;
return 0;
}
实现open与否,肯定是有不同的,具体不同体现在哪里呢?
可以看出,对驱动的xx_read、xx_write操作时,定位设备的方式产生了影响。
不过,若xx_open函数没有实现 “dev = container_of(inode->i_cdev , struct xx_dev ,cdev); filp->private_data = dev ; ” ,那实现xx_open与否,设备定位的方式也没什么两样。
为了保持规范性,xx_open是应该实现的;有时候为了简单,就不去实现这些麻烦的封装。
_______________________________________________________________________________
那么,open实现与否有何影响?
QQ 潘老师 12:45:31
字符设备框架中open接口是必须实现的
原因如下:
1、一切皆是"文件",字符设备也是"文件"
2、要操作一个文件必须先open,才能read/write
字符设备的原理如下:
1、cdev表征一个字符设备对象
通过cdev_alloc构造,cdev_init初始化好
然后用cdev_add把这个对象插入内核管理数据区(链表,插入节点)
2、mknod创建设备文件
就是在VFS树形结构中创建了一个节点inode
inode根据类型,字符设备的默认open方法仅仅查找cdev对象节点
3、open过程
open通过设备文件查找到inode,并回调inode中的默认打开方面找到cdev对象,这样就找到了cdev中封装的file_operations,即操作方法函数集合,同时open过程会创建一个file对象(原因是open的时候有标志:譬如只读、只写、阻塞等等,两次打开的时候标志可以不一致,则每次创建一个file对象来抽象"文件",实际就是存储这些标志,并保存指向cdev中保存的file_operations方法)
4、read/write过程
通过文件描述符找到open时创建的file对象,就找到了open时初始化好的file中指向file_operations,则找到了驱动中对应的操作函数
百度知道Wu_Roc
如果不实现open的话,驱动会默认设备的打开永远成功。打开成功时open返回0。
内核里是若open函数未定义的话,会跳过这个函数。但是其他步骤不变。
关键代码:__dentry_open函数里
...
if (open) {
error = open(inode, f);
if (error)
goto cleanup_all;
}
...
所以sys_open调用的话会依旧照常进行。如果你定义了open,他就会调用的你写的open,如果没定义,就跳过这一步。
这里说明不实现open是允许的。
___________________________________________________________
经过多方对比参考,在网上一篇文章找到了想要的答案。[1]
大多数linux驱动工程师都遵循一个"潜规则",那就是将文件的私有的数据private_data指向设备结构体,在read(),write,ioctl(),llseek等函数通过private_data访问设备结构体。一般,我们在open里,讲设备结构体赋值给文件私有数据指针,然后我们在ioctl,read,write等函数里面,通过filep找到设备结构体,并对设备进行操作。代码如下:
struct globalmem_dev{
struct cdev cdev;//cdev结构体,用于描述设备的结构体
unsigned char mem[GLOBALMEM_SIZE];//全局内存
};//自定义设备结构体
static struct globalmem_dev *globalmem_devp;//指向设备的结构体指针
int globalmem_open(struct inode *inode,struct file *filp)
{
filp->private_data = globalmem_devp;//将设备结构体指针赋给文件私有数据指针,也就是将文件的私有的数据private_data指向设备结构体
return 0;
}
//设备控制函数
static int globalmem_ioctl(struct inode *inodep,struct file *filp,unsigned int cmd,unsigned long arg)
{
struct globalmem_dev *dev = filp->private_data;//获得设备结构体指针
switch (cmd) {
case MEM_CLEAR :
memset(dev->mem,0,GLOBALMEM_SIZE);//清除全局内存
printk(KERN_INFO "globalmem is set to zero\n");
break; default :
return -EINVAL;
}
return 0;
}
从中可以看出,open的实现结合read,write,实现了一种规范化的操作。在用户程序中,我们通过filename找到设备的inode或者filep,然后找到设备,并对其进行读写和控制。
第一步:filename-->inode/filep(在用户程序中,我们通过filename找到设备的inode或者filep的具体过程不清楚,猜测是udev、sysfs文件系统完成的,待补充。)
第二步:struct globalmem_dev *dev=filp->private_data;
第三步:MINOR(dev->cdev->devno)得到minor。
第四步:switch(minor){case xx:.....}。对比下面紧接着的代码[韦东山的代码],这似乎是在其上加了一层封装。
在韦东山的代码中,因为没有实现open,使用了这样的方式操作设备:
在app中,通过filename(借助udev,sysfs 这样的文件系统以及class的管理[2])识别子设备号,直接通过设备号对设备进行操作。
第一步:filename-->minor
第二步:minor = MINOR(inode->i_rdev);
或minor = MINOR(filp->f_dentry->d_inode->i_rdev);
第三步:switch(minor){….. }
这样的话,是可以正常操作led灯的,只是这样没有利用filp->private_data这样的系统给我们预设的变量。(这样可能在需要用到filp->private_data的功能中会有所欠缺吧?具体的还不清楚。或者说不符合人们的操作习惯)
——————————————————————————————————————————————————————————————————————————————————————
附代码:
不实现file_operations.open函数的led驱动
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h> //static int leds_major = 237 ;//指定默认分配主设备号为237
static int leds_major = ;//不指定主设备号 #define LEDS_DEV_NAME "leds_dev_name"
#define LEDS_BASE_MINOR 0
#define LEDS_DEV_COUNT 4
/* bit0<=>D10, 0:亮, 1:灭
* bit1<=>D11, 0:亮, 1:灭
* bit2<=>D12, 0:亮, 1:灭
*/
static char leds_status = 0x0 ;
static DECLARE_MUTEX(leds_lock) ; static struct class *leds_class ;
static struct class_device *leds_class_devs[] ; typedef struct cdev LEDS_DEV_ST ;
LEDS_DEV_ST *leds_cdev ; //成功时,返回读取的字节数。
//失败返回一个负值。
static int s3c24xx_leds_read(struct file* filp , char __user *buff , size_t count,loff_t *offp)
{
int minor = MINOR(filp->f_dentry->d_inode->i_rdev);
char val;
printk("info new: in s3c24xx_leds_read!\n");
switch ( minor )
{
case ://minor==0 : leds all
copy_to_user(buff ,&leds_status, );
break;
case :
down(&leds_lock);
val = leds_status & 0x1;
up(&leds_lock);
copy_to_user(buff ,&val, );
break;
case :
down(&leds_lock);
val = (leds_status>>) & 0x1;
up(&leds_lock);
copy_to_user(buff, (const void *)&val, );
break;
case :
down(&leds_lock);
val = (leds_status>>) & 0x1;
up(&leds_lock);
copy_to_user(buff, (const void *)&val, );
break; default:
return -EFAULT;
} return ;
} //可用write操作led灯。
//buf 0/1 来自用户的开/关指令
//app中:fd = open(filename, O_RDWR); write(fd, &val, 1);
//filename = leds/led0/led1/led2 , 见s3c24xx_leds_init 的 class_device_create .
static ssize_t s3c24xx_leds_write(struct file *filp ,const char __user *buf ,size_t count ,loff_t *ppos)
{
int minor = MINOR(filp->f_dentry->d_inode->i_rdev);
char val; copy_from_user(&val, buf, ); switch (minor)
{
case : /* /dev/leds */
{
s3c2410_gpio_setpin(S3C2410_GPF4, (val & 0x1));
s3c2410_gpio_setpin(S3C2410_GPF5, (val & 0x1));
s3c2410_gpio_setpin(S3C2410_GPF6, (val & 0x1)); down(&leds_lock);
leds_status = val;
up(&leds_lock);
break;
} case : /* /dev/led1 */
{
s3c2410_gpio_setpin(S3C2410_GPF4, val); if (val == )
{
down(&leds_lock);
leds_status &= ~(<<);
up(&leds_lock);
}
else
{
down(&leds_lock);
leds_status |= (<<);
up(&leds_lock);
}
break;
} case : /* /dev/led2 */
{
s3c2410_gpio_setpin(S3C2410_GPF5, val);
if (val == )
{
down(&leds_lock);
leds_status &= ~(<<);
up(&leds_lock);
}
else
{
down(&leds_lock);
leds_status |= (<<);
up(&leds_lock);
}
break;
} case : /* /dev/led3 */
{
s3c2410_gpio_setpin(S3C2410_GPF6, val);
if (val == )
{
down(&leds_lock);
leds_status &= ~(<<);
up(&leds_lock);
}
else
{
down(&leds_lock);
leds_status |= (<<);
up(&leds_lock);
}
break;
} } return ;//len
}
//可用 ioctl 操作led灯。
//cmd 0/1 来自用户的开/关指令
//app中:fd = open(filename, O_RDWR); ioctl(fd,cmd);
//filename = /dev/leds(led0,led1,led2) , 名字和s3c24xx_leds_init 的 class_device_create中保持一致 .
static int s3c24xx_leds_ioctl(struct inode * inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
#if 0
int minor = MINOR(inode->i_rdev); //ok
#else
int minor = MINOR(filp->f_dentry->d_inode->i_rdev);//ok: filp->f_dentry->d_inode->i_rdev == inode->i_rdev
#endif
printk("info new: in s3c24xx_leds_ioctl!\n");
switch(minor)
{
case : /* /dev/leds */
{
// 配置3引脚为输出
s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);
s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP);
s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP); // 都输出0
s3c2410_gpio_setpin(S3C2410_GPF4, cmd);
s3c2410_gpio_setpin(S3C2410_GPF5, cmd);
s3c2410_gpio_setpin(S3C2410_GPF6, cmd); down(&leds_lock);
if(==cmd){
leds_status = ;
}else if(==cmd){
leds_status = (<<)|(<<)|(<<);//cmd==0 是开。
}
up(&leds_lock);
break;
} case : /* /dev/led1 */
{
s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);
s3c2410_gpio_setpin(S3C2410_GPF4, cmd); down(&leds_lock);
if(==cmd){
leds_status &= ~(<<);
}else if(==cmd){
leds_status |= (<<);
}
up(&leds_lock);
break;
} case : /* /dev/led2 */
{
s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP);
s3c2410_gpio_setpin(S3C2410_GPF5, cmd); if(==cmd){
leds_status &= ~(<<);
}else if(==cmd){
leds_status |= (<<);
}
break;
} case : /* /dev/led3 */
{
s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP);
s3c2410_gpio_setpin(S3C2410_GPF6, cmd); down(&leds_lock);
if(==cmd){
leds_status &= ~(<<);
}else if(==cmd){
leds_status |= (<<);
}
up(&leds_lock);
break;
} default:
return -EINVAL ;
}
return ;
} static struct file_operations s3c24xx_leds_fops ={
.owner = THIS_MODULE ,
//.open = s3c24xx_leds_open,
.read = s3c24xx_leds_read ,
.write = s3c24xx_leds_write ,
.ioctl = s3c24xx_leds_ioctl
}; static int __init s3c24xx_leds_init()
{
int ret ;
int minor = ;
printk("\t init : leds_major=%d\n" ,leds_major);
leds_cdev = (LEDS_DEV_ST*)kmalloc(sizeof(LEDS_DEV_ST),GFP_KERNEL) ;
if(!leds_cdev){
ret = -ENOMEM;
goto fail_exit;
}
dev_t devno = MKDEV(leds_major , );
/*申请设备号,当xxx_major不为0时,表示静态指定;当为0时,表示动态申请*/
if(leds_major){
ret = register_chrdev_region(devno , LEDS_DEV_COUNT , LEDS_DEV_NAME); //register_chrdev_region若成功,返回值0
printk("\t reg :devno=%d , leds_major=%d\n",devno,leds_major);
}else{
ret = alloc_chrdev_region(&devno, LEDS_BASE_MINOR, LEDS_DEV_COUNT, LEDS_DEV_NAME);
leds_major = MAJOR(devno);
printk("\t reg :devno=%d , leds_major=%d\n",devno,leds_major);
}
if(ret<){
goto fail_register_chrdev_region;
} //初始化并添加cdev结构体
cdev_init(leds_cdev , &s3c24xx_leds_fops );
leds_cdev->owner = THIS_MODULE ;
leds_cdev->ops = &s3c24xx_leds_fops;
ret = cdev_add(leds_cdev , devno , LEDS_DEV_COUNT); if(ret){
printk(LEDS_DEV_NAME"Error %d adding leds_cdev",ret);
ret = -EFAULT;
goto fail_cdev_add;
} //oo00 :begin : 分配了四个子设备号 minor == 0 1 2 3
//class_create动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加到内核中。创建的逻辑类位于/sys/class/。
leds_class = class_create(THIS_MODULE, "leds_class"); // /sys/class/下的类名
if (IS_ERR(leds_class)){
ret = PTR_ERR(leds_class);
goto fail_class_create;
} for (minor = ; minor < LEDS_DEV_COUNT ; minor++){
leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(leds_major, minor), NULL, (minor==)?"leds":"led%d", minor);
if (unlikely(IS_ERR(leds_class_devs[minor]))){
ret = PTR_ERR(leds_class_devs[minor]);
goto fail_class_device_create;
}
}
//oo00 :end //device_create or class_device_create ?
//device_destroy or class_device_unregister ?
//答:均可。 printk(LEDS_DEV_NAME" initialized\n");
return ; fail_class_device_create:
for( minor = ;minor<LEDS_DEV_COUNT;minor++){
class_device_unregister(leds_class_devs[minor]);
}
class_destroy(leds_class);
fail_class_create:
cdev_del(leds_cdev); //删除结构体
fail_cdev_add:
fail_register_chrdev_region:
kfree(leds_cdev);
fail_exit:
return ret ;
} static void __exit s3c24xx_leds_exit()
{
dev_t devno = MKDEV(leds_major , );
int minor;
for( minor = ;minor<LEDS_DEV_COUNT;minor++){
class_device_unregister(leds_class_devs[minor]);//device_destroy(leds_class,devno);
}
class_destroy(leds_class); cdev_del(leds_cdev);//删除结构体
unregister_chrdev_region(devno, LEDS_DEV_COUNT);//注销设备区域
kfree(leds_cdev);
printk("exit:devno=%d,leds_major=%d\n" , devno,leds_major);
}
module_init(s3c24xx_leds_init);
module_exit(s3c24xx_leds_exit); MODULE_AUTHOR("http://www.100ask.net");
MODULE_VERSION("0.1.0");
MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");
MODULE_LICENSE("GPL");
对应的操作led的用户程序:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h> /*
* ledtest <dev> <on|off>
*/ void print_usage(char *file)
{
printf("Usage:\n");
printf("%s <dev> <on|off>\n",file);
printf("eg. \n");
printf("%s /dev/leds on\n", file);
printf("%s /dev/leds status\n", file); //status {bit0~2:led1~led3; \ bit==0:led on}
printf("%s /dev/leds off\n", file);
printf("%s /dev/led1 on\n", file);
printf("%s /dev/led1 off\n", file);
} int main(int argc, char **argv)
{
int fd;
char* filename;
char val;
char string[]={};
char *pstring;
if (argc != )
{
print_usage(argv[]);
return ;
} filename = argv[]; fd = open(filename, O_RDWR);
if (fd < )
{
printf("error, can't open %s\n", filename);
return ;
} if (!strcmp("on", argv[]))
{
// 亮灯
val = ;
ioctl(fd, );
}
else if(!strcmp("off", argv[])){
// close
val = ;
ioctl(fd,);
}
else if (!strcmp("status", argv[]))
{
// read status
val = ;
read(fd, &val, );
printf("1111 status val = %x\n",val);
}
else
{
print_usage(argv[]);
return ;
} return ;
}
mdev自动创建NODE:
class_create// malloc + init + class_register;
class_destroy//- destroys a struct class structure; the pointer to be destroyed must have been created with a call to class_create().
class_device_destroy // - removes a class device that was created with class_device_create()
class_device_create // malloc + init + class_device_register; creates a class device and registers it with sysfs
参考:
1. 使用文件私有数据的globalmem设备驱动
http://blog.sina.com.cn/s/blog_95268f5001015bkd.html
2.借助udev,sysfs 文件系统以及class管理设备
http://www.cnblogs.com/mylinux/p/4036589.html
字符设备驱动1:新的方式添加cdev + 在open函数中将文件私有数据指向设备结构体的更多相关文章
- Linux I2C核心、总线和设备驱动
目录 更新记录 一.Linux I2C 体系结构 1.1 Linux I2C 体系结构的组成部分 1.2 内核源码文件 1.3 重要的数据结构 二.Linux I2C 核心 2.1 流程 2.2 主要 ...
- LINUX设备驱动模型之class
转自 https://blog.csdn.net/qq_20678703/article/details/52754661 1.LINUX设备驱动模型中的bus.device.driver,.其中bu ...
- Introduction the naive“scull” 《linux设备驱动》 学习笔记
Introduction the naive "scull" 首先.什么是scull? scull (Simple Character Utility for Loading Lo ...
- linux设备驱动归纳总结(三):1.字符型设备之设备申请【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-59416.html linux设备驱动归纳总结(三):1.字符型设备之设备申请 操作系统:Ubunru ...
- 【Linux驱动】字符设备驱动
一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 1.字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面 ...
- linux字符设备驱动--基本知识介绍
一.设备驱动的分类 1.字符设备 字符设备是指那些能一个字节一个字节读取数据的设备,如LED灯.键盘.鼠标等.字符设备一般需要在驱动层实现open().close().read().write().i ...
- 【Linux开发】linux设备驱动归纳总结(三):1.字符型设备之设备申请
linux设备驱动归纳总结(三):1.字符型设备之设备申请 操作系统:Ubunru 10.04 实验平台:S3C2440 + linux2.6.29内核 注:在今后驱动程序的学习中经常需要查看内核源代 ...
- (57)Linux驱动开发之三Linux字符设备驱动
1.一般情况下,对每一种设备驱动都会定义一个软件模块,这个工程模块包含.h和.c文件,前者定义该设备驱动的数据结构并声明外部函数,后者进行设备驱动的具体实现. 2.典型的无操作系统下的逻辑开发程序是: ...
- Linux高级字符设备驱动
转载:http://www.linuxidc.com/Linux/2012-05/60469p4.htm 1.什么是Poll方法,功能是什么? 2.Select系统调用(功能) Select ...
随机推荐
- 关于php支持的协议与封装协议
<?php /* * php://stdin 标准输入流 * php://stdout 标准输入流 * php://stderr 标准错误流 * php://output 只写的数据流 * ph ...
- Git使用方法记录(一)
记录下git的基本使用方法,这里是以ubuntu14.04为例. 1,使用前的初始设置 git config –global user.name “FirstName LastName” git co ...
- HTTP协议中POST、GET、HEAD、PUT等请求方法以及一些常见错误 #Reprinted#
请求方法是请求一定的Web页面的程序或用于特定的URL. 可选用下列几种: GET: 请求指定的页面信息,并返回实体主体. HEAD: 只请求页面的首部. POST: 请求服务器接受所指定的文档作为对 ...
- java断言
public class New{ public static void main(String[] args){ assert false; System.out.println("pas ...
- 帝国cms7.0 内容页控制简介字数!
帝国cms7.0 内容页有简介部分,使用以下代码可以有效控制字数限制! 下载类简介:<?=esub($navinfor[softsay],160)?> 文章类简介:<?=esub($ ...
- QT实现拖放文件(有例子,并且图文并茂,非常清楚)
转自:http://my.oschina.net/voler/blog/345722 目录[-] 0. 源代码下载地址 1. 简单文件拖放 2. 复杂文件拖放 3. 通过按钮来完成列表数据的转移 4. ...
- Windows Azure 社区新闻综述(#74 版)
欢迎查看最新版本的每周综述,其中包含有关云计算和 Windows Azure 的社区推动新闻.内容和对话.以下是本周的亮点. 文章.视频和博客文章 · Azure CDN:吸取的宝贵经验(10 月 ...
- PhoneGap 开发笔记
1 调死调活都调不出来的情况下,可以考虑更换下phoneGap 版本,尽量用比较新的版本. 2 form submit 会返回 3 jquery mobile 的4个初始化事件 第一个触发的事件是mo ...
- JAVA GUI学习 - JList列表、JScrollPane滚动条组件学习
/** * 本例结合JList和JScrollPane共同使用 * @author Wfei * */ public class JListKnow extends JFrame { JList jL ...
- HashMap和HashTable 学习
1. HashMap 1) hashmap的数据结构 Hashmap是一个数组和链表的结合体(在数据结构称“链表散列“),如下图示: 当我们往hashmap中put元素的时候,先根据key的hash ...