Linux 内核学习(7) --- 字符设备驱动

概述

Linux 中主要有三类设备的驱动程序,分别是字符设备驱动程序,块设备驱动程序和网络设备驱动程序
字符设备是指在 I/O 传输过程中以字符为单位进行传输的设备,例如键盘,打印机等,字符设备的驱动程序结构如下图所示:
字符设备驱动.png

字符设备可以通过文件节点来访问,设备文件和普通文件差别在于对普通文件的访问可以前后移动访问位置,而大多数字符设备是一个只能顺序访问的数据通道,当然也存在数据区特性的字符设备,访问它们可以前后移动访问位置,比如FrameBuffer Device 就是这样一个设备,app 可以用 mmap 或者 lseek 访问获取整个图像
字符设备文件类型是 c(char),设备文件没有文件大小,取而代之的是两个号:主设备号次设备号

基本逻辑

Linux 内部一切设备皆文件,所有的硬件设备操作到应用层都会抽象为文件的操作,Linux 访问文件的基本逻辑如下:

  1. Linux 文件系统中,每一个文件都用一个struct inode 结构体来描述,这个结构体里面记录这个文件的所有信息,比如文件类型,访问权限等
  2. Linux 操作系统中,每个驱动程序在应用层 /dev 目录下都会有一个设备文件和它对应,称为设备节点,该设备节点存在 主设备号 和 次设备 号
  3. 每打开一次文件,LinuxVFS 都会分配一个 struct file 结构体来描述打开的文件,该结构体用于维护文件打开的权限,文件指针偏移值,私有的内存信息等
    驱动程序的主设备号和次设备号信息保存在 struct cdev 结构中
struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
};

调用流程

  1. 系统调用 open 函数打开设备文件,可以根据设备文件对应的 struct inode 结构体描述的信息,判断当前的要操作的设备类型,还会分配一个 struct file 结构体
  2. struct file 结构体包含了对此设备的操作函数集合 const struct file_operations *f_op
  3. 根据 struct inode 结构中记录的设备号,可以找到对应的驱动程序,Linux 中每个字符设备都有一个 struct cdev 结构体来对应struct cdev 描述了字符设备的所有信息,其中最重要的就是字符设备的操作接口
  4. 找到 struct cdev 结构体后,Linux 内核会将 struct cdev 结构体所在的内存空间首地址记录在 struct inode 结构体的 i_cdev 成员中,将 struct cdev 结构体中记录的函数操作接口记录在 struct file 结构体的 fops 成员中
  5. 任务完成,VFS 会给应用层返回一个文件描述符,这个 fd 是和 struct file 结构体想对应的,上层应用程序的调用就可以通过 fd 找到对应的 struct file,由 struct file 找到操作字符设备接口的函数了

代码路径:/include/linux/fs.h

struct file 类型的实现:

struct file {
	union {
		struct llist_node	fu_llist;
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		f_path;
	struct inode		*f_inode;	/* cached value */
	u64			f_version;
	const struct file_operations	*f_op;
	        ...
	/* needed for tty driver, and maybe others */
	void			*private_data;
	.....
};

private_data 可以用于保存驱动的私有数据,用于在驱动的各个操作函数之间共享
file 数据结构中包含 inode 数据结构,struct inode *f_inode

struct inode *inode = file_inode(file);

struct inode {
	umode_t			i_mode;
	unsigned short		i_opflags;
	kuid_t			i_uid;
	kgid_t			i_gid;
	unsigned int		i_flags;
	.....
	dev_t			i_rdev; //保存字符设备驱动设备号
}

其中的 dev_t i_rdev; 包含了设备的主次设备号
主次设备号 是 一个32位的数 12 位表示主设备号 20 位表示次设备号
/include/linux/fs.h 提供下面操作设备号的函数

// inlude/linux/kdev_t.h
#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))

//从 inode 中获取主次设备号 /include/linux/fs.h
static inline unsigned iminor(const struct inode *inode)
static inline unsigned imajor(const struct inode *inode)

struct file_operations 里面包含了驱动操作函数的函数指针

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
        ......
}

可以看到驱动程序的操作函数中,参数都会带有 struct file *,指向内核分配的 struct file 类型

实现字符设备驱动

  1. 实现驱动模块的加载和卸载入口函数
module_init(drm_core_init);
module_exit(drm_core_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxxxx");
MODULE_DESCRIPTION("A module used for show task of each thread!");

module_init(fbmem_init);
linux 中,所有标志为 __init 的函数在连接的时候都放在 .init.text 这个区段,此外,所有的__init 函数
在区段 .initcall.init 中还保存了一份函数指针,在初始化内核会通过这些函数指针调用这些 __init 函数,并
在初始化完成之后,释放 init区段,(包括 .init.text .initcall.init )

module_exit(fbmem_exit);
一般以 __exit 标志命名。 主要完成的工作:

  • 注销设备
  • 对于申请的内存需要动态释放
  • 释放硬件资源 终端 DMA通道 I/O 端口 I/O 内存管理
  • 开启了硬件一定要关闭
  1. 申请主设备号
extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
extern int register_chrdev_region(dev_t, unsigned, const char *);
  1. 手动/自动创建设备节点
    设备节点可以手动创建 mknod /dev/hello 250 0
    自动创建设备节点:
    Linux 系统中还存在 udevmdev 机制,可以遍历 /sys/class/xxx 下的 uevent 文件,根据这些这些 uevent 文件获取创建设备节点的信息,然后调用 mknod 程序在 /dev 下创建设备节点,结束之后,udev就开始等待内核空间的event
    创建设备类的一般流程:
static struct class *cls;
static struct device *test_device;

devno = MKDEV(major,minor);
cls = class_create(THIS_MODULE, "democlass");
if(IS_ERR(cls)) {
	unregister_chrdev(major,"hello");
	return result;
}
test_device = device_create(cls, NULL, devno, NULL, "hellodevice");
if(IS_ERR(test_device )) {
	class_destroy(cls);
	unregister_chrdev(major,"hello");
	return result;
}
  1. 实现 file_operation
  • 定义一个结构体 static struct file_operations 变量,在其中定义一些设备的打开,关闭,读,写,控制函数
    字符设备提供给应用程序的流控制接口有 open,close,read,write,ioctl;添加一个字符设备驱动程序的过程,实际上就是给上述操作添加代码的过程,Linux 对这些设备操作统一做了抽象
struct file_operations s3c_rotator_fops = {
	.owner      = THIS_MODULE,
	.open       = dev_fifo_open,
	.release    = xxxx,
	.mmap       = xxx,
	.ioctl      = xxxx,
	.poll       = xxxx,
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值