LINUX设备驱动程序如何与硬件通信

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LINUX设备驱动程序如何与硬件通信相关的知识,希望对你有一定的参考价值。

参考技术A

  LINUX设备驱动程序是怎么样和硬件通信的?下面将由我带大家来解答这个疑问吧,希望对大家有所收获!

   LINUX设备驱动程序与硬件设备之间的通信

  设备驱动程序是软件概念和硬件电路之间的一个抽象层,因此两方面都要讨论。到目前为止,我们已经讨论详细讨论了软件概念上的一些细节,现在讨论另一方面,介绍驱动程序在Linux上如何在保持可移植性的前提下访问I/O端口和I/O内存。

  我们在需要示例的场合会使用简单的数字I/O端口来讲解I/O指令,并使用普通的帧缓冲区显存来讲解内存映射I/O。

  I/O端口和I/O内存

  计算机对每种外设都是通过读写它的寄存器进行控制的。大部分外设都有几个寄存器,不管是在内存地址空间还是在I/O地址空间,这些寄存器的访问地址都是连续的。

  I/O端口就是I/O端口,设备会把寄存器映射到I/O端口,不管处理器是否具有独立的I/O端口地址空间。即使没有在访问外设时也要模拟成读写I/O端口。

  I/O内存是设备把寄存器映射到某个内存地址区段(如PCI设备)。这种I/O内存通常是首先方案,它不需要特殊的处理器指令,而且CPU核心访问内存更有效率。

  I/O寄存器和常规内存

  尽管硬件寄存器和内存非常相似,但程序员在访问I/O寄存器的时候必须注意避免由于CPU或编译器不恰当的优化而改变预期的I/O动作。

  I/O寄存器和RAM最主要的区别就是I/O操作具有边际效应,而内存操作则没有:由于内存没有边际效应,所以可以用多种 方法 进行优化,如使用高速缓存保存数值、重新排序读/写指令等。

  编译器能够将数值缓存在CPU寄存器中而不写入内存,即使储存数据,读写操作也都能在高速缓存中进行而不用访问物理RAM。无论是在编译器一级或是硬件一级,指令的重新排序都有可能发生:一个指令序列如果以不同于程序文本中的次序运行常常能执行得更快。

  在对常规内存进行这些优化的时候,优化过程是透明的,而且效果良好,但是对I/O操作来说这些优化很可能造成致命的错误,这是因为受到边际效应的干扰,而这却是驱动程序访问I/O寄存器的主要目的。处理器无法预料某些 其它 进程(在另一个处理器上运行,或在在某个I/O控制器中发生的操作)是否会依赖于内存访问的顺序。编译器或CPU可能会自作聪明地重新排序所要求的操作,结果会发生奇怪的错误,并且很难调度。因此,驱动程序必须确保不使用高速缓冲,并且在访问寄存器时不发生读或写指令的重新排序。

  由硬件自身引起的问题很解决:只要把底层硬件配置成(可以是自动的或是由Linux初始化代码完成)在访问I/O区域(不管是内存还是端口)时禁止硬件缓存即可。

  由编译器优化和硬件重新排序引起的问题的解决办法是:对硬件(或其他处理器)必须以特定顺序的操作之间设置内存屏障(memory barrier)。Linux提供了4个宏来解决所有可能的排序问题:

  #include <linux/kernel.h>

  void barrier(void)

  这个函数通知编译器插入一个内存屏障,但对硬件没有影响。编译后的代码会把当前CPU寄存器中的所有修改过的数值保存到内存中,需要这些数据的时候再重新读出来。对barrier的调用可避免在屏障前后的编译器优化,但硬件完成自己的重新排序。

  #include <asm/system.h>

  void rmb(void);

  void read_barrier_depends(void);

  void wmb(void);

  void mb(void);

  这些函数在已编译的指令流中插入硬件内存屏障;具体实现方法是平台相关的。rmb(读内存屏障)保证了屏障之前的读操作一定会在后来的读操作之前完成。wmb保证写操作不会乱序,mb指令保证了两者都不会。这些函数都是barrier的超集。

  void smp_rmb(void);

  void smp_read_barrier_depends(void);

  void smp_wmb(void);

  void smp_mb(void);

  上述屏障宏版本也插入硬件屏障,但仅仅在内核针对SMP系统编译时有效;在单处理器系统上,它们均会被扩展为上面那些简单的屏障调用。

  设备驱动程序中使用内存屏障的典型形式如下:

  writel(dev->registers.addr, io_destination_address);

  writel(dev->registers.size, io_size);

  writel(dev->registers.operation, DEV_READ);

  wmb();

  writel(dev->registers.control, DEV_GO);

  在这个例子中,最重要的是要确保控制某种特定操作的所有设备寄存器一定要在操作开始之前已被正确设置。其中的内存屏障会强制写操作以要求的顺序完成。

  因为内存屏障会影响系统性能,所以应该只用于真正需要的地方。不同类型的内存屏障对性能的影响也不尽相同,所以最好尽可能使用最符合需要的特定类型。

  值得注意的是,大多数处理同步的内核原语,如自旋锁和atomic_t操作,也能作为内存屏障使用。同时还需要注意,某些外设总线(比如PCI总线)存在自身的高速缓存问题,我们将在后面的章节中讨论相关问题。

  在某些体系架构上,允许把赋值语句和内存屏障进行合并以提高效率。内核提供了几个执行这种合并的宏,在默认情况下,这些宏的定义如下:

  #define set_mb(var, value) do var = value; mb(); while 0

  #define set_wmb(var, value) do var = value; wmb(); while 0

  #define set_rmb(var, value) do var = value; rmb(); while 0

  在适当的地方,<asm/system.h>中定义的这些宏可以利用体系架构特有的指令更快的完成任务。注意只有小部分体系架构定义了set_rmb宏。

  使用I/O端口

  I/O端口是驱动程序与许多设备之间的通信方式——至少在部分时间是这样。本节讲解了使用I/O端口的不同函数,另外也涉及到一些可移植性问题。

  I/O端口分配

  下面我们提供了一个注册的接口,它允允许驱动程序声明自己需要操作的端口:

  #include <linux/ioport.h>

  struct resource *request_region(unsigned long first, unsigned long n, const char *name);

  它告诉内核,我们要使用起始于first的n个端口。name是设备的名称。如果分配成功返回非NULL,如果失败返回NULL。

  所有分配的端口可从/proc/ioports中找到。如果我们无法分配到我们要的端口集合,则可以查看这个文件哪个驱动程序已经分配了这些端口。

  如果不再使用这些端口,则用下面函数返回这些端口给系统:

  void release_region(unsigned long start, unsigned long n);

  下面函数允许驱动程序检查给定的I/O端口是否可用:

  int check_region(unsigned long first, unsigned long n);//不可用返回负的错误代码

  我们不赞成用这个函数,因为它返回成功并不能确保分配能够成功,因为检查和其后的分配并不是原子操作。我们应该始终使用request_region,因为这个函数执行了必要的锁定,以确保分配过程以安全原子的方式完成。

  操作I/O端口

  当驱动程序请求了需要使用的I/O端口范围后,必须读取和/或写入这些端口。为此,大多数硬件都会把8位、16位、32位区分开来。它们不能像访问系统内存那样混淆使用。

  因此,C语言程序必须调用不同的函数访问大小不同的端口。那些只支持映射的I/O寄存器的计算机体系架构通过把I/O端口地址重新映射到内存地址来伪装端口I/O,并且为了易于移植,内核对驱动程序隐藏了这些细节。Linux内核头文件中(在与体系架构相关的头文件<asm/io.h>中)定义了如下一些访问I/O端口的内联函数:

  unsigned inb(unsigned port);

  void outb(unsigned char byte, unsigned port);

  字节读写端口。

  unsigned inw(unsigned port);

  void outw(unsigned short word, unsigned port);

  访问16位端口

  unsigned inl(unsigned port);

  void outl(unsigned longword, unsigned port);

  访问32位端口

  在用户空间访问I/O端口

  上面这些函数主要是提供给设备驱动程序使用的,但它们也可以用户空间使用,至少在PC类计算机上可以使用。GNU的C库在<sys/io.h>中定义了这些函数。如果要要用户空间使用inb及相关函数,则必须满足正下面这些条件:

  编译程序时必须带有-O选项来强制内联函数的展开。

  必须用ioperm(获取单个端口的权限)或iopl(获取整个I/O空间)系统调用来获取对端口进行I/O操作的权限。这两个函数都是x86平台特有的。

  必须以root身份运行该程序才能调用ioperm或iopl。或者进程的祖先进程之一已经以root身份获取对端口的访问。

  如果宿主平台没有以上两个系统调用,则用户空间程序仍然可以使用/dev/port设备文件访问I/O端口。不过要注意,该设备文件的含义与平台密切相关,并且除PC平台以处,它几乎没有什么用处。

  串操作

  以上的I/O操作都是一次传输一个数据,作为补充,有些处理器实现了一次传输一个数据序列的特殊指令,序列中的数据单位可以是字节、字、双字。这些指令称为串操作指令,它们执行这些任务时比一个C语言编写的循环语句快得多。下面列出的宏实现了串I/O:

  void insb(unsigned port, void *addr, unsigned long count);

  void outsb(unsigned port, void *addr, unsigned long count);从内存addr开始连续读/写count数目的字节。只对单一端口port读取或写入数据

  void insw(unsigned port, void *addr, unsigned long count);

  void outsw(unsigned port, void *addr, unsigned long count);对一个16位端口读写16位数据

  void insl(unsigned port, void *addr, unsigned long count);

  void outsl(unsigned port, void *addr, unsigned long count);对一个32位端口读写32位数据

  在使用串I/O操作函数时,需要铭记的是:它们直接将字节流从端口中读取或写入。因此,当端口和主机系统具有不同的字节序时,将导致不可预期的结果。使用inw读取端口将在必要时交换字节,以便确保读入的值匹配于主机的字节序。然而,串函数不会完成这种交换。

  暂停式I/O

  在处理器试图从总线上快速传输数据时,某些平台(特别是i386)就会出现问题。当处理器时钟比外设时钟(如ISA)快时就会出现问题,并且在设备板上特别慢时表现出来。为了防止出现丢失数据的情况,可以使用暂停式的I/O函数来取代通常的I/O函数,这些暂停式的I/O函数很像前面介绍的那些I/O函数,不同之处是它们的名字用_p结尾,如inb_p、outb_p等等。在linux支持的大多数平台上都定义了这些函数,不过它们常常扩展为非暂停式I/O同样的代码,因为如果不使用过时的外设总线就不需要额外的暂停。

  平台相关性

  I/O指令是与处理器密切相关的。因为它们的工作涉及到处理器移入移出数据的细节,所以隐藏平台间的差异非常困难。因此,大部分与I/O端口相关的源代码都与平台相关。

  回顾前面函数列表可以看到有一处不兼容的地方,即数据类型。函数的参数根据各平台体系架构上的不同要相应地使用不同的数据类型。例如,port参数在x86平台上(处理器只支持64KB的I/O空间)上定义为unsigned short,但在其他平台上定义为unsigned long,在这些平台上,端口是与内存在同一地址空间内的一些特定区域。

  感兴趣的读者可以从io.h文件获得更多信息,除了本章介绍的函数,一些与体系架构相关的函数有时也由该文件定义。

  值得注意的是,x86家族之外的处理器都不为端口提供独立的地址空间。

  I/O操作在各个平台上执行的细节在对应平台的编程手册中有详细的叙述;也可以从web上下载这些手册的PDF文件。

  I/O端口示例

  演示设备驱动程序的端口I/O的示例代码运行于通用的数字I/O端口上,这种端口在大多数计算机平台上都能找到。

  数字I/O端口最常见的一种形式是一个字节宽度的I/O区域,它或者映射到内存,或者映射到端口。当把数字写入到输出区域时,输出引脚上的电平信号随着写入的各位而发生相应变化。从输入区域读取到的数据则是输入引脚各位当前的逻辑电平值。

  这类I/O端口的具体实现和软件接口是因系统而异的。大多数情况下,I/O引脚由两个I/O区域控制的:一个区域中可以选择用于输入和输出的引脚,另一个区域中可以读写实际的逻辑电平。不过有时情况简单些,每个位不是输入就是输出(不过这种情况下就不能称为“通用I/O"了);在所有个人计算机上都能找到的并口就是这样的非通用的I/O端口。

  并口简介

  并口的最小配置由3个8位端口组成。第一个端口是一个双向的数据寄存器,它直接连接到物理连接器的2~9号引脚上。第二个端口是一个只读的状态寄存器;当并口连接打印机时,该寄存器 报告 打印机状态,如是否是线、缺纸、正忙等等。第三个端口是一个只用于输出的控制寄存器,它的作用之一是控制是否启用中断。

  如下所示:并口的引脚

  示例驱动程序

  while(count--)

  outb(*(ptr++), port);

  wmb();

  

  使用I/O内存

  除了x86上普遍使的I/O端口之外,和设备通信的另一种主要机制是通过使用映射到内存的寄存器或设备内存,这两种都称为I/O内存,因为寄存器和内存的差别对软件是透明的。

  I/O内存仅仅是类似RAM的一个区域,在那里处理器可以通过总线访问设备。这种内存有很多用途,比如存放视频数据或以太网数据包,也可以用来实现类似I/O端口的设备寄存器(也就是说,对它们的读写也存在边际效应)。

  根据计算机平台和所使用总线的不同,i/o内存可能是,也可能不是通过页表访问的。如果访问是经由页表进行的,内核必须首先安排物理地址使其对设备驱动程序可见(这通常意味着在进行任何I/O之前必须先调用ioremap)。如果访问无需页表,那么I/O内存区域就非常类似于I/O端口,可以使用适当形式的函数读取它们。

  不管访问I/O内存是否需要调用ioremap,都不鼓励直接使用指向I/O内存的指针。相反使用包装函数访问I/O内存,这一方面在所有平台上都是安全的,另一方面,在可以直接对指针指向的内存区域执行操作的时候,这些函数是经过优化的。并且直接使用指针会影响程序的可移植性。

  I/O内存分配和映射

  在使用之前,必须首先分配I/O区域。分配内存区域的接口如下(在<linux/ioport.h>中定义):

  struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);

  该函数从start开始分配len字节长的内存区域。如果成功返回非NULL,否则返回NULL值。所有的I/O内存分配情况可从/proc/iomem得到。

  不再使用已分配的内存区域时,使用如下接口释放:

  void release_mem_region(unsigned long start, unsigned long len);

  下面函数用来检查给定的I/O内存区域是否可用的老函数:

  int check_mem_region(unsigned long start, unsigned long len);//这个函数和check_region一样不安全,应避免使用

  分配内存之后我们还必须确保该I/O内存对内存而言是可访问的。获取I/O内存并不意味着可引用对应的指针;在许多系统上,I/O内存根本不能通过这种方式直接访问。因此,我们必须由ioremap函数建立映射,ioremap专用于为I/O内存区域分配虚拟地址。

  我们根据以下定义来调用ioremap函数:

  #include <asm/io.h>

  void *ioremap(unsigned long phys_addr, unsigned long size);

  void *ioremap_nocache(unsigned long phys_addr, unsigned long size);在大多数计算机平台上,该函数和ioremap相同:当所有I/O内存已属于非缓存地址时,就没有必要实现ioremap的独立的,非缓冲版本。

  void iounmap(void *addr);

  记住,由ioremap返回的地址不应该直接引用,而应该使用内核提供的accessor函数。

  访问I/O内存

  在某些平台上我们可以将ioremap的返回值直接当作指针使用。但是,这种使用不具有可移植性,访问I/O内存的正确方法是通过一组专用于些目的的函数(在<asm/io.h>中定义)。

  从I/O内存中读取,可使用以下函数之一:

  unsigned int ioread8(void *addr);

  unsigned int ioread16(void *addr);

  unsigned int ioread32(void *addr);

  其中,addr是从ioremap获得的地址(可能包含一个整数偏移量);返回值是从给定I/O内存读取到的值。

  写入I/O内存的函数如下:

  void iowrite8(u8 value, void *addr);

  void iowrite16(u16 value, void *addr);

  void iowrite32(u32 value, void *addr);

  如果必须在给定的I/O内存地址处读/写一系列值,则可使用上述函数的重复版本:

  void ioread8_rep(void *addr, void *buf, unsigned long count);

  void ioread16_rep(void *addr, void *buf, unsigned long count);

  void ioread32_rep(void *addr, void *buf, unsigned long count);

  void iowrite8_rep(void *addr, const void *buf, unsigned long count);

  void iowrite16_rep(void *addr, const void *buf, unsigned long count);

  void iowrite32_rep(void *addr, const void *buf, unsigned long count);

  上述函数从给定的buf向给定的addr读取或写入count个值。count以被写入数据的大小为单位。

  上面函数均在给定的addr处执行所有的I/O操作,如果我们要在一块I/O内存上执行操作,则可以使用下面的函数:

  void memset_io(void *addr, u8 value, unsigned int count);

  void memcpy_fromio(void *dest, void *source, unsigned int count);

  void memcpy_toio(void *dest, void *source, unsigned int count);

  上述函数和C函数库的对应函数功能一致。

  像I/O内存一样使用I/O端口

  某些硬件具有一种有趣的特性:某些版本使用I/O端口,而其他版本则使用I/O内存。导出给处理器的寄存器在两种情况下都是一样的,但访问方法却不同。为了让处理这类硬件的驱动程序更加易于编写,也为了最小化I/O端口和I/O内存访问这间的表面区别,2.6内核引入了ioport_map函数:

  void *ioport_map(unsigned long port, unsigned int count);

  该函数重新映射count个I/O端口,使其看起来像I/O内存。此后,驱动程序可在该函数返回的地址上使用ioread8及其相关函数,这样就不必理会I/O端口和I/O内存之间的区别了。

  当不需要这种映射时使用下面函数一撤消:

  void ioport_unmap(void *addr);

  这些函数使得I/O端口看起来像内存。但需要注意的是,在重新映射之前,我们必须通过request_region来分配这些I/O端口。

  为I/O内存重用short

  前面介绍的short示例模块访问的是I/O端口,它也可以访问I/O内存。为此必须在加载时通知它使用I/O内存,另外还要修改base地址以使其指向I/O区域。

  下例是在MIPS开发板上点亮调试用的LED:

  mips.root# ./short_load use_mem=1 base = 0xb7ffffc0

  mips.root# echo -n 7 > /dev/short0

  下面代码是short写入内存区域时使用的循环:

  while(count--)

  iowrite8(*ptr++, address);

  wmb();

  

  1MB地址空间之下的ISA内存

  最广为人知的I/O内存区之一就是个人计算机上的ISA内存段。它的内存范围在64KB(0xA0000)到1MB(0x100000)之间,因此它正好出现在常规系统RAM的中间。这种地址看上去有点奇怪,因为这个设计决策是20世纪80年代早期作出的,在当时看来没有人会用到640KB以上的内存。

