我们知道,生成设备文件节点的方法有三个:
手动mknod
利用devfs
利用udev
Udev是今后发展的趋势
第一、什么是udev?
这篇文章UDEV Primer给我们娓娓道来,花点时间预习一下是值得的。当然,不知道udev是什么也没关系,
把它当个助记符好了,有了下面的上路指南,可以节省很多时间。我们只需要树立一个信念:udev很简单!
嵌入式的udev应用尤其简单。
第二、为什么udev要取代devfs?
这是生产关系适应生产力的需要,udev好,devfs坏,用好的不用坏的。
udev是硬件平台无关的,属于user space的进程,它脱离驱动层的关联而建立在操作系统之上,基于这种设计实现,我们可以随时修改及删除/dev下的设备文件名称和指向,随心所欲地按照我们的愿望安排和管理设备文件系统,而完成如此灵活的功能只需要简单地修改udev的配置文件即可,无需重新启动操作系统。udev已经使得我们对设备的管理如探囊取物般轻松自如。
第三、如何得到udev?
udev的主页在这里:http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html
我们按照下面的步骤来生成udev的工具程序,以arm-linux为例:
1、下载udev-100.tar.bz2
2、tar xjf udev-100.tar.bz2
3、cd udev-100 编辑Makefile,查找CROSS_COMPILE,修改CROSS_COMPILE ?= arm-linux-
4、make
没有什么意外的话当前目录下生成udev,udevcontrol,udevd,udevinfo,udevmonitor,udevsettle,udevstart,
udevtest,udevtrigger九个工具程序,在嵌入式系统里,我们只需要udevd和udevstart就能使udev工作得很好,
其他工具则帮助我们完成udev的信息察看、事件捕捉或者更高级的操作。
第四、如何配置udev?
首先,udev需要内核sysfs和tmpfs的支持,sysfs为udev提供设备入口和uevent通道,tmpfs为udev设备文件提 供存放空间,也就是说,在上电之前系统上是没有足够的设备文件可用的,我们需要一些技巧让kernel先引导 起来。
首先使你的内核支持sysfs和tmpfs。如果采用2.6的2410默认配置,这步可以省略了。
其次,需要做的工作就是重新生成rootfs,把udevd和udevstart复制到/sbin目录。然后我们需要在/etc/下为udev
建立设备规则,这可以说是udev最为复杂的一步。这篇文章提供了最完整的指导:Writing udev rules文中描述的复杂规则我们可以暂时不用去理会,上路指南将带领我们轻松穿过这片迷雾。这里提供一个由简入繁的方法,对于嵌入式系统,这样做可以一劳永逸。
1、在前面用到的udev-100目录里,有一个etc目录,里面放着的udev目录包含了udev设备规则的详细样例文
本。为了简单而又简洁,我们只需要用到etc/udev/udev.conf这个文件,在我们的rootfs/etc下建立一个udev文件夹,把它复制过去,这个文件很简单,除了注释只有一行,是用来配置日志信息的,嵌入式系统也许用不上日志,但是udevd需要检查这个文件。
一下是文件内容,基本不用动
# udev.conf
# The initial syslog(3) priority: "err", "info", "debug" or its
# numerical equivalent. For runtime debugging, the daemons internal
# state can be changed with: "udevcontrol log_priority=<value>".
udev_log="err"
2、在rootfs/etc/udev下建立一个rules.d目录,生成一个空的配置文件
touch etc/udev/rules.d/udev.rules。
保存它,我们的设备文件系统基本上就可以了,udevd和udevstart会自动分析这个文件。
3、为了使udevd在kernel起来后能够自动运行,我们在rootfs/etc/init.d/rcS中增加以下几行:
##################################
echo "Starting udevd..."
/sbin/udevd --daemon
/sbin/udevstart
##################################
放到后面的位置
4、重新生成rootfs,烧写到flash指定的rootfs part中。
5、如果需要动态改变设备规则,可以把etc/udev放到jffs或yaffs part,以备修改,根据需求而定,可以随时扩充udev.conf中的配置项。
上面叙述的是使我们的系统支持udev,相应的代码要做一些改变,在init和exit中添加,具体实例如下:
#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 <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/device.h>//for udev
#include <asm-arm/arch-s3c2410/hardware.h>
#include <linux/ioport.h>
#include <asm/arch-s3c2410/regs-gpio.h>
#define GLOBALMEM_MAJOR 0 /*预设的ioport的主设备号*/
static unsigned int gpfcon_old = 0;
static unsigned int gpfdat_old = 0;
static unsigned int gpfup_old = 0;
static int globalmem_major = GLOBALMEM_MAJOR;
struct resource *IO_port_resource;
/*globalmem设备结构体*/
struct globalmem_dev
{
struct cdev cdev; /*cdev结构体*/
};
struct globalmem_dev *globalmem_devp; /*设备结构体指针*/
/*文件打开函数*/
int globalmem_open(struct inode *inode, struct file *filp)
{
/*将设备结构体指针赋值给文件私有数据指针*/
filp->private_data = globalmem_devp;
printk("In the open process! turn off the led!\n");
outl(0x5500 | inl((unsigned long )S3C2410_GPFCON),(unsigned long )S3C2410_GPFCON);
outl(0xF0 | inl((unsigned long )S3C2410_GPFUP),(unsigned long)S3C2410_GPFUP);
outl(0xF0 | inl((unsigned long )S3C2410_GPFDAT),(unsigned long)S3C2410_GPFDAT);
return 0;
}
/*文件释放函数*/
int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*读函数*/
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
loff_t *ppos)
{
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
return ret;
}
/*写函数*/
static ssize_t globalmem_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
{
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
unsigned char *userbuf;
/*用户空间->内核空间*/
// get_user (status, userbuf);
if (copy_from_user(userbuf, (unsigned char *)buf, count))
ret = - EFAULT;
else
{
outl ((*userbuf), (unsigned long)S3C2410_GPFDAT);
// writel (*userbuf, (unsigned long)S3C2410_GPFDAT);
printk("write data from user to ioport!\n");
}
return ret;
}
/*文件操作结构体*/
static const struct file_operations globalmem_fops =
{
.owner = THIS_MODULE,
.read = globalmem_read,
.write = globalmem_write,
.open = globalmem_open,
.release = globalmem_release,
};
struct class *my_class;
/*初始化并注册cdev*/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err, devno = MKDEV(globalmem_major, index);
cdev_init(&dev->cdev, &globalmem_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_ALERT "Error %d adding globalmem%d", err, index);
//下面是为udev准备的
my_class =class_create(THIS_MODULE, "ioport");
if(IS_ERR(my_class)) {
printk("Err: failed in creating class.\n");
return ;
}
class_device_create(my_class,devno,NULL,"ioport",0);
//到此结束
}
/*设备驱动模块加载函数*/
int globalmem_init(void)
{
int result;
dev_t devno = MKDEV(globalmem_major, 0);
/* 申请设备号*/
if (globalmem_major)
result = register_chrdev_region(devno, 1, "ioport");
else /* 动态申请设备号 */
{
result = alloc_chrdev_region(&devno, 0, 1, "ioport");
globalmem_major = MAJOR(devno);
}
if (result < 0)
return result;
/* 动态申请设备结构体的内存*/
globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
if (!globalmem_devp) /*申请失败*/
{
result = - ENOMEM;
goto fail;
}
memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
globalmem_setup_cdev(globalmem_devp, 0);
if ((IO_port_resource=request_region((unsigned long)S3C2410_GPFCON, 0x0c,"ioport"))==NULL)
goto fail;
else{
printk("In the init process! \n");
gpfcon_old = (unsigned int) inl((unsigned long)S3C2410_GPFCON);
gpfdat_old = (unsigned int) inl((unsigned long)S3C2410_GPFDAT);
gpfup_old = (unsigned int) inl((unsigned long)S3C2410_GPFUP);
printk("S3C2410_GPFCON is %8X\n",gpfcon_old);
printk("S3C2410_GPFUP is %8X\n",gpfup_old);
printk("S3C2410_GPFDAT is %8X\n",gpfdat_old);
//测试hardware.h的读写函数
s3c2410_gpio_setpin(S3C2410_GPF4, 1);
s3c2410_gpio_setpin(S3C2410_GPF5, 1);
s3c2410_gpio_setpin(S3C2410_GPF6, 1);
s3c2410_gpio_setpin(S3C2410_GPF7, 1);
return 0;
}
fail: unregister_chrdev_region(devno, 1);
return result;
}
/*模块卸载函数*/
void globalmem_exit(void)
{
if (IO_port_resource!=NULL) release_region((unsigned long)S3C2410_GPFCON, 0x0c);
cdev_del(&globalmem_devp->cdev); /*注销cdev*/
kfree(globalmem_devp); /*释放设备结构体内存*/
//测试hardware.h的读写函数
s3c2410_gpio_setpin(S3C2410_GPF4, 0);
s3c2410_gpio_setpin(S3C2410_GPF5, 0);
s3c2410_gpio_setpin(S3C2410_GPF6, 0);
s3c2410_gpio_setpin(S3C2410_GPF7, 0);
//下面的是为udev准备的
class_device_destroy(my_class, MKDEV(globalmem_major, 0));
class_destroy(my_class);
//结束
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); /*释放设备号*/
}
MODULE_AUTHOR("AK-47");
MODULE_LICENSE("Dual BSD/GPL");
module_param(globalmem_major, int, S_IRUGO);
module_init(globalmem_init);
module_exit(globalmem_exit);
通过上述代码增加后,当我们insmod我们生成的.ko文件后,在/dev下面就可以生成相应的设备文件节点了。省去了mknod去创建文件节点的麻烦。而且由于udevd做成了守护进程,每当加载新的驱动程序的时候会自动的创建设备文件节点。
当然前提不要忘记了:udevd、udevstart已经运行。
附录:
再次改动后的rcs文件
#! /bin/sh
echo "Processing etc/init.d/rcS"
#hostname ${HOSTNAME}
hostname AK-47
echo " Mount all"
/bin/mount -a
echo " Start mdev...."
#/bin/echo /sbin/mdev > proc/sys/kernel/hotplug
#mdev -s
echo "****************************************************"
echo " rootfs by NFS, s3c2410"
echo " Created by Mr.Liu @ 2008.11.28"
echo " Good Luck"
echo " www.neusoft.edu.cn"
echo "****************************************************"
echo
export QTDIR=/usr/
export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH
export PATH=$QTDIR/qt_bin:$QTDIR/bin:$PATH
ln -sf /dev/ts0 /dev/h3600_tsraw
ln -sf /dev/ts0 /dev/h3600_ts
ln -s /dev/scsi/host0/bus0/target0/lun0/part1 /dev/sda1
echo "ifconfig eth0 192.168.0.1"
/sbin/ifconfig eth0 192.168.0.1
echo "start udev!"
/sbin/udevd --daemon
/sbin/udevstart
echo "finished run udev!"
#/bin/menu -qws &