Linux下生成驱动设备节点文件的方法有3个:1、手动mknod;2、利用devfs;3、利用udev

在刚开始写Linux设备驱动程序的时候,很多时候都是利用mknod命令手动创建设备节点,实际上Linux内核为我们提供了一组函数,可以用来在模块加载的时候自动在/dev目录下创建相应设备节点,并在卸载模块时删除该节点。

在2.6.17以前,在/dev目录下生成设备文件很容易

devfs_mk_bdev
devfs_mk_cdev
devfs_mk_symlink
devfs_mk_dir
devfs_remove

这几个是纯devfs的api,2.6.17以前可用,但后来devfs被sysfs+udev的形式取代,同时期sysfs文件系统可以用的api:class_device_create_file
在2.6.26以后也不行了,现在,使用的是device_create ,从2.6.18开始可用

struct device *device_create(struct class *class, struct device *parent,dev_t devt, const char *fmt, ...)

从2.6.26起又多了一个参数drvdata: the data to be added to the device for callbacks
不会用可以给此参数赋NULL

struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)

下面着重讲解第三种方法udev
从linux2.6.13开始,devfs不复存在了,改用udev。相比devfs,udev(mdev)存在应用层。利用udev创建设备文件自动创建很简单,在初始化代码中create_class为该设备文件创建一个class,再为每个设备文件调用device_create(),创建设备文件。

struct class* my_class=create_class(THIS_MODULE,"my_devices_driver");
device_create(my_class,NULL,MKDEV(major_num,0),NULL,"my_device");

被加载后,udev自动在/dev目录下创建my_device设备文件。my_class这个类存放于sysfs下面,一旦创建好了这个类,再调用 device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应 device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。

example

下面以一个简单字符设备驱动来展示如何使用这几个函数

static struct class *spidev_class;
static int __devinit spidev_probe(struct spi_device *spi)
{
        ...
        dev =device_create(spidev_class, &spi->dev, spidev->devt,spidev, "spidev%d.%d",spi->master->bus_num, spi->chip_select);
        ...
} 
static int __devexit spidev_remove(struct spi_device *spi)
{
    ......
   device_destroy(spidev_class, spidev->devt);
    .....
    return 0;
}
static struct spi_driver spidev_spi = {
    .driver = {
        .name =        "spidev",
        .owner =    THIS_MODULE,
    },
    .probe =    spidev_probe,
    .remove =    __devexit_p(spidev_remove),
};
 static int __init spidev_init(void)
{
    ....

    spidev_class =class_create(THIS_MODULE, "spidev");
    if (IS_ERR(spidev_class)) {
        unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
        return PTR_ERR(spidev_class);
    }
    ....
}
static void __exit spidev_exit(void)
{
    ......
   class_destroy(spidev_class);
    ......
}
module_init(spidev_init);
module_exit(spidev_exit);
MODULE_DESCRIPTION("User mode SPI device interface");
MODULE_LICENSE("GPL");
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>

int HELLO_MAJOR = 0;
int HELLO_MINOR = 0;
int NUMBER_OF_DEVICES = 2;

struct class *my_class;
//struct cdev cdev;
//dev_t devno;
struct hello_dev {
struct device *dev;
dev_t chrdev;
struct cdev cdev;
};

static struct hello_dev *my_hello_dev = NULL;
struct file_operations hello_fops = {
 .owner = THIS_MODULE
};

static int __init hello_init (void){
    int err = 0;
    struct device *dev;
    my_hello_dev = kzalloc(sizeof(struct hello_dev), GFP_KERNEL);
    if (NULL == my_hello_dev) {
        printk("%s kzalloc failed!\n",__func__);
        return -ENOMEM;
    }

    devno = MKDEV(HELLO_MAJOR, HELLO_MINOR);
    if (HELLO_MAJOR)
        err= register_chrdev_region(my_hello_dev->chrdev, 2, "memdev");
    else
    {
        err =alloc_chrdev_region(&my_hello_dev->chrdev, 0, 2, "memdev");
        HELLO_MAJOR = MAJOR(devno);
    }  
    if (err) {
    printk("%s alloc_chrdev_region failed!\n",__func__);
    goto alloc_chrdev_err;
    }
    printk("MAJOR IS %d\n",HELLO_MAJOR);

    cdev_init(&(my_hello_dev->cdev), &hello_fops);
    my_hello_dev->cdev.owner = THIS_MODULE;
    err = cdev_add(&(my_hello_dev->cdev), my_hello_dev->chrdev, 1);
    if (err) {
    printk("%s cdev_add failed!\n",__func__);
    goto cdev_add_err;
    }
    printk (KERN_INFO "Character driver Registered\n");
my_class=class_create(THIS_MODULE,"hello_char_class");  //类名为hello_char_class
    if(IS_ERR(my_class)) 
    {
        err = PTR_ERR(my_class);
        printk("%s class_create failed!\n",__func__);
        goto class_err;
    }
        dev =device_create(my_class,NULL,my_hello_dev->chrdev,NULL,"memdev%d",0);      //设备名为memdev
    if (IS_ERR(dev)) {
        err = PTR_ERR(dev);
        gyro_err("%s device_create failed!\n",__func__);
        goto device_err;
    }
        printk("hello module initialization\n");
    return 0;
        device_err:
    device_destroy(my_class, my_hello_dev->chrdev);
    class_err:
    cdev_del(my_hello_dev->chrdev);
    cdev_add_err:
    unregister_chrdev_region(my_hello_dev->chrdev, 1);
    alloc_chrdev_err:
    kfree(my_hello_dev);
    return err;
}
 static void __exit hello_exit (void)
{
    cdev_del (&(my_hello_dev->cdev));
    unregister_chrdev_region (my_hello_dev->chrdev,1);
    device_destroy(my_class, devno);         //delete device node under /dev//必须先删除设备,再删除class类
    class_destroy(my_class);                 //delete class created by us
    printk (KERN_INFO "char driver cleaned up\n");
}

module_init (hello_init);
module_exit (hello_exit);
MODULE_LICENSE ("GPL");

这样,模块加载后,就能在/dev目录下找到memdev这个设备节点了。
接下来就是udev应用,udev是应用层的东西,udev需要内核sysfs和tmpfs的支持,sysfs为udev提供设备入口和uevent通道,tmpfs为udev设备文件提供存放空间udev的源码可以在去相关网站下载,然后就是对其在运行环境下的移植,指定交叉编译环境,修改Makefile下的CROSS_COMPILE,如为mipsel-linux-,DESTDIR=xxx,或直接make CROSS_COMPILE=mipsel-linux-,DESTDIR=xxx 并install
把主要生成的udevd、udevstart拷贝rootfs下的/sbin/目录内,udev的配置文件udev.conf和rules.d下的rules文件拷贝到rootfs下的/etc/目录内
并在rootfs/etc/init.d/rcS中添加以下几行:
echo “Starting udevd…”
/sbin/udevd –daemon
/sbin/udevstart
(原rcS内容如下:

#mount filesystems
/bin/mount -t proc /proc /proc
/bin/mount -t sysfs sysfs /sys
/bin/mount -t tmpfs tmpfs /dev
#create necessary devices
/bin/mknod /dev/null c 1 3
/bin/mkdir /dev/pts
/bin/mount -t devpts devpts /dev/pts
/bin/mknod /dev/audio c 14 4
/bin/mknod /dev/ts c 10 16
)

这样当系统启动后,udevd和udevstart就会解析配置文件,并自动在/dev下创建设备节点文件