Linux基础管理——磁盘管理

1、设备文件


I/O Ports: I/O设备地址。


设备文件的使用:

   通过将文件关联至一个设备驱动程序,进而能够跟与之对应硬件设备进行通信。
    一切皆文件,所以磁盘的也是也是基于设备文件与之通信的:open(), read(), write(), close()


设备号码:

    主设备号:major number, 标识设备类型;
    次设备号:minor number, 标识同一类型下的不同设备。


硬盘接口类型:

    并行:    
        IDE:133MB/s
        SCSI:640MB/s
    串口:
        SATA:6Gbps
        SAS:6Gbps
    USB:480MB/s
        rpm: rotations
        per minute



磁盘设备的设备文件命名:

    /dev/DEV_FILE
    
    SCSI, SATA, SAS, IDE,USB: /dev/sd;
    虚拟磁盘:/dev/vd;
    
    不同磁盘标识:a-z,aa,ab…/dev/sda, /dev/sdb, ...;
    同一设备上的不同分区:1,2, .../dev/sda1, /dev/sda5;



2、磁盘结构


2.1、机械硬盘(HDD)


技术分享图片

技术分享图片


2.2、固态硬盘(SSD)和机械硬盘(HDD)


技术分享图片



2.3、硬盘存储术语及扇区结构


