Linux设备文件的创建和mdev

以下内容源于微信公众号嵌入式企鹅圈,有格式内容上的修改,如有侵权请告知删除。

本文将从代码级去理解Linux设备类和设备文件的创建过程。

一、设备类相关知识

  • 设备类是虚拟的,并没有直接对应的物理实物,只是为了更好地管理同一类设备导出到用户空间而产生的目录和文件。
  • 整个过程涉及到sysfs文件系统,该文件系统是为了展示Linux设备驱动模型而构建的文件系统,是基于ramfs,linux根目录中的/sysfs即挂载了sysfs文件系统。
  • Struct kobject数据结构是sysfs的基础,kobject在sysfs中代表一个目录;
  • linux的驱动(struct driver)、设备(struct device)、设备类(struct class)均是从kobject进行派生的,因此他们在sysfs中都对应于一个目录。
  • 数据结构中附属的struct device_attribute、driver_attribute、class_attribute等属性数据结构在sysfs中则代表一个普通的文件。
  • Struct kset是struct kobject的容器,即Struct kset可以成为同一类struct kobject的父亲,而其自身也有kobject成员,因此其又可能和其他kobject成为上一级kset的子成员。

二、两种创建设备文件的方式

  • 在设备驱动中,cdev_add将struct file_operations和设备号注册到系统后,为了能够自动产生驱动对应的设备文件,需要调用class_create和device_create,并通过uevent机制调用mdev(嵌入式linux由busybox提供)来调用mknod创建设备文件。
  • 当然也可以不调用这两个接口,那就手工通过命令行mknod来创建设备文件。

 

三、设备类和设备相关数据结构

(1)include/linux/kobject.h

[cpp] view plain copy

  1. struct kobject   
  2. {  
  3.   const char *name;//名称  
  4.   struct list_head entry;//kobject链表  
  5.   struct kobject *parent;//即所属kset的kobject  
  6.   struct kset *kset;//所属kset  
  7.   struct kobj_type *ktype;//属性操作接口  
  8.   …  
  9. };  
  10.   
  11. struct kset   
  12. {  
  13.   struct list_head list;//管理同属于kset的kobject  
  14.   struct kobject kobj;//可以成为上一级父kset的子目录  
  15.   const struct kset_uevent_ops *uevent_ops;//uevent处理接口  
  16. };  
  • 假设kobject A代表一个目录,kset B代表几个目录(包括A)的共同的父目录。则A.kset=B;A.parent=B.kobj.

(2)include/linux/device.h

[cpp] view plain copy

  1. struct class   
  2. {//设备类  
  3.   const char *name; //设备类名称  
  4.   struct module *owner;//创建设备类的module  
  5.   struct class_attribute *class_attrs;//设备类属性  
  6.   struct device_attribute *dev_attrs;//设备属性  
  7.   struct kobject *dev_kobj;//kobject再sysfs中代表一个目录  
  8.   …  
  9.   struct class_private *p;//设备类得以注册到系统的连接件  
  10. };  

(3)drivers/base/base.h

[cpp] view plain copy

  1. struct class_private   
  2. {  
  3.   //该设备类同样是一个kset,包含下面的class_devices;同时在class_subsys填充父kset  
  4.   struct kset class_subsys;  
  5.   struct klist class_devices;//设备类包含的设备(kobject)  
  6.   …  
  7.   struct class *class;//指向设备类数据结构,即要创建的本级目录信息  
  8. };  

(4)include/linux/device.h

[cpp] view plain copy

  1. struct device   
  2. {//设备  
  3.   struct device *parent;//sysfs/devices/中的父设备  
  4.   struct device_private *p;//设备得以注册到系统的连接件  
  5.   struct kobject kobj;//设备目录  
  6.   const char *init_name;//设备名称  
  7.   struct bus_type *bus;//设备所属总线  
  8.   struct device_driver *driver; //设备使用的驱动  
  9.   struct klist_node knode_class;//连接到设备类的klist  
  10.   struct class *class;//所属设备类  
  11.   const struct attribute_group **groups;  
  12.   …  
  13. }  

