基于Linux操作系统的底层驱动技术转

本文详细介绍了Linux操作系统的底层驱动技术,包括设备驱动概述、设备类型分类,如字符设备、块设备、网络设备和杂项设备。设备驱动程序的关键数据结构,如file_operations、inode和file结构也进行了阐述。此外,文章还讨论了设备驱动模板的实现,如open、release、read、write和ioctl函数,并通过proc fs获取设备状态。最后,讨论了设备驱动程序的使用方法,包括insmod和rmmod命令,以及如何通过ioctl进行设备管理。
摘要由CSDN通过智能技术生成

5.3  基于Linux操作系统的底层驱动技术

这里的底层驱动是指Linux下的底层设备驱动,这些驱动通常都是加载在内核态的,可以提供给上层用户态的应用程序访问底层设备的能力。也就是说,上层应用程序通过底层的驱动程序可以实现输入/输出的管理等功能。

5.3.1  设备驱动概述

设备管理即输入/输出子系统,可分为上下两部分:一部分是上层的,与设备无关,这部分根据输入/输出请求,通过特定的设备驱动程序接口来与设备进行通信。另一部分是下层的,与设备有关,常称为设备驱动程序,它直接与相应设备打交道,并且向上层提供一组访问接口。

设备管理的目标是对所有外接设备进行良好的读、写、控制等操作。由于用户希望能用同样的应用程序和命令来访问设备和普通文件。为此,Linux中的设备管理应用了设备文件这个概念来统一设备的访问接口。简单地说,系统试图使它对所有各类设备的输入、输出看起来就好像对普通文件的输入、输出一样。用户希望能用同样的应用程序和命令来访问设备和普通文件。

由于Linux中将设备当做文件来处理,所以对设备进行操作的系统调用和对文件的操作类似,主要包括open()、read()、write()、ioctl()、close()等。应用程序发出系统调用指令

以后,会从用户态转换到内核态,通过内核将open()这样的系统调用转换成对物理设备的操作。

Linux下的设备驱动任务包括以下两个。

(1)自动配置和初始化子程序:这部分程序仅在初始化的时候被调用一次。

(2)服务于I/O请求的子程序:这部分是系统调用的结果。在执行这部分程序的时候,系统仍认为和进行调用的进程属于同一个进程,只是由用户态变成了内核态,并具有进行此系统调用的用户程序运行环境,所以可以在其中调用sleep()等与进程运行环境有关的函数。

5.3.2  设备类型分类

纵览linux/drivers目录,大概还有35个以上的子目录,每个子目录基本上就代表了一种设备驱动,有atm、block、char、misc、input、net、usb、sound、video等。这里只描述在嵌入式系统里面用得最为广泛的3种设备。

1.字符设备(char device)

字符设备是Linux最简单的设备,可以像文件一样访问。初始化字符设备时,它的设备驱动程序向Linux登记,并在字符设备向量表中增加一个device_struct数据结构条目,这个设备的主设备标识符用做这个向量表的索引。一个设备的主设备标识符是固定的。chrdevs向量表中的每一个条目,一个device_struct数据结构,包括两个元素:一个登记设备驱动程序名称的指针和一个指向一组文件操作的指针。可以参考的代码是include/linux/ major.h。

一般来说像鼠标、串口、键盘等设备都属于字符设备。

2.块设备(block device)

块设备是文件系统的物质基础,它也可以像文件一样被访问。Linux用blkdevs向量表维护已经登记的块设备文件。它像chrdevs向量表一样,使用设备的主设备号作为索引。它的条目也是device_struct数据结构。与字符设备不同的是,块设备分为SCSI类和IDE类。向Linux内核登记并向核心提供文件操作。一种块设备类的设备驱动程序向这种类提供和类相关的接口。可以参考的代码是fs/devices.c。

