以下内容转载于博客http://blog.csdn.net/yueqian_scut/article/details/45938557。有删改和格式调整,如有侵权,请告知删除 。

一、应用层的程序

  • 很简单,open设备文件,read、write、ioctl,最后close退出。
[cpp] view plaincopy
  1. int main(int argc ,char *argv[])
  2. {
  3. unsigned char val[1] = 1;
  4. int fd =open("/dev/LED",O_RDWR);//打开设备
  5. write(fd,val,1);//写入设备,这里代表LED全亮
  6. close(fd);//关闭设备
  7. return 0;
  8. }

二、/dev目录、文件系统

三、设备文件的创建

(1)/dev目录下的设备文件基本上都是通过mdev来动态创建的。mdev是一个用户态的应用程序,位于busybox工具箱中。其创建过程包括:

  • 驱动初始化或者总线匹配后,会调用驱动的probe接口。该接口会调用device_create(设备类, 设备号, 设备名),在“/sys/class/设备类”目录生成唯一的设备属性文件(包括设备号和设备名等信息),并且发送uvent事件(KOBJ_ADD和环境变量,如路径等信息)到用户空间(通过socket方式)。
  • mdev是一个work_thread线程,收到事件后会分析“/sys/class/设备类”的对应文件,最终调用mknod动态来创建设备文件。
  • 设备文件内容主要是设备号(这个设备文件对应的inode,会记录文件的属性是一个设备(其他属性还包括目录,一般文件,符号链接等))。
  • 应用程序open最重要的一步就是(通过文件系统接口)获得该设备文件的内容,即设备号。

(2)如果初始化过程中没有调用device_create接口来创建设备文件,则需要手动通过命令行调用mknod接口来创建设备文件。

(3)mknod接口分析

四、open设备文件

(1)open设备文件,是为了获取(该设备驱动的)file_operations操作集。

  • 该接口集是struct file的成员,open返回file数据结构指针:
[cpp] view plaincopy
  1. struct file
  2. {
  3. const struct file_operations *f_op;
  4. unsigned int f_flags;//可读,可写等
  5. };

(2)以下是led设备驱动的操作接口。open("/dev/LED",O_RDWR)就是为了获得led_fops。

[cpp] view plaincopy
  1. static const struct file_operations led_fops = {
  2. .owner =THIS_MODULE,
  3. .open =led_open,
  4. .write = led_write,
  5. };
  • 仔细看应用程序int fd =open("/dev/LED",O_RDWR),open的返回值是int,并不是file,其实是为了操作系统和安全考虑。
  • fd位于应用层,而file位于内核层,它们都同属进程相关概念。
  • 在linux中,同一个文件(对应于唯一的inode)可以被不同的进程打开多次,而每次打开都会获得file数据结构。
  • 每个进程都会维护一个已经打开的file数组,fd就是对应file结构的数组下标。因此,file和fd在进程范围内是一一对应的关系。

(3)open接口分析

通过系统调用后对应调用sys_open,其是vfs层的接口Sys_open(/dev/led)

  • SYSCALL_DEFINE3(open,const char __user *, filename, int, flags, int, mode)
  • do_sys_open(AT_FDCWD,/dev/tty, flags, mode);
  • fd = get_unused_fd_flags(flags);
  • struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);
  • path_init(dfd, pathname, LOOKUP_PARENT, &nd);//path_init返回时nd->dentry即为搜索路径文件名的起点
  • link_path_walk(pathname, &nd);//link_path_walk一步步建立打开路径的各个目录的dentry和inode
  • do_last(&nd, &path, open_flag, acc_mode, mode, pathname);
  • filp = nameidata_to_filp(nd);//通过inode节点创建file
  • __dentry_open()
  • f->f_op =fops_get(inode->i_fop);

在__dentry_open()函数中有

[cpp] view plaincopy
  1. if (!open && f->f_op)
  2. open = f->f_op->open;
  3. if (open) {
  4. error = open(inode, f);
  5. if (error)
  6. goto cleanup_all;
  7. }
  • 其中inode->i_fop在mknod的init_special_inode调用中被赋值为def_chr_fops。
  • open(inode, f)即调用到chrdev_open。其可以看出是字符设备所对应的文件系统接口,我们姑且称其为字符设备文件系统。
