linux device drivers ch03
ch03.字符设备驱动程序
编写驱动程序的第一步就是定义驱动程序为用户程序提供的能力(机制)。接下来以scull(“Simple Character Utility for Loading Localities”区域装载的简单字符工具,scull是一个操作内存区域的字符设备驱动程序,这片内存区域就相当于一个设备)为例说明。
主设备号和次设备号
对字符设备的访问时通过文件系统内的设备名称进行的。这些名称被称为特殊文件、设备文件或者简单称之为文件系统树的节点。它们通常位于/dev目录,字符设备驱动程序的设备文件可通过ls -l命令输出的第一列中的“c”来识别。
ls -l执行后,可在设备文件项的最后修改日期前看到两个数(用逗号隔开),这两个数就是对应设备的主次设备号。通常而言,主设备号标识设备对应的驱动程序。linux内核允许多个驱动程序共享主设备号。此设备号由内核使用,用于正确确定设备文件所指的设备。
内核中,dev_t类型用来保存设备编号----包括主次设备号。dev_t是一个32位的数,其中的12位用来表示主设备号,其余20位用来表示此设备号。要获得dev_t的主设备号或此设备号,使用:
获取主设备号:MAJOR(dev_t dev);
获取此设备号:MINOR(dev_t dev);
将主次设备号转换成dev_t类型:MKDEV(int major , int minor );
分配和释放设备编号
在建立字符设备之前,我们的驱动程序首先要做的就是获得一个或者多个设备编号。
int register_chrdev_region(dev_t first,unsigned int count,char *name); (静态分配设备号 <linux/fs.h>)
参数1:要分配的设备编号范围的起始值 (first的次设备号通常被设置为0) 参数2:所请求得连续设备号的个数 参数3:name是和该编号相关的设备名称(出现在/proc/devices和sysfs中)
返回值:成功 0
失败 负的错误码
如果提前知道所需要的设备编号,则register_chrdev_region会工作得很好。但是我们经常不知道设备将要使用哪些主设备号;因此,linux内核开发社区一直在努力转向设备编号的动态分配。
int alloc_chrdev_region(dev_t *dev ,unsigned int firstminor ,unsigned int count ,char *name); (动态分配所需要的主设备号)
参数1:&dev 参数2:要分配的第一个次设备号,通常为0 参数3和4与register_chrdev_region函数一样。
在不使用时释放这些设备编号:
void unregister_chrdev_region(dev_t first ,unsigned int count);
通常在模块清除函数中使用unregister_chrdev_region函数。
在用户空间程序可访问上述设备编号之前,驱动程序需要将设备编号和内部函数链接起来,这些内部函数用来实现设备的操作。
重要的数据结构
设备号的注册仅仅是驱动程序代码必须完成的许多工作中的第一件事情。大部分基本的驱动程序操作涉及到三个重要的内核数据结构,分别是file_operations、file和inode。
文件操作file_operations结构体
file_operations结构就是用来建立驱动程序操作和设备编号的连接的。(<linux/fs.h>)其中包含了一组函数指针。习惯上,file_operations结构体或者指向这类结构体的指针称为fops,这个结构体中的每一个字段都必须指向驱动程序中实现特定操作的函数,对于不支持的操作,对应的字段可置为NULL值。
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 *); //__user表示指针是一个用户空间地址,因此不能
//被直接引用
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
};
struct file_operations Code
struct module *owner;
第一个file_operations字段并不是一个操作,相反,它是指向“拥有”该结构的模块的指针。内核使用这个字段以避免在模块的操作正在被使用时卸载该模块。几乎所有的情况下,该成员都会被初始化为THIS_MODULE。
ssize_t (*read)(struct file* ,char __user* ,size_t ,loff_t *);
用来从设备中读取数据。
ssize_t (*write)(struct file* ,const char __user* ,size_t ,loff_t *);
用来向设备发送数据。
unsigned int (*poll)(struct file*,struct poll_table_struct *);
poll方法是poll、epoll和select这三个系统调用的后端实现。这三个系统调用可用来查询某个或者多个文件描述符的读取或写入是否会被阻塞。poll方法应该返回一个位掩码,用来指出非阻塞的读取或写入是否可能,并且也会向内核提供将调用进程置于休眠状态直到i/o变为可能时的信息。如果驱动程序将poll方法定义为NULL,则设备会被认为既可读也可写,并且不会被阻塞。
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
unlocked_ioctl提供了一种执行设备特定命令的方法。
int (*open)(struct inde*,struct file*);
尽管这始终是对设备文件执行的第一个操作,然而却并不要求驱动程序一定要声明一个相应的方法。如果这个入口为NULL,设备的打开操作永远成功,但系统不会通知驱动程序。
int (*release)(struct inode*,struct file*);
当file结构体被释放时,将调用这个操作。
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.unlocked_ioctl = scull_unlocked_ioctl,
.open = scull_open,
.release = scull_release,
};
常用的file_opreations初始化 Code
file结构
设备驱动程序使用的第二个最重要的内核数据结构是struct file。file结构代表一个打开的文件(它并不仅仅限定于设备驱动程序,系统中每个打开的文件在内核空间都有一个对应的file结构)。它由内核在open时创建,并传递给在该文件上进行操作的所有函数,直到最后的close函数,内核会释放这个数据结构。内核源码中,指向struct file的指针通常被称为filep。
mode_t f_mode;
文件模式
loff_t f_pos;
当前的读/写位置。loff_t是一个64位的数(用gcc的术语就是long long)。如果驱动程序需要直到文件中的当前位置,可以读取这个值,但不要去修改它。read/write会使用它们接收到的最后那个指针参数来更新这一位置,而不是直接对filp->f_pos进行操作。
struct file_opreations *f_op;
与文件相关的操作。
void *private_data;
私有数据。
inode结构
内核用inode结构在内部表示文件,因此它和file结构不同,后者表示打开的文件描述符。对单个文件,可能会有许多个表示打开的文件描述符的file结构,但他们都指向单个inode结构(包含了大量的文件信息)。
为了鼓励编写可移植性更强的代码,内核开发者增加了两个新的宏,可用来从一个inode中获得主设备号和次设备号:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
字符设备的注册
内核内部使用struct cdev结构来表示字符设备。在内核调用设备操作之前,必须分配并注册一个或者多个上述结构(<linux/cdev.h>)。
void cdev_init(struct cdev *cdev,struct file_operations *fops);
初始化已分配到的结构。(struct cdev也有一个所有者字段,应该被设置为THIS_MODULE)
在cdev结构体设置好之后,最后的步骤是通过cdev_add告诉内核该结构的信息(注册):
int cdev_add(struct cdev *cdev,dev_t num,unsigned int count);
参数1:cdev结构 参数2:该设备对应的第一个设备编号 参数3:和该设备关联的设备编号的个数 (通常取1)
注:在驱动程序还没有完全准备好处理设备上的操作时(初始化),就不能调用cdev_add。
cdev_del(struct cdev * dev);
从系统中移除(注销)一个字符设备。
scull中的设备注册
scull内部通过struct scull_dev的结构体来表示每个设备。
cdev结构体镶嵌在该结构体中,现在,我们的注意力在cdev上,即内核和设备间的接口struct cdev。该结构必须如上所述的被初始化并添加到系统中。
open和release
open方法
int (*open)(struct inode *inode,struct file *filp);
其中的inode参数在其i_cdev字段中包含可我们所需要的信息,及我们先前设置的cdev结构。现在的问题是,我们通常不需要cdev结构本身,而是希望得到包含cdev结构的scull_dev结构。内核中实现了一种技巧,通过定义在<linux/kernel.h>头文件中的
container_of宏函数实现:
container_of(pointer,container_type,container_field); ----已知结构体成员的指针反推结构体的指针
这个宏函数需要一个container_field字段的指针,该字段包含在container_type类型的结构中,然后返回包含该字段的结构指针。
struct scull_dev *dev; /*device information*/ dev = container_of(inode->i_cdev,struct scull_dev,cdev);
filp->private_data = dev; /*for other methods*/
container_of Code
一旦代码找到scull_dev结构之后,scull将一个指针保存到了file结构的private_data字段中,这样可以方便今后对该指针的访问。
==》经过些微简化的scull_open代码:
int scull_open(struct inode *inode,struct file *filp)
{
struct scull_dev *dev; /*device information*/
dev = container_of(inode->i_cdev,struct scull_dev,cdev);
filp->private_data = dev; /*for other methods*/
}
scull_open Code
release方法
release方法的作用正好和open相反。完成下面的任务:1.释放由open分配的、保存在filp->private_data中的所有内容。2.在最后一次关闭操作时关闭设备。
read和write
read:从内核拷贝数据到应用程序空间 对应调用copy_to_user
write:从应用程序空间拷贝数据到内核 对应调用copy_from_user
ssize_t read(struct file *filp,char __user *buff,size_t count,loff_t *offp);
ssize_t write(struct file *filp,const char __user *buff,size_t count,loff_t *offp);
参数1:filp文件流指针 参数2:指向用户空间的缓冲区 参数3:请求传输的数据长度 参数4:指针,指明用户在文件中进行存取操作的位置。
注:read和write方法的buff参数是用户空间的指针,因此,内核代码不能直接引用其中的内容。
原因:1.在内核模式中运行时,用户空间的指针可能是无效的,该地址可能根本无法被映射到内核空间,或者可能指向某些随机数据。2.用户空间的内存是分页的,对用户空间内存的直接引用将导致页错误,结果可能是‘oops’。3.如果我们的驱动程序盲目引用用户提供的指针,讲导致系统出现打开的后门,从而允许用户空间程序随意访问或覆盖系统中的内存。危及用户系统安全。
然而,驱动程序必须访问用户空间的缓冲区以便完成自己的工作。为了确保安全,这种访问应该始终通过内核提供的专用函数完成。
unsigned long copy_to_user(void __user *to,const void *from,unsigned long count);
unsigned long copy_from_user(void *to,const void __user *from,unsigned long count);
这两个函数的作用并不限于在内核空间和用户空间之间拷贝数据,它们还检查用户空间的指针是否有效。如果指针无效,就不会进行拷贝;如果在拷贝过程中遇到无效地址,则仅仅会复制部分数据。
返回值的问题:read和write方法在出错时都返回一个负值,大于等于0的返回值告诉调用程序成功传输了多少字节。如果在正确传输部分数据之后发生了错误,则返回值必须是成功传输的字节数,但这个错误只能在下一次函数调用时才会得到报告。
linux device drivers ch03的更多相关文章
- 《Linux Device Drivers》第十四章 Linux 设备型号
基本介绍 2.6内核设备模型来提供的抽象叙述性描述的一般系统的结构,为了支持各种不同的任务 电源管理和系统关机 用户空间与通信 热插拔设备 设备类型 kobject.kset和子系统 kobject是 ...
- 《Linux Device Drivers》第十二章 PCI司机——note
一个简短的引论 它给这一章总线架构的高级概述 集中访问讨论Peripheral Component Interconnect(PCI,外围组件互连)外设内核函数 PCI公交车是最好的支持的内核总线 本 ...
- 《Linux Device Drivers》 第十七章 网络驱动程序——note
基本介绍 第三类是标准的网络接口Linux设备,本章介绍的内核,其余的交互网络接口描述 网络接口,必须使用特定的内核数据结构本身注册,与外部分组交换数据线打电话时准备 经常使用的文件上的网络接口操作是 ...
- 《Linux Device Drivers》第十五章 内存映射和DMA——note
简单介绍 很多类型的驱动程序编程都须要了解一些虚拟内存子系统怎样工作的知识 当遇到更为复杂.性能要求更为苛刻的子系统时,本章所讨论的内容迟早都要用到 本章的内容分成三个部分 讲述mmap系统调用的实现 ...
- 《Linux Device Drivers》第十六章 块设备驱动程序——note
基本介绍 块设备驱动程序通过主传动固定大小数据的随机访问设备 Linux核心Visual块设备作为基本设备和不同的字符设备类型 Linux块设备驱动程序接口,使块设备最大限度地发挥其效用.一个问题 一 ...
- 《Linux Device Drivers》第十八章 TTY驱动程序——note
简单介绍 tty设备的名称是从过去的电传打字机缩写而来,最初是指连接到Unix系统上的物理或虚拟终端 Linux tty驱动程序的核心紧挨在标准字符设备驱动层之下,并提供了一系列的功能,作为接口被终端 ...
- 《Linux Device Drivers》第十章 中断处理——note
概述:系统要及时的感知硬件的状态,通常有两种方式:一种是轮询.一种是通过响应硬件中断.前者会浪费处理器的时间,而后者不会. 准备并口 在没有节设定产生中断之前,并口是不会产生中断的 并口的标准规定设置 ...
- linux device drivers ch02
ch02.构造和运行模块 模块的构造: #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE(&qu ...
- linux device drivers ch01
ch01. 设备驱动程序简介 设备驱动程序的作用在于提供机制(需要提供什么功能),而不是提供策略(如何使用这些功能). 内核功能划分: 进程管理:进程创建.销毁.进程间通信.共享cpu调度器. 内存管 ...
随机推荐
- hadoop集群的搭建
hadoop集群的搭建 1.ubuntu 14.04更换成阿里云源 刚刚开始我选择了nat模式,所有可以连通网络,但是不能ping通,我就是想安装一下mysql,因为安装手动安装mysql太麻烦了,然 ...
- android WebView技术笔记(存储技术)
作为刚刚开始工作的新手程序猿,各种知识还不算熟练,所以将学习到的知识记录一下以免以后忘记. WebView加载h5的知识现在可以说非常的常用,通过加载h5的网页可以在不更新app的情况下来更新app的 ...
- Android ViewPager+Fragment 在Activity中获取Fragment的控件
如果ViewPager+Fragment实现Tab切换,在activity中利用adapter.getItem获取到fragment然后再根据fragment.的方法获取控件 //隐藏求租,以下代码用 ...
- 基于GitLab的Code Review教程
一.前言 1.本文主要内容 GitLab Code Review机制说明 Git Workflow 与 Git Code Review Workflow GitLab Code Review 配置说明 ...
- MyDAL - in && not in 条件 使用
索引: 目录索引 一.API 列表 C# 代码中 接口 IList.Contains() 方法生成 SQL 对应的 in(val1,val2,... ...) 如:.Queryer<Agent& ...
- python使用rabbitMQ介绍三(发布订阅模式)
一.模式介绍 在前面的例子中,消息直接发送到queue中. 现在介绍的模式,消息发送到exchange中,消费者把队列绑定到exchange上. 发布-订阅模式是把消息广播到每个消费者,每个消费者接收 ...
- JAVA EE获取浏览器和操作系统信息
一.原理说明: 1. 浏览器访问服务端时,Http请求头上会带上客户端一些信息,可通过"user-agent"获取. //java获取方法如下,其他语言也有自己获取方法 Stri ...
- LNMP环境下部署搭建wordpress
1. 下载WordPress安装包 访问官方网站https://cn.wordpress.org/ 点击Download.tar.gz下载linux平台安装包 2. 安装软件 2.1.上传安装包 使用 ...
- [ gczdac ] HDU1000
地址:http://acm.hdu.edu.cn/showproblem.php?pid=1000 Problem Description Calculate A + B. Input Eac ...
- Echarts学习之路2(基本配置项)
title:标题组件,包含主标题和副标题. title:{ text:"",//主标题 link:"",//主标题文本超链接 target:"&quo ...