linux下I/O体系结构和设备驱动程序

一台计算机包括集中不同类型的总线,它们通过被称为“桥”的硬件设备连接在一起。

任何I/O设备有且仅能连接一条总线。

linux内核包含了辅助函数来简化汇编语言指令的访问I/O端口(inb(),inb_p(),outb(),outb_p()).

通常I/O设备驱动程序为了探测硬件设备,需要盲目地向某一I/O端口写入数据,但是,如果其他硬件设备已经使用了这个端口,那么系统就会崩溃,为了防止这种情况发生,内核必须使用资源来记录分配给每个硬件设备的I/O端口。

init_main()->set_arch()->resource_init();->request_resource();//申请了内存资源

当前分配给I/O设备的所有I/O地址的树都可以从/proc/ioports文件中获取。

系统中所有硬件设备由内核全权负责电源管理

/proc文件系统是首次被设计成允许用户态应用程序访问内核内部数据结构的一种文件系统。

sysfs文件系统的目标是要展现设备驱动程序模型组件间的层次关系。其中的不同文件的主要作用还是表示驱动程序和设备的属性

设备驱动程序模型的核心数据结构是一个普通的数据结构叫做kobject,每个kobject对应于sysfs文件系统中的一个目录

一个kset就是kobject集合体,但是他依赖于层次数中用于引用计数和连接的更高层kobject。//???

sysfs_create_file()函数接收kobject的地址和属性描述符作为它的参数,并在合适的麋鹿中创建特殊文件。

对于任何总线类型来说,都有一个链表存放连接到该类型总线上的所有设备。

device_register()函数的功能是往设备驱动程序模型中插入一个新的device对象,并自动地在/sys/devices目录下为其创建一个新的目录。

当内核检查一个给定的设备是否可以由给定的驱动程序处理时,就会执行mathc()方法。

当特定类型总线上的设备必须改变其供电状态时,就会执行suspend和resume方法。

设备文件的索引节点并不包含指向磁盘上数据块的指针 ,它们是空的,必须包含硬件设备上的一个标识符,它对应字符或块设备文件。

mknod();用来创建设备文件。//???和device_create();有什么区别

注意字符设备和块设备有独立的编号

在某些情况下,设备文件不会和任何实际的硬件对应,而是表示一个虚拟的硬件设备

内核在动态分配设备号时,是不能永久创建设备文件,只在设备驱动程序初始化一个主设备号和次设备号时才创建。此时可以吧主设备号和次设备号存放在/sys/class/子目录下的dev属性中。

//

udev工具集可以自动创建相应的设备文件,因为设备驱动程序模型支持设备的热插拔。当发现一个新的设备时,内核就会产生一个新的进程来执行用户态shell脚本文件/sbin/hotplug,并将新设备上的有用信息作为环境变量传递给shell脚本。用户态脚本文件读取配置文件信息并关注完成新设备初始化必需的任何操作。

设备文件的VFS处理,通过适当的文件系统函数ext3_iget()读取磁盘上的相对应的索引节点来对索引节点对象初始化,当这个函数确定磁盘索引节点与设备文件对应时,则调用init_special_inode();该函数把索引节点对象的i_fop字段设置成def_blk_fops或者def_chr_fops文件操作的地址(根据设备文件类型),open()系统调用的服务例程调用dentry_open()函数,后者分配一个新的文件对象,并将其f_op字段设置为i_fop中存放的地址,即再一次指向def_blk_fops或def_chr_fops的地址。正是这两个表的引入,才使得在设备文件上所发出的任何系统调用都将激活设备驱动程序的函数而不是基本文件系统的函数。//***

由于每个设备都有一个唯一的I/O控制器,因此就有唯一的命令和唯一的状态信息,所以大部分I/O设备都有自己的驱动程序。

如果设备文件对应的驱动程序以前没有注册,则该设备文件的访问会返回错误码-ENODEV

对设备驱动程序注册和初始化是两件不同的事,设备驱动程序应当尽快被注册,以便用户应用程序能通过相应的设备文件使用它,相反,设备驱动程序在最后可能的时刻才被初始化。

一般而言,所有使用中断的I/O驱动程序都依赖中断处理程序及read和write方法均访问的数据结构

内核程序作用于线性地址,因此I/O共享存储器单元必须表示成大于PAGE_OFFSET的地址,驱动程序必须把I/O共享存储器单元的物理地址转换成内核空间的线性地址。

为了在内核页表中包括对超过系统RAM最大的物理地址的I/O物理地址进行映射的线性地址,必须对页表进行修改。可以通过ioremap()或者ioremap_nocache()函数来实现。

DMA一旦被CPU激活,就可以自行传送数据,当数据传送完成之后,DMA就发出一个中断请求。

因为DMA的设置时间相当长,所以在传送数量很少的数据时直接使用CPU效率更高。

设备驱动程序可以采用两种方式使用DMA,分别是同步DMA(数据传送是由进程触发的),异步DMA(数据传送是由硬件设备触发的)

存储器地址:逻辑地址,线性地址,物理地址,总线地址(除CPU之外的硬件设备驱动数据总线时所用的存储器地址)。在80x86体系结构中,总线地址和物理地址是一致的。

DMA的每次数据传送至少需要一个内存缓冲区,它包含硬件设备要读出或写入的数据。

使用DMA的所有I/O驱动程序在启动一次数据传送前必须设置好IO-MMU

在linux中,数据类型dma_addr_t代表一个通用的总线地址。

dma_set_mask()用于检查总线是否可以接受给定大小的总线地址。

执行DMA映射操作时,DMA辅助函数必须考虑硬件高速缓存

一般来说,如果CPU和DMA处理器以不可预知的方式去访问一个缓冲区,那么必须强制使用一致性DMA映射方式。

流式DMA映射的内存缓冲区通常在数据传送之前被映射,在传送之后被取消映射。

为了避免高速缓存一致性问题,驱动程序在开始从RAM到设备的DMA数据传送之前,如果有必要,应该调用dma_sync_single_for_device()函数刷新与DMA缓冲区对应的高速缓存行;同样的,从设备到RAM的一次DMA数据传送完成之前设备驱动程序是不可以访问内存缓冲区的。

字符设备驱动程序是由一个cdev结构体描述的

  struct cdev {
          struct kobject kobj;
          struct module *owner;
          const struct file_operations *ops;
          struct list_head list;
          dev_t dev;
          unsigned int count;
  };

一个设备驱动程序对应的设备号可以是一个范围,而不仅仅是一个号;设备号位于同一范围内的所有设备文件均有同一个字符设备驱动程序处理。

为了记录目前已经分配了哪些字符设备号,内核使用散列表chrdevs,表的大小不超过设备号的范围。




你可能感兴趣的:(linux下I/O体系结构和设备驱动程序)