字符设备驱动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 = 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