从linux内核2.6的某个版本之后,devfs不复存在,udev成为devfs的替代。相比devfs,udev有很多优势,在此就不罗嗦了,提醒一点,udev是应用层的东东,不要试图在内核的配置选项里找到它;加入对udev的支持很简单,以作者所写的一个字符设备驱动为例,在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用 class_device_create创建对应的设备。(不太明白什么是devfs,udev,对linux中的文件系统没有什么概念呢。)
大致用法如下:
struct class *myclass = class_create(THIS_MODULE, “my_device_driver”);
class_device_create(myclass, NULL, MKDEV(major_num, 0), NULL, “my_device”);
这样的module被加载时,udev daemon就会自动在/dev下创建my_device设备文件。
class_create()
-------------------------------------------------
linux-2.6.22/include/linux/device.h
struct class *class_create(struct module *owner, const char *name)
class_create - create a struct class structure
@owner: pointer to the module that is to "own"this struct class
@name: pointer to a string for the name of this class.
在/sys/class/下创建类目录
class_device_create()
-------------------------------------------------
linux-2.6.22/include/linux/device.h
struct class_device *class_device_create(structclass *cls,
struct class_device *parent,
dev_t devt,
struct device *device,
const char *fmt, ...)
class_device_create - creates a class device and registersit with sysfs
@cls: pointer to the struct class that this device should beregistered to.
@parent: pointer to the parent struct class_device of thisnew device, if any.
@devt: the dev_t for the char device to be added.
@device: a pointer to a struct device that is assiociatedwith this class device.
@fmt: string for the class device's name
在驱动模块初始化函数中实现设备节点的自动创建
我们在刚开始写Linux设备驱动程序的时候,很多时候都是利用mknod命令手动创建设备节点,实际上Linux内核为我们提供了一组函数,可以用来在模块加载的时候自动在/dev目录下创建相应设备节点,并在卸载模块时删除该节点,当然前提条件是用户空间移植了udev。
内核中定义了struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。
注意,在2.6较早的内核版本中,device_create(…)函数名称不同,是class_device_create(…),所以在新的内核中编译以前的模块程序有时会报错,就是因为函数名称不同,而且里面的参数设置也有一些变化。
struct class和device_create(…) 以及device_create(…)都定义在/include/linux/device.h中,使用的时候一定要包含这个头文件,否则编译器会报错。
在2.6.26.6内核版本中,struct class定义在头文件include/linux/device.h中
/*
* device classes
*/
struct class {
const char *name;
struct module *owner;
nbsp;struct kset subsys;
structlist_head devices;
structlist_head interfaces;
structkset class_dirs;
struct semaphore sem; /* lockschildren, devices, interfaces */
struct class_attribute *class_attrs;
structdevice_attribute *dev_attrs;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
};
class_create(…)在/drivers/base/class.c中实现:
/**
* class_create - create a struct class structure
* @owner: pointer to the module that is to "own"this struct class
* @name: pointer to a string for the name of this class.
*
* This is used to create a struct class pointer that canthen be used
* in calls to device_create().
*
* Note, the pointer created here is to be destroyed whenfinished by
* making a call to class_destroy().
*/
struct class *class_create(struct module *owner, const char *name)
{
struct class *cls;
int retval;
cls = kzalloc(sizeof(*cls), GFP_KERNEL);
if (!cls) {
retval = -ENOMEM;
goto error;
}
cls->name = name;
cls->owner = owner;
cls->class_release = class_create_release;
retval = class_register(cls);
if (retval)
goto error;
return cls;
error:
kfree(cls);
return ERR_PTR(retval);
}
第一个参数指定类的所有者是哪个模块,第二个参数指定类名。
在class.c中,还定义了class_destroy(…)函数,用于在模块卸载时删除类。
device_create(…)函数在/drivers/base/core.c中实现:
/**
* device_create - creates a device and registers itwith sysfs
* @class: pointer to the struct class that this deviceshould be registered to
* @parent: pointer to the parent struct device of thisnew device, if any
* @devt: the dev_t for the char device to be added
* @fmt: string for the device's name
*
* This function can be used by char device classes. Astruct device
* will be created in sysfs, registered to thespecified class.
*
* A "dev" file will be created, showing thedev_t for the device, if
* the dev_t is not 0,0.
* If a pointer to a parent struct device is passed in,the newly created
* struct device will be a child of that device insysfs.
* The pointer to the struct device will be returnedfrom the call.
* Any further sysfs files that might be required canbe created using this
* pointer.
*
* Note: the struct class passed to this function musthave previously
* been created with a call to class_create().
*/
struct device *device_create(struct class *class, structdevice *parent,
dev_t devt, const char *fmt, ...)
{
va_list vargs;
struct device *dev;
va_start(vargs, fmt);
dev =device_create_vargs(class, parent, devt, NULL, fmt, vargs);
va_end(vargs);
return dev;
}
第一个参数指定所要创建的设备所从属的类,第二个参数是这个设备的父设备,如果没有就指定为NULL,第三个参数是设备号,第四个参数是设备名称,第五个参数是从设备号。
下面以一个简单字符设备驱动来展示如何使用这几个函数
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>
#include "memdev.h"
static int mem_major =MEMDEV_MAJOR;
struct mem_dev *mem_devp; /*设备结构体指针*/
bool have_data = false; /*表明设备有足够数据可供读*/
struct cdev cdev;
struct class *myclass=NULL;
/*文件打开函数*/
int mem_open(struct inode *inode, struct file *filp)
{
struct mem_dev *dev;
/*获取次设备号*/
int num = MINOR(inode->i_rdev);
if (num >=MEMDEV_NR_DEVS)
return-ENODEV;
dev = &mem_devp[num];
/*将设备描述结构指针赋值给文件私有数据指针*/
filp->private_data = dev;
return 0;
}
/*文件释放函数*/
int mem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*读函数*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size,loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*判断读位置是否有效*/
if (p >= MEMDEV_SIZE)
return 0;
if (count > MEMDEV_SIZE - p)
count = MEMDEV_SIZE - p;
while (!have_data) /* 没有数据可读,考虑为什么不用if,而用while,中断信号唤醒 */
{
if (filp->f_flags &O_NONBLOCK)
return-EAGAIN;
wait_event_interruptible(dev->inq,have_data);
}
/*读数据到用户空间*/
if (copy_to_user(buf, (void*)(dev->data + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %d\n",(int)count, (int)p);
}
have_data = false; /* 表明不再有数据可读 */
return ret;
}
/*写函数*/
static ssize_t mem_write(struct file *filp, const char __user *buf, size_tsize, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= MEMDEV_SIZE)
return 0;
if (count > MEMDEV_SIZE - p)
count = MEMDEV_SIZE - p;
/*从用户空间写入数据*/
if (copy_from_user(dev->data + p, buf, count))
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %d\n",(int)count, (int)p);
}
have_data = true; /* 有新的数据可读 */
/* 唤醒读进程 */
wake_up(&(dev->inq));
return ret;
}
/* seek文件定位函数 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{
loff_t newpos;
switch(whence){
case 0: /* SEEK_SET */
newpos = offset;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + offset;
break;
case 2: /* SEEK_END */
newpos = MEMDEV_SIZE -1 + offset;
break;
default: /* can't happen */
return -EINVAL;
}
if ((newpos<0) || (newpos>MEMDEV_SIZE))
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
//static int mem_ioctl(struct inode * inode,struct file *flip,unsigned int cmd,
// unsignedlong arg)
static int mem_ioctl(struct file *flip,unsigned int cmd,
unsignedlong arg)
{
struct mem_dev *dev = flip->private_data; /*获得设备结构体指针*/
int err = 0;
int ret = 0;
int ioarg = 0;
/* 检测命令的有效性 */
if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC)
return -EINVAL;
if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR)
return -EINVAL;
/* 根据命令类型,检测参数空间是否可以访问 */
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void*)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void*)arg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;
/* 根据命令,执行相应的操作 */
switch(cmd) {
/*打印当前设备信息 */
case MEMDEV_IOCPRINT:
printk("<--- CMD MEMDEV_IOCPRINTDone--->\n\n");
break;
case MEMDEV_IOCMEMEST:
memset(dev->data, 0, MEMDEV_SIZE);
break;
/* 获取参数 */
case MEMDEV_IOCGETDATA:
ioarg = 1101;
ret = __put_user(ioarg, (int *)arg);
break;
/* 设置参数 */
case MEMDEV_IOCSETDATA:
ret = __get_user(ioarg, (int *)arg);
printk("<--- In Kernel MEMDEV_IOCSETDATAioarg = %d --->\n\n",ioarg);
break;
default:
return -EINVAL;
}
return ret;
}
/*文件操作结构体*/
static const struct file_operations mem_fops =
{
.owner = THIS_MODULE,
.llseek = mem_llseek,
.read = mem_read,
.write = mem_write,
.open = mem_open,
.release = mem_release,
.unlocked_ioctl = mem_ioctl,
};
/*设备驱动模块加载函数*/
static int memdev_init(void)
{
int result;
int i;
dev_t devno =MKDEV(mem_major, 0);
if (mem_major)
result = register_chrdev_region(devno,2,"ZXJ");
else
{
result =alloc_chrdev_region(&devno,0,2,"ZXJ");
mem_major = MAJOR(devno);
}
if (result < 0)
return result;
/*
cdev_init(&cdev,&mem_fops);
cdev.owner = THIS_MODULE;
cdev.ops = &mem_fops;
cdev_add(&cdev,MKDEV(mem_major, 0),MEMDEV_NR_DEVS);
*/
/* 为设备描述结构分配内存*/
mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
if (!mem_devp) /*申请失败*/
{
result = - ENOMEM;
goto fail_malloc;
}
/* 清零存储器 */
memset(mem_devp, 0, sizeof(struct mem_dev) * MEMDEV_NR_DEVS);
/* 创建类/sys文件夹下面 */
myclass=class_create(THIS_MODULE,"test");
/*为设备分配内存*/
for (i=0; i < MEMDEV_NR_DEVS; i++)
{
mem_devp[i].size = MEMDEV_SIZE;
mem_devp[i].data = kmalloc(MEMDEV_SIZE,GFP_KERNEL);
if (!mem_devp[i].data)
printk("BadKmalloc\n");
memset(mem_devp[i].data, 0,MEMDEV_SIZE);
sprintf(mem_devp[i].name,"memdev%d",i);
cdev_init(&mem_devp[i].cdev,&mem_fops);
mem_devp[i].cdev.owner = THIS_MODULE;
result =cdev_add(&mem_devp[i].cdev,MKDEV(mem_major, i),1);
if (result)
{
printk("Badcdev\n");
return result;
}
/* 初始化信号量 */
init_waitqueue_head(&(mem_devp[i].inq));
device_create(myclass,NULL,MKDEV(mem_major,i),NULL,"memdev%d",i);
}
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return result;
}
/*模块卸载函数*/
static void memdev_exit(void)
{
int i;
for (i=0; i < MEMDEV_NR_DEVS; i++)
{
device_destroy(myclass,MKDEV(mem_major, i));//删除设备文件
cdev_del(&mem_devp[i].cdev); /*注销设备*/
kfree(mem_devp[i].data); /*释放内存*/
}
class_destroy(myclass);
kfree(mem_devp); /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
}
MODULE_AUTHOR("Smart.zhao");
MODULE_LICENSE("GPL");
module_init(memdev_init);
module_exit(memdev_exit);
当加载模块的时候,会在/dev/memdev0,/dev/memdev1两个设备文件,如图所示
本人使用的linux-3.1的内核