Presentation is loading. Please wait.

Presentation is loading. Please wait.

设备驱动程序. 2 设备驱动程序的作用 设备驱动程序是一个软件层,该软件层使硬件 设备响应预定义好的编程接口,我们已经熟悉 这些接口,它由一组控制设备的函数 (open,read,ioctl 等等 ) 组成,这些函数的实际实 现由设备驱动程序全权负责。 设备驱动程序 ( 应该只是 ) 为系统的其它部分提.

Similar presentations


Presentation on theme: "设备驱动程序. 2 设备驱动程序的作用 设备驱动程序是一个软件层,该软件层使硬件 设备响应预定义好的编程接口,我们已经熟悉 这些接口,它由一组控制设备的函数 (open,read,ioctl 等等 ) 组成,这些函数的实际实 现由设备驱动程序全权负责。 设备驱动程序 ( 应该只是 ) 为系统的其它部分提."— Presentation transcript:

1 设备驱动程序

2 2 设备驱动程序的作用 设备驱动程序是一个软件层,该软件层使硬件 设备响应预定义好的编程接口,我们已经熟悉 这些接口,它由一组控制设备的函数 (open,read,ioctl 等等 ) 组成,这些函数的实际实 现由设备驱动程序全权负责。 设备驱动程序 ( 应该只是 ) 为系统的其它部分提 供各种使用设备的能力,使用设备的方法应该 由应用程序决定。

3 3 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 *); int (*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 inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); 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 *); 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); }; struct file_operations include/linux/fs.h

4 设备驱动程序 则编写设备驱动程序的主要工作就是编写如上子 函数,并填充 file_operations 的各个域

5 一个最简单字符驱动程序,由下面 7 个函数和 1 个结构体就可组成。 Open (), Release, 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 count, loff_t * ppos ) { 设备写入时的操作 … } static int my_read(struct file *file, const char * buffer, size_t count, loff_t * ppos ) { 设备读取时的操作 … } () Write (), Read () Ioctl () Init (), Exit () Struct file_operation

6 static int __init my_init(void) { 初始化硬件,注册设备,创建设备节点 … } static void __exit my_exit(void) { 删除设备节点,注销设备 … } { 设备的控制操作 …… } Static int my_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) static struct file_operations my_fops = { 对文件操作结构体成员定义初始值 … }

7 7 在操作系统中的位置 设备驱动程序是内核代码的一部分。 驱动程序的地址空间是内核的地址空间 ( copy_to_user 函数等 ) 。 驱动程序的代码直接对设备硬件 ( 实际是设备的各种寄 存器 ) 进行控制 ( 实际就是读写操作 ) 。 应用程序通过操作系统的系统调用执行相应的驱动程 序函数。中断则直接执行相应的中断程序代码。 设备驱动程序的 file_operations 结构体的地址被注册到 内核中的设备链表中。 块设备和字符设备以设备文件的方式建立在文件系统 中的 /dev 目录下,而且每个设备都有一个主设备号和一 个次设备号。

8 设备号 主设备号 驱动程序在初始化时,会注册它的驱动及对应主设备 号到系统中,这样当应用程序访问设备节点时,系统 就知道它所访问的驱动程序了。你可以通过 /proc/devices 文件来查看驱动系统设备的主设备号。 次设备号 驱动程序遍历设备时,每发现一个它能驱动的设备, 就创建一个设备对象,并为其分配一个次设备号以区 分不同的设备。这样当应用程序访问设备节点时驱动 程序就可以根据次设备号知道它说访问的设备了。

9 9 ls -l /dev crw-r----- 1 root root 1, 1 Jan 1 00:00 mem crw-r----- 1 root root 1, 2 Jan 1 00:00 kmem crw-rw-rw- 1 root root 1, 3 Jan 1 00:00 null crw-r----- 1 root root 1, 4 Jan 1 00:00 port crw-rw-rw- 1 root root 1, 5 Jan 1 00:00 zero crw-rw-rw- 1 root root 1, 7 Jan 1 00:00 full crw-r--r-- 1 root root 1, 8 Jan 1 00:00 random crw-r--r-- 1 root root 1, 9 Jan 1 00:00 urandom crw-rw-rw- 1 root root 5, 0 Jan 1 00:00 tty crw------- 1 root root 5, 1 Jan 1 00:00 console crw-rw-rw- 1 root root 5, 2 Jan 1 00:00 ptmx drwxr-xr-x 1 root root 0 Jan 1 00:00 pty drwxr-xr-x 2 root root 0 Jan 1 00:00 pts drwxr-xr-x 1 root root 0 Jan 1 00:00 rd drwxr-xr-x 1 root root 0 Jan 1 00:00 mtd drwxr-xr-x 1 root root 0 Jan 1 00:00 mtdblock crw------- 1 root root 4, 64 Jan 1 00:15 ttyS0 crw------- 1 root root 4, 65 Jan 1 00:00 ttyS1 crw------- 1 root root 4, 66 Jan 1 00:00 ttyS2 crw------- 1 root root 4, 67 Jan 1 00:00 ttyS3 crw------- 1 root root 4, 68 Jan 1 00:00 ttyS4 drwxr-xr-x 1 root root 0 Jan 1 00:00 misc c: 字符设备 b: 块设备 主设备号次设备号

