Linux字符设备驱动框架

1 从需求的角度理解驱动:

操作系统的作用:为应用程序提供一个对计算机硬件的一致视图。
应用程序和内核模块的区别:
应用程序:一般是重头到尾执行单个任务。
内核模块:只是预先注册自己,以便服务于将来的某个请求,让后其初始化函数就结束。内核模块不能像应用程序那样顺序执行,其大多数操作是和某个特定的进程相关。
系统为了保护硬件资源,在CPU中实现不同的操作模式。在低级别(用户态)中进制某些操作。在高级别(内核态)模式中CPU才能进行所有操作。用户空间和内核空间各自有自己独立的内存映射(地址空间)
操作系统是为了给应用层提供良好的接口而进行总线设备驱动管理、内存管理、文件管理、进程管理等等。Linux平台有各种子系统、各种总线、各种驱动,Linux系统对它们的管理就是软件框架的组成。
软件框架指的是用面向对象的思维去解决某种特定领域的问题而专门设计的一套行之有效的解决方案。Linux平台上的各个子系统,如设备驱动模型、input子系统、I2C总线、frame buffer驱动等等都属于软件框架,它是针对特定的硬件体系需求以面向对象的思维去设计的一种软件解决方案。
C语言通过struct数据结构和函数指针可以出色地完成面向对象抽象的工作。
总线代表着同类设备需要共同遵守的工作时序,不同的总线对于物理电平的要求是不一样的,对于每个比特的电平维持宽度也是不一样,而总线上传递的命令也会有自己的格式约束。如I2C总线、USB总线、PCI总线等等。以I2C总线为例,在同一组I2C总线上连接着不同的I2C设备。
设备代表真实的、具体的物理器件,在软件上用器件的独特的参数属性来代表该器件。如I2C总线上连接的I2C从设备都有一个标识自己的设备地址,由这个设备地址来确定主设备发过来的命令是否该由它来响应。
驱动代表着操作设备的方式和流程。对于应用来说,应用程序open打开设备后,接着就read访问这个设备,驱动就是如何实现这个访问的具体的过程。驱动主要包括两部分,第一是通过对SOC的控制寄存器进行编程,按总线要求输出时序和命令,成功地与外围设备进行交互;第二是对第一步中得到的数据进行处理,并向应用层提供特定格式的数据。

2 Linux驱动模型:

用户空间程序通过sysfs虚拟文件系统访问设备的相关信息。这些信息被组织成层次结构,用sysfs虚拟文件系统来表示,用户通过对sysfs的操作,就可以控制设备或者读取设备的信息。
内核用struct bus_type,struct device和struct device_driver来描述总线、设备和驱动。总线中既定义了设备,也定义了驱动;设备中有总线和驱动;驱动有总线和设备相关的信。内核要求每次出现一个设备,就要向总线注册;每次出现一个驱动,也要向总线注册。系统初始化时,会扫描连接了哪些设备,并为每一个设备建立一个struct device变量,为每一个驱动程序准备一个struct device_driver结构的变量。把这些量变量加入相应的链表,形成一条设备链表和一条驱动链表。这样,就能通过总线找到注册挂载在该总线上的每一个设备和每一个驱动程序.当一个struct device诞生,总线就会去driver链表找设备对应的驱动程序。如果找到就执行设备的驱动程序,否则就等待。反之亦然。

2.1 sysfs
sysfs文件系统只存在于内存中,动态的表示内核数据结构,是内核对象(kobject)、属性(kobj_type)、及它们相互关系(kset)的一种表现。用户可以从sysfs中读出内核数据,也可以将用户数据写入内核。
设备启动时,设备驱动模型会注册kobject对象,并在sysfs文件系统中产生sys下目录文件。如sys/bus:下列出了注册到系统中的总线,如USB总线、platform总线。sys/class:注册到内核中的设备类,如声音类(sound)、输入类(input)。

