本例中,驱动入口处,使用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