c-linux高级编程.doc
Four short words sum up what has lifted most successful individuals above the crowd: a little bit more.-author-datec-linux高级编程编译原理实验报告(三)C/linux高级编程设计报告 题目: 字符驱动设备设计 学生姓名: 冯永强 学 号: 123821003 专 业: 计算机科学与技术 2014年5月25日 字符驱动设备设计一、 课程设计目的 Linux 系统的开源性使其在嵌入式系统的开发中得到了越来越广泛的应用,但其本身并没有对种类繁多的硬件设备都提供现成的驱动程序,特别是由于工程应用中的灵活性,其驱动程序更是难以统一,这时就需开发一套适合于自己产品的设备驱动。对用户而言,设备驱动程序隐藏了设备的具体细节,对各种不同设备提供了一致的接口,一般来说是把设备映射为一个特殊的设备文件,用户程序可以像对其它文件一样对此设备文件进行操作。通过这次课程设计可以了解linux的模块机制,懂得如何加载模块和卸载模块,进一步熟悉模块的相关操作。加深对驱动程序定义和设计的了解,了解linux驱动的编写过程,提高自己的动手能力。二、 课程设计内容与要求(1) 设计Windows XP或者Linux操作系统下的设备驱动程序;(2) 设备类型可以是字符设备、块设备或者网络设备;(3) 设备可以是虚拟的也可以是实际设备;三、 系统分析与设计 (1) 系统分析 系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:1、对设备初始化和释放;2、把数据从内核传送到硬件和从硬件读取数据;3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据;4、检测和处理设备出现的错误。Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得Windows的设备操作犹如文件一般。在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作,如open ()、close ()、read ()、write () 等。Linux主要将设备分为二类:字符设备和块设备。字符设备是指设备发送和接收数据以字符的形式进行;而块设备则以整个数据缓冲区的形式进行。字符设备提供给应用程序的是一个流控制接口,主要包括open、close(或release)、read、write、ioctl、poll和mmap等。在系统中添加一个字符设备驱动程序,实际上就是给上述操作添加对应的代码。对于字符设备和块设备,Linux内核对这些操作进行了统一的抽象,把它们定义在结构体file_operations中。在驱动程序中,当多个线程同时访问相同的资源时(驱动程序中的全局变量是一种典型的共享资源),可能会引发"竞态",因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。自旋锁与信号量"类似而不类",类似说的是它们功能上的相似性,"不类"指代它们在本质和实现机理上完全不一样,不属于一类。自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环查看是否该自旋锁的保持者已经释放了锁,"自旋"就是"在原地打转"。而信号量则引起调用者睡眠,它把进程从运行队列上拖出去,除非获得锁。这就是它们的"不类"。但是,无论是互斥信号量,还是自旋锁,在任何时刻,最多只能有一个保持者,即在任何时刻最多只能有一个执行单元获得锁。这就是它们的"类似"。鉴于自旋锁与信号量的上述特点,一般而言,自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用;信号量适合于保持时间较长的情况,只能在进程上下文使用。如果被保护的共享资源只在进程上下文访问,则可以以信号量来保护该共享资源,如果对共享资源的访问时间非常短,自旋锁也是好的选择。但是,如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。阻塞操作是指,在执行设备操作时,若不能获得资源,则进程挂起直到满足可操作的条件再进行操作。非阻塞操作的进程在不能进行设备操作时,并不挂起。被挂起的进程进入sleep状态,被从调度器的运行队列移走,直到等待的条件被满足。在Linux驱动程序中,我们可以使用等待队列(wait queue)来实现阻塞操作。wait queue很早就作为一个基本的功能单位出现在Linux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。等待队列可以用来同步对系统资源的访问,上节中所讲述Linux信号量在内核中也是由等待队列来实现的。结合阻塞与非阻塞访问、poll函数可以较好地解决设备的读写,但是如果有了异步通知就更方便了。异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上"中断"地概念,比较准确的称谓是"信号驱动(SIGIO)的异步I/O"。在本课程设计中主要实现一个简单的字符驱动设备,运用信号量和自旋锁进行相关的并发控制。(2) 系统设计 2.1 测试环境: 系统: linuxmint 15 kernel version : 3.8.0-23-generic gcc version : 4.7.3 2.2 模块设计: 打开设备 读操作 写操作释放设备 退出设备字符设备驱动2.3 数据结构说明:字符设备驱动主要应用了三种数据结构:file_operations结构,这是设备驱动程序所提供的一组用一个结构向系统进行说明的入口点;file结构,主要用于与文件系统对应的设备驱动程序。代表一个打开的文件,它由内核在open时创建,并传递给在该文件上进行操作的所有函数,直到碰到最后的close函数。在文件的所有实例都被关闭之后,内核会释放这个数据结构; inode结构,提供了关于特殊设备文件/dev/mydev的信息。使用cat /proc/kmsg &来监测Kernel中的输出信息,即程序中的printk的输出信息。同时通过#include<errno.h>来引入相关的错误定义,在对应错误发生的时候向内核输出出错信息。各个结构的定义如下:(1)file_operations结构: static const struct file_operations my_fops = .owner = THIS_MODULE, .llseek = my_llseek, .read = my_read, .write = my_write, .open = my_open, .release = my_release, .unlocked_ioctl = ioctl, ;(2)file结构: 1)读 static ssize_t my_read(struct file *filp, char _user *buf, size_t size, loff_t *ppos) 2)写 static ssize_t my_write(struct file *filp, const char _user *buf, size_t size, loff_t *ppos) 3)seek文件定位 static loff_t my_llseek(struct file *filp, loff_t offset, int whence) 4)IO控制 static int ioctl (struct file *file, unsigned int cmd, unsigned long arg)(3)inode结构: 1) 打开 int my_open(struct inode *inode, struct file *filp) 2) 释放 int my_release(struct inode *inode, struct file *filp)(4) 信号量定义:1) 定义信号量 static struct semaphore sem;2) 初始化信号量为1 sema_init ( &sem, 1 );3) 获取信号量 up( &sem )4) 释放信号量 down( &sem )(5) 自旋锁定义:1) 获得自旋锁 spin_lock ( &spin );2) 释放自旋锁 spin_unlock ( &spin );(6) 模块初始化和退出1) 模块初始化 module_init(mydev_init);2) 模块退出 module_exit(mydev_exit);2.4 算法流程图如下:结束 文件释放函数 mydev_release 设备驱动模块 卸载函数mydev_exit()开始设备驱动模块加载函数ly_init()文件打开函数ly_open() 信号量 获取 自旋锁 获取读函数 mydev_read 写函数mydev_write四、 系统调试与分析4.1 使用su用户 4.2 对源程序进行编译4.3 打开后台内核输出监控4.4 加载驱动程序并查看4.5 显示主设4.6 创建节点并查看4.7 编译测试程序4.8 运行测试程序4.9 打开设备4.11 启动另一个终端,打开设备4.10 读设备(创建设备的时候已经在缓存区设置数据)4.11 写入数据4.12 继续读数据(MAX_BUFF是20)4.13 释放设备4.14 退出测试程序五、 程序清单mydev.c :#include <linux/init.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/cdev.h>#include <asm/uaccess.h>#include <linux/semaphore.h>#define DEFAULT_MSG "Hello,Welcome to OS course design" /*默认字符设备数据*/#define DEVICE_NAME "mydev" /*设备名*/#define MAXBUF 100 /*设备数据缓冲区大小*/static unsigned char mydev_bufMAXBUF; /*设备内存数据缓冲区*/static struct semaphore sem; /* 定义互斥信号量 */static int globalvar_count = 0; /* 定义设备计数 */static spinlock_t spin = SPIN_LOCK_UNLOCKED; #SPIN_LOCK_UNLOCKED has been deprecated since 2.6.19static DEFINE_SPINLOCK ( spin ); /* 定义自旋锁 */*定义读写释放打开*/static int mydev_open ( struct inode *inode, struct file *file );static int mydev_release ( struct inode *inode, struct file *file );static ssize_t mydev_read ( struct file *file, char _user *buf, size_t count, loff_t *pos );static ssize_t mydev_write ( struct file *file, const char _user *buf, size_t count, loff_t *pos );static int mydev_open ( struct inode *inode, struct file *file ) /获得自选锁 spin_lock ( &spin ); /临界资源访问 if ( globalvar_count ) spin_unlock ( &spin ); return -EBUSY; globalvar_count+; /释放自选锁 spin_unlock ( &spin ); return 0;static int mydev_release ( struct inode *inode, struct file *file ) globalvar_count-; printk ( "设备资源释放!n" ); return 0;static ssize_t mydev_read ( struct file *file, char _user *buf, size_t count, loff_t *pos ) /*从设备读取count个数据到用户数据区buf中*/ int size = count < MAXBUF ? count : MAXBUF; /*检测读取的数据大小count是否比设备数据缓冲区大,如何大则截取MAXBUF的大小*/ printk ( "mydev: This is my device!n" ); /*把设备内存mydev_buf中的数据拷贝到用户空间buf中,数量为size*/ if ( copy_to_user ( buf, mydev_buf, size ) ) up ( &sem ); return -ENOMEM; /*内存不足错误*/ up ( &sem ); return size;static ssize_t mydev_write ( struct file *filp, const char _user *buf, size_t count, loff_t *pos ) /*把buf中count个数据写入设备内存空间中*/ int size = count < MAXBUF ? count : MAXBUF; /*检测写入的数据大小count是否比设备数据缓冲区大*/ /获得信号量 if ( down_interruptible ( &sem ) ) return - ERESTARTSYS; printk ( "mydev:This is my device!n" ); memset ( mydev_buf, 0, sizeof ( mydev_buf ) ); /*将设备内存清空*/ /*把buf中的用户数据写入到设备内存mydev_buf中,数量为size*/ if ( copy_from_user ( mydev_buf, buf, size ) ) up ( &sem ); return -ENOMEM; up ( &sem ); return size;static struct file_operations mydev_fops = .read = mydev_read, .write = mydev_write, .open = mydev_open, .release = mydev_release,;static struct cdev *mydev_cdev; /*新设备指针*/static int _init mydev_init ( void ) /*模块初始化*/ dev_t dev; /*设备号*/ int error; error = alloc_chrdev_region ( &dev, 0, 2, DEVICE_NAME ); /*动态分配一个设备号*/ if ( error ) /*返回值不为0表示分配失败*/ printk ( "动态分配设备号失败!n" ); return error; mydev_cdev = cdev_alloc(); /*新分配一个字符设备对象*/ if ( mydev_cdev = NULL ) printk ( "动态分配字符设备对象失败!n" ); unregister_chrdev_region ( dev, 2 ); /*注销一个分配的设备号区域*/ return -ENOMEM; mydev_cdev->ops = &mydev_fops; /*设定字符设备操作函数指针*/ mydev_cdev->owner = THIS_MODULE; /*设备的属主*/ error = cdev_add ( mydev_cdev, dev, 1 ); /*将设备添加到内核中去*/ if ( error ) printk ( "设备添加失败!n" ); unregister_chrdev_region ( dev, 2 ); /*注销一个分配的设备号区域*/ cdev_del ( mydev_cdev ); /*删除字符设备对象*/ return error; memset ( mydev_buf, 0, sizeof ( mydev_buf ) ); /*清空设备缓冲区数据*/ memcpy ( mydev_buf, DEFAULT_MSG, sizeof ( DEFAULT_MSG ) ); /*设定设备缓冲区默认数据*/ printk ( "设备添加成功,设备缓冲区默认数据: Hello,Welcome to OS course design!n" ); sema_init ( &sem, 1 ); return 0;static void _exit mydev_exit ( void ) /*模块卸载*/ unregister_chrdev_region ( mydev_cdev->dev, 2 ); cdev_del ( mydev_cdev ); printk ( "设备删除成功!n" );module_init ( mydev_init );module_exit ( mydev_exit );MODULE_LICENSE ( "GPL" );test.c:#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#define MAXBUF 20int main() int testdev; int i; int len; int t; char sel; int flag; char bufMAXBUF, tmpMAXBUF; printf ( "1、打开设备n2、写操作n3、读操作n4、释放设备n5、退出n" ); while ( 1 ) printf ( "请输入要执行的操作:" ); sel = getchar(); getchar(); switch ( sel ) case '1': testdev = open ( "/dev/mydev", O_RDWR ); if ( testdev < 0 ) printf ( "设备打开失败 n" ); break; flag = 0; printf ( "设备打开成功!n" ); break; case '2': if ( flag ) printf ( "请先打开设备!n" ); continue; printf ( "请输入要写入的字符串:" ); gets ( tmp ); len = sizeof ( tmp ); /strlen(tmp); t = write ( testdev, tmp, len ); if ( t < 0 ) perror ( "写操作失败!n" ); exit ( -1 ); printf ( "字符串:%s 写入成功!n", tmp ); break; case '3': if ( flag ) printf ( "请先打开设备!n" ); continue; lseek ( testdev, 0, SEEK_SET ); t = read ( testdev, buf, MAXBUF ); if ( t < 0 ) perror ( "读操作失败!n" ); exit ( -1 ); printf ( "读操作成功!结果为:%sn", buf ); break; case '4': if ( flag ) printf ( "请先打开设备!n" ); break; /release(testdev); close ( testdev ); printf ( "设备释放成功!n" ); flag = 1; break; case '5': close ( testdev ); exit ( 0 ); default: printf ( "输入有误!n" ); break; makefile:DEBFLAGS = -O2EXTRA_CFLAGS += $(DEBFLAGS)# CFLAGS += -I$(LDDINC)ifneq ($(KERNELRELEASE),)# call from kernel build systemxbrige-objs := mydev.o obj-m:= mydev.oelseKERNELDIR ?= /lib/modules/$(shell uname -r)/buildPWD := $(shell pwd)modules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules#$(MAKE) -C $(KERNELDIR) M=$(PWD) LDDINC=$(PWD)/./include modulesendifclean:rm -rf *.o * core .depend .*.cmd *.ko *.mod.c .tmp_versionsdepend .depend dep:$(CC) $(CFLAGS) $(EXTRA_CFLAGS) -M *.c > .dependifeq (.depend,$(wildcard .depend)include .dependendif六、 课设总结在这次课程设计之前就一直在使用linux系统,在笔记本电脑上使用的是linuxmint15,所以在进行本次课程设计的时候选题为第二个会比较容易搭建环境。但是使用linux也主要是在应用层面进行一些编程和娱乐活动,并没有涉及到驱动或者内核层次的hack,所以在本次课程设计中选择驱动开发也可以锻炼我相关的能力驱动相较于Linux系统是更加熟悉的一个名词,每次重装系统都要安装各种各样的驱动,不然计算机就不能正常运行,各个硬件就不能发挥作用,通过这次课程设计,对Linux系统的驱动有了比较深入的认识:。Linux下的设备驱动程序分为字符设备驱动、块设备驱动和网络设备驱动程序。驱动程序在硬件和软件之间起纽带的作用,用户进程是通过设备文件来与实际的硬件打交道.每个设备文件都有其文件属性(c/b),表示是字符设备还块设备。另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备。这次的课程设计提高了自己的自我学习能力和交流能力,Linux系统是以前学习从未接触到的东西,为了完成设计,需要自己查询各种资料,并且与同学交流学习心得,讨论程序运行的细节,完善自己的程序。这次课程设计使我反省很多,无论Linux还是驱动程序都是挂在嘴边的东西,但是对于这些自己并没有进行过深入的了解,导致这次课程设计一切都要从头开始,进行的并不顺利,以后对于一些经常提起,在将来有可能用的到的东西要未雨绸缪,先做了解,将来的时候才能轻松应对,事半功倍。当然,我们编程期间也遇到了不少问题,比如C语言包含的头文件在windows中有在linux中却没有,网上的代码是基于kernel2.6,但是我的系统是kernel3.8,很多相关的API已经过时,需要按照最新的标准调整我的代码。另外对程序的层次划分也不够明确。一个好的程序当然是要将程序中不同功能的实现划分成一个个的子函数,然后通过主函数来调用这些这些子函数运行。这样一来可以是的程序的结构分明,二来也可以减少主函数的代码量。我们这次做的系统严格意义上来说离老师的标准还差很多,仅仅是一个字符设备的简单读写和简单并发控制,并没有考虑阻塞和进一步用信号量进行更高层次的并发控制虽然整个过程中出现的问题不少,但收获远远是多于犯错的,努力就会有回报,我想课程设计的目的,就是为了一方面进一步加深对课本知识的理解,另一方面,便是锻炼我们的编程能力,我们必须认真对待。七、 参考文献1 汤子瀛 编著,计算机操作系统(修订版),西安电子科技大学出版社,2001年2 Alessandro Rubini.Linux设备驱动程序M.魏永明,耿兵,钟书毅,译.北京:中国电力出版社,2006.3 Sreekrishnan ,精通Linux设备驱动程序开发,人民邮电出版社,2010年4Arm Corporation.ARM920T Technical Reference Manual.2001.5 Atmel Corporation.AT91RM9200 datasheet.2005.-