Linux 设备驱动概述
description
Transcript of Linux 设备驱动概述
©2007 ZTE corporation
LinuxLinux 设备驱动概述设备驱动概述成研所操作系统团队
目录• Linux 设备驱动简介• 内核和驱动模块• 字符型设备• 块设备• 网络接口设备• 中断处理• 常用调试技术
设备驱动的作用 系统与硬件的接口 部分应用与硬件的接口 让硬件为用户可用,一般驱动本身不给用户定制策略 许多驱动程序是同用户程序一起发行的,这样对于硬件使用策略提供给了用户态的应用层
设备分类 字符型设备
仅仅是数据通道,其读写实质是数据的输入和输出 存取时没有缓存
块设备 文件系统的宿主 块设备的读写,内核都有缓存来支持,块设备必须能够随机存取
网络设备 不再调用 read/write ,而是调用数据包协议 上层协议自动调用发送程序 硬件中断通知接收
设备文件 设备文件
Linux 认为“一切皆文件”,设备驱动也可以像文件一样读、写、开、关。 驱动设备文件都在 /dev 下,如 /dev/hda1Block 型设备文件类型为 b , Character 型设备为
c , Socket 设备为 s 查看设备文件
file 设备名 ls -l 设备名
设备文件 主设备号和从设备号
针对字符型和块设备文件,使用主、从设备号来描述设备• 主设备编号表示某种驱动程序• 辅设备区分不同设备 例如 /dev/hda1 、 2 、 3 ( block 3/1 )• 同一块硬盘的 3 块分区• 3/1 的 3 为主设备号, 1 为从设备号
创建设备文件mknod harddisk b 3 0
设备文件 /proc 目录
设备文件与驱动的桥梁• 访问设备文件时,查找 /proc 目录下的值,确定驱动模块
/proc 目录下的文件为虚拟文件,实际是内核在内存中的参数。 每次启动系统,自动将 /etc/fstab 中的设置信息作为 proc 文件加载到 /proc 目录下。
目录• Linux 设备驱动简介• 内核和驱动模块• 字符型设备• 块设备• 网络接口设备• 中断处理• 常用调试技术
内核态与用户态 内核态
内核态程序是运行在特权模式下,可以运行特权指令,可以访问特殊地址空间。 运行于内核态的进程拥有独立的地址空间,各进程互不影响,因此相对于用户态的程序安全而可靠,独立而封闭。 设备驱动通常运行于内核态
用户态 一般为应用程序 用户态进程优先级低于内核态进程 独立性、安全性不如内核态
设备驱动加载方式 内核
包含最基本的驱动• CPU• PCI 总线• TCP/IP 协议• APM (高级电源管理)• VFS
内核模块 一般的外设驱动 不一定是硬件的驱动,如 ext3 文件系统驱动 动态加载进内核后,成为内核一部分
模块命令 lsmod
列出已加载的模块 modprobe
智能插入模块。根据模块间依存关系,以及 /etc/modules.conf 文件中的内容插入。
insmod 插入模块,但不会解决依赖关系。
rmmod 删除模块
modinfo 查看模块信息
目录• Linux 设备驱动简介• 内核和驱动模块• 字符型设备• 块设备• 网络接口设备• 中断处理• 常用调试技术
字符型设备注册 register_chrdev 注册函数
int register_chrdev (unsigned int major, const char * name, struct file_operations *fops);
major: 被请求的主设备号name: 设备名称fops: 一个指向一组操作( open , read 等等)表的指 针。这个表的每一个项都指向由驱动程序定义的 处理相应请求的函数。
文件操作数据结构 file_operations 结构:由内核调用来访问驱动程序的函数
struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char *, size_t, loff_t *);ssize_t (*write) (struct file *, const char *, size_t, loff_t *);unsigned int (*poll) (struct file *, struct poll_table_struct *);int (*ioctl) (struct inode *, struct file *, unsigned int, unsigne
d long);int (*open) (struct inode *, struct file *);int (*release) (struct inode *, struct file *)
}
主要操作 open
递增使用计数检查设备特定的错误(如未就绪或类似硬件问题)如果设备是首次打开,则对其初始化识别次设备号,并且如果有必要,更新 f_o
p 指针分配并填写被置于 filp->private_data (驱动程序自定义的数据区)里的数据结构
主要操作 release ( 与 open 相反 )
释放由 open 分配的、保存在 filp->private_data 中的所有内容 在最后一次关闭操作时关闭设备 使用计数减 1
read/write 从设备拷贝数据给应用程序 ( 或相反 ) 使用 copy_to_user 和 copy_from_user 实现内核空间与用户空间的虚拟内存拷贝。
竞态与解决方案 为何出现竞态
当多个线程同时访问相同的资源 信号量
引起调用者睡眠,它把进程从运行队列上拖出去,除非获得锁 耗资源大,适合单处理器,保持时间长的情况 只能在进程上下文中访问
自旋锁 如果自旋锁已经被别的执行单元保持,调用者就一直循环查看是否该自旋锁的保持者已经释放了锁 ,即原地打转 适合多处理器、 SMP环境 可在任何上下文使用
设备操作通信机制 为何需要同步机制
要读取或写入的数据需要同其他硬件交换而得到 阻塞与非阻塞
阻塞:在执行设备操作时,若不能获得资源,则进程挂起直到满足可操作的条件再进行操作 非阻塞:进程在不能进行设备操作时,不进行挂起,直接返回
异步触发 一旦设备就绪,向所有注册异步触发的进程发送
SIGIO 信号,这样应用程序根本就不需要查询设备状态 ,效率很高。
目录• Linux 设备驱动简介• 内核和驱动模块• 字符型设备• 块设备• 网络接口设备• 中断处理• 常用调试技术
块设备驱动注册 int register_blkdev
(unsigned int major, const char *name,struct block_device_operations *bdops);
major: 主设备号*name: 设备名*bdops: 指向块设备数据结构 block_device_operatio
ns 的指针
bdops 数据结构 block_device_operations {
int (*open) (struct inode *, struct file *);int (*release) (struct inode *, struct file *);int (*ioctl) (struct inode *, struct file *, unsigned,
unsigned long);int (*check_media_change) (kdev_t);int (*revalidate) (kdev_t);struct module *owner;
};
主要操作 open 和 release
同字符型设备类似 check_media_change
用于支持可移动设备,不支持的设置成 NULL 检查自从上次访问以来设备是否发生过变化 kdev_t 参数为 1 表示发生变化
revalidate 主要用于支持可移动设备磁盘发生变化之后重新初始化驱动程序的状态 通常用来更新内部的状态信息以便反映出新的设备
读、写的 request 机制 对块设备的读写通过一种叫 request 的机制间接实现:
blk_init_queue (request_queue_t *queue, request_fn_proc *request)
在块设备的注册过程中,需要初始化 request队列,这一动作通过 blk_init_queue 来完成。blk_init_queue 函数建立队列,并将该驱动程序的 request 函数关联到队列
读、写的 request 机制 request_fn_proc 是一个函数指针类型,定义为 :
typedef void (request_fn_proc) (request_queue_t *q);
struct request {struct list_head queue;int elevator_sequence;kdev_t rq_dev;int cmd; /* READ or WRITE */
} rfn 函数根据输入的 request->cmd 来决定读操作和写操作。 系统内核决定何时调用 rfn 函数进行实际的物理读写操作;
目录• Linux 设备驱动简介• 内核和驱动模块• 字符型设备• 块设备• 网络接口设备• 中断处理• 常用调试技术
体系结构
主要设计内容
网络接口设备 Linux 中,把所有网络设备都抽象为一个接口,这个接口提供对所有网络设备的操作集合 一个网络接口必须在 device 数据结构中注册自己,从而在与外部世界交换包时可以被调用 网口设备既包括纯软件网络设备接口,如环路( Loopback ),也可以包括硬件网络设备接口,如以太网卡
核心数据结构 网络驱动程序的核心数据结构 net_device
该数据结构定义了所有网络设备驱动的属性和方法,包括 init 函数、 open 和 stop 函数、包发送函数 hard_ start_xmit ,以及中断处理函数等
位置 : </include/linux/netdevice.h>
注册函数 register_netdeviceregister_netdev (struct net_device *dev);
数据包操作 数据包数据结构 sk_buff
内核处理后的每一个数据包位于一个 socket 缓冲区结构 sk_buff 中
该结构定义了数据长度,数据缓冲的指针。以及各种协议头的定义位置: </Include/Linux/skbuff.h>
数据包操作 数据包发送
init
open
hard_header
hard_start_xmit
register_netdevice
数据包操作 数据包接收
见 /linux/net/core/dev.c
数据到达
产生中断信号
数据包接收程序
netif_rx
目录• Linux 设备驱动简介• 内核和驱动模块• 字符型设备• 块设备• 网络接口设备• 中断处理• 常用调试技术
安装中断处理程序 申请中断
int request_irq (unsigned int irq,void (*handler) (int irq, void *dev_id, struct pt_regs *regs),unsigned long irqflags,const char * devname,void *dev_id)
irq:要申请的硬件中断号handler: 中断发生时,系统调用它传递 dev_id 参数irqflags: 中断处理属性dev_id: 中断共享时使用
安装中断处理程序 释放中断 free_irq
void free_irq (unsigned int irq,void *dev_id) 调用时机
request_irq: 设备第一次打开、硬件被告知产生中 断之前 free_irq:最后一次关闭设备、硬件被告知不要再 中断处理器之后
快中断与慢中断 2 者区别
快中断:保证中断的原子处理, “开启中断”处理器标志位 (IF) 是关闭的,因此在服务该中断时不允许被中断。 慢中断:不保证。调用慢速中断处理时,内核启动微处理器的中断报告,因此在运行慢速中断处理程序时其它中断仍可以得到服务
中断的底半部处理机制 使用底半部机制的缘由
系统响应一次设备中断需要完成一定数量的工作,但是中断处理程序需要尽快结束而不是中断阻塞的时候过长,这两个需求彼此冲突。 两半部的功能
上半部:在屏蔽中断的上下文中运行,用于完成关键性的处理动作。底半部:完成中断事件的大多数使命,可被新的中断打断。
中断的底半部处理机制 底半部的实现方式
mark_bh方法( 2.4 以前 ) task_let方法 (2.4引入 )
• struct tasklet_struct { struct tasklet_struct *next; /* 队列指针 */ unsigned long state; /* tasklet 的状态*/ atomic_t count; /* 引用计数, 1 表示 disabled */ void (*func)(unsigned long); /* 函数指针 */ unsigned long data; /* func(data) */ };
目录• Linux 设备驱动简介• 内核和驱动模块• 字符型设备• 块设备• 网络接口设备• 中断处理• 常用调试技术
调试方法 打印调试
内核命令 printk 实现与 print 类似功能。8 种日志级别 , 根据严重程度对消息进行分类。 Linux 处理消息可以在任何地方调用 printk
查询调试 为避免大量输出信息,设置几个 ioctl命令 ioctl命令从驱动程序空间复制相关数据到进程空间,在进程空间里检查这些数据。获取 2 进制信息,速度较快
使用观察点调试strace工具
调试器和工具 最好的工具是自己的大脑 gdb
支持用户应用的基本调试功能 支持内核运行查看
kgdb 支持 x86 CPU 主机 - 目标机调试模式 在内核上进行 gdb 调试功能
©2007 ZTE corporation