字符设备驱动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里,讲设备结构体赋值给文件私有数据指针,然后我们在ioctlreadwrite等函数里面,通过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的实现结合readwrite,实现了一种规范化的操作。在用户程序中,我们通过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 = 0 ;//不指定主设备号

#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[4] ;
 
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 0 ://minor==0 : leds all
            copy_to_user(buff ,&leds_status, 1);
            break;
        case 1 :
            down(&leds_lock);
            val = leds_status & 0x1;
            up(&leds_lock);
            copy_to_user(buff ,&val, 1);
            break;
        case 2 :
            down(&leds_lock);
            val = (leds_status>>1) & 0x1;
            up(&leds_lock);
            copy_to_user(buff, (const void *)&val, 1);
            break;
        case 3 :
            down(&leds_lock);
            val = (leds_status>>2) & 0x1;
            up(&leds_lock);
            copy_to_user(buff, (const void *)&val, 1);
            break;
 
        default:
            return -EFAULT;
    }
 
    return 1;
}

//可用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, 1);
 
    switch (minor)
    {
        case 0: /* /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 1: /* /dev/led1 */
        {
            s3c2410_gpio_setpin(S3C2410_GPF4, val);
 
            if (val == 0)
            {
                down(&leds_lock);
                leds_status &= ~(1<<0);
                up(&leds_lock);
            }
            else
            {
                down(&leds_lock);
                leds_status |= (1<<0);
                up(&leds_lock);
            }
            break;
        }
 
        case 2: /* /dev/led2 */
        {
            s3c2410_gpio_setpin(S3C2410_GPF5, val);
            if (val == 0)
            {
                down(&leds_lock);
                leds_status &= ~(1<<1);
                up(&leds_lock);
            }
            else
            {
                down(&leds_lock);
                leds_status |= (1<<1);
                up(&leds_lock);
            }
            break;
        }
 
        case 3: /* /dev/led3 */
        {
            s3c2410_gpio_setpin(S3C2410_GPF6, val);
            if (val == 0)
            {
                down(&leds_lock);
                leds_status &= ~(1<<2);
                up(&leds_lock);
            }
            else
            {
                down(&leds_lock);
                leds_status |= (1<<2);
                up(&leds_lock);
            }
            break;
        }
 
    }
 
    return 1;//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 0: /* /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(0==cmd){
            leds_status =0 ;
         }else if(1==cmd){
            leds_status = (1<<0)|(1<<1)|(1<<2);//cmd==0 是开。
         }
            up(&leds_lock);
            break;
        }
 
        case 1: /* /dev/led1 */
        {
            s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);
            s3c2410_gpio_setpin(S3C2410_GPF4, cmd);
 
            down(&leds_lock);
         if(0==cmd){
            leds_status &= ~(1<<0);
         }else if(1==cmd){
            leds_status |= (1<<0);
         }
            up(&leds_lock);
            break;
        }
 
        case 2: /* /dev/led2 */
        {
            s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP);
            s3c2410_gpio_setpin(S3C2410_GPF5, cmd);
 
            if(0==cmd){
            leds_status &= ~(1<<1);
         }else if(1==cmd){
            leds_status |= (1<<1);
         }
            break;
        }
 
        case 3: /* /dev/led3 */
        {
            s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP);
            s3c2410_gpio_setpin(S3C2410_GPF6, cmd);
 
            down(&leds_lock);
         if(0==cmd){
            leds_status &= ~(1<<2);
         }else if(1==cmd){
            leds_status |= (1<<2);
         }
            up(&leds_lock);
            break;
        }
 
      default:
         return -EINVAL ;
   }
    return 0;
}
 
 
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 = 0 ;
    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 , 0);
/*申请设备号,当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<0){
        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 = 0; minor < LEDS_DEV_COUNT ; minor++){
      leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(leds_major, minor), NULL, (minor==0)?"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 0;
 
fail_class_device_create:
    for( minor = 0;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 , 0);
    int minor;
    for( minor = 0;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[100]={0};
    char *pstring;
    if (argc != 3)
    {
        print_usage(argv[0]);
        return 0;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if (fd < 0)
    {
        printf("error, can't open %s\n", filename);
        return 0;
    }

    if (!strcmp("on", argv[2]))
    {
        // 亮灯
        val = 0;
        ioctl(fd, 0);
    }
    else if(!strcmp("off", argv[2])){
        // close
        val = 1;
        ioctl(fd,1);
    }
    else if (!strcmp("status", argv[2]))
    {
        // read status
        val = 1; 
        read(fd, &val, 1); 
        printf("1111 status val = %x\n",val); 
    }
    else
    {
        print_usage(argv[0]);
        return 0;
    }
    

    return 0;
}

 

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

posted @ 2014-10-21 11:05  oucaijun  阅读(1668)  评论(0编辑  收藏  举报
下载TeamViewer完整版 下载TeamViewer