head:磁头;
track:磁道;
cylinder: 柱面;
sector: 扇区,512bytes;


(1)早期扇区结构


技术分享图片


(2)区位记录磁盘扇区结构:ZBR(Zoned Bit Recording)

技术分享图片



2.4、CHS和LBA


CHS:

   采用24bit位寻址;
    最大寻址空间8GB;

LBA(logical block addressing)

   LBA采用48个bit位寻址
    最大寻址空间128PB


    注意:

    由于CHS寻址方式的寻址空间在大概8GB以内,所以在磁盘容量小于大概8GB时,可以使用CHS寻址方式或是LBA寻址方式;在磁盘容量大于大概8GB时,则只能使用LBA寻址方式。



2.5、使用分区空间


    设备识别;
    设备分区;
    创建文件系统;
    标记文件系统;
    在/etc/fstab文件中创建条目;
    挂载新的文件系统;


2.5、磁盘分区的意义及分区类型


磁盘分区意义:

    优化I/O性能
    实现磁盘空间配额限制
    提高修复速度
    隔离系统和程序
    安装多个OS
    采用不同文件系统



两种分区方式:MBR,GPT


2.5.1、MBR


MBR:

    Master Boot Record,1982年,使用32位表示扇区数,分区不超过2T;    
    按柱面