10 10 设备驱动程序源代码的基本结构 /* * 驱动程序简单说明: * 驱动程序的作用:这是一个字符设备驱动程序的基本框架结构 * 被驱动设备的简单描述:将使用 AT91RM9200 的 PB 端口为例进行说明 * 一些特殊的考虑等:如 PB21 作为可以产生中断的输入引脚(本例未实现) * 版本,创建日期,作者等: 1.0 版, 2006 年 1 月 6 日 */ #ifndef __KERNEL__ #define __KERNEL__ #endif #ifndef MODULE #define MODULE #endif #include... #include 表明这个模块将用于内核,也可以在编译时通 过 –D 选项指定,如 gcc –D__KERNEL__ 。参 见 Makefile 。 内核头文件,需要根据具体 驱动程序和用到的内核模块 确定。 表明这个驱动程序将以模块的方式编译和使用, 也可以在编译时通过 –D 选项指定,如 gcc –DMODULE 。参见 Makefile 。

11 11 /* * 驱动程序中使用的各种函数的原型声明。标准的作法是将函数原型声明 * 放在一个头文件中,然后在该文件开始处使用 #include 引用,并在该 * 文件中定义。 * * 这里我们将函数的声明和定义放在一起。所以下面的代码既是函数的声明, * 也是函数的定义。 */ static ssize_t spioc_read(struct file *filp, char *buff size_t cnt, loof_t *off) { /* 这里是 read 函数的代码 */ return ret; } static ssize_t spioc_write(struct file *filp, char *buff size_t cnt, loff_t *off) { /* 这里是 write 函数的代码 */ return ret; }

12 12 static int spioc_ioctl(struct inode *inode, struct file *filp unsigned int cmd, unsigned long arg) { /* 这里是 ioctl 函数的代码,它的一般格式为一个 switch 分支语句 *switch(cmd) { *case CMD1: *... *break; *... *case CMDn: *... *break *default: *... *break; *} */ return ret; } ioctl() 函数用于控制驱动程序本身的一些特性和参数,如设定驱动 程序使用的缓冲区的大小,设定串行通讯的速率等。

13 13 static int spioc_open(struct inode *inode, struct file *filp) { /* 这里是 open 函数的代码 */ return ret; } static int spioc_close(struct inode *inode, struct file *filp) { /* 这里是 close 函数的代码 */ return ret; } 上述 5 个函数,既 read(), write(), ioctl(), open(), close() ,是一个字符 设备驱动程序最基本的需要由驱动程序的作者完成的函数。 这 5 个函数将对应于相应的 5 个系统调用: read() -> spioc_read() write() -> spioc_write() ioctl() -> spioc_ioctl() open() -> spioc_open() close() -> spioc_close() 系统调用 驱动程序函数

14 14 static struct file_operations spioc_fops = { read:spioc_read, write:spioc_write, ioctl:spioc_ioctl, open:spioc_open, release:spioc_close, }; file_operations 是一个结构体类型,定义在 include/linux/fs.h 中。 上述代码定义了一个 file_operations 类型的结构体 spioc_fops ,并将 其中的一些成员赋了初值。由于 spioc_fops 是一个静态变量,所以其他成员 的初值是 “ 零 ” 。 结构体 spioc_fops 将作为一个参数在注册一个设备驱动程序时传递给内核。 内核使用设备链表维护各种注册的设备。不同类型的设备使用不同的链表。

15 15 static int __init spioc_init(void) { /* 设备初始化代码等 */ if(register_chrdev(SPIOC_MAJOR, “spioc”, &spioc_fops)) { printk(KERN_ERR “spioc.c: unable to register ” “the device with major %d.\n”, SPIOC_MAJOR); return –EIO; } /* 其他初始化代码 */ return ret; } static void __exit spioc_exit(void) { /* 设备撤消代码 */ if(unregister_chrdev(SPIOC_MAJOR, “spioc”)) { printk(KERN_ERR “spioc.c: unable to remove the ” “device with major %d.\n”, SPIOC_MAJOR); return; } /* 其它设备撤消代码 */ return; }

16 注册设备函数 int register_chrdev(unsigned int major, const char *name, struct file_operations *fops); major 是为设备驱动程序向系统申请的主设备号, 如果为 0 则系 统为此驱动程序动态分配一个主设备号 ; name 是设备名 ; fops 是对各个系统调用的入口点的说明 ;

17 撤销设备代码 int unregister_chrdev(unsigned int major, const char *name); major :注册时的主设备号,必须和注册时相同 name: 设备名 返回: 成功 0 设备名从 proc/devices 下消失 失败 -1