[cpp] view plaincopy
  1. const struct file_operations def_chr_fops = {
  2. .open = chrdev_open,
  3. };

(4) 继续分析chrdev_open

  • Kobj_lookup(cdev_map,inode->i_rdev, &idx)即是通过设备的设备号(inode->i_rdev)在cdev_map中查找设备对应的操作集file_operations。
  • 关于如何查找,我们在理解字符设备驱动如何注册自己的file_operations后再回头来分析这个问题。

五、字符设备驱动的注册

(1)字符设备对应的结构体cdev

[cpp] view plaincopy
  1. struct cdev
  2. {
  3. struct kobject kobj; // 每个 cdev 都是一个 kobject
  4. struct module* owner; // 指向实现驱动的模块
  5. const struct file_operations *ops; // 操纵这个字符设备文件的方法
  6. struct list_head list; //对应的字符设备文件的inode->i_devices 的链表头
  7. dev_t dev; // 起始设备编号
  8. unsigned int count; // 设备范围号大小
  9. };

(2)led设备驱动初始化和设备驱动注册

  • cdev_init是初始化cdev结构体,并将led_fops填入该结构。
  • cdev_add函数
[cpp] view plaincopy
  1. int cdev_add(struct cdev *p, dev_t dev, unsigned count)
  2. {
  3. p->dev = dev;
  4. p->count = count;
  5. return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
  6. }
  • kobj_map函数
  • (1)kobj_map函数使用hash散列表来存储cdev数据结构(即存储设备的信息)。
  • (2)通过(注册设备的主设备号major)来获得cdev_map->probes数组的索引值i(i = major % 255);
  • (3)然后把一个类型为struct probe的节点对象加入到probes[i]所管理的链表中
  • (4)probes[i]->data即是cdev数据结构,而probes[i]->dev和range代表字符设备号和范围。
  • (5)其中参数cdev_map的类型如下:

六、再述open设备文件

通过第五步的字符设备的注册过程,应该很容易理解Kobj_lookup查找led_ops的过程(这里不写)。

至此,获得led设备驱动的led_ops。

(1)接着调用file->f_ops->open来调用led_open

  • 该函数中对led用到的GPIO进行ioremap,并设置GPIO方向、上下拉等硬件初始化。

(2)最后chrdev_open一步步返回

  • 最后到do_sys_open函数中的“struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);”返回。
  • fd_install(fd, f),是在当前进程中将存有led_ops的file指针填入进程的file数组中,下标是fd。最后将fd返回给用户空间。而用户空间只要传入fd即可找到对应的file数据结构。

七、设备操作

这里以设备写为例,主要是控制led的亮和灭。

write(fd,val,1)系统调用后对应sys_write,其对应所有的文件写,包括目录、一般文件和设备文件。

一般文件有位置偏移的概念,即读写之后,当前位置会发生变化,所以如要跳着读写,就需要fseek。对于字符设备文件,没有位置的概念。因此重点跟踪vfs_write的过程。

  • fget_light,在当前进程中通过fd来获得file指针;
  • vfs_write
  • 对于led设备,file->f_op->write即是led_write。在该接口中实现对led设备的控制。

八、再论字符设备驱动的初始化

综上所述,字符设备的初始化包括两个主要环节:

(1)字符设备驱动的注册

  • 即通过cdev_add向系统注册cdev数据结构,提供file_operations操作集和设备号等信息,最终file_operations存放在全局指针变量cdev_map指向的Hash表中,其可以通过设备号索引并遍历得到。

(2)创建属性文件、设备文件

  • 通过device_create(设备类,设备号,设备名)在“sys/class/设备类”中创建设备属性文件并发送uevent事件,而mdev利用该信息自动调用mknod在/dev目录下创建对应的设备文件,以便应用程序访问。

