1、1TMCIDDCCIDDC Linux设备驱动程序设备驱动程序开发基础开发基础主讲人主讲人:TonyShen:TonyShen2TMCIDDC2Linux 驱 动 程 序 开 发 基 础课程目标课程目标nLinux设备驱动程序开发简介设备驱动程序开发简介nLinux设备驱动程序结构设备驱动程序结构nLinux设备驱动程序加载方式设备驱动程序加载方式n实验实验:编写一个字符设备驱动程序编写一个字符设备驱动程序(LED或蜂鸣器或蜂鸣器)分别用静态编译分别用静态编译,模块动态加载方法实现加入内核模块动态加载方法实现加入内核3TMCIDDC3Linux 驱 动 程 序 开 发 基 础设备驱动程序设备驱
2、动程序应应用用程程序序驱驱动动程程序序设设备备writereadioctlioctl4TMCIDDC4Linux 驱 动 程 序 开 发 基 础设备驱动程序特点设备驱动程序特点n核心代码:核心代码:设备驱动程序是核心的一部分,像核心中其他的代码一样,出错将导致系统的严重损伤。一个编写不当的设备驱动程序甚至能够使系统崩溃导致文件系统的破坏和数据的丢失;n标准接口:标准接口:设备驱动程序必须为Linux核心或者其从属的子系统提供一个标准的接口;n核心机制:核心机制:设备驱动程序可以使用标准的核心服务比如内存分配、中断发送和等待对列等;n动态可加载:动态可加载:多数的Linux设备驱动程序可以在核心
3、模块发出加载请求时进行加载,同时在不使用设备时进行卸载,这样核心可以有效地利用系统的资源n可配置:可配置:Linux设备驱动属于核心的一部分,用户可以根据自己的需要进行配置来选择适合自己的驱动5TMCIDDC5Linux 驱 动 程 序 开 发 基 础用户态与内核态用户态与内核态nLinux运转在两种模式下,一种是用户态,另一种是内核态。运转在两种模式下,一种是用户态,另一种是内核态。n内核态有较高的权限,可以控制处理器内存的映射和分配方内核态有较高的权限,可以控制处理器内存的映射和分配方式,访问外设空间和处理器状态寄存器,控制中断等。式,访问外设空间和处理器状态寄存器,控制中断等。n用户态只
4、能运行系统上的应用程序。用户态只能运行系统上的应用程序。n驱动程序作为系统内核的一部分,工作在内核态。驱动程序作为系统内核的一部分,工作在内核态。n通过通过get_user put_user copy_from_user copy_to_user等函数实现应用程序和驱动程序之间传送数据(指针)。等函数实现应用程序和驱动程序之间传送数据(指针)。6TMCIDDC6Linux 驱 动 程 序 开 发 基 础Linux设备的分类设备的分类n字符设备字符设备n以字节为单位逐个进行以字节为单位逐个进行I/O操作操作n字符设备中的缓存是可有可无字符设备中的缓存是可有可无n不支持随机访问不支持随机访问n如串
5、口设备如串口设备n块设备块设备n块设备的存取是通过块设备的存取是通过buffer、cache来进行来进行n可以进行随机访问可以进行随机访问n例如例如IDE硬盘设备硬盘设备n可以支持可安装文件系统可以支持可安装文件系统n网络设备网络设备n通过通过BSD套接口访问套接口访问7TMCIDDC7Linux 驱 动 程 序 开 发 基 础设备文件设备文件nLinux抽象了对硬件的处理,所有的硬件设备都可以作为普抽象了对硬件的处理,所有的硬件设备都可以作为普通文件一样来看待通文件一样来看待n可以使用和操作文件相同的、标准的系统调用接口来完成打可以使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读
6、写和开、关闭、读写和I/O控制操作控制操作n对用户来说,设备文件与普通文件并无区别对用户来说,设备文件与普通文件并无区别n字符设备和块设备是通过文件节点访问的。在字符设备和块设备是通过文件节点访问的。在Linux的文件的文件系统中,可以找到(或者使用系统中,可以找到(或者使用mknod创建)设备对应的文创建)设备对应的文件名,称这种文件为设备文件件名,称这种文件为设备文件。8TMCIDDC8Linux 驱 动 程 序 开 发 基 础设备文件设备文件n命令命令 ls l/dev 可列出系统的设备文件可列出系统的设备文件9TMCIDDC9Linux 驱 动 程 序 开 发 基 础主设备号和次设备号
7、主设备号和次设备号n主设备号:标识该设备的种类,也标识了该设备所使用的驱主设备号:标识该设备的种类,也标识了该设备所使用的驱动程序动程序n主设备号的范围只能是1-255nLinux内核支持动态分配主设备号n次设备号:标识使用同一设备驱动程序的不同硬件设备次设备号:标识使用同一设备驱动程序的不同硬件设备n同一个驱动程序可以管理多个设备,它们依靠次设备号来区同一个驱动程序可以管理多个设备,它们依靠次设备号来区别。次设备号只在驱动程序内部使用,系统内核直接把次设别。次设备号只在驱动程序内部使用,系统内核直接把次设备号传递给驱动程序,由驱动程序去管理。备号传递给驱动程序,由驱动程序去管理。10TMCI
8、DDC10Linux 驱 动 程 序 开 发 基 础设备文件系统设备文件系统nLinux内核自己管理设备文件,完成设备文件节点的创建、内核自己管理设备文件,完成设备文件节点的创建、删除。(删除。(devfs,device file system)n在在linux2.4/2.6内核中,设备文件系统可在配置内核时定制。内核中,设备文件系统可在配置内核时定制。nlinux2.4内核配置层次n File systems-/dev file system supportnlinux2.6内核配置层次 nFile systems-Pseudo file system-/dev file system su
9、pportn系统驱动程序通过系统驱动程序通过CONFIG_DEVFS_FS宏定义判断系统是宏定义判断系统是否有对设备文件系统的支持。否有对设备文件系统的支持。11TMCIDDC11Linux 驱 动 程 序 开 发 基 础配置设备文件系统配置设备文件系统12TMCIDDC12Linux 驱 动 程 序 开 发 基 础Linux设备驱动程序结构设备驱动程序结构nLinux设备驱动程序的代码结构大致可以分为如下几个部分:设备驱动程序的代码结构大致可以分为如下几个部分:n驱动程序的注册与注销、设备的打开与释放、设备的读写操作、设驱动程序的注册与注销、设备的打开与释放、设备的读写操作、设备的控制操作、
10、设备的中断和轮询处理。备的控制操作、设备的中断和轮询处理。n一个最简单字符驱动程序,由下面一个最简单字符驱动程序,由下面5个函数和个函数和1个结构体就可组成。个结构体就可组成。static int my_open(struct inode*inode,struct file*filp)设备打开时的操作设备打开时的操作 static int my_release(struct inode*inode,struct file*filp)设备关闭时的操作设备关闭时的操作 static int my_write(struct file*file,const char*buffer,size_t cou
11、nt,loff_t*ppos)设备写入时的操作设备写入时的操作 13TMCIDDC13Linux 驱 动 程 序 开 发 基 础驱动程序结构驱动程序结构static struct file_operations my_fops=对文件操作结构体成员定义初始值对文件操作结构体成员定义初始值static int _init my_init(void)初始化硬件,注册设备,创建设备节点初始化硬件,注册设备,创建设备节点 static void _exit my_exit(void)删除设备节点,注销设备删除设备节点,注销设备 14TMCIDDC14Linux 驱 动 程 序 开 发 基 础头文件与系
12、统定义头文件与系统定义#include#include#include#include#include#include#include#include 9200.h“#ifndef _KERNEL_#define _KERNEL_#endif#ifndef MODULE#define MODULE#endif#define LED AT91C_PIO_PB11static AT91PS_SYS AT91_SYS1=(AT91PS_SYS)AT91C_VA_BASE_SYS;#define DEVICE_NAME=“my_led”static int Led_Major=0;#ifdef CON
13、FIG_DEVFS_FSstatic devfs_handle_t Devfs_Led_Dir,Devfs_Led_Raw;#endif15TMCIDDC15Linux 驱 动 程 序 开 发 基 础打开和关闭操作打开和关闭操作nmy_open和和my_release函数会在设备打开和关闭时被调用,其工作很简单,仅函数会在设备打开和关闭时被调用,其工作很简单,仅仅执行两个宏:仅执行两个宏:”MOD_INC_USE_COUNT”,”MOD_DEC_USE_COUNT”。这两个宏负责记录(增加或者减少)设备模块被使用的情况,防止当有应用程这两个宏负责记录(增加或者减少)设备模块被使用的情况,防止当
14、有应用程序使用驱动程序时,此模块被意外地卸载。序使用驱动程序时,此模块被意外地卸载。static int my_open(struct inode*inode,struct file*filp)MOD_INC_USE_COUNT;return 0;static int my_release(struct inode*inode,struct file*filp MOD_DEC_USE_COUNT return 0;16TMCIDDC16Linux 驱 动 程 序 开 发 基 础写入操作写入操作static int my_write(struct file*file,const char*buf
15、fer,size_t count,loff_t*ppos)char led_status=0;copy_from_user(&led_status,buffer,sizeof(led_status);if(led_status=0 x01)/如果应用程序传来的数据是如果应用程序传来的数据是0 x01AT91F_PIOB_SetOutput(LED);/打开打开LED elseAT91F_PIOB_ClearOutput(LED);/关闭关闭LED return 0;17TMCIDDC17Linux 驱 动 程 序 开 发 基 础文件操作结构体初始化文件操作结构体初始化static struct
16、 file_operations my_fops=open:my_open,write:my_write,release:my_release,;18TMCIDDC18Linux 驱 动 程 序 开 发 基 础struct file_operationsstruct 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*);int(*
17、readdir)(struct file*,void*,filldir_t);unsigned int(*poll)(struct file*,struct poll_table_struct*);int(*ioctl)(struct inode*,struct file*,unsigned int,unsigned long);int(*mmap)(struct file*,struct vm_area_struct*);int(*open)(struct inode*,struct file*);int(*flush)(struct file*);int(*release)(struct
18、inode*,struct file*);int(*fsync)(struct file*,struct dentry*,intdatasync);int(*fasync)(int,struct file*,int);int(*lock)(struct file*,int,struct file_lock*);ssize_t(*readv)(struct file*,const struct iovec*,unsigned long,loff_t*);ssize_t(*writev)(struct file*,const struct iovec*,unsigned long,loff_t*)
19、;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);文件操作结构体文件操作结构体19TMCIDDC19Linux 驱 动 程 序 开 发 基 础设备初始化设备初始化static int _init my_init(void)/硬件初始化硬件初始化 AT91F_PIOB_Enable(LED);AT91F
20、_PIOB_OutputEnable(LED);/字符设备注册字符设备注册Led_Major=register_chrdev(0,DEVICE_NAME,&my_fops);if(Led_Major 0)printk(DEVICE_NAME cant get major numbern);return Led_Major;/创建设备文件创建设备文件#ifdef CONFIG_DEVFS_FS Devfs_Led_Dir=devfs_mk_dir(NULL,led,NULL);Devfs_Led_Raw=devfs_register(Devfs_Led_Dir,0,DEVFS_FL_DEFAUL
21、T,Led_Major,1,S_IFCHR|S_IRUSR|S_IWUSR,&my_fops,NULL);#endif20TMCIDDC20Linux 驱 动 程 序 开 发 基 础设备注销设备注销static void _exit my_exit(void)/删除设备文件删除设备文件#ifdef CONFIG_DEVFS_FSdevfs_unregister(Devfs_Led_Raw);devfs_unregister(Devfs_Led_Dir);#endif/注销设备注销设备unregister_chrdev(Led_Major,DEVICE_NAME);module_init(my_
22、init);/向向Linux系统记录设备初始化的函数名称系统记录设备初始化的函数名称module_exit(my_exit);/向向Linux系统记录设备退出的函数名称系统记录设备退出的函数名称21TMCIDDC21Linux 驱 动 程 序 开 发 基 础驱动程序编译驱动程序编译nMakefile文件内容文件内容OBJ=io_led.oSOURCE=io_led.cCC=arm-linux-gccCOMP=-Wall-O2-DMODULE-D_KERNEL_-I/home/armlinux/linux-2.4.19-rmk7/include c$(OBJ):$(SOURCE)$(CC)$(C
23、OMP)$(SOURCE)clean:rm$(OBJ)n运行运行make 命令命令,编译通过后当前目录下就生成名为编译通过后当前目录下就生成名为io_led.o的驱动程序的驱动程序22TMCIDDC22Linux 驱 动 程 序 开 发 基 础驱动程序加载驱动程序加载nLinux内核有内核有2种加载驱动程序的方法种加载驱动程序的方法:nLinux系统启动时系统启动时,通过代码自身加载模块通过代码自身加载模块.这种方式称为静态编译入这种方式称为静态编译入内核内核,驱动程序开发完毕后一般使用这种方式驱动程序开发完毕后一般使用这种方式.nLinux系统启动后系统启动后,通过通过insmod等命令加载
24、模块等命令加载模块.这种方式称为动态加这种方式称为动态加载载,驱动程序开发调试过程中一般使用这种方式驱动程序开发调试过程中一般使用这种方式.23TMCIDDC23Linux 驱 动 程 序 开 发 基 础模块动态加载模块动态加载n驱动程序模块插入内核驱动程序模块插入内核n查看是否载入查看是否载入,如果载入成功会显示你的设备名称如果载入成功会显示你的设备名称my_ledn从内核移除设备从内核移除设备#insmod io_led.o#cat/proc/devices#rmmod io_led24TMCIDDC24Linux 驱 动 程 序 开 发 基 础静态编译进内核静态编译进内核n1.程序放入内
25、核目录程序放入内核目录nlinux-2.49-rmk7driverscharn2.修改修改linux-2.49-rmk7driverscharMakefile文件文件n添加 obj-$(CONFIG_9200LED)+=io_led.on3.修改修改linux-2.49-rmk7driverscharConfig.in文件文件n添加 tristate Support 9200LED device CONFIG_9200LEDn4.配置内核配置内核,编译内核编译内核.n在配置菜单的字符设备中会增加 Support 9200LED device 项目25TMCIDDC25Linux 驱 动 程 序
26、 开 发 基 础内核中配置模块内核中配置模块n配置内核加载驱动程序配置内核加载驱动程序26TMCIDDC26Linux 驱 动 程 序 开 发 基 础驱动测试应用程序驱动测试应用程序int main(void)int fd;char led_on=0 x01;fd=open(/dev/led/0,O_RDWR);/打开打开led设备设备 if(fd=-1)printf(can not open devicen);exit(1);write(fd,&led_on,1);/LED开开close(fd);/关闭设备文件关闭设备文件return 0;27TMCIDDC27Linux 驱 动 程 序 开 发 基 础问题回顾问题回顾n设备驱动程序功能与特点设备驱动程序功能与特点?n什么是用户态什么是用户态,内核态内核态.主设备号主设备号,次设备号次设备号,设备文件系统设备文件系统?n设备驱动程序基本结构设备驱动程序基本结构?n设备驱动程序加载方式设备驱动程序加载方式?28TMCIDDCCIDDC 谢谢 谢谢!