MBR分区结构:

硬盘主引导记录MBR由4个部分组成:

    主引导程序(偏移地址0000H--0088H):它负责从活动分区中装载,并运行系统引导程序。
    出错信息数据区:偏移地址0089H--00E1H为出错信息,0E2H--01BDH全为0字节。
    
    分区表(DPT,Disk Partition Table):含4个分区项,偏移地址01BEH--01FDH,每个分区表项长16个字节,共64字节;为分区项1、分区项2、分区项3、分区项4
    结束标志字:偏移地址01FE--01FF的2个字节值为结束标志55AA。



2.5.2、GPT分区

GPT:

    GUID(Globals Unique Identifiers) partition table;    
    支持128个分区,使用64位,支持8Z(512Byte/block)、64Z (4096Byte/block);
    使用128位UUID(Universally Unique Identifier) 表示磁盘和分区;
    
    GPT分区表自动备份在头和尾两份,并有CRC校验位UEFI;
    (统一扩展固件接口)硬件支持GPT,使操作系统启动


3、分区管理


3.1、图形化

图形化磁盘管理功能工具:

    点击“应用程序” ->“系统工具” ->“磁盘”;
    执行命令gnome-disks


3.2、分区管理


列出块设备:lsblk;
创建分区使用:
    fdisk 创建MBR分区;
    gdisk 创建GPT分区;
    parted 高级分区操作;
    