每一个块设备驱动程序必须提供普通的文件操作接口和对于buffer cache的接口。每一个块设备驱动程序填充blk_dev向量表中的blk_dev_struct数据结构。此向量表的索引是设备的主设备号。其中blk_dev_struct数据结构包括一个请求例程的地址和一个指针,指向一个request数据结构的列表,每一个都表达buffer cache向设备读/写一块数据的一个请求。

可以参考的源代码是drivers/block/ll_rw_blk.c和include/linux/blkdev.h。

当buffer cache从一个已登记的设备读/写一块数据,或者希望读、写一块数据到其他

位置时,就在blk_dev_struct中增加一个request数据结构。每个request数据结构都有一个指向一个或多个buffer_head数据结构的指针,每一个都是读/写一块数据的请求。如果buffer_head数据结构被锁定(buffer_cache),可能会有一个进程在等待这个缓冲区的阻塞进程完成。每一个request数据结构都是从all_request表中分配的。如果request增加到空的request列表中,就调用驱动程序的request函数处理这个request队列,否则驱动程序只是简单地处理request队列中的每一个请求。

块设备驱动程序和字符设备驱动程序的主要区别是:在对字符设备发出读、写请求时,实际的硬件I/O一般紧接着就发生了,块设备则不然,它利用一块系统内存作为缓冲区,当用户进程对设备请求能满足用户的要求时,就返回请求的数据,如果不能就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备的,以免耗费过多的CPU时间来等待。

块设备主要有硬盘、光盘驱动器等。可以查看文件/proc/devices获得。

3.网络设备(net device)

网络设备在系统中的作用类似于一个已挂载的块设备。块设备将自己注册到blk_dev数据及其他内核结构中,然后通过自己的request函数在发生请求时传输和接收数据块,同样网络设备也必须在特定的数据结构中注册自己,以便与外界交换数据包时被调用。网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD UNIX的Socket机制。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系统里支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。

4.杂项设备(misc device)

杂项设备也是在嵌入式系统中用得比较多的一种设备驱动,在第11章里面介绍的sub LCD和弦芯片的驱动等都是采用 misc device 的驱动方式实现的。在 Linux 内核的include/linux目录下有Miscdevice.h文件,要把自己定义的misc device从设备定义在这里。其实是因为这些字符设备不符合预先确定的字符设备范畴,所有这些设备采用主编号10,一起归于misc device,其实misc_register就是用主标号10调用register_chrdev()的。

5.3.3  设备驱动中关键数据结构

1.file_operations数据结构

内核内部通过file结构识别设备,通过file_operations数据结构提供文件系统的入口点

函数。file_operations定义在<linux/fs.h>中的函数指针表。这个结构的每一个成员的名字都对应着一个系统调用。从某种意义上说,写驱动程序的任务之一就是完成file_operations

中的函数指针。如果在2.4版本内核下开发的驱动很可能在2.6版本中无法使用,需要进行移植。通常file_operations提供了包括open()、write()、read()、release()、poll()、ioctl()等文件系统的入口函数。

下面简单描述一下几个重要的入口函数。

1)open():

static int mydriver_open(struct inode *inode, struct file *filp)

当上层对mydriver执行open操作时调用该函数,其中参数inode为设备特殊文件的inode(索引节点)结构指针,参数file是指向这一设备的文件结构指针。open()的主要任务是确定硬件处在就绪状态、验证次设备号的合法性(次设备号可以用MINOR(inode→i - rdev)取得)、控制使用设备的进程数、根据执行情况返回状态码(0表示成功,负数表示存在错误)等。

2)write():

static ssize_t mydriver_write(struct file *filp, const char *buf, size_t size, loff_t *offp)

当设备特殊文件进行write系统调用时,将调用驱动程序的write()函数,向设备发送数据。如果没有这个函数,write 系统调用会向调用程序返回一个-EINVAL。如果返回值非负,则表示成功写入的字节数。Write函数通常就是把数据从用户空间复制到内核空间,所以在write函数里经常会看到copy_from_user()函数。

