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,一经查实,立即删除!

相关文章

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…

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

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

博客园的CSRF

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

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

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

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

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

Struts2中ValueStack结构和总结

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

浅谈mysql数据库引擎

2019独角兽企业重金招聘Python工程师标准>>> 数据库是数据的集合&#xff0c;计算机中的数据库是存储器上一些文件的集合或者是内存数据的集合。Mysql,SQL server数据库都是可以存储数据&#xff0c;并提供数据查询&#xff0c;更新功能的数据库管理系统。Mysql数据…

linux ssh抓包,如何在SSH连接Linux系统的环境下使用wireshark抓包?

TSINGSEE青犀视频云边端架构EasyNVR、EasyDSS、EasyGBS等都是有两种操作系统的版本&#xff0c;一种是linux&#xff0c;一种是windows。而大多数开发者用户都会使用linux版本进行安装。对于安装部署出现的问题&#xff0c;TSINGSEE青犀视频团队研发的经常为客户远程调试&#…

ASP.NET后台调用前台JS函数的三种常见方法

为什么80%的码农都做不了架构师&#xff1f;>>> 第一种&#xff1a;使用普通的添加控件中的Attributes属性进行调用 例如&#xff0c;像一般的普通的按钮&#xff1a;Button1.Attributes.Add("onclick","MyFun();"); 此方法只能在Onload中或者…

嵌入式数据库sqlite在ARM上的的移植和使用

参考SQLite的编译、安装和使用_whz_zb的博客-CSDN博客&#xff0c;如有侵权&#xff0c;请告知删除。 参考&#xff1a;头文件路径问题 Linux下的头文件搜索路径 - 心哲 - 博客园 参考&#xff1a;进一步学习资源 SQlite - 标签 - likebeta - 博客园 一、源码获取 SQLite Do…

贪心法

贪心法的证明 —归纳证明&#xff1a; —贪心法使用的条件是&#xff1a;最优子结构和贪心选择正确性 —贪心算法是一步一步实现的&#xff0c; —在归纳证明的时候&#xff0c;贪心的第一步贪心选择策略的正确性就是归纳基础&#xff0c;因为以后都是一个子问题的选取&#xf…

第一季5:Hi3518EV200的环境搭建

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 一、内容总结 本文讲述如何安装交叉编译工具链&#xff0c;与编译源码得到uboot、kernel、rootfs镜像文件。 &#xff08;1&#xff09;安装交叉编译工具链&#xff0c;主要是通过执行osdrv/opensou…

Android动画的实现 上

在Android系统中也能经常见到动画&#xff0c;那么如何实现动画效果呢&#xff1f;本文就来为大家介绍动画的实现方式。 Android中动画的实现分两种方式&#xff0c;一种方式是补间动画Tween Animation&#xff0c;就是说你定义一个开始和结束&#xff0c;中间的部分由程序运算…

第一季2:HI3518EV200的初体验(检测板子是否正常工作)

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 一、检测步骤 1、设置PC主机有线网卡的ip地址设为192.168.1.10&#xff0c;关闭防火墙。 2、虚拟机桥接到有线网卡&#xff0c;并设置虚拟机的静态ip地址为192.168.1.141。 3、在uboot控制台设置ub…

第一季3:HI3518E方案整体架构介绍(硬件和软件支持)

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 1、硬件资源 &#xff08;1&#xff09;HI3518E单芯片提供&#xff1a;CPU DSP 内置64MB DDR ETHERNET MAC。 &#xff08;2&#xff09;外置16MB的SPI接口的Flash用来存放程序&#xff08;ubo…

(一)FlexViewer之整体框架解析

文章版权由作者李晓晖和博客园共有&#xff0c;若转载请于明显处标明出处&#xff1a;http://www.cnblogs.com/naaoveGIS/。 1.FlexViewer简介 FlexViewer框架为Esri提供的可以高效开发基于WEB的地理信息应用系统的一种完全免费的应用程序框架。目前有两种版本&#xff0c;一种…

三阶魔方复原操作方法

在女票的指导下&#xff0c;我花了一个晚上学习如何复原三阶魔方&#xff0c;虽然是知其然不知其所以然&#xff0c;但好歹也能把魔方复原了。下面都是一些傻瓜式的操作&#xff0c;里面涉及的理论我不清楚。魔方总共分三层&#xff0c;下面是每层复原方法。 第一层 1、先以“…

Phaser开源2d引擎 javascript/html5游戏框架

功能特点&#xff08;Features&#xff09; 易维护代码&#xff08;Easy Asset Loading&#xff09; Phaser可以加载图片&#xff0c;音频文件&#xff0c;数据文件&#xff0c;文本文件和自动解析精灵图和纹理地图集数据&#xff08;出口纹理封隔器或Flash CS6&#xf…