partprobe-重新设置内存中的内核分区表版本;


3.3、parted命令


注意:parted的操作都是实时生效的,小心使用

语法:

    parted [选项]... [设备 [命令 [参数]...]...]

示例:

    parted /dev/sdb mklabel gpt|msdos
    parted /dev/sdb print
    parted /dev/sdb mkpart primary 1 200 (默认M)
    parted /dev/sdb rm 1
    parted -l


3.3、分区工具:fdisk & gdisk


    gdisk /dev/sdb:类fdisk 的GPT分区工具;

    fdisk -l [-u] [device...] 查看分区;
    fdisk /dev/sdb 管理分区;


子命令:

    p 分区列表
    t 更改分区类型
    n 创建新分区
    d 删除分区
    v 校验分区
    u 转换单位
    w 保存并退出
    q 不保存并退出



查看内核是否已经识别新的分区:
    cat /proc/partations


同步分区表:

centos6通知内核重新读取硬盘分区表:
    新增分区用:
        partx -a /dev/DEVICE
        kpartx -a /dev/DEVICE -f: force
    删除分区用:
        partx -d --nr M-N /dev/DEVICE

CentOS 5,7:使用partprobe
    partprobe [/dev/DEVICE]



4、文件系统管理


4.1、文件系统类型

    Linux文件系统:ext2(Extended file system), ext3,ext4, xfs(SGI), btrfs(Oracle), reiserfs, jfs(AIX), swap
    光盘:iso9660
    Windows:FAT32, exFAT,NTFS
    Unix:FFS(fast), UFS(unix), JFS2
    
    网络文件系统:NFS, CIFS
    集群文件系统:GFS2, OCFS2(oracle)
    分布式文件系统: fastdfs,ceph, moosefs, mogilefs,glusterfs, Lustre
    RAW:未经处理或者未经格式化产生的文件系统


    查前支持的文件系统:cat /proc/filesystems