Linux字符设备驱动剖析相关推荐

  1. ()shi linux字符设备,Linux字符设备驱动基础(三)

    Linux字符设备驱动基础(三) 6 创建设备节点 6.1 手动创建设备节点 查看申请的设备名及主设备号: cat /proc/devices # cat /proc/devices Characte ...

  2. linux设备模型 字符设备,Linux 字符设备驱动模型之框架解说

    一.软件操作硬件设备模型 在进行嵌入式开发的过程中,在常做的事情就是驱动配置硬件设 备,然后根据功能需求使用硬件设备,实现功能的逻辑.如下图为其 相互之间的关系. 如上图所示: 驱动程序:主要作为操作 ...

  3. linux字符设备文件的打开操作,Linux字符设备驱动模型之字符设备初始化

    因为Linux字符设备驱动主要依赖于struct cdev结构,原型为: 所以我们需要对所使用到的结构成员进行配置,驱动开发所使用到的结构成员分别为:[unsigned int count;].[de ...

  4. linux生成驱动编译的头文件,嵌入式Linux字符设备驱动——5生成字符设备节点

    嵌入式Linux字符设备驱动开发流程--以LED为例 前言 留空 头文件 #include 查看系统设备类 ls /sys/class 设备类结构体 文件(路径):include/linux/devi ...

  5. linux字符设备驱动的 ioctl 幻数

    在Linux字符设备驱动入门(一)中,我们实现了字符设备的简单读写字符功能,接下来我们要在这个基础上加入ioctl功能.首先,我们先来看看3.0内核下../include/linux/fs.h中fil ...

  6. Linux 字符设备驱动结构(四)—— file_operations 结构体知识解析

    前面在 Linux 字符设备驱动开发基础 (三)-- 字符设备驱动结构(中) ,我们已经介绍了两种重要的数据结构 struct inode{...}与 struct file{...} ,下面来介绍另 ...

  7. linux字符设备驱动在哪里设置,从点一个灯开始学写Linux字符设备驱动!

    原标题:从点一个灯开始学写Linux字符设备驱动! [导读] 前一篇文章,介绍了如何将一个hello word模块编译进内核或者编译为动态加载内核模块,本篇来介绍一下如何利用Linux驱动模型来完成一 ...

  8. Linux字符设备驱动

    /*Linux字符设备驱动源代码scdd.c*/ #include <linux/init.h>   /*模块头文件*/ #include <linux/module.h> # ...

  9. Linux字符设备驱动中container_of宏的作用

    Linux字符设备驱动中container_of宏的作用 首先看看这个宏的原型: container_of(ptr,type,member) 功能:根据一个结构体变量中的一个成员变量的指针来获取指向整 ...

最新文章

  1. Kaggle神器LightGBM最全解读!
  2. 力扣练习题(数组中数据反转)
  3. QPS/TPS/并发量/系统吞吐量概念和公式
  4. Linux系统之更改默认块大小
  5. CERT.RSA中证书的格式
  6. 《高效对话》— 综合素质提升书籍
  7. Java-集合第三篇List集合
  8. 常见的概率论问题清单及其答案
  9. WebSphere-解决办法:UndefinedVariableException: Undefined variable HOST
  10. Scikit-learn_聚类算法_K均值聚类
  11. 五子棋 手打稍加改变自慕课网hyman
  12. python多态_python多态和规范
  13. 会员自动续费服务协议
  14. 46家中外知名企业笔试面试题目
  15. Android实现渐色变圆形
  16. 什么是JTAG和SWD接口协议,和各类仿真器
  17. javascript利用iframe打印pdf文档失败的问题
  18. ELF PLT Hook 原理简述
  19. 2021年计算机考研失败感想
  20. 创业,不能兼职(五)—找到那些爱学习的小白鼠

热门文章

  1. 矩阵快速幂 POJ 3070 Fibonacci
  2. TI CC2480 -- Z-Accel介绍
  3. 计算机网络读书笔记(1)
  4. PyQt安装和环境配置
  5. 视差滚动(Parallax Scrolling)插件补充
  6. ionic3 cordova ionic-native插件
  7. 云计算(cloud computing)十大问答
  8. (五)Maven中的聚合和继承
  9. gojs实现最短路径寻址实例
  10. linux实例 批量修改图片文件名