我们在linux驱动基础概念以及驱动程序框架搭建中已经介绍过,linux 将所有的外设分为 3 类:字符设备、块设备、网络设备。
字符设备是能够像字节流(比如文件)一样被访问的设备,就是说对它的读写是以字节为单位的。 比如串口在进行收发数据时就是一个字节一个字节的进行的,我们可以在驱动程序内部使用缓冲区来存放数据以提高效率,但是串口本身对这并没有要求。字符设备的驱动程序中实现了 open、close、read、write 等系统调用,应用程序可以通过设备文件(比如/dev/ttySAC0 等)来访问字符设备。
字符设备一般包括:led、按键、usb鼠标、usb键盘等等。
块设备上的数据以块的形式存放,比如 NAND FLASH上的数据就是以页为单位存放的。块设备驱动程序向用户层提供的接口与字符设备一样, 应用程序也可以通过相应的设备文件(比如/dev/mtdblock0、/dev/hda1 等)来调用 open、close、read、write 等系统调用,与块设备传送任意字节的数据。对用户而言,字符设备和块设备的访问方式没有差别。块设备驱动程序的特别之处如下。
块设备一般包括SD卡、TF卡、eMMC、Nand Flash、Nor Flash:
针对不同的存储设备实现了不同的I/O调度算法;
块设备结构的不同其I/O算法也会不用,比如对于eMMC、SD卡、Nand Flash这里没有任何机械设备的存储设备就可以任意读写任意的扇区;
但是对于机械硬盘这种带有刺头的设备,读取不同的盘面或者磁道里面的数据,磁头都需要进行移动,因此对于机械硬盘而言,将那些杂乱的访问按照一定的顺序进行排列可以有效提高磁盘性能;
网络设备同时具有字符设备、块设备的部分特点,无法将它归入这两类中:
UNIX 式的操作系统访问网络接口的方法是给它们分配一个唯一的名字(比如 eth0),但这个名字在文件系统中(比如/dev 目录下)不存在对应的节点项。应用程序、内核和网络驱动程序间的通信完全不同于字符设备、块设备,库、内核提供了一套和数据包传输相关的函数,而不是 open、read、write 等。
段:由若干个块组成,段的大小只与块有关,必须是块的整数倍,段在内核中由结构struct bio_vec来描述;
块:块是虚拟文件系统传输数据的基本单位,通常由1个或多个扇区组成。在Linux中,块的大小必须是2的幂,但是不能超过一个页的大小(4k)。(在X86平台,一个页的大小是4094个字节,所以块大小可以是512,1024,2048,4096);
扇区:扇区是硬件传输数据的基本单位,硬件一次传输一个扇区的数据到内存中。通常在512字节到32768字节之间,默认512字节;
比如当我们要写一个很小的数据到txt文件某个位置时,由于块设备写的数据是按扇区为单位,但又不能破坏txt文件里其它位置,那么就引入了一个“缓存区”,将所有数据读到缓存区里,然后修改缓存数据,再将整个数据放入txt文件对应的某个扇区中,当我们对txt文件多次写入很小的数据的话,那么就会重复不断地对扇区读出,写入,这样会浪费很多时间在读/写硬盘上,所以内核提供了一个队列的机制,在没有关闭txt文件之前,会将读写请求进行优化,排序,合并等操作,从而提高访问硬盘的效率。
其具体流程如下:
linux内核将块设备驱动相关的代码放在目录drivers/block下:
root@zhengyang:/work/sambashare/linux-5.2.8/drivers# ls block/ amiflop.c built-in.a loop.c nbd.c pktcdvd.c rsxx swim_asm.S virtio_blk.c zram aoe cryptoloop.c loop.h null_blk.h ps3disk.c skd_main.c swim.c xen-blkback ataflop.c drbd loop.o null_blk_main.c ps3vram.c skd_s1120.h sx8.c xen-blkfront.c brd.c floppy.c Makefile null_blk_zoned.c rbd.c sunvdc.c umem.c xsysace.c brd.o Kconfig mtip32xx paride rbd_types.h swim3.c umem.h z2ram.c
参考文章:
[1]linux块设备驱动简述(Linux驱动开发篇)
[2]LINUX驱动之块设备驱动
[3]22.Linux-块设备驱动之框架详细分析(详解)
[4]Linux块设备驱动详解