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下创建设备节点文件