3)read():

static ssize_t mydriver_read(struct file *filp, char *buf, size_t size, loff_t *offp)

当对设备特殊文件进行read系统调用时,将调用驱动程序read()函数,用来从设备中读取数据。当该函数指针被赋为NULL 值时,将导致read 系统调用出错并返回-EINVAL(“Invalid argument,非法参数”)。函数返回非负值表示成功读取的字节数(返回值为“signed size”数据类型,通常就是目标平台上的固有整数类型)。Read()函数则通常是把数据从内核空间复制到用户空间,一般都会调用copy_to_user()函数。

4)release():

static int mydriver_release(struct inode *inode, struct file *filp)

当最后一个打开设备的用户进程执行close()系统调用时,内核将调用驱动程序的release()函数,release()函数的主要任务是清理未结束的输入/输出操作、释放资源、用户自定义其他标志的复位等。

5)ioctl():

static int mydriver_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)

该函数是特殊的控制函数,可以通过它向设备传递控制信息或从设备取得状态信息,unsigned int参数为设备驱动程序需要执行的命令代码,由用户自定义。unsigned long参数为相应的命令提供参数,类型可以是整型、指针等。如果设备不提供ioctl入口点,则对任何内核未预先定义的请求,ioctl系统调用将返回错误(-ENOTTY,“No such ioctl fordevice,该设备无此ioctl命令”)。如果该设备方法返回一个非负值,那么该值会被返回给调用程序以表示调用成功。

6)poll():

static unsigned int mydriver_poll(struct file *filp, poll_table *wait)

poll方法是poll和select 这两个系统调用的后端实现,用来查询设备是否可读、可写或是否处于某种特殊状态。

2.inode(索引节点)

文件系统处理的文件所需要的信息在inode(索引节点)中。一个filesystem可以粗略地分成inode table与data area两部分。Inode table上有许多的inode,每个inode分别记录一个档案的属性,以及这个档案分布在哪些data block上。inode包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息。它是Linux管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁。inode结构中的静态信息取自物理设备上的文件系统,由文件系统指定的函数填写,它只存在于内存中,可以通过inode缓存访问。虽然每个文件都有相应的inode节点,但是只有在需要的时候,系统才会在内存中为其建立相应的inode数据结构,建立的inode结构将形成一个链表,可以通过遍历这个链表得到所需要的文件节点。

3.file结构

file结构主要用于与文件系统对应的设备驱动程序使用。在Linux里,每一个档案都有一个file结构和inode结构,inode结构是用来让Kernel做管理的,而file结构则是平常对档案读、写或开启,关闭所使用的。当然,从user的观点来看是看不出什么的。比起inode结构,file结构就显得小多了,file结构也是用串行来管理的,f_next会指到下一个file结构,而f_pprev则会指到上一个file结构的地址,f_dentry会记录其inode的dentry地址,f_mode为档案存取种类,f_pos则是目前档案的offset,每次读写都从offset记录的位置开始读写,f_count是此file结构的reference cout,f_flags则是开启此档案的模式,f_reada,f_ramax, f_raend,f_ralen,f_rawin则是控制read ahead的参数,f_owner记录了要接收SIGIO和SIGURG的行程ID或行程群组ID,private_data则是tty driver所使用的字段。

5.3.4  设备驱动程序模板与实现

Linux下的驱动程序虽然复杂,但是总结下来还是有很多的规律可寻。Linux下的设备驱动开始编程时显得比较容易,可以轻松地开始驱动编写,但是要把驱动写好也的确需要花一定的时间去研究。

1.设备驱动模板

设备驱动模板代码如例程5-4所示。

例程5‑4  Mydriver.c

#include <linux/module.h>

#include <linux/config.h>

#include <linux/types.h>

#include <linux/kernel.h>

#include <linux/init.

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值