2.2 Kobject、kobj_type、kset
Linux的虚拟文件系统/sysfs,内核启动挂接文件系统后会将sysfs文件系统挂接到/sys下。内核启动时会初始化并注册一些总线、设备,这些总线、设备等会在sys下创建目录,来存储,如/sys/bus/platform 下存储了平台设备信息。如何组织管理设备,并创建目录呢,通过设备驱动模型的几个重要结构体:Kobject、kobj_type、kset来组织和管理目录及文件结构。kset是同类型kobject对象的集合,可以说是一个容器。kobject是总线、驱动、设备的三种对象的一个基类,实现公共接口。ktype,记录了kobject对象的一些属性。
内核结构与sysfs对应关系:
Struct kset (组织kobject目录的)目录
Struct kobject -目录
Struct kobj_type 属性文件
对于sysfs中的普通文件读写操作都是由kobject->ktype->sysfs_ops来完成的。设备驱动模型的核心即是kobject,是为了管理日益增多的设备,使得设备在底层都具体统一的接口。他与sysfs文件系统紧密相连,每个注册的kobject都对应sysfs文件系统中的一个目录。为了直观管理,统一存放的路径,使用了kset。但是仅仅有这些目录没有意义,这两个结构体只能表示出设备的层次关系,所以基本不单独使用,会嵌入到更大的结构体中,(如希望在驱动目录下能看到挂在该总线上的各种驱动,而在设备目录下能看到挂在该总线的各种设备,就将kobject嵌入到描述设备以及驱动的结构体中,这样每次注册设备或驱动,都会在sys目录下有描述)。
设备模型的目的是:为内核建立起一个统一的设备模型,从而有一个对系统结构的一般性抽象描述。
/sys/device/目录是内核对系统中所有设备的分层次表达模型,保存了系统所有的设备。/sys/dev目录下维护一个按照字符设备和块设备的主次号码(major:minor)链接到真实设备(/sys/devices)的符号链接文件,应用程序通过对这些文件的读写和控制,可以访问实际的设备。
kobject的作用:
(1)kobject始终代表sysfs文件系统中的一个目录,name成员指定了目录名,不是文件。
(2)parent成员指定了kobject在sysfs中的目录,从而形成一个树形结构。
(3)ktype是kobject的属性,属性用文件来表示,放在kobject对应目录下。

struct kobject {
	const char	*name;//kobject的名称,显示在sysfs文件系统中,作为一个目录的名字。
	struct list_head	entry;            //链接下一个kobject结构
	struct kobject		*parent;       //指向父kobject结构体
	struct kset		*kset;             //指向所属的kest集合
	struct kobj_type		*ktype;      
/*指向kobject的属性文件,每个对象都有属性。将属性单独组织成数据结构kobj_type存放在ktype中,对于sysfs中的普通文件读写操作都是由kobject->ktype->sysfs_ops来完成的*/
	struct sysfs_dirent	*sd;        //对应sysfs的文件目录
	struct kref		kref;           //kobject的引用计数
	unsigned int state_initialized:1;           //初始化状态
	unsigned int state_in_sysfs:1;            //是否已经加入到sysfs中
	unsigned int state_add_uevent_sent:1;
	unsigned int state_remove_uevent_sent:1;
	unsigned int uevent_suppress:1;
};

2.3 总线
代表同类设备共同遵守的工作时序,标准。总线在软件层面主要是负责管理设备和驱动。

struct bus_type {
      const char      *name;
      struct bus_attribute    *bus_attrs;
      struct device_attribute *dev_attrs;  //总线中定义的设备
      struct driver_attribute *drv_attrs;   //总线中定义的驱动
    	 int (*match)(struct device *dev, struct device_driver *drv);
};

2.4设备
包含设备属性,GPIO,中断等所用到的SOC资源

 struct device {
     struct device       *parent;
     struct device_private   *p;
     	struct kobject kobj; //是kobject的子类
     const char      *init_name; /* initial name of the device */
     struct device_type  *type;
     	struct mutex        mutex;  /* mutex to synchronize calls to*/
     struct bus_type *bus;	//设备所属的总线类型
     struct device_driver *driver;  //设备关联的驱动
     void (*release)(struct device *dev);
}

可以看出,device是kobject的子类,其对象对应着一个sysfs目录。

2.5驱动
驱动是操作设备的方式和流程。

struct device_driver {
     const char      *name;
     struct bus_type *bus;	//驱动所属的总线类型
    	struct module *owner;
    	 const char *mod_name; /* used for built-in modules */
    	 bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
 #if defined(CONFIG_OF)
    	 const struct of_device_id *of_match_table; //驱动支持的设备
#endif
    	 int (*probe) (struct device *dev);//驱动的初始化函数
    	 int (*remove) (struct device *dev); };
}

2.6 app调用驱动流程
app -> open/read/write -> driver -> device
open设备文件,read、write、ioctl,最后close退出。
int fd = open(“设备文件名”);read(fd, buf, len) write(fd, buf, len);
其中app访问的设备文件和open等方法,都由驱动程序中的probe函数完成,设备和驱动在初始化时要明确属于哪种总线,分别向总线注册自己。总线通过match匹配关联的设备和驱动。当驱动 匹配 到了对应的设备之后,就会调用probe()函数来驱动设备.
if(match(device,driver) == OK){driver->probe()};
probe()要完成的工作:
I.探测设备是否正常
II.注册操作接口cdev_add(struct file_operations)
III.创建设备文件device_create()

3 字符设备驱动模板

3.1 驱动模型主要结构
struct cdev :设备结构, struct dev_t :设备号, struct file_operations: 设备动作
cdev结构体来描述字符设备
成员dev_t来定义设备号,以确定字符设备的唯一性。
成员file_operations来定义字符设备驱动提供给VFS的接口函数,如open/read/write/ioctl…

