Linux驱动开发(三)

一、 驱动开发生成设备节点两种方式

  • 手动生成设备文件 mknod /dev/xxx c 主设备号 次设备号
  • 自动创建设备文件

二、自动生成设备节点

1、深入理解mdev和udev

(1)、udev的工作原理

当系统内核发现系统中添加或者删除了某个新的设备时,内核检测到后会产生一个hotplug event并查找/proc/sys/kernel/hotplug去找出管理设备连接的用户空间程序。若udev已经启动,内核会通知udev去检测sysfs中关于这个新设备的信息并创建设备节点。udev就会去执行udevd,以便让udevd可以产生或者删除硬件的设备文件。 接着udevd会通过libsysfs读取sys文件系统,以便取得该硬件设备的信息(如/dev/vcs,在/sys/class/tty/vcs/dev存放的是”7:0”,既/dev/vcs的主次设备号);然后再向namedev查询该外部设备的设备文件信息,例如文件的名称、权限等。最后,udevd就依据上述的结果,在/dev/目录中自动建立该外部设备的设备文件,同时在/etc/udev/rules.d下检查有无针对该设备的使用权限。

当设备插入或移除时,hotplug机制会让内核会通过netlink socket通讯(内核调用kobject_uevent函数发送netlink message给用户空间,该功能由内核的统一设备模型里的子系统这一层实现)向用户传递一个事件的发生,Udevd通过标准的socket机制,创建socket连接来获取内核广播的uevent事件 并解析这些uevent事件。

运行udevd以后,使用udevtrigger的时候,会把内核中已存在的设备的节点创建出来,其具体过程为:udevtrigger通过向/sysfs 文件系统下现有设备的uevent节点写"add"字符串,从而触发uevent事件,使得udevd能够接收到这些事件,并创建buildin的设备驱动的设备节点连同任何已insmod的模块的设备节点。
所以,我们也能够手工用命令行来模拟这一过程:

# echo "add" > /sys/block/mtdblock2/uevent

实际上,不管您往uevent里面写什么,都会触发add事件,这个从kernel内部对uevent属性的实现函数能够看出来.

而udevstart的实现方式和udevtrigger就不同了,他基本上是重复实现了udevd里面的机制,通过遍历sysfs,自己完成设备节点的创建,不通过udevd来完成。

(2)、mdev的工作原理

udev是linux2.6内核引入的一种新的设备文件管理机制,用于取代老的devfs.udev最大的有点就是可以动态的管理/dev目录下的设备文件,而不用再系统初始化时就将可能要用到的设备都创建起来,还可以根据设备具体信息命名设备节点,而不是有内核统一分配。但因为udev较mdev复杂,不太适合嵌入式使用,所以在嵌入式邻域一般更多的使用mdev。它是一个简化版的udev,是busybox所带的程序,十分适合嵌入式系统。

udev 和mdev 是两个使用uevent 机制处理热插拔问题的用户空间程序,两者的实现机理不同。udev 是基于netlink 机制的,它在系统启动时运行了一个deamon 程序udevd,通过监听内核发送的uevent 来执行相应的热拔插动作,包括创建/删除设备节点,加载/卸载驱动模块等等。mdev 是基于uevent_helper 机制的,它在系统启动时修改了内核中的uevnet_helper 变量(通过写/proc/sys/kernel/hotplug),值为“/sbin/mdev”。这样内核产生uevent 时会调用uevent_helper 所指的用户级程序,也就是mdev,来执行相应的热拔插动作。

udev 使用的netlink 机制在有大量uevent 的场合效率高,适合用在PC 机上;而mdev 使用的uevent_helper 机制实现简单,适合用在嵌入式系统中。另外要说明的一点是,uevent_helper 的初始值在内核编译时是可配置的,默认值为/sbin/hotplug。如果想修改它的值,写/proc/sys/kernel/hotplug 文件就可以了,例如:

echo “/sbin/mdev” > /proc/sys/kernel/hotplug

而如果使用的是udevd,那么uevent_helper变量应为空,即

echo "" > /proc/sys/kernel/hotplug

2. 在/dev下生成设备文件节点

(1)创建和删除类

自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添加自动创建设备节点相关代码。首先要创建一个 class 类,class 是个结构体,定义在文件include/linux/device.h 里面。class_create 是类创建函数,class_create 是个宏定义,内容如下:

#define class_create(owner, name)	\
({	\
	static struct lock_class_key __key; 	\
	__class_create(owner, name, &__key);	\
})

struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key)

根据上述代码,将宏 class_create 展开以后内容如下:

struct class *class_create (struct module *owner, const char *name)

class_create 一共有两个参数,参数 owner 一般为 THIS_MODULE,参数 name 是类名字。返回值是个指向结构体 class 的指针,也就是创建的类。

卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy,函数原型如下:

void class_destroy(struct class *cls);

