第三章:字符设备驱动程序

主设备号和次设备号

对字符设备的访问是通过文件系统内的设备名称进行的,他们通常位于/dev目录,在此目录下ls –l 命令可以查看所有设备,每一行第一列”c”表示字符设备,”b”表示块设备。

主设备号标识设备对应的驱动程序,虽然现代linux运行多个驱动程序共享主设备号,但是大多数设备依然按照“一个主设备号对应一个驱动程序”的原则组织。

次设备号有内核使用,可以获得指向内核设备的一个指针。

设备编号的内部表达

在内核中,dev_t类型(<linux/types.h>中定义)用来保存设备编号(主设备号以及次设备号)。

我们应该始终使用<linux/kdev_t.h>中的宏来获得主设备号以及次设备号:

MAJOR(dev_t dev);

MINOR(dev_t dev);

同样,可以将主设备号以及次设备号转换为对应的dev_t类型:

MKDEV(int major, int minor);

分配和释放设备编号

在建立一个字符设备之前,我们的驱动程序首先要获得一个或者多个设备编号。可以使用定义在<linux/fs.h>中的函数完成该工作。

int register_chrdev_region(dev_t first, unsigned int count, char *name);

在我们提前明确知道所需要的设备编号时,此函数会工作的很好。first是所要分配的设备编号范围的起始值,通常被设置为0count是所请求的连续设备的编号的个数。name是和该编号范围关联的设备名称,会出现在/proc/devices/sysfs中。

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);

动态分配设备编号。dev是输出参数,在成功完成调用后保存已分配范围的第一个编号。firstminor应该是要使用的被请求的第一个此设备编号。

释放设备编号

void unregister_chrdev_region(dev_t first, unsigned int count);

通常,我们在模块的清楚函数中调用释放设备编号函数。

动态分配主设备号

作为驱动程序开发者,有两种选择:1.简单选定一个未被使用的编号;2.由系统动态分配一个设备编号。强烈建议使用第二种方法。

动态分配编号的缺点是:由于无法预知主设备号,所以无法预先创建设备节点。但一旦分配了设备号,就可以从/proc/devices中读取到。因此,对insmod的调用可替换为一个简单的脚本,该脚本在调用insmod之后,读取/proc/devices以获得新分配的设备编号,然后创建对应的设备文件。

一些重要的数据结构

文件操作

定义在<linux/fs.h>中的file_operations结构用来将任何驱动程序操作连接到已经申请的设备编号。

驱动程序通常的file_operations结构被初始化如下:

struct file_operations scull_fops = {
.owner  =  THIS_MODULE,
.llseek   =  scull_llseek,
.read    =  scull_read,
.write   =   scull_write,
.ioctl    =  scull_ioctl,
.open   =  scull_open,
.release  =  scull_release
}

此声明采用标准C的标记化结构初始化语法。它使驱动程序在结构发生变化时更具有可移植性。

file结构

定义在<linux/fs.h>中的struct file与用户空间的FILE没有任何关联。系统中每个打开的文件在内核空间都有一个对应的file结构。

inode结构

内核用inode结构在内部表示文件。file结构表示打开的文件描述符。对单个文件,可能会有许多个表示打开的文件描述符的file结构,但它们都指向单个inode结构。

字符设备的注册

内核内部使用包含在<linux/cdev.h>中的struct cdev结构来表示字符设备。

注册字符设备时可能用到的两个函数:

void cdev_init(struct cdev *cdev, struct file_operations *fops);

初始化以分配的结构。

int cdev_add(struct cdev *cdev, dev_t num, unsigned int count);

num是该设备对应的第一个设备编号,count是应该和该设备关联的设备编号的数量。此函数可能会返回失败,一般情况下会返回成功,返回成功以后我们的设备会被内核调用,因此一定要保证驱动程序完全准备好处理设备上的操作时才调用该函数。

从系统中移除一个字符设备调用如下函数:

void cdev_del(struct cdev *cdev);

早期的方法

注册一个字符设备驱动程序的经典方式是:
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

和它对应的从系统中移除设备的函数是:

int unregister_chrdev(unsigned int major, const char *name);

open方法

open方法提供给驱动程序以初始化的能力,一般情况下,open应该完成以下工作:

l 检查设备特定的错误(如设备未就绪或类似的硬件问题)

l 如果设备是首次打开,则对其进行初始化。

l 如有必要,更新f_op指针。

l 分配并填写置于filp->private_data里的数据结构。

open的原型为:int (*open)(struct inode *inode, struct file *filp);

<linux/kernel.h>中的container_of宏:container_of(pointer, container_type, container_field);可以通过一个结构体中某一个成员变量的指针得到指向这个结构体的指针。方法:通过指针偏移。

release方法

release方法主要完成以下工作:

l 释放由open分配,保存的filp->private_data中所有的内容

l 在最后一次关闭操作时关闭设备