4.2、创建文件系统

mkfs命令:
    (1) mkfs.FS_TYPE /dev/DEVICE
        ext4
        xfs
        btrfs
        vfat
    (2) mkfs -t FS_TYPE /dev/DEVICE
    -L 'LABEL': 设定卷标


创建ext文件系统:

mke2fs:
    ext系列文件系统专用管理工具;
    -t {ext2|ext3|ext4}
    -b {1024|2048|4096}
    -L 'LABEL'
    -j: 相当于 -t ext3;
        mkfs.ext3 = mkfs -t ext3 = mke2fs -j = mke2fs -t ext3
        
    -i #: 为数据空间中每多少个字节创建一个inode;此大小不应该小于block的大小;
    -N #:指定分区中创建多少个inode;
    -I 一个inode记录占用的磁盘空间大小,128---4096;
    -m #: 默认5%,为管理人员预留空间占总空间的百分比;
    -O FEATURE[,...]:启用指定特性;
    -O ^FEATURE:关闭指定特性;


文件系统标签:

    指向设备的另一种方法    
    与设备无关


blkid:
    块设备属性信息查看;
    语法:
        blkid [OPTION]... [DEVICE]
            -U UUID: 根据指定的UUID来查找对应的设备
            -L LABEL:根据指定的LABEL来查找对应的设备