参数 cls 就是要删除的类。

(2)创建和删除设备

一小节创建好类以后还不能实现自动创建设备节点,我们还需要在这个类下创建一个设备。使用 device_create 函数在类下面创建设备,device_create 函数原型如下:

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

device_create 是个可变参数函数,参数 class 就是设备要创建哪个类下面;参数 parent 是父设备,一般为 NULL,也就是没有父设备;参数 devt 是设备号;参数 drvdata 是设备可能会使用的一些数据,一般为 NULL;参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx这个设备文件。返回值就是创建好的设备。
    同样的,卸载驱动的时候需要删除掉创建的设备,设备删除函数为 device_destroy,函数原型如下:

void device_destroy(struct class *class, dev_t devt)

参数 class 是要删除的设备所处的类,参数 devt 是要删除的设备号。

参考示例

struct user_dev_t {
	struct class *class; /* 类 */
	struct device *device; /* 设备 */
    dev_t devid;
};
struct user_dev_t user_dev;

#define DEV_CLASS_NAME "yyy"
#define DEV_NAME	"xxx"

/* 驱动入口函数 */
static int __init xxx_init(void)
{
	/* 创建类 */
	user_dev.class = class_create(THIS_MODULE, DEV_CLASS_NAME);
	/* 创建设备 */
	user_dev.device = device_create(user_dev.class, NULL, user_dev.devid, NULL, DEV_NAME);
	return 0;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
	/* 删除设备 */
	device_destroy(user_dev.class, user_dev.devid);
	/* 删除类 */
	class_destroy(user_dev.class);
}

module_init(xxx_init);
module_exit(xxx_exit);
(3)在/sys/class中生成设备文件节点

有时候,我们会采取在 /sys/class 中生成设备文件节点,而不是 /dev 中。

(4)创建、删除类和设备

首先,我们需要使用结构struct class_attribute创建一个属性数组,来组织设备文件的读写函数,struct class_attribute定义如下:

struct class_attribute {
	struct attribute attr;
	ssize_t (*show)(struct class *class, struct class_attribute *attr, 
    		char *buf);
	ssize_t (*store)(struct class *class, struct class_attribute *attr,
			const char *buf, size_t count);
};

attr为属性,show函数为读文件操作函数,store函数为写文件操作函数。
其中,结构struct attribute结构定义如下:

struct attribute {
	const char		*name;
	umode_t			mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	bool			ignore_lockdep:1;
	struct lock_class_key	*key;
	struct lock_class_key	skey;
#endif
};

name为设备文件名称,mode为模式。
    然后,需要定义一个struct class结构,为其成员class_attrs指定为上述结构。结构全部定义完毕之后,便可以使用函数class_register将class注册到系统。class_register定义如下:

#define class_register(class)			\
({						\
	static struct lock_class_key __key;	\
	__class_register(class, &__key);	\
})

int __class_register(struct class *cls, struct lock_class_key *key)

其为一个宏,其中, 参数class为上述定义的struct class。我们将其展开后得到如下接口:

int class_register(struct class *cls);

在驱动卸载的时候,需要删除这个class,使用函数class_unregister,其定义如下:

void class_unregister(struct class *cls);

参考示例

在下面的参考示例中,会生成设备文件 /sys/class/eurphan/xxx 。

/*
 * @description		: sysfs文件节点读函数
 * @param - class	: 传递给驱动的class
 * @param - attr	: sysfs文件节点属性
 * @param - buff	: 接收数据的缓存
 * @return			: 读取到的数据个数
 */
static ssize_t xxx_show(struct class *class, struct class_attribute *attr, char *buff)
{
	return 0;
}

/*
 * @description		: sysfs文件节点写函数
 * @param - class	: 传递给驱动的class
 * @param - attr	: sysfs文件节点属性
 * @param - buff	: 写入的数据缓存
 * @param - count	: 写入的数据个数
 * @return			: 实际写入的数据个数
 */
static ssize_t xxx_store(struct class *class, struct class_attribute *attr, const char *buff, size_t count)
{
	return count;
}
/* class属性结构 */
static struct class_attribute xxx_class_attr[] = {
	__ATTR(xxx, 0644, xxx_show, xxx_store),
	__ATTR_NULL,
};

/* class结构 */
static struct class xxx_class = {
	.name = "eurphan",
	.owner = THIS_MODULE,
	.class_attrs = xxx_class_attr,
};

/*
 * @description		: 驱动卸载函数
 * @param			: 无
 * @return			: 加载是否成功
 */
static __init int xxx_init(void)
{
	class_register(&xxx_class);
	return 0;
}

/*
 * @description		: 驱动卸载函数
 * @param			: 无
 * @return			: 无
 */
static __exit void xxx_exit(void)
{
	class_unregister(&xxx_class);
}

module_init(xxx_init);
module_exit(xxx_exit);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值