第10章 设备管理
第10章 主要内容 本章主要介绍了设备管理方面的有关知识: ◆ 系统管理设备的方式。 ◆ 驱动程序运作过程。 ◆ 驱动程序的具体实例。
第10章 目录 10.1 设备管理结构 10.2 驱动程序 10.3 驱动程序编写实例
第10章 目录 10.1 设备管理结构 10.2 驱动程序 10.3 驱动程序编写实例
10.1 设备管理结构: 概述 设备管理即输入输出子系统,分为上下两部分: 1. 上层的,与设备无关的,这部分根据输入输出请求,通过特定的设备驱动程序接口,来与设备进行通信。 2. 下层的,与设备有关的,常称为设备驱动程序,它直接与相应设备打交道,并且向上层提供一组访问接口。
10.1 设备管理结构:概述 设备管理的目标是对所有的外接设备进行良好的读、写、控制等操作。 待解决问题: 怎样将任意的一个设备的所有操作进行归纳,设计出统一的接口。 内核常常使用设备类型、主设备号和次设备号来标识一个具体的设备。
10.1 设备管理结构:概述 Linux中的设备管理应用了设备文件这个概念来统一设备的访问接口。 简单的说,系统试图使它对所有各类设备的输入、输出看起来就好像对普通文件的输入、输出一样。
如图10-1所示,应用程序通过Linux的系统调用与内核通信。
10.1 设备管理结构:概述 由于Linux中将设备当作文件来处理,所以对设备进行操作的系统调用和对文件操作的类似,主要包括open()、read()、write()、ioctl()、close()等。 应用程序发出系统调用指令以后,会从用户态转换到内核态,通过内核将open()这样的系统调用转换成对物理设备的操作。
10.1 设备管理结构: 字符设备与块设备 字符设备以字节为单位进行数据处理。字符设备通常只允许按顺序访问,一般不使用缓存技术。如鼠标,声卡等。 块设备以块为单位进行处理,块的大小通常为0.5KB到32KB等。
10.1 设备管理结构:字符设备与块设备 大多数块设备允许随机访问,而且常常采用缓存技术。 块设备有硬盘、光盘驱动器等。可以查看文件/proc/devices获得。 这里主要讨论字符设备,有兴趣的读者可参考其它书籍中有关块设备的内容。
10.1 设备管理结构: 主设备号和次设备号 设备管理中,除了设备类型(字符设备或块设备)以外,内核还需要一对称做主、次设备号的参数,才能唯一表示设备。
10.1 设备管理结构:主设备号和次设备号 主设备号(major number)相同的设备使用相同的驱动程序,而次设备号 (minor number) 用来区分具体设备的实例。 例如:第一IDE接口上的所有磁盘及其分区共用同一主设备号3,而次设备号则为0,1,2,3 …。
10.1 设备管理结构: Linux设备命名习惯 Linux习惯上将设备文件放在目录/dev或其子目录之下。 设备文件命名(通常由两部分组成)规则为: 第一部分通常较短,可能只有2或3个字母组成,用来表示设备大类。 例如:普通硬盘如IDE接口的为“hd”,软盘为“fd”。
10.1 设备管理结构: Linux设备命名习惯 第二部分通常为数字或字母用来区别设备实例。 例如: /dev/hda、/dev/hdb、/dev/hdc表示第一、二、三块硬盘;而 dev/hda1、/dev/hda2、/dev/hda3则表示第一硬盘的第一、二、三分区。
第10章 目录 10.1 设备管理结构 10.2 驱动程序 10.3 驱动程序编写实例
10.2 驱动程序: 驱动程序基本功能 在Linux操作系统中驱动程序是操作系统内核与硬件设备之间的桥梁,它屏蔽了硬件的细节 (如总线协议、DMA操作等),在应用程序看来硬件设备只是一个特殊的文件。
10.2 驱动程序:驱动程序基本功能 驱动程序的基本功能为: 1. 对设备初始化和释放。如对音频设备而言包括向内核注册设备,设置音频的输入输出参数 (如采样频率、采样宽度等)、分配音频设备使用的内核内存等工作。 2. 对设备进行管理。包括实时参数设置以及提供对设备的操作接口。
10.2 驱动程序:驱动程序基本功能 3. 读取应用程序传送给设备文件的数据并回送应用程序请求的数据。这需要在用户空间、内核空间、总线及外设之间传输数据。 4. 检测和处理设备出现的错误。
结合大家比较熟悉的键盘来了解其运作过程:
10.2 驱动程序:驱动程序的运作过程 当一个程序读/dev/tty文件(此为键盘)时,就会执行系统调用sys_read()(在fs/read_write.c中),该系统调用在判别出所读文件是一个字符设备文件时,即会调用rw_char()函数(在fs/char_dev.c中),该函数则会根据所读设备的设备类型,主、次设备号等参数,由字符设备读写函数表(设备开关表)调用rw_tty(),最终调用到这里的终端读操作函数tty_read()
10.2 驱动程序:驱动程序的运作过程 当用户在键盘上键入了一个字符时,会引起键盘中断响应,此时键盘中断处理程序就会从键盘控制器读入对应的键盘扫描码,然后根据使用的键盘扫描码映射表译成相应字符,放入tty读队列read_q中。
10.2 驱动程序:驱动程序的运作过程 然后调用中断处理程序的do_tty_interrupt()函数,它又直接调用行规则函数copy_to_cooked()对该字符进行过滤处理,并放入tty辅助队列secondary中,供上述tty_read()读取。
10.2 驱动程序:驱动程序的运作过程 同时把该字符放入tty写队列write_q中,并调用写控制台函数con_write()。 此时如果该终端的回显(echo)属性是设置的,则该字符会显示到屏幕上(注:do_tty_interrupt()和copy_to_cooked()函数在tty_io.c中实现)。
10.2 驱动程序: 常用接口介绍 open(): 打开设备,并初始化设备准备进行操作。可以为NULL,这样每次打开设备总会成功,而且不通知设备驱动程序。 read(): 从设备中读数据,需要提供字符串指针。 write(): 向字符设备写数据,需要提供所写内容指针。 ioctl(): 控制设备,例如控制光盘的弹出等。需要提供符合设备预先定义的命令字。
10.2 驱动程序:常用接口介绍 llseek(): 重新定位读、写位置,需要提供偏移量参数。 flush(): 清除内容。 release(): 关闭设备,并释放资源等。 mmap(): 将设备内存映射到进程地址空间。通常只有块设备驱动程序使用。
10.2 驱动程序: 常用函数原型 1. 设备操作函数原形 { struct module * owner; 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);
int (mmap)(struct file *,struct vm_area_struct *); 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 *);
10.2 驱动程序:常用函数原型 int (*fasync )(int,struct file *,int); 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 *); } 10.2 驱动程序:常用函数原型
10.2 驱动程序:常用函数原型 2.向系统注册的函数原形 int register_chrdev(unsigned int major,const char * name,struct file_operations * fops) { if (major = = 0 ) write_lock(&chrdevs_lock);
10.2 驱动程序:常用函数原型 { if (chrdevs[major].fops= =NULL) for(major=MAX_CHRDEV-1;major>0;major--) { if (chrdevs[major].fops= =NULL) chrdevs[major].name=name; chrdevs[major].fops=fops; write_unlock(&chrdevs_lock); return major; } 10.2 驱动程序:常用函数原型
10.2 驱动程序:常用函数原型 return -EBUSY; } if(major>MAX_CHRDEV) write_unlock(&chrdevs_lock); return -EBUSY; } if(major>MAX_CHRDEV) return -EINVAL; write_lock(&chrdevs_lock); 10.2 驱动程序:常用函数原型
10.2 驱动程序:常用函数原型 { write_unlock(&chrdevs_lock); return -EBUSY; } if(chrdevs[major].fops && chrdevs[major].fops!=fops) { write_unlock(&chrdevs_lock); return -EBUSY; } chrdevs[major].name=name; chrdevs[major].fops=fops; return 0; 10.2 驱动程序:常用函数原型
第10章 目录 10.1 设备管理结构 10.2 驱动程序 10.3 驱动程序编写实例
10.3 驱动程序编写实例 由于基于特殊的硬件设备实现的驱动程序难度较大,而且不方便验证,下面举一个虚拟设备驱动程序的例子。 为了更清楚地讲述Linux中设备驱动程序的编写,加深读者对启动程序的了解。下面介绍一个简单的设备驱动的实现过程。 由于基于特殊的硬件设备实现的驱动程序难度较大,而且不方便验证,下面举一个虚拟设备驱动程序的例子。
10.3 驱动程序编写实例: 设备功能介绍 实现虚拟设备的写入、读出等操作。这个驱动程序并不是基于特定硬件设备的,实际上仅仅是对内存进行读、写操作。
10.3 驱动程序编写实例:设备功能介绍 1. 函数mydrv_read()的功能是从mybuf[100]中读取字符串,并传递给调用的进程。 2. 函数mydrv_write()的功能是将调用的进程传入的字符串赋值给mybuf,如果字符串的长度超过100,则只取前100个字符。 3. 函数mydrv_ioctl()中仅仅实现了一个控制功能:清除mybuf存储区。
10.3 驱动程序编写实例: 具体实现 首先,要根据设备功能的需要,编写file_operations结构中的操作函数。 其次,要向系统注册该设备,包括字符设备的注册,devfs节点的注册与中断响应函数的注册。然后就可以利用对应的文件进行设备操控了。具体如下:
10.3 驱动程序编写实例:具体实现 1.源程序: # include <linux/module.h> # include <linux/kernel.h> # include <linux/fs.h> # include <linux/types.h> # include <linux/malloc.h> # include <asm/uaccess.h> # include <asm/page.h> # include <linux/ermo.h> # include <linux/config.h> 10.3 驱动程序编写实例:具体实现
10.3 驱动程序编写实例:具体实现 # define MYDRV_CLS_IO( 'c' ,0x01 ) //定义清存储区命令字 char mybuf[100]; //存储区域 int mydrv_major = 99; //主设备号 devfs_handle_t dev_handle; //保存设备文件系统的注册句柄 //第一步:编写file_operations函数 10.3 驱动程序编写实例:具体实现
ssize_t mydrv_read(struct file. filp, char. buf, size_t count,loff_t ssize_t mydrv_read(struct file * filp, char * buf, size_t count,loff_t * f_pos); //函数声明 static ssize_t mydrv_write(struct file * filp,const char * buf,size_t count,loff_t * ppos); static int mydrv_ioctl( struct inode * inode,struct file * file, unsigned int cmd, unsigned long arg); int mydrv_open (struct inode * inode,struct file *filp); int mydrv_release(struct inode * inode, struct file * filp); //函数声明 10.3 驱动程序编写实例:具体实现
10.3 驱动程序编写实例:具体实现 struct file_operations mydrv_ops={ //设备函数接口 open: mydrv_open , //实现对设备的操作 read: mydrv_read, write: mydrv_write, ioctl: mydrv_ioctl, release: mydrv_release; }; // mydrv_read()将内核空间的mybuf中的字符串赋给用户空间的buf区 10.3 驱动程序编写实例:具体实现
ssize_t mydrv_read(struct file. filp, char. bur, size_t count,loff_t ssize_t mydrv_read(struct file * filp, char * bur, size_t count,loff_t * f_pos) //filp:指向设备文件的指针;f_pos:偏移量 int length = strlen(mybuf); if(count > 99) count = 99; //忽略大于100部分 count = length - * f_pos; //计算字符个数的技巧 10.3 驱动程序编写实例:具体实现
10.3 驱动程序编写实例:具体实现 if(copy_to_user(buf, mybuf, count) ) { //重内核区复制到用户区 printk("error reading, copy_to_user\n”); retum -EFAULT; } *f_pos += count; //下一个 retum count; // mydtv_write()将用户空间的buf字符串赋给内核空间的mybuf [ ]数组中 10.3 驱动程序编写实例:具体实现
static ssize_t mydrv_write(struct file. filp,const char static ssize_t mydrv_write(struct file * filp,const char * buf, size_t count,loff_t * ppos){ int num; num=count<100? count: 100; if(copy_from_user(mybuf, buf, num)) //mybufbuf return -EFAULT; printk("mydrv_write succeed! \ n”); return num; } 10.3 驱动程序编写实例:具体实现
static int mydrv_ioctl( struct inode. inode,struct file static int mydrv_ioctl( struct inode * inode,struct file * file, //如果传人的命令字是 unsigned int cmd, unsigned long arg){ //MYDRV-CLS则清除mybuf数组内容 switch ( cmd ){ case MYDRV_CLS: mybuf[0] = 0x0; return 0; default: return -EINVAL; } //打开mydrv设备时调用 10.3 驱动程序编写实例:具体实现
10.3 驱动程序编写实例:具体实现 # define MAX_MYDRV_DEV 2 int mydrv_open (struct inode * inode,struct file *filp){ //inede:设备文件节点 unsigned int dev = MINOR(inode- >i_rdev); if(mydrv_num) return -1; 10.3 驱动程序编写实例:具体实现
if (dev > = MAX_MYDRV_DEV) return -ENODEV; filp- > f_ap = &mydrv_ops; //指向操作函数 printk(“open success\n”); MOD_INC_USE_COUNT; //只是简单地加1 return 0; } //关闭mydrv设备,这里只是将引用次数减1 int mydrv_release(struct inode * inode, struct file * filp){ MOD_DEC_USE_COUNT; 10.3 驱动程序编写实例:具体实现
10.3 驱动程序编写实例:具体实现 //第二步:向系统注册该设备 // module的安装,采用两种方式进行了设备的注册 int init_module(void) { int result; printk(“initing...\ n”); result = devfs_register_chrdev(mydrv_major, “mydrv”,&mydrv_ops); 10.3 驱动程序编写实例:具体实现
10.3 驱动程序编写实例:具体实现 if (result < 0){ printk(KERN_WARNING “mydrv: unable to get major %d \ n”, mydrv_major); return result; } dev_handle = devfs_register( NULL, “mydrv", DEVFS_FL_DEFAULT, 99,0, S_IFCHR, &mydrv_ops, NULL); //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) 10.3 驱动程序编写实例:具体实现
10.3 驱动程序编写实例:具体实现 if (mydrv_major:= = 0) mydrv_major = result; strcpy(mybuf,"Hello, please write anything ( length <100)to mydrv.”); printk(“succeed in getting buffer \ n"); printk("%s \ n", mybuf); retum 0; } // module的卸载,进行设备的注销 10.3 驱动程序编写实例:具体实现
10.3 驱动程序编写实例:具体实现 void cleanup_module(void) { devfs_unregister_chrdev(mydrv_major, “mydrv”); devfs_unregister( dev_handle ); printk("exiting... \ n"); } 10.3 驱动程序编写实例:具体实现
10.3 驱动程序编写实例:具体实现 2.设备驱动程序编译和安装 采用下面的命令可以对mydrv.c进行编译: [root@Linux root]# gcc -c mydrv.c –D__KERNEL__ -DMODULE -O2 -g -Wall -o 如果没有出错的话,将会在本目录下生成一个mydrv.o文件。
10.3 驱动程序编写实例:具体实现 下面的操作必须是以root身份进行的(用命令su转换成root身份): 先执行模块的插入操作, [root@Unuxroot]#/sbin/insmod mydrv.o
10.3 驱动程序编写实例:具体实现 如果设备文件系统已经应用起来的话,此时在设备文件系统挂接的目录(通常是/dev)下,就可以找到mydrv文件节点了。如果没有应用设备文件系统,则需要手工为设备添加文件节点: [root@Linux/dev]#mknod mydrv c 99 0 此时就可以对设备进行读、写、ioctl等操作了。
10.3 驱动程序编写实例:具体实现 当不再需要对设备进行操作时,可以采用下面的命令卸载模块: [root@Linux/dev]#/sbin/rmmod mydrv
3.设备的使用 下面的小程序可以对任何文件进行先写后读的操作: #include <stdio.h> int main() { FILE * fp; char buf[l00]; printf("Please input file name: "); scanf("% s", buf);
10.3 驱动程序编写实例:具体实现 if((fp = fopen(buf, "wb")) = = NULL) { printf("Could not opened! \ n"); return -1; } else printf("File open ok! \ n"); printf("Please input( < 100): "); scanf("% s", buf); 10.3 驱动程序编写实例:具体实现
10.3 驱动程序编写实例:具体实现 if(fputs(buf,fp) = = EOF){ printf("Error writing file ! "); return -2; } fgets(but, 100,fp); //由文件读取一字符串 printf("the File content is: %s \ n",buf); fclose(fp); return 0; 10.3 驱动程序编写实例:具体实现
当然,也可以采用cat命令得到mydrv设备的输出内容。 10.3 驱动程序编写实例:具体实现 直接用gcc编译生成可执行文件之后,就可以用该程序对mydrv设备的文件节点进行操作。 当然,也可以采用cat命令得到mydrv设备的输出内容。
小结 本章主要介绍了设备管理方面的有关知识. 首先介绍了系统是怎样来管理设备的,即把设备看作一种的特殊的文件,从而实现了对设备的有关操作。 然后,说明了驱动程序运作过程。 最后,结合一个具体实例,阐明了驱动程序的具体的编写方法。
练习题 1.操作系统是怎么实现对设备进行管理的? 2.举出5个驱动程序的常用接口函数。 3.编写驱动程序一般有几个步骤,具体各是什么?