内核在进程退出的时候,通过在内部使用close系统调用自动关闭所有相关的文件。

scull的内存使用

linux中用于内存管理的两个核心函数定义在<linux/slab.h>中,它们是

void *kmalloc(size_t size, int flags);

void kfree(void *ptr);

我们不可以将非kmalloc返回的指针传递给kfree,但是可以将NULL传递给kfree

readwrite

readwrite完成的任务很相似,他们的函数原型是:

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);

filp是文件指针,count是请求传输的长度,buff是指向用户空间的缓冲区的一个指针,offp是一个指向“长偏移量类型(long offset type)”对象的指针,这个对象指明用户在文件中进行存取操作的位置。

buff是指向用户空间的指针,内核不能直接引用其内容,所以大多数readwrite方法实现的核心部分是:

unsigned long copy_to_user(void __user *to, const void *form, unsigned long count);

unsigned long copy_from_user(void *to, const void __user *from, unsigned long count);

这两个函数在函数内部进行了指针有效性的检查,如果不需要检查指针有效性的情况下,可以使用__copy_to_user, __copy_from_user函数,如果我们没有真正的检查指针就调用此函数,可能会导致内核崩溃或者建立安全漏洞。

访问用户空间的任何函数必须是可重入的,并且必须能够和其他驱动程序函数并发执行。

readvwritev

这两个函数用来一次read或者write若干个数据块。

查漏补缺

file_operations结构保存了字符驱动程序的方法,struct file表示一个打开的文件,struct inod 表示一个磁盘上的文件。

#include <asm/uaccess.h>该文件声明了在内核代码和用户空间之间移动数据的函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LINUX设备驱动第三版_ 前言 第一章 设备驱动程序简介 设备驱动程序的作用 内核功能划分 设备和模块的分类 安全问题 版本编 许可证条款 加入内核开发社团 本书概要 第二章 构造和运行模块 设置测试系统 Hello World模块 核心模块与应用程序的对比 编译和装载 内核符表 预备知识 初始化和关闭 模块参数 在用户空间编写驱动程序 快速参考 第三章 字符设备驱动程序 scull的设计 主设备和次设备 一些重要的数据结构 字符设备的注册 open和release scull的内存使用 read和write 试试新设备 快速参考 第四章 调试技术 内核中的调试支持 通过打印调试 通过查询调试 通过监视调试 调试系统故障 调试器和相关工具 第五章 并发和竞态 scull的缺陷 并发及其管理 信量和互斥体 completion 自旋锁 锁陷阱 除了锁之外的办法 快速参考 第六章 高级字符驱动程序操作 ioctl 阻塞型I/O poll和select 异步通知 定位设备 设备文件的访问控制 快速参考 第七章 时间、延迟及延缓操作 度量时间差 获取当前时间 延迟执行 内核定时器 tasklet 工作队列 快速参考 第八章 分配内存 kmalloc函数的内幕 后备高速缓存 get_free_page和相关函数 vmalloc及其辅助函数 per-CPU变量 获取大的缓冲区 快速参考 第九章 与硬件通信 I/O端口和I/O内存 使用I/O端口 I/O端口示例 使用I/O内存 快速参考 第十章 中断处理 准备并口 安装中断处理例程 实现中断处理例程 顶半部和底半部 中断共享 中断驱动的I/O 快速参考 第十一章 内核的数据类型 使用标准C语言类型 为数据项分配确定的空间大小 接口特定的类型 其他有关移植性的问题 链表 快速参考 第十二章 PCI驱动程序 PCI接口 ISA回顾 PC/104和PC/104+ 其他的PC总线 SBus NuBus 外部总线 快速参考 第十三章 USB驱动程序 USB设备基础 USB和Sysfs USB urb 编写USB驱动程序 不使用urb的USB传输 快速参考 第十四章 Linux设备模型 kobject、kset和子系统 低层sysfs操作 热插拔事件的产生 总线、设备驱动程序 类 各环节的整合 热插拔 处理固件 快速索引 第十五章 内存映射和DMA Linux的内存管理 mmap设备操作 执行直接I/O访问 直接内存访问 快速参考 第十六章 块设备驱动程序 注册 块设备操作 请求处理 其他一些细节 快速参考 第十七章 网络驱动程序 snull设计 连接到内核 net_device结构细节 打开和关闭 数据包传输 数据包的接收 中断处理例程 不使用接收中断 链路状态的改变 套接字缓冲区 MAC 地址解析 定制 ioctl 命令 统计信息 组播 其他知识点详解 快速参考 第十八章 TTY驱动程序 小型TTY驱动程序 tty_driver函数指针 TTY线路设置 ioctls proc和sysfs对TTY设备的处理 tty_driver结构详解 tty_operations结构详解 tty_struct结构详解 快速参考 参考书目 9112405-1_o.jpg (85.53 KB, 下载次数: 50)

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值