Linux 系统驱动概述
description
Transcript of Linux 系统驱动概述
北 京 博 创 兴 业 科 技 有 限 公 司BEIJNG UNIVERSAL PIONEERING TECHNOLOGY Co . , LTD
博创科技 嵌入互动
Linux 系统驱动概述
博创科技
博创科技 嵌入互动
© 2010 博创科技
Linux 系统驱动概述
操作系统功能的划分 linux下的设备驱动的特点 Linux 设备的分类 典型的 Linux 字符型设备驱动结构 Linux 驱动中的数据结构 Linux 驱动中常用的操作函数 Linux 下的设备管理的问题
博创科技 嵌入互动
© 2010 博创科技
操作系统功能的划分 进程管理 内存管理 文件系统 设备控制 网络功能
博创科技 嵌入互动
© 2010 博创科技
linux 下的设备驱动的特点
linux 下的设备控制由驱动程序完成: Linux 下对外设的访问只能通过驱动程序
Linux 对于驱动程序有统一的接口,以文件的形式定义系统的驱动程序:Open、 Release、 read、write、 ioctl…
驱动程序属于内核的一部分,可以使用中断、 DMA 等操作
驱动程序需要在用户态和内核态之间传递数据,是应用程序与内核及外设通讯的桥梁
uClinux 下可以在应用层直接访问外设,操作寄存器,但是无法处理中断——不推荐使用
博创科技 嵌入互动
© 2010 博创科技
Linux 设备的分类
按照上述系统内核的功能, Linux 中把系统的设备定义成如下三类:
字符设备 支持面向字符的 I/O 操作 使用独立的缓冲区结构 顺序存取数据
块设备 仅支持面向块的 I/O 操作 I/O 操作都通过在内核地址空间中的 I/O 缓冲区进行 随机存取:支持几乎任意长度和任意位置上的 I/O 请求
网络设备
博创科技 嵌入互动
© 2010 博创科技
典型的 Linux 字符型设备驱动所包含的内容
初始化函数: xxx_init ,向操作系统注册及硬件初始化(包括中断的request_irq 和使能)
主体代码 : file_operations 里面注册的操作函数 中断处理函数
示例
博创科技 嵌入互动
© 2010 博创科技
Linux 驱动中的数据结构 - file_operations
file_operations 数据结构,定义在 include/linux/fs.h 中,驱动程序很大一部分工作就是要“填写”结构体中定义的函数。
lseek :移动文件指针的位置,只能用于随机存取设备read :读操作write :写操作,与 read 类似readdir :取得下一目录节点,文件系统相关的设备驱动程序使用select :选择操作,如果没有提供将会认为设备已经准备好ioctl :读、写以外的其它操作,参数为自定义的的命令数mmap :内核空间到用户空间的映射open :打开设备,为 I/O 操作做准备release :即 close 操作
博创科技 嵌入互动
© 2010 博创科技
Linux 驱动中的数据结构 - file_operations
Open 函数 Open 方法提供给驱动程序初始化设备的能力,从而为以后的设备操
作做好准备,此外 open 操作一般还会递增使用计数,用以防止文件关闭前模块被卸载出内核。在大多数驱动程序中 Open 方法应完成如下工作:
递增使用计数1. 检查特定设备错误。2. 如果设备是首次打开,则对其进行初始化。3. 识别次设备号,如有必要修改 f_op 指针。4. 分配并填写 filp->private_data 中的数据。
博创科技 嵌入互动
© 2010 博创科技
Linux 驱动中的数据结构 - file_operations
Release 函数与 open 方法相反, release 方法应完成如下功能:1. 释放由 open 分配的 filp->private_data 中的所有内容2. 在最后一次关闭操作时关闭设备3. 使用计数减一
博创科技 嵌入互动
© 2010 博创科技
Linux 驱动中的数据结构 - file_operations
Read和Write 函数 read 方法完成将数据从内核拷贝到应用程序空间, write 方法相反,
将数据从应用程序空间拷贝到内核。对于者两个方法,参数 filp 是文件指针, count 是请求传输数据的长度, buffer 是用户空间的数据缓冲区, ppos 是文件中进行操作的偏移量,类型为 64 位数。由于用户空间和内核空间的内存映射方式完全不同,所以不能使用象memcpy 之类的函数,必须使用如下函数:
unsigned long copy_to_user (void *to,const void *from,unsigned long count);
unsigned long copy_from_user(void *to,const void *from,unsigned long count);
博创科技 嵌入互动
© 2010 博创科技
Linux 驱动中的数据结构 - file_operations
ioctl 函数 ioctl 方法主要用于对设备进行读写之外的其他控制,比如配置设备、
进入或退出某种操作模式,这些操作一般都无法通过 read/write 文件操作来完成,比如在 UP-NETARM2410-S 中的 SPI 设备通道的选择操作,无法通过 write 操作控制,这就是 ioctl 操作的功能。
驱动程序中定义的 ioctl 方法原型为: int (*ioctl) (struct inode *inode, struct file *file,unsigned int cmd,
unsigned long arg)
inode 和 filp两个指针对应应用程序传递的文件描述符 fd,cmd 不会被修改地传递给驱动程序,可选的参数 arg 则无论用户应用程序使用的是指针还是其他类型值,都以 unsigned long 的形式传递给驱动。
博创科技 嵌入互动
© 2010 博创科技
Linux 驱动中的数据结构
inode 结构: 提供关于设备文件的 /dev/xxx 的信息
file 结构: 提供关于被打开的文件的信息
博创科技 嵌入互动
© 2010 博创科技
Linux 驱动中常用的操作函数
申请中断: int request_irq(unsigned int irq, void (*handler)(int, void *, struct
pt_regs *), unsigned long irq_flags, const char * devname, void *dev_id)
释放中断: void free_irq(unsigned int irq, void *dev_id);
申请内存: void * kmalloc(unsigned int len, int priority); 释放内存: void kfree(void * obj); 访问 I/O端口: inb, outb, inw, outw; inl, outl 时钟、定时器有关的系统函数: add_timer, del_timer, init_timer 打开和关闭中断允许: cli(), sti() 内核打印: printk
博创科技 嵌入互动
© 2010 博创科技
设备管理的问题
如今, Linux 支持很多不同种类的硬件。这意味着 /dev 中都有数百个特殊文件来表示所有这些设备。而且,这些特殊文件中大多数甚至不会映射到系统中存在的设备上
博创科技 嵌入互动
© 2010 博创科技
Linux 设备驱动的路径 Linux 的设备以文件的形式存在于 /dev 目录下 设备文件是特殊文件,使用 ls /dev -l 命令可以看到:
c- 字符设备 b- 块设备 l- 符号连接
crw------- 1 root root 10, 7 Aug 31 2002 amigamouse1 crw------- 1 root root 10, 134 Aug 31 2002 apm_bios brw-rw---- 1 root disk 29, 0 Aug 31 2002 aztcd lr-xr-xr-x 1 root root 9 Dec 26 05:52 tty0 -> /dev/vc/0
博创科技 嵌入互动
© 2010 博创科技
主设备号和次设备号
主设备号标识设备对应的驱动程序一个驱动程序可以控制若干个设备,次设备号提供了一种区分它们的方
法系统增加一个驱动程序就要赋予它一个主设备号。这一赋值过程在驱动
程序的初始化过程中进行:对于字符设备:int register_chrdev(unsigned int major, const char*name,struct
file_operations *fops);
对于查看 /dev 目录下的设备的主次设备号可以使用如下命令:[/mnt/yaffs]ls /dev -l crw------- 1 root root 5, 1 Jan 1 00:00 console crw------- 1 root root 5, 64 Jan 1 00:00 cua0
博创科技 嵌入互动
© 2010 博创科技
动态分配设备号
在 Documentation/device.txt 文件中可以找到已经静态分配给大部分设备的列表
由于许多数字已经分配了,为新设备选择一个唯一的号码是很困难的 如果调用 register_chrdev 时的 major 为零,函数就会选择一个空闲号
码并做为返回值返回
博创科技 嵌入互动
© 2010 博创科技
动态分配的问题
动态分配的主设备号不能保证总是一样的,无法事先创建设备节点
可以从 /proc/devices 读取cat /proc/devices
利用脚本动态创建设备文件节点
博创科技 嵌入互动
© 2010 博创科技
使用设备文件系统 --devfs
在 Linux 2.4 的内核里引入了 devfs来解决linux 下设备文件管理的问题
在驱动程序中通过 devfs_register() 函数创建设备文件系统的节点 其原型为: devfs_register( devfs_handle_t dir, const char *name,
unsigned int flags,unsigned int major, unsigned int minor, umode_t mode, void *ops, void *info)
系统启动的时候mount 设备文件系统 所有需要的设备节点都由内核自动管理。
/dev 目录下只有挂载的设备
注:在 linux-2.6.12 之后的内核的解决办法: udev 文件系统 http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html
博创科技 嵌入互动
© 2010 博创科技
使用设备文件系统 -- udev
udev 完全在用户态 (userspace) 工作,利用设备加入或移除时内核所发送的 hotplug 事件 (event) 来工作。
关于设备的详细信息是由内核输出 (export) 到位于 /sys 的 sysfs 文件系统的。所有的设备命名策略、权限控制和事件处理都是在用户态下完成的。
与此相反, devfs 是作为内核的一部分工作的。
博创科技 嵌入互动
© 2010 博创科技
创建设备节点 --mknod
mknod /dev/xxx c major minor 关于主次设备号: major + minor 上层应用对驱动的调用:
fd=open(“/dev/xxx”,...); file_operations xxx_open read( fd,buffer,count) ; file_operations xxx_ read
博创科技 嵌入互动
© 2010 博创科技
Linux 驱动程序原理
应用程
序
驱动程
序
硬件相
关的
寄存
器
WriteIoctl
ReadIoctl
fd=open(“/dev/xxx”,...) file_operations xxx_open write( fd,buffer,count) file_operations xxx_ write read( fd,buffer,count) file_operations xxx_ read
博创科技 嵌入互动
© 2010 博创科技
Linux 下的驱动加载方式
一种是直接编译到内核,当内核启动之后,新的驱动程序随之运行; 二是编译为模块,动态加载运行
对模块操作需要使用 module-utiles : insmod 将编译的模块直接插入内核 rmmod 从内核中卸载模块 lsmod显示已安装的模块
gcc编译参数: -D__KERNEL__ -DMODULE –I$(KERNELDIR_INCLUDE)
在调试的过程中一般使用模块动态加载的方式,它的调试效率较高。当驱动调试完成后,在发行的过程就集成进内核。但编译进内核是某些驱动运行的唯一方法。例如: console 驱动, flash 驱动和对至少一种文件系统的支持等等。
博创科技 嵌入互动
© 2010 博创科技
Linux 下的驱动加载方式
module_init: insmod 时自动调用,负责模块初始化工作。 对应的操作是 module_init
module_exit :卸载时调用,负责清除工作。 对应的操作是 module_exit
参见: kernel/include/linux/init.h
博创科技 嵌入互动
© 2010 博创科技
module_init( 1)
include/linux/init.h 中#define module_init(x) __initcall(x);
#define __initcall(fn) \
static initcall_t __initcall_##fn __init_call = fn
static initcall_t
__initcall_name_of_initialization_routine __init_call = name_of_initialization_routine
博创科技 嵌入互动
© 2010 博创科技
module_init( 2)
include/linux/init.h 中定义:
#define __init_call __attribute__ ((unused,__section__ (".initcall.init")))typedef int (*initcall_t)(void);
arch/arm/vmlinux-armv.lds.in 文件中:__initcall_start = .;
*(.initcall.init)__initcall_end = .;. = ALIGN(4096);__init_end = .;
init/main.c 文件中定义了 do_initcalls 函数
博创科技 嵌入互动
© 2010 博创科技
__init宏
在 include/linux/init.h 中 对于非模块加载的驱动程序:#define __init __attribute__ \ ((__section__ (".text.init")))
通过 __init ,会把函数中的代码放到 .text.init段。这个段在系统启动以后会被释放。在系统内核启动以后,会看到:
Freeing init memory: 68K
博创科技 嵌入互动
© 2010 博创科技
Linux 下驱动的调试1 、 使用 printk 函数
printk 函数中可以使用附加不同的日志级别或消息优先级
2 、使用 /proc 文件系统内核利用它向外输出信息
3 、使用 ioctl 方法ioctl 系统调用会调用驱动的 ioctl 方法,我们可以通过设
置不同的命名号来编写一些测试函数,使用 ioctl 系统调用在用户级调用这些函数进行调试。
4 、使用 strace 命令进行调试strace 命令是一个功能强大的工具,它可以显示用户空
间的程序发出的全部系统调用,不仅可以显示调用,还可以显示调用的参数和用符号方式表示的返回值。
博创科技 嵌入互动
© 2010 博创科技
Linux 下的中断驱动
中断概念 基本定义:中断,异常,中断源,中断嵌套,中断优先级等 中断向量:标识中断的无符号数,组成中断向量表。 ARM有 7个:
复位:当复位电平有效时产生。程序跳转到复位异常处理程序处 未定义指令:遇到不能处理的指令时产生 软件中断:执行 SWI 指令产生。用于实现系统调用功能 指令预取中止:预取指令的地址不存在,或该地址不允许当前指令访问,发出中止信号,但指令被执行时产生异常
数据中止:数据访问指令的地址不存在,或该地址不允许当前指令访问时,产生数据中止异常
IRQ :标准中断请求 FIQ :快速中断请求
博创科技 嵌入互动
© 2010 博创科技
Linux 下的中断驱动
地址 异常
0x0000,0000 复位
0x0000,0004 未定义指令
0x0000,0008 软件中断
0x0000,000C 终止(预取指令)
0x0000,0010 终止(数据)
0x0000,0014 保留
0x0000,0018 IRQ
0x0000,001C FIQ
博创科技 嵌入互动
© 2010 博创科技
ARM 的中断过程
中断的进入 将下一条指令的地址存入相应连接寄存器 LR 将 CPSR复制到相应的 SPSR中 根据中断 /异常类型,强制设置 CPSR的运行模式位 强制 PC从相关的异常向量地址取下一条指令执行,从而跳转到相应的异常处理程序
从中断返回 将连接寄存器 LR的值减去相应的偏移量后送到 PC中 将 SPSR复制回 CPSR中 如果进入时设置了中断禁止位,那么清除该标志 中断返回
注意:给 IRQ脚中断信号前,必须先打开该中断的使能寄存器和正确设置对应的屏蔽寄存器。当这两个寄存器都设置正确了,中断产生了, CPU保存当前程序运行环境,跳到中断入口, ARM芯片一般是 0x18地址处。设置好中断向量,中断向量一般是个跳转语句,跳到正式的中断处理过程,在这里可以关闭所有中断,清中断,处理等等,然后退出。某些处理器一定要清中断,否则下次再给中断信号时就没有反应了 。
博创科技 嵌入互动
© 2010 博创科技
中断相关函数说明 int set_external_irq(int irq, int edge, int pullup);
功能:设置中断线相应的属性,完成注册中断前的设置 参数: irq :需要设置的中断号。可以自定义数值,也可以直接使用 kernel/arch/arm/kernel/irqs.h 中预定义的值。例如:
IRQ_EINT0 , IRQ_EINT4_7, IRQ_DMA0 等
edge :中断触发方式,可以是EXT_LOWLEVEL, EXT_HIGHLEVEL, EXT_FALLING_EDGE, EXT_RISING_EDGE, EXT_BOTH_EDGES
pullup :是否拉高。可以设为 GPIO_PULLUP_EN 和 GPIO_PULLUP_DIS 方式。当设为 GPIO_PULLUP_EN 时,在 GPIO 没有输入输出时,线路保持高电平。否则始终保持低电平。一般需要设置成拉高状态
void enable_irq(unsigned int irq); 参数: irq: set_external_irq 中设置的中断线号 功能:使能所选的中断线
void disable_irq(unsigned int irq); 参数: 功能:使得所选择的中断线无效 和 enable_irq 配对使用,实现相反功能
博创科技 嵌入互动
© 2010 博创科技
中断相关函数说明 int request_irq(unsigned int irq, void (*handler)(int,void *,struct pt_regs
*), unsigned long irq_flags,const char * devname, void * dev_id); 功能:定位中断源,使能中断线和 IRQ 处理函数 irq :外设所使用的中断号 handler 函数指针:设备驱动程序所实现的 ISR 函数 irqflags :中断请求类型标志,可以是下列值的“或”:
SA_SHIRQ :中断是共享的。此时要求参数 dev_id 必须有效,不能为NULL; IRQ 号参数 irq 不能大于 NR_IRQS;且 handler 指针不能为NULL 。否则将出现错误号为 -22 的参数无效错,无法注册中断服务程序
SA_INTERRUPT :当处理中断时,其他局部中断不可用 SA_SAMPLE_RANDOM :中断可以作为随机计量单位熵使用
devname 指针:设备名字字符串 dev_id :指向全局唯一的设备标识 ID, void 类型的指针,供驱动程
序自行解释
博创科技 嵌入互动
© 2010 博创科技
中断相关函数说明
void free_irq(unsigned int irq, void *dev_id); 功能:释放和中断号绑定的中断处理函数 注意:必须和 register_irq() 配对使用,它的参数必须和
register_irq() 里注册的参数一致。 dev_id 不能是中断服务程序地址,否则执行 rmmod删除该将出现错误,必须重启系统才能再次 insmod该模块
示例
博创科技 嵌入互动
© 2010 博创科技
功能描述
编写简单的虚拟硬件驱动程序并进行调试 参考驱动代码框架如下,其中的 demo_read, demo_write 函数完成驱
动的读写接口功能, do_write 函数实现将用户写入的数据逆序排列,通过读取函数读取转换后的数据。这里只是演示接口的实现过程和内核驱动对用户的数据的处理。 demo_ioctl 函数演示 ioctl调用接口的实现过程。
博创科技 嵌入互动
© 2010 博创科技
static int demo_open(struct inode *inode, struct file *filp)
{
/* 初始化一些资源,如将缓存区清零等。有些资源在设备未打开即未被 * 使用之前最好不要打开,如中断、时钟等。 */
memset(wbuff, 0, BUFFSIZE);
memset(rbuff, 0, BUFFSIZE);
count = 0;
return 0;
}
static int demo_close(struct inode *inode, struct file *filp)
{
return 0;
}
博创科技 嵌入互动
© 2010 博创科技
static ssize_t demo_read(struct file *filp, char *buf, size_t cnt, loff_t *off){
if(count < cnt)cnt = count;
if(cnt)copy_to_user(buf, rbuff, cnt); /* 向应用程序传输数据 */
return cnt;}static ssize_t demo_write(struct file *filp, const char *buf,
size_t cnt, loff_t *off){
if(cnt > BUFFSIZE)cnt = BUFFSIZE;
copy_from_user(wbuff, buf, cnt); /* 接受来自应用程序的数据 */count = cnt;return cnt;
}
博创科技 嵌入互动
© 2010 博创科技
static int demo_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
switch(cmd) {
default:
break;
}
return 0;
}
博创科技 嵌入互动
© 2010 博创科技
static struct file_operations demo_fops = {
owner: THIS_MODULE,
write: demo_write,
read: demo_read,
ioctl: demo_ioctl,
open: demo_open,
release: demo_close,
};
博创科技 嵌入互动
© 2010 博创科技
static int __init demo_init(void){
int result;/* 为设备驱动程序分配资源,比如分配一些内存
*我们分配两小片的缓存区给驱动程序。当向设备写数据时,数据被保存在* 写缓存区 (wbuff) 中,当从设备读数据时, 数据从读缓存区 (rbuff) 中读出。驱动程
序负责将数据从 wbuff 写入 rbuff 。 */wbuff = kmalloc(BUFFSIZE, GFP_KERNEL);rbuff = kmalloc(BUFFSIZE, GFP_KERNEL);/* 注册设备 */result = register_chrdev(DEMO_MAJOR,, “demo”, &demo_fops);if(result < 0) { /* 如果设备注册出错,打印错误提示,返回错误代码 */
printk(KERN_ERR “Cannot register demo device.”);return -EIO;
}return 0; /* 一般返回 0表示没有错误 */
}
博创科技 嵌入互动
© 2010 博创科技
static void __exit demo_exit(void){
int result;/* 移除设备 */
result = unregister_chrdev(DEMO_MAJOR, "demo");if(result < 0) {
printk(KERN_ERR "Cannot remove demo device.\n");return;
}/* 释放存储区 */
if(wbuff) kfree(wbuff);
if(rbuff)kfree(rbuff);
return;}MODULE_LICENSE("GPL"); module_init(demo_init); /*标记模块初始化函数 */module_exit(demo_exit); /*标记模块清除函数 */
博创科技 嵌入互动
© 2010 博创科技
用户测试程序 test_demo.c
编写 test_demo.c测试驱动程序 参见实验指导书
博创科技 嵌入互动
© 2010 博创科技
实验步骤
参见实验指导书
博创科技 嵌入互动
© 2010 博创科技
设备文件系统 devfs
2.4版本 Linux 内核中引入了设备文件系统 Device Filesystem( devfs),所有的设备文件作为一个可以挂装的文件系统,这样就可以被文件系统进行统一管理,从而设备文件就可以挂装到任何需要的地方。命名规则也发生了变化,一般将主设备建立一个目录,再将具体的子设备文件建立在此目录下。比如在 UP-NETARM2410-S 中的 MTD 设备为: /dev/mtdblock/0 。使用 devfs 要求内核编译时定义了符号CONFIG_DEVFS_FS
博创科技 嵌入互动
© 2010 博创科技
与 devfs 有关的函数
devfs_register 函数用于向内核注册设备文件,其原型为:devfs_handle_t devfs_register( devfs_handle_t dir, const char *name,
unsigned int flags,unsigned int major, unsigned int minor, umode_t mode, void *ops, void *info)
devfs_mk_dir 函数用于将设备文件创建在一个特定的目录内,其原型:devfs_handle_t devfs_mk_dir( devfs_handle_t dir, const char *name,
void *info)
devfs_unregister 函数用于移除设备文件,其原型为:void devfs_unregister(devfs_handle_t deventry);
博创科技 嵌入互动
© 2010 博创科技
参数说明
dir新创建的设备文件的父目录,一般设为 null, 表示父目录为 /dev name 设备名称 , 如想包含子目录 , 可以直接在名字中包含’ /’ flagsdevfs标志的位掩码。 major 主设备号如果在 flags 参数中指定为 DEVFS_FL_AUTO_DEVNUM ,
则主次设备号就无用了。 minor 次设备号 mode 设备的访问模式 ops 设备的文件操作数据结构指针 infofilp->private_data 的默认值。 deventry 指向 devfs_register调用后获得的 devfs 入口
博创科技 嵌入互动
© 2010 博创科技
下面在驱动程序里使用 devfs ,请各位老师根据已给出的程序完成使用 devfs的 demo 程序
博创科技 嵌入互动
© 2010 博创科技
………………………………………………………#ifdef CONFIG_DEVFS_FSstatic devfs_handle_t devfs_demo_dir, devfs_demoraw;#endif ………………………………………………………static int __init demo_init(void){ /*other code here*/#ifdef CONFIG_DEVFS_FS devfs_demo_dir = devfs_mk_dir(NULL, "demo", NULL); devfs_demoraw = devfs_register(devfs_demo_dir, "0", DEVFS_FL_DEFAULT, DEMO_MAJOR, DEMO_MINOR, S_IFCHR | S_IRUSR | S_IWUSR, &demo_fops, NULL);#endif /*other code here*/
}static void __exit demo_exit(void){ /*other code here*/#ifdef CONFIG_DEVFS_FS
devfs_unregister(devfs_demoraw);devfs_unregister(devfs_demo_dir);
#endif /*other code here*/
博创科技 嵌入互动
© 2010 博创科技
在驱动模块成功插入后,会在 /dev 下面建立一个叫做 demo 的设备文件,若使用 devfs ,则无需建立设备节点,插入模块后系统会自动建立 /dev/demo/0 文件节点。