e2label:管理ext系列文件系统的LABEL
    e2label DEVICE [LABEL]


findfs :查找分区
    findfs [options] LABEL=<label>
    findfs [options] UUID=<uuid>



tune2fs:

    重新设定ext系列文件系统可调整参数的值;
    
    -l:查看指定文件系统超级块信息;super block
    -L 'LABEL':修改卷标
    -m #:修预留给管理员的空间百分比
    -j: 将ext2升级为ext3
    -O: 文件系统属性启用或禁用, –O ^has_journal
    -o: 调整文件系统的默认挂载选项,–o ^acl
    -U UUID: 修改UUID号


dumpe2fs:

    块分组管理,32768块    
    -h:查看超级块信息,不显示分组信息


4.3、文件系统检测和修复

    常发生于死机或者非正常关机之后;    
    挂载为文件系统标记为“ no clean”;
    注意:一定不要在挂载状态下修复;
fsck: File System Check

fsck.FS_TYPE
fsck -t FS_TYPE
    -p: 自动修复错误
    -r: 交互式修复错误
    FS_TYPE一定要与分区上已经文件类型相同;


e2fsck:ext系列文件专用的检测修复工具
    -y:自动回答为yes
    -f:强制修复



5、挂载


挂载:

    将额外文件系统与根文件系统某现存的目录建立起关联关系,
    进而使得此目录做为其它文件访问入口的行为


注意:

    挂载点下原有文件在挂载完成后会被临时隐藏;    
    挂载点目录一般为空;
    事先存在;建议使用空目录;
    进程正在使用中的设备无法被卸载;


语法:

mount DEVICE MOUNT_POINT
mount [-fnrsvw] [-t vfstype] [-o options] device dir
    -t vsftype:指定要挂载的设备上的文件系统类型
    -r: readonly,只读挂载
    -w: read and write, 读写挂载
    -n: 不更新/etc/mtab,mount不可见
    -a:自动挂载所有支持自动挂载的设备(定义在了/etc/fstab
    文件中,且挂载选项中有auto功能)
    -L 'LABEL': 以卷标指定挂载设备
    -U 'UUID': 以UUID指定要挂载的设备
    -B, --bind: 绑定目录到另一个目录上
    
    查看内核追踪到的已挂载的所有设备
    cat /proc/mounts



-o options:(挂载文件系统的选项),多个选项使用逗号分隔

    async:异步模式    
    sync:同步模式,内存更改时,同时写磁盘
    atime/noatime:包含目录和文件
    diratime/nodiratime:目录的访问时间戳
    auto/noauto:是否支持自动挂载,是否支持-a选项
    exec/noexec:是否支持将文件系统上运行应用程序
    dev/nodev:是否支持在此文件系统上使用设备文件
    suid/nosuid:是否支持suid和sgid权限
    remount:重新挂载
    ro:只读
    rw:读写
    user/nouser:是否允许普通用户挂载此设备,/etc/fstab使用
    acl:启用此文件系统上的acl功能
    loop: 使用loop设备
defaults:相当于rw, suid, dev, exec, auto, nouser, async



6、卸载命令


查看挂载情况

findmnt MOUNT_POINT|device


查看正在访问指定文件系统的进程

lsof MOUNT_POINT
fuser -v MOUNT_POINT


终止所有在正访问指定的文件系统的进程

fuser -km MOUNT_POINT


卸载

umount DEVICE
umount MOUNT_POINT




7、文件挂载配置文件


注意:使用mount -a 命令挂载/etc/fstab中的所有文件系统

/etc/fstab每行定义一个要挂载的文件系统:

1)要挂载的设备或伪文件系统;
    设备文件;
    LABEL:LABEL="";
    UUID:UUID="";
    伪文件系统名称:proc, sysfs;

2)挂载点;

3)文件系统类型;

4)挂载选项:defaults;

5)转储频率:0:不做备份 1:每天转储 2:每隔一天转储;

6)自检次序:
    0:不自检;
    1:首先自检;一般只有rootfs才用1;




8、处理交换文件和分区


交换分区是系统RAM的补充。


8.1、基本设置

    创建交换分区或者文件    
    使用mkswap写入特殊签名
    在/etc/fstab文件中添加适当的条目
    使用swapon -a 激活交换空间




交换分区的启用与禁用:

    启用:swapon
    swapon [OPTION]... [DEVICE]
        -a:激活所有的交换分区;
        -p PRIORITY:指定优先级
        /etc/fstab:pri=value
    禁用:swapoff [OPTION]... [DEVICE]


8.2、SWAP的优先级



    可以指定swap分区0到32767的优先级,值越大优先级越高;    
    
    如果用户没有指定,那么核心会自动给swap指定一个优先级,这个优先级从-1开始,
    每加入一个新的没有用户指定优先级的swap,会给这个优先级减一;
    
    先添加的swap的缺省优先级比较高,除非用户自己指定一个优先级,
    而用户指定的优先级(是正数)永远高于核心缺省指定的优先级(是负数);
    
    优化性能:分布存放,高性能磁盘存放;



技术分享图片

以上是关于LINUX设备驱动程序如何与硬件通信的主要内容,如果未能解决你的问题,请参考以下文章

Linux下驱动开发_块设备驱动开发(硬件上采用SD卡+SPI协议)

Linux下驱动开发_块设备驱动开发(硬件上采用SD卡+SPI协议)

[架构之路-46]:目标系统 - 系统软件 - Linux OS硬件设备驱动-UIO用户空间IO驱动框架与用户空间协议栈

Linux设备驱动故障定位指引与实例

[架构之路-38]:目标系统 - 系统软件 - Linux OS硬件设备驱动必须熟悉的六大工作机制之(并发与互斥阻塞与非阻塞异步通知)

[架构之路-38]:目标系统 - 系统软件 - Linux OS硬件设备驱动必须熟悉的六大工作机制之(并发与互斥阻塞与非阻塞异步通知)