1 / 27

设备驱动程序

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

hila
Download Presentation

设备驱动程序

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 设备驱动程序

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

  3. struct file_operations include/linux/fs.h 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); }; 3

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

  5. 一个最简单字符驱动程序,由下面7个函数和1个结构体就可组成。Open(),Release,一个最简单字符驱动程序,由下面7个函数和1个结构体就可组成。Open(),Release, ()Write(),Read()Ioctl()Init(),Exit()Struct file_operation 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) { 设备读取时的操作 … }

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

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

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

  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:块设备 主设备号 次设备号 9

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

  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; } 11

  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()函数用于控制驱动程序本身的一些特性和参数,如设定驱动 程序使用的缓冲区的大小,设定串行通讯的速率等。 12

  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() 驱动程序函数 系统调用 13

  14. static structfile_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将作为一个参数在注册一个设备驱动程序时传递给内核。 内核使用设备链表维护各种注册的设备。不同类型的设备使用不同的链表。 14

  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; } 15

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

  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()一定要成对出现。 19

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

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

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

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

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

  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上编译、运行

More Related