嵌入式Linux驱动开发2---字符驱动框架

字符设备

”字符设备是指那些只能一个字节一个字节读写数据的设备,不能随机读取设备内存中的某一数据,需要按照先后顺序依次读取。看起来字符设备是面向数据流的设备,数据像水流一样流入,常见的字符设备有鼠标,键盘,串口,控制台和LED等设备“ 《linux驱动开发入门与实战》

块设备

“块设备指那些可以从任意位置读取指定数据长度的设备,如硬盘,磁盘,SD卡“

如何辨别他俩

在/dev目录下保存着这两种设备,每个设备对应着一个文件,通过ls -l查看他们的属性
c表示字符设备(char),b表示块设备(block)
在这里插入图片描述

驱动程序和设备

一个嵌入式系统中可以同时存在很多同类设备(如一个系统中可以有很多个led),他们可以由同一份驱动模块所驱动(可能是led_drv.ko)。设备和驱动一多,他们在程序中的组织和管理就显得十分重要。

在linux系统中不同的驱动程序通过主设备号来标识,而使用了该驱动程序的设备各自都有一个次设备号。 每一对设备和驱动的设备号,都用一个dev_t的变量存储,dev_t被typedef为unsigned long
为四字节,32位,高12位存储主设备号,低20位存储次设备号:
在这里插入图片描述
如图中所示,有两个Led灯,公用一个驱动程序drv_led。其中驱动程序由主设备号5标定,led1,led2
分别由次设备号1,2标定。

这些设备号可以由自己指定设置,也可以让系统帮我们设置,不过我不打算首先讲这一块。
假定已经知道系统中的设备和驱动都有了相应的设备号,我更好奇系统是如何组织和管理他们的。

字符驱动设备的组织与管理

字符设备的抽象–cdev

linux内核中使用cdev结构体描述字符设备,它将所有字符设备所共有的特性集合在一起,可以用他来表示字符驱动设备:
所在路径:kernel\drivers\crypto\qat\qat_common\adf_ctl_drv.c

struct cdev {
	struct kobject kobj;				    //kobject结构  用于内核通过他来对字符驱动模型的管理
	struct module *owner;                   //一旦有模块包含了cdev结构体,就会用这个指针来指向它
	const struct file_operations *ops;		//file_operations是字符设备操作函数集,ops就指向它
	struct list_head list;				    //list_head是一种只有指针域的结构体 list作为双向链表的起始节点,后面会看到它将字符设备连接成一个链表
	dev_t dev;								//这个就是前面所说的设备号
	unsigned int count;				        //表示目前有多少个字符设备在使用该驱动
} __randomize_layout;

kobject结构

kobject是一个牵扯更多问题的东西,暂时不深入讨论,只需要知道系统的sysfs中显示的每一个对象,都对应一个kobject,用它来与内核交互。它还可以实现设备模型上友好而复杂的数据结构。

list_head

Linux内核对它的注释这样写到:

The linkage struct for list nodes. This struct must be part of your
to-be-linked struct. struct list_head is required for both the head of
the list and for each list node.

struct list_head {
    struct list_head *next, *prev;
};

list_head结构本身,只能通过next和prev成员,将每一个同类型结构串联成一个双向环形链表。但是这样的双向环形链表却能够将其他的实例挂载到它的每个节点上。
在这里插入图片描述

inode结构体

内核使用inode结构在其内部表示文件,包含了大量的与文件相关的信息,对于驱动程序员它有三个值得关注的成员。