18 18 module_init(spioc_init); module_exit(spioc_exit); 这两个函数, module_init() 和 module_exit() ,用于告诉内核, 当一个驱动程序加载和退出(或撤消)时,需要执行的操作。 不同驱动程序在加载和退出时,除了基本的向内核注册设备 驱动程序外,还有各自的针对具体设备的操作。

19 19 要点总结 : 宏: __KERNEL__, MODULE, __VERSION__ __KERNEL__ :表明这将是用于内核的代码,否则很多内核过程将无法使用。 MODULE :如果是以模块方式编译,需要定义这个宏;如果是静态连接则不用。 __VERSION__ :定义这个宏则需要驱动程序的内核版本要和内核版本一致。 module_init()/module_exit(): [spioc_init()/spioc_exit()] 每个驱动程序都要有这两个函数,它们分别用于设备驱动程序的加载和撤消。 static struct file_operations spioc_fops: 每个驱动程序都要有这样的结构体,可能不止一个。用 register_chrdev () 注册驱动程序时这个结构体的起始地址被传送到内核的设备表中。 SPIOC_MAJOR: 每个设备驱动程序有一个主设备号 (major number) 。不同设备驱动程序不能 使用相同的主设备号。一个设备驱动程序可以管理不同的 ( 但一般是同一类的 ) 设备,通过次设备号 (minor number) 区分。 spioc_ open()/close() , read()/write(), ioctl(): 根据具体驱动程序定义和使用。一般 open()/close() 总是需要的,而且 open() 和 close() 一定要成对出现。

20 20 设备驱动程序的使用 驱动程序模块的动态链接和静态链接 创建设备文件 使用设备

21 21 设备驱动程序被静态编译到内核中的情况 : module_init() 指示内核在启动过程中运行设备的初始化函数,如 spioc_init() 函数。驱动程序的加载随内核的启动一起完成。 静态编译的内核模块不能被动态卸载,只有到系统关闭时由内 核执行相应的卸载函数,如 spioc_exit() 。 嵌入式操作系统一般使用静态内核模块以减少系统的尺寸和复 杂性。 驱动程序模块的加载 设备驱动程序被动态加载到内核中的情况 : 首先,驱动程序需要被编译成目标文件,如 spioc.o 。 在操作系统运行之后,使用 insmod 命令将驱动程序模块动态加 载到内核中 $ insmod spioc.o 使用 insmod 命令动态加载的内核模块可以使用 rmmod 命令动态 地从内核中卸载 $ rmmod spioc.o 使用内核的动态模块加载 / 卸载功能需要内核支持 kmod 功能。

22 22 创建设备文件 Linux 操作系统将字符设备和块设备作为一种特殊的文件对待,这 就是设备文件。 使用 mknod 命令建立设备文件。 $ mknod c 21 0 /dev/spioc c :字符设备 b :块设备 主设备号次设备号 设备文件 crw------- 1 root root 21, 0 Jan 1 00:15 spioc /dev

23 23 使用设备驱动程序 应用程序  系统调用  设备驱动程序  设备(寄存器) 使用一个设备一般需要执行如下一些操作: 1. 打开设备文件。 2. 对设备进行必要的设置,如设置串口速率。 3. 对设备进行读、写等操作,如通过串口收发数据。 4. 结束对设备的使用之前,如果改变了设备的某些设置, 则将其恢复到缺省状态,保证设备停用后没有任何不 好的副作用。 5. 关闭设备。

24 24 int main(int argc, char **argv) {... pd = open(“/dev/spioc”, O_RDWT);... } 应用程序 crw------- 1 root root 21, 0 Jan 1 00:15 spioc /dev open(const char *, int) 系统调用 static struct file_operations spioc_fops = { read:spioc_read, write:spioc_write, ioctl:spioc_ioctl, open:spioc_open, release:spioc_close, }; 设备驱动程序 static int spioc_open(struct inode *inode, struct file *filp) { /* 这里是 open 函数的代码 */ return ret; } 设备驱动程序 设备和驱动程 序的使用

25 实验步骤 主页下载代码: make 如果编译的时候出现问题,可能是在 /usr/src 下没 有建立一个 linux 链接,可以使用下面的命令: cd /usr/src/ ln -sf linux2.4.20-8 linux (为什么?) ls ( 可见如下文件 ) debug linux linux-2.4 linux2.4.20-8 redhat

26 mknod /dev/demo c 254 0 insmod demo.o./test_demo 实验步骤

27 作业:参考附件的代码,编写一个简单 驱动程序,当某个应用程序读取这个装 置的时候,可以读取到连续的随机数或 者是 0 ,程序可以在 PC 机器或 ARM 上编 译、运行


Download ppt "设备驱动程序. 2 设备驱动程序的作用 设备驱动程序是一个软件层,该软件层使硬件 设备响应预定义好的编程接口,我们已经熟悉 这些接口,它由一组控制设备的函数 (open,read,ioctl 等等 ) 组成,这些函数的实际实 现由设备驱动程序全权负责。 设备驱动程序 ( 应该只是 ) 为系统的其它部分提."

Similar presentations


Ads by Google