struct cdev {
                struct kobject  kobj;  // 每个 cdev 都是一个 kobject
                struct module  *owner;  // 指向实现驱动的模块
                const struct file_operations  *ops;   // 操纵这个字符设备文件的方法,
                struct list_head  list;   //对应的字符设备文件的inode->i_devices 的链表头
                dev_t  dev;    // 起始设备编号(主、次)
                unsigned int  count;   // 设备范围号大小
        };

从结构体定义可以看书,kobject结构嵌入到cdev中,说明cdev是kobject的子类.
cdev_map封装了系统中的所有的cdev结构和对应的设备号,最多为255个字符设备。
设备号Dev_t dev 为 32 位,其中高 12 位为主设备号,低20 位为次设备号。
主设备号  标识驱动程序。
次编号  操作的是哪个设备。
设备号的获得与生成:
获得主设备号:MAJOR(dev_t dev);
获得次设备号:MINOR(dev_t dev);
生成设备号:MKDEV(int major,int minor);
每个linux驱动都必须要有module_init与module_exit宏定义修饰模块的入口函数,其定义在/linux/init.h中,负责驱动的加载和卸载。
驱动的初始化函数通过module_init注册,然后在通过menuconfig配置的时候选择随内核一起编译(非模块),系统在启动的时候就能够自动调用驱动初始化函数了。
int init_moudule(void) 供insmod在加载此模块的时候自动调用,负责模块的初始化工作。
void cleanup_module(void) 供rmmod在卸载此模块时调用,负责进行设备驱动程序的清除工作。

3.2 设备驱动加载流程
驱动初始化定义通常如下:

Static  int  __init	xx_init_fun(void)
{
	//驱动初始化代码
}

module_init(xx_init_fun)
module_init宏定义。
其在驱动代码中增加了一个特殊的段,用于说明模块初始化函数xx_init_fun所在的位置。没有它,初始化函数就不会被调用。
__init 标记的作用 : 表明该函数仅在模块初始化时候使用,过后被释放。
同理,__exit 标记表示该函数仅在模块被移除时调用,之后被释放。
所有此类标记不可用在模块中的其他函数。

(1)驱动初始化module_init(xx_init_fun) 
分配cdev 
静态申请设备号

dev_id = MKDEV(major, 0); //生成设备号
register_chrdev_region(dev_t from, unsigned count, const char *name)

动态申请设备号:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, 
unsigned count,const char *name)

major = MAJOR(dev_id); //获得主设备号

初始化cdev 
void cdev_init(struct cdev *, struct file_operations *);
初始化 cdev 的成员,并建立 cdev 和 file_operations 之间的连接。

注册cdev 
int cdev_add(struct cdev *, dev_t, unsigned);
把cdev结构嵌入到 kobj_map cdev_mapprobe (cdev)
内核中所有都字符设备都会记录在一个 kobj_map 结构的 cdev_map 变量中,该变量是一个哈希表。
cdev_add调用之后,就已经可以通过文件系统的接口呼叫到我们的驱动程序

(2)总线匹配match(dev,driver) -->
(3)调用 probe -->
device_create 函数:在/sys/class/ 生成设备属性文件
发送uvent事件到用户空间 
mdev(用户态的守护线程)收到uvent事件,分析/sys/class/对应文件,最终调用mknod动态创建设备文件/dev/device_name 该文件最重要的内容是设备号。
应用程序 open(/dev/device_name,…)通过调用此文件操作设备,获取file_operation。
module_init() -> match() -> probe() -> device_creat() -> /sys/class/, uevent -> mdev() -> /dev/dev_name

3.3 硬件初始化
主要是硬件资源的申请与配置,主要涉及地址映射,寄存器读写等相关操作.

3.4 实现设备操作

用户空间的程序以访问文件的形式访问字符设备,通常进行open、read、write、close等系统调用。而这些系统调用的最终落实则是file_operations结构体中成员函数,它们是字符设备驱动与内核的接口。

struct file_operations {
int (*open) (struct inode *, struct file *);
int (*ioctl) (struct inode *, struct file *, ...);
ssize_t (*read) (struct file *, char __user *,...);
ssize_t (*write) (struct file *, const char __user *, ...);}

3.5 设备注销
模块卸载函数module_exit()通过cdev_del()函数向系统删除一个cdev,完成字符设备的注销。
void cdev_del(struct cdev *);//原型,删除cdev
cdev_del(&btn_cdev);//例子
在调用cdev_del()函数从系统注销字符设备之后,unregister_chrdev_region()应该被调用以释放原先申请的设备号
void unregister_chrdev_region(dev_t from, unsigned count);//原型
unregister_chrdev_region(MKDEV(major, 0), 1);//例子

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值