struct inode {
	dev_t i_rdev;				    //设备文件对应的设备号
	struct cdev *i_cdev;			//这个会指向前面说的cdev结构体
	struct list_head *i_devices;	//前面说的cdev结构体中也有这个类型的成员list,list会通过i_devices成员将inode串联起来

Linux的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在**/dev**目录 下,称为设备文件。应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。
在终端上使用命令mknode xxx 100 1可以在/dev目录下创建一个主设备号为100,次设备号为1的字符设备
当然你也可以通过代码让程序自动创建
创建后设备文件后自动分配一个inode节点(有主次设备号),存在文件系统当中。

cdev中的file_operations成员

用户程序可以使用访问普通文件的方法访问设备文件,进而访问设备。这样的方法极大地减轻了程序员的编程负担,不必去熟悉新的驱动接口就可以访问设备
访问普通文件一般是open() read() write() close() ioctl()等方法,file_operations就列出了这些方法成员,他们各自指向驱动层程序员写好的对应xxx_驱动函数。应用层的open调用会对应得执行到file_operations中open成员指向的xxx_open驱动层函数,具体过程随后详解。

cdev_add函数

这个函数就会将cdev对象注册到系统中
cdev_add函数的源码如下:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	int error;
	p->dev = dev;
	p->count = count;
	error = kobj_map(cdev_map, dev, count, NULL,
			 exact_match, exact_lock, p);
	if (error)
		return error;
	kobject_get(p->kobj.parent);
	return 0;
}

注释:

 * cdev_add() adds the device represented by @p to the system, making it
 * live immediately.  A negative error code is returned on failure.

可以看到这个函数的大致作用就是将cdev结构初始化并挂载到

系统对字符驱动的组织和管理图

经过上面的基础知识的介绍,已经有部分能力来理解调用从应用层到驱动层再到硬件的这个过程了,经过我的疯狂画图:
图中的第一步,第二步…就是我们搭建这个通路的过程。
第一步是自定义xxx_dev设备结构体,这里面除了cdev中的信息外还可以定义一些字符设备特定的信息。
第二步就是将编写驱动函数xxx_open等并将他们与file_operations类型的fops中相应的接口连接起来
第三步是编写xxx_init函数,这里面应该分配设备号并初始化cdev结构体,会将上面做好的fops放到cdev中
第四步不一定要在程序中实现,如果要让程序自动创建设备节点inode,可以执行第四歩里的函数
第五歩就是将写好的xxx_init函数注册到系统中,后面insmod时才可以调用到xxx_init函数
在这里插入图片描述
应用层实际调用时的过程是这样的
事先需要在linux命令行执行模块加载命令insmod,它会执行到对应的xxx_init还是函数,注册cdev字符设备结构
open(“/dec/xxx”,)时会进入C库函数open中,它会触发system call进入内核,并且调用函数时指定的/dev/xxx所对应的inode结构也会传入到系统中,cdev会在list所在的双向链表中寻找已经是否有注册的设备号相同的inode节点,如果有的话会根据它内部的i_devices指针找到cdev内部的fops,从而找到led_open函数并执行。最后达到操作硬件的目的。
(使用class_create创建类时会在sysfs文件夹下创建对应文件夹"myled")

填坑 — inode和file结构体

内核中用inode结构表示具体的文件,而用file结构表示打开的文件描述符。

inode是linux内核的文件系统里边最重要的数据结构,可以说,一个inode就代表了一个文件,inode结构体保存了文件的大小,创建时间,文件的块大小等各种参数。
当我们在创建文件后,它会建立一个文件和它所对应的inode,并将这个inode和文件名写入当前文件目录下的数据块中。

文件结构 代表一个打开的文件描述符,它不是专门给驱动程序使用的,系统中每一个打开的文件在内核中都有一个关联的struct file。它由内核在open时创建,并传递给在文件上操作的任何函数,知道最后关闭。当文件的所有实例都关闭之后,内核释放这个数据结构。
struct file结构体定义在include/linux/fs.h中定义。文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 struct file。它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构。

填坑 — 设备号的申请与释放

设备号申请主要是为设备驱动设定主设备号,为能够调用这个驱动的设备预留一个区间的次设备号,后期inode文件被创建时,如果它内部的次设备号不在这个区间内,是无法调用到这个驱动的。

//静态申请
register_chrdev_region()
//动态申请
alloc_chrdev_region()

最后小结

来自《Linux驱动开发入门与实战》
在这里插入图片描述

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值