(5)drivers/base/base.h

[cpp] view plain copy

  1. struct device_private   
  2. {  
  3.   struct klist klist_children;//连接子设备  
  4.   struct klist_node knode_parent;//加入到父设备链表  
  5.   struct klist_node knode_driver;//加入到驱动的设备链表  
  6.   struct klist_node knode_bus;//加入到总线的链表  
  7.   struct device *device;//对应设备结构  
  8. };  

(6)解释

  • class_private是class的私有结构,class通过class_private注册到系统中;
  • device_private是device的私有结构,device通过device_private注册到系统中。
  • 所谓注册到系统中,即把相应的数据结构加入到系统已经存在的链表中,但是这些链接的细节并不希望暴露给用户,所以才有private的结构。
  • 而class和device则通过sysfs向用户层提供信息。

四、创建设备类目录文件

1、在驱动通过cdev_add将struct file_operations接口集和设备注册到系统后,即利用class_create接口来创建设备类目录文件。

led_class = class_create(THIS_MODULE, "led_class");

__class_create(owner, name, &__key);

cls->name = name;//设备类名

cls->owner = owner;//所属module

retval = __class_register(cls, key);

struct class_private *cp;

//将类的名字led_class赋值给对应的kset

kobject_set_name(&cp->class_subsys.kobj, "%s", cls->name);

//填充class_subsys所属的父kset:ket:sysfs/class.

cp->class_subsys.kobj.kset = class_kset;

//填充class属性操作接口

cp->class_subsys.kobj.ktype = &class_ktype;

cp->class = cls;//通过cp可以找到class

cls->p = cp;//通过class可以找到cp

//创建led_class设备类目录

kset_register(&cp->class_subsys);

//在led_class目录创建class属性文件

add_class_attrs(class_get(cls));

2、继续展开kset_register

kset_register(&cp->class_subsys);

kobject_add_internal(&k->kobj);

// parent即class_kset.kobj,即/sysfs/class对应的目录

parent = kobject_get(kobj->parent);

create_dir(kobj);

//创建一个led _class设备类目录

sysfs_create_dir(kobj);

//该接口是sysfs文件系统接口,代表创建一个目录,不再展开。

3.上述提到的class_kset在class_init被创建

class_kset = kset_create_and_add("class", NULL, NULL);

第三个传参为NULL,代表默认在/sysfs/创建class目录。

五、创建设备目录和设备属性文件

1、利用class_create接口来创建设备类目录文件后,再利用device_create接口来创建具体设备目录和设备属性文件。

led_device = device_create(led_class, NULL, led_devno, NULL, "led");

device_create_vargs

dev->devt = devt;//设备号

dev->class = class;//设备类led_class

dev->parent = parent;//父设备,这里是NULL

kobject_set_name_vargs(&dev->kobj, fmt, args)//设备名”led”

device_register(dev)注册设备

2、继续展开device_register(dev)

device_initialize(dev);

dev->kobj.kset = devices_kset;//设备所属/sysfs/devices/

device_add(dev)

device_private_init(dev)//初始化device_private

dev_set_name(dev, "%s", dev->init_name);//赋值dev->kobject的名称
setup_parent(dev, parent);//建立device和父设备的kobject的联系
//kobject_add在/sysfs/devices/目录下创建设备目录led,kobject_add是和kset_register相似的接口,只不过前者针对kobject,后者针对kset。
kobject_add(&dev->kobj, dev->kobj.parent, NULL);
kobject_add_varg
kobj->parent = parent;
kobject_add_internal(kobj)
create_dir(kobj);//创建设备目录
//在刚创建的/sysfs/devices/led目录下创建uevent属性文件,名称是”uevent”
device_create_file(dev, &uevent_attr);
//在刚创建的/sysfs/devices/led目录下创建dev属性文件,名称是”dev”,该属性文件的内容就是设备号
device_create_file(dev, &devt_attr);
//在/sysfs/class/led_class/目录下建立led设备的符号连接,所以打开/sysfs/class/led_class/led/目录也能看到dev属性文件,读出设备号。
device_add_class_symlinks(dev);
//创建device属性文件,包括设备所属总线的属性和attribute_group属性
device_add_attrs()
bus_add_device(dev) //将设备加入总线
//触发uevent机制,并通过调用mdev来创建设备文件。
kobject_uevent(&dev->kobj, KOBJ_ADD);
//匹配设备和总线的驱动,匹配成功就调用驱动的probe接口,不再展开

bus_probe_device(dev);

3、展开kobject_uevent(&dev->kobj, KOBJ_ADD);

kobject_uevent_env(kobj, action, NULL);
kset = top_kobj->kset; 
uevent_ops = kset->uevent_ops; //即device_uevent_ops
// subsystem即设备所属的设备类的名称”led_class”
subsystem = uevent_ops->name(kset,
 kobj);
//devpath即/sysfs/devices/led/
devpath = kobject_get_path(kobj,
 GFP_KERNEL);
//添加各种环境变量
add_uevent_var(env, "ACTION=%s",
 action_string);
add_uevent_var(env, "DEVPATH=%s",
 devpath);
add_uevent_var(env, "SUBSYSTEM=%s",
 subsystem);
uevent_ops->uevent(kset, kobj,
 env);
add_uevent_var(env, "MAJOR=%u",
 MAJOR(dev->devt));
add_uevent_var(env, "MINOR=%u",
 MINOR(dev->devt));
add_uevent_var(env, "DEVNAME=%s",
 name);
add_uevent_var(env, "DEVTYPE=%s",
 dev->type->name);
//还会增加总线相关的一些属性环境变量等等。
#if defined(CONFIG_NET)//如果是PC的linux会通过socket的方式向应用层发送uevent事件消息,但在嵌入式linux中不启用该机制。
#endif
argv [0] = uevent_helper;//即/sbin/mdev
argv [1] = (char *)subsystem;//”led_class”
argv [2] = NULL;
add_uevent_var(env, "HOME=/");
add_uevent_var(env,"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
call_usermodehelper(argv[0],
 argv,env->envp, UMH_WAIT_EXEC);

4、上述提到的devices_kset在devices_init被创建
devices_kset = kset_create_and_add("devices",
 &device_uevent_ops, NULL);

//第三个传参为NULL,代表默认在/sysfs/创建devices目录

5、上述设备属性文件

static struct device_attribute devt_attr =
__ATTR(dev,
 S_IRUGO, show_dev, NULL);


static ssize_t show_dev(struct device *dev, struct device_attribute
 *attr,char *buf){{
return
 print_dev_t(buf, dev->devt);//即返回设备的设备号

}

6、devices设备目录响应uevent事件的操作

static const struct kset_uevent_ops device_uevent_ops = {
.filter =dev_uevent_filter,
.name = dev_uevent_name,
.uevent = dev_uevent,

};

7、call_usermodehelper是从内核空间调用用户空间程序的接口。

8、对于嵌入式系统来说,busybox采用的是mdev,在系统启动脚本rcS中会使用命令“echo /sbin/mdev > /proc/sys/kernel/hotplug”。

uevent_helper[]数组即读入/proc/sys/kernel/hotplug文件的内容,即“/sbin/mdev” .

六、创建设备文件

  • 以上描述都是在sysfs文件系统中创建目录或者文件,而应用程序访问的设备文件则需要创建在/dev/目录下。该项工作由mdev完成
  • Mdev的原理:解释/etc/mdev.conf文件定义的命名设备文件的规则,并在该规则下根据环境变量的要求来创建设备文件。
  • Mdev.conf由用户层指定,因此更具灵活性。
  • 下面是mdev配置脚本,最终我们会跟踪到mknod在/dev/目录下创建了设备文件。

[cpp] view plain copy

  1. Busybox/util-linux/mdev.c  
  2. int mdev_main(int argc UNUSED_PARAM, char **argv)  
  3. xchdir("/dev");  
  4. if (argv[1] && strcmp(argv[1], "-s")//系统启动时mdev  
  5.  –s才会执行这个分支  
  6. else  
  7. action= getenv("ACTION");  
  8. env_path= getenv("DEVPATH");  
  9. G.subsystem= getenv("SUBSYSTEM");  
  10. snprintf(temp,PATH_MAX, "/sys%s", env_path);//到/sysfs/devices/led目录  
  11. make_device(temp,/*delete:*/ 0);  
  12. strcpy(dev_maj_min,"/dev"); //读出dev属性文件,得到设备号  
  13. open_read_close(path,dev_maj_min + 1, 64);  
  14. ….  
  15. mknod(node_name,rule->mode | type, makedev(major, minor))  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/461047.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux查看执行过的命令行,在Linux命令终端中查看和编辑曾执行过的命令 – LINUX笔记 – CFEI.NET...

今天我们来讲讲linux的知识,积累的这些知识就是我们以后的财富,各位加油.因为水平有限,难免有疏忽或者不准确的地方,希望大家能够直接指出来,我会及时改正。一切为了知识的分享。history 命令可以用来显示曾执行过的命令,也可以根…

烂泥:【解决】word复制windows live writer没有图片

本文由秀依林枫提供友情赞助,首发于烂泥行天下。 在使用windows live writer发表博客,博客先是在是word2013中进行编辑,编辑完毕后我会复制到windows live writer中,然后发表出去。 使用了几年都没有问题,就是最近这个…

平台设备与平台驱动的注册

以下内容源于网络资源的学习与整理,如有侵权请告知删除。 参考资料 linux中 probe函数何时调用 platform总线的probe函数调用_Linux编程_Linux公社-Linux系统门户网站 Linux设备驱动模型之platform(平台)总线详解 Linux设备驱动模型2——总线式设备驱动组织方式_天…

JDK源码 - BitSet的实现

java.util.BitSet是个很有趣的类&#xff0c;了解其内部实现对正确的使用非常重要。 对象构造&#xff1a; Java代码 private final static int ADDRESS_BITS_PER_WORD 6; private final static int BITS_PER_WORD 1 << ADDRESS_BITS_PER_WORD; private long[] wor…

细粒度权限控制 linux,利用docker插件实现细粒度权限控制

前言我们在实际的docker运行环境下&#xff0c;大都会遇到多用户的情况&#xff0c;为了安全起见&#xff0c;有些用户我们不想给予其全面的docker控制权限&#xff0c;比如不想某些用户执行docker stop 以及docker rm 等危险指令&#xff0c;当然我们可以从系统账号权限来控制…

linux查找命令、find、grep总结

find 命令 语法&#xff1a;find 搜索路径 匹配表达式 功能&#xff1a;该命令用于在指定路径中查找符合条件的文件&#xff0c;搜索路径可以是多个目录&#xff0c;不同目录之间以空格分隔 &#xff08;1&#xff09;匹配表达式1 -name filename&#xff1a;要查找的文件…

Sharepoint学习笔记—ECM系列--根据位置设置的默认元数据值(Location-Based Metadata Defaults)...

如果有这样一个需求&#xff1a;客户在一个SharePoint 2010的站点的document library中创建了不同的文件夹FolderA和FolderB&#xff0c;对于上传到此文件夹的文件记录中有某一个列ColumnM,现在他实现当上传文件到不同的文件夹FolderA或FolderB时&#xff0c;列ColumnM使用不同…

linux上安装fio教程,fio工具安装及使用

fio是一种I / O工具&#xff0c;用于基准测试和压力/硬件验证。它支持19种不同类型的I / O引擎(sync&#xff0c;mmap&#xff0c;libaio&#xff0c;posixaio&#xff0c;SG v3&#xff0c;splice&#xff0c;null&#xff0c;network&#xff0c;syslet&#xff0c;guasi&…

Maven的学习资料收集--(九) 构建SSH项目以及专栏maven

在这里整合一下&#xff0c;使用Maven构建一个SSH项目 1.新建一个Web项目 可以参照前面的博客 2.添加依赖&#xff0c;修改pom.xml [html] view plaincopy <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-i…

博客园的CSRF

CSRF全称 Cross Site Request Forgery&#xff0c;跨站请求伪造。通俗理解&#xff1a;攻击者盗用当前用户身份&#xff0c;发请当前用户的恶意请求&#xff1a;如邮件&#xff0c;银行转账等。 CSRF原理 CSRF过程 登录网站A&#xff0c;生成本地Cookie信息&#xff1b;登录危…

linux 设备驱动总结,linux设备驱动归纳总结.doc

linux设备驱动归纳总结linux设备驱动归纳总结内核&#xff1a;用于管理软硬件资源&#xff0c;并提供运行环境。如分配4G虚拟空间等。 linux设备驱动&#xff1a;是连接硬件和内核之间的桥梁。linux系统按个人理解可按下划分&#xff1a;应用层&#xff1a;包括POSIX接口&#…

开发板——在X210开发板上进行裸机开发的细节

以下内容是学习裸机开发过程中的一些细节内容的记录。 1、汇编语言函数细节 用汇编写的函数&#xff0c;末尾应该添加mov pc,lr语句。 2、裸机代码相关文件 3、关于链接地址 4、关于重定位的理解 &#xff08;1&#xff09;在sram内部重定位 这是在sram内部重定位&#xff0c;因…

linux报网络设备繁忙,【分享】linux常用命令

压缩与备份:bzip2/bunzip2 .bz2文件的压缩/解压缩程序cpio 备份文件dump 备份文件系统gzip/gunzip .gz文件的压缩/解压缩程序gzexe 压缩可执行文件restore 还原由倾倒(Dump)操作所备份下来的文件或整个文件系统(一个分区)tar 将若干文件存档或读取存档文件unarj 解压缩.…

HDU-4454 Stealing a Cake 三分枚举

题意&#xff1a;给定一个点&#xff0c;一个圆&#xff0c;以及一个矩形&#xff0c;现在问从一个点到一个圆再到一个矩形的最短距离为多少&#xff1f;到达一个目标可以只挨着或者穿过它。 解法&#xff1a;目前只知道从一个点到圆上按照[0,PI]&#xff0c;[PI,2*PI]的两个半…

VIP - virtual IP address

virtual IP address (虚拟 IP 地址)1、是集群的ip地址&#xff0c;一个vip对应多个机器2、与群集关联的唯一 IP 地址see wiki&#xff1a; A virtual IP address (VIP or VIPA) is an IP address assigned to multiple applications residing on a single server, multiple dom…

linux上perl怎么传输参数,如何在perl子函数中传递参数?

慕村225694Perl 可以通过函数元型在编译期进行有限的参数类型检验。如果你声明sub mypush ()那么 mypush() 对参数的处理就同内置的 push() 完全一样了。函数声明必须要在编译相应函数调用之前告知编译器(编译器在编译函数调用时会对相应函数用 prototype来查询它的元型来进行参…

Android中级之网络数据解析一之Json解析

本文来自http://blog.csdn.net/liuxian13183/ &#xff0c;引用必须注明出处&#xff01; 在网络传输的时候&#xff0c;经常用到的解析方式有xml和json两种&#xff0c;今天我们主要来说下json、解析&#xff0c;以及其要点。 首先json格式&#xff1a; “[”标识json解析开始…

Struts2中ValueStack结构和总结

【ValueStack和ActionContext的关系】首先&#xff0c;从结构上来看ValueStack是ActionContext的一个组成部分&#xff0c;是对ActionContext功能的扩展。ActionContext是一个容器结构&#xff0c;是Struts2中用于数据存储的的场所&#xff0c;而ValueStack则是一个具备表达式引…