Linux字符设备架构是如何实现的?
参考上图,编写字符设备驱动步骤如下:
1. 实现模块加载和卸载入口函数module_init (hello_init);
module_exit (hello_exit);
2. 申请主设备号
申请主设备号 (内核中用于区分和管理不同字符设备)
register_chrdev_region (devno, number_of_devices, "hello");
3. 创建设备节点
创建设备节点文件 (为用户提供一个可操作到文件接口--open())创建设备节点有两种方式:手动方式创建,函数自动创建。手动创建:
mknod /dev/hello c 250 0
自动创建设备节点
除了使用mknod命令手动创建设备节点,还可以利用linux的udev、mdev机制,而我们的ARM开发板上移植的busybox有mdev机制,那么就使用mdev机制来自动创建设备节点。
在etc/init.d/rcS文件里有一句:
echo /sbin/mdev > /proc/sys/kernel/hotplug
该名命令就是用来自动创建设备节点。
udev 是一个工作在用户空间的工具,它能根据系统中硬件设备的状态动态的更新设备文件,包括设备文件的创建,删除,权限等。这些文件通常都定义在/dev 目录下,但也可以在配置文件中指定。udev 必须有内核中的sysfs和tmpfs支持,sysfs 为udev 提供设备入口和uevent 通道,tmpfs 为udev 设备文件提供存放空间。
udev 运行在用户模式,而非内核中。udev 的初始化脚本在系统启动时创建设备节点,并且当插入新设备——加入驱动模块——在sysfs上注册新的数据后,udev会创新新的设备节点。
注意,udev 是通过对内核产生的设备文件修改,或增加别名的方式来达到自定义设备文件的目的。但是,udev 是用户模式程序,其不会更改内核行为。也就是说,内核仍然会创建sda,sdb等设备文件,而udev可根据设备的唯一信息来区分不同的设备,并产生新的设备文件(或链接)。
例如:
如果驱动模块可以将自己的设备号作为内核参数导出,在sysfs文件中就有一个叫做uevent文件记录它的值。
由上图可知,uevent中包含了主设备号和次设备号的值以及设备名字。
在Linux应用层启动一个udev程序,这个程序的第一次运行的时候,会遍历/sys目录,寻找每个子目录的uevent文件,从这些uevent文件中获取创建设备节点的信息,然后调用mknod程序在/dev目录下创建设备节点。结束之后,udev就开始等待内核空间的event。这个设备模型的东西,我们在后面再详细说。这里大就可以这样理解,在Linux内核中提供了一些函数接口,通过这些函数接口,我们可在sysfs文件系统中导出我们的设备号的值,导出值之后,内核还会向应用层上报event。此时udev就知道有活可以干了,它收到这个event后,就读取event对应的信息,接下来就开始创建设备节点啦。
如何创建一个设备类?
第一步 :通过宏class_create() 创建一个class类型的对象; This is a #define to keep the compiler from merging different
* instances of the __key variable
#define class_create(owner, name)
({
static struct lock_class_key __key;
__class_create(owner, name, &__key);
})
参数:
@owner THIS_MODULE
@name 类名字
返回值
可以定义一个struct class的指针变量cls接受返回值,然后通过IS_ERR(cls)判断
是否失败,如果成功这个宏返回0,失败返回非9值(可以通过PTR_ERR(cls)来获得
失败返回的错误码)
在Linux内核中,把设备进行了分类,同一类设备可以放在同一个目录下,该函数启示就是创建了一个类,例如:
第二步:导出我们的设备信息到用户空间*
* device_create - creates a device and registers it with sysfs
* @class: pointer to the struct class that this device should be registered to
* @parent: pointer to the parent struct device of this new device, if any
* @devt: the dev_t for the char device to be added
* @drvdata: the data to be added to the device for callbacks
* @fmt: string for the device's name
*
* This function can be used by char device classes. A struct device
* will be created in sysfs, registered to the specified class.
*
* A "dev" file will be created, showing the dev_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 in sysfs.
* The pointer to the struct device will be returned from the call.
* Any further sysfs files that might be required can be created using this
* pointer.
*
* Returns &struct device pointer on success, or ERR_PTR() on error.
*
* Note: the struct class passed to this function must have previously
* been created with a call to class_create().
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
自动创建设备节点使用实例:
static struct class *cls;
static struct device *test_device;
devno = MKDEV(major,minor);
cls = class_create(THIS_MODULE,"helloclass");
if(IS_ERR(cls))
{
unregister_chrdev(major,"hello");
return result;
}
test_device = device_create(cls,NULL,devno,NULL,"hellodevice");
if(IS_ERR(test_device ))
{
class_destroy(cls);
unregister_chrdev(major,"hello");
return result;
}
4 实现file_operationsstatic const struct file_operations fifo_operations = {
.owner = THIS_MODULE,
.open = dev_fifo_open,
.read = dev_fifo_read,
.write = dev_fifo_write,
.unlocked_ioctl = dev_fifo_unlocked_ioctl,
};
open、release对应应用层的open()、close()函数。实现比较简单,
直接返回0即可。其中read、write、unloched_ioctrl 函数的实现需要涉及到用户空间和内存空间的数据拷贝。
在Linux操作系统中,用户空间和内核空间是相互独立的。也就是说内核空间是不能直接访问用户空间内存地址,同理用户空间也不能直接访问内核空间内存地址。
如果想实现,将用户空间的数据拷贝到内核空间或将内核空间数据拷贝到用户空间,就必须借助内核给我们提供的接口来完成。
1. read接口实现
用户空间-->内核空间
字符设备的write接口定义如下:
ssize_t (*write)(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
参数:
filp:待操作的设备文件file结构体指针
buf:待写入所读取数据的用户空间缓冲区指针
count:待读取数据字节数
f_pos:待读取数据文件位置,写入完成后根据实际写入字节数重新定位
返回:
成功实际写入的字节数,失败返回负值
如果该操作为空,将使得write系统调用返回负EINVAL失败,正常返回实际写入的字节数。
用户空间向内核空间拷贝数据需要使用copy_from_user函数,该函数定义在arch/arm/include/asm/uaccess.h中。
static inline int copy_from_user(void *to, const void __user volatile *from,unsigned long n)
参数:
to:目标地址(内核空间)
from:源地址(用户空间)
n:将要拷贝数据的字节数
返回:
成功返回0,失败返回没有拷贝成功的数据字节数
还可以使用get_user宏:
int get_user(data, ptr);
参数:
data:可以是字节、半字、字、双字类型的内核变量
ptr:用户空间内存指针
返回:
成功返回0,失败返回非0
2. write接口实现
内核空间-->用户空间
字符设备的read接口定义如下:
ssize_t (*read)(struct file *filp, char __user *buf, size_t count, lofft *f_pos);
参数:
filp: 待操作的设备文件file结构体指针
buf: 待写入所读取数据的用户空间缓冲区指针
count:待读取数据字节数
f_pos:待读取数据文件位置,读取完成后根据实际读取字节数重新定位
__user :是一个空的宏,主要用来显示的告诉程序员它修饰的指针变量存放的是用户空间的地址。
返回值:
成功实际读取的字节数,失败返回负值
注意:如果该操作为空,将使得read系统调用返回负EINVAL失败,正常返回实际读取的字节数。
用户空间从内核空间读取数据需要使用copy_to_user函数:
static inline int copy_to_user(void __user volatile *to, const void *from,unsigned long n)
参数:
to:目标地址(用户空间)
from:源地址(内核空间)
n:将要拷贝数据的字节数
返回:
成功返回0,失败返回没有拷贝成功的数据字节数
在这里插入图片描述
还可以使用put_user宏:
int put_user(data, prt)
参数:
data:可以是字节、半字、字、双字类型的内核变量
ptr:用户空间内存指针
返回:
成功返回0, 失败返回非0
这样我们就可以实现read、write函数了,实例如下:
ssize_t hello_read (struct file *filp, char *buff, size_t count, loff_t *offp)
{
ssize_t result = 0;
if (count > 127)
count = 127;
if (copy_to_user (buff, data, count))
{
result = -EFAULT;
}
else
{
printk (KERN_INFO "wrote %d bytes", count);
result = count;
}
return result;
}
ssize_t hello_write (struct file *filp,const char *buf, size_t count, loff_t *f_pos)
{
ssize_t ret = 0;
//printk (KERN_INFO "Writing %d bytes", count);
if (count > 127) return -ENOMEM;
if (copy_from_user (data, buf, count)) {
ret = -EFAULT;
}
else {
data[count] = '';
printk (KERN_INFO"Received: %s", data);
ret = count;
}
return ret;
}

图片新闻
最新活动更多
-
4月25日立即报名>> 【线下论坛】新唐科技2025新品发布会
-
限时免费下载立即下载 >>> 2024“机器人+”行业应用创新发展蓝皮书
-
即日-5.15立即报名>>> 【在线会议】安森美Hyperlux™ ID系列引领iToF技术革新
-
5月16日立即参评>> 【评选】维科杯·OFweek2025中国工业自动化及数字化行业年度评选
-
5月16日立即参评>> 【评选】维科杯·OFweek 2025 传感器行业年度评选
-
5月22日立即预约>>> 宾采尔激光焊接领域一站式应用方案在线研讨会
推荐专题
发表评论
请输入评论内容...
请输入评论/评论长度6~500个字
暂无评论
暂无评论