字符设备驱动程序课程设计报告

合集下载

字符设备驱动开发实验

字符设备驱动开发实验

字符设备驱动实验实验步骤:1、将设备驱动程序使用马克file文件编译生成模块firstdev.ko2、将模块加载到系统中insmod firstdev.ko3、手动创建设备节点mknod /dev/first c 122 04、使用gcc语句编译firsttest.c生成可执行文件5、运行可执行文件firsttest,返回驱动程序中的打印输出语句。

查看设备号:cat /proc/devices卸载驱动:rmmod firstdev删除设备节点:rm /dev/first显示printk语句,(打开一个新的终端)while truedosudo dmesg -csleep 1done源码分析设备驱动程序firstdev.c#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/delay.h>#include <asm/irq.h>//#include <asm/hardware.h>static int first_dev_open(struct inode *inode, struct file *file){//int i;printk("this is a test!\n");return 0;}static struct file_operations first_dev_fops ={.owner = THIS_MODULE,.open = first_dev_open,};static int __init first_dev_init(void){int ret;ret = register_chrdev(122,"/dev/first",&first_dev_fo ps);printk("Hello Modules\n");if(ret<0){printk("can't register major number\n");return ret;}printk("first_dev initialized\n");return 0;}static void __exit first_dev_exit(void){unregister_chrdev(122,"/dev/first");printk("Bye Modules\n");}module_init(first_dev_init);module_exit(first_dev_exit);makefile分析:ifneq ($(KERNELRELEASE),)obj-m:= firstdev.oelseKDIR :=/lib/modules/3.13.0-32-generic/buildall:make -C $(KDIR) M=$(PWD) modules clean:rm -f *.ko *.o *.mod.o *.mod.c *.symvers endif应用程序firsttest.c#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<sys/ioctl.h>int main(){int fd;fd = open ("/dev/first",0);if (fd<0){printf("can't open /dev/first");return -1;}close(fd);return 0; }。

字符设备驱动程序课程设计报告

字符设备驱动程序课程设计报告

字符设备驱动程序课程设计报告字符设备驱动程序课程设计报告中南大学字符设备驱动程序课程设计报告姓名:王学彬专业班级:信安1002班学号:0909103108课程:操作系统安全课程设计指导老师:张士庚一、课程设计目的1.了解Linux字符设备驱动程序的结构;2.掌握Linux字符设备驱动程序常用结构体和操作函数的使用方法;3.初步掌握Linux字符设备驱动程序的编写方法及过程;4.掌握Linux字符设备驱动程序的加载方法及测试方法。

二、课程设计内容5.设计Windows XP或者Linux操作系统下的设备驱动程序;6.掌握虚拟字符设备的设计方法和测试方法;7.编写测试应用程序,测试对该设备的读写等操作。

三、需求分析3.1驱动程序介绍驱动程序负责将应用程序如读、写等操作正确无误的传递给相关的硬件,并使硬件能够做出正确反应的代码。

驱动程序像一个黑盒子,它隐藏了硬件的工作细节,应用程序只需要通过一组标准化的接口实现对硬件的操作。

3.2 Linux设备驱动程序分类Linux设备驱动程序在Linux的内核源代码中占有很大的比例,源代码的长度日益增加,主要是驱动程序的增加。

虽然Linux内核的不断升级,但驱动程序的结构还是相对稳定。

Linux系统的设备分为字符设备(chardevice),块设备(block device)和网络设备(network device)三种。

字符设备是指在存取时没有缓存的设备,而块设备的读写都有缓存来支持,并且块设备必须能够随机存取(random access)。

典型的字符设备包括鼠标,键盘,串行口等。

块设备主要包括硬盘软盘设备,CD-ROM 等。

网络设备在Linux里做专门的处理。

Linux的网络系统主要是基于BSD unix的socket机制。

在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据传递。

系统有支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。

linux课课程设计字符设备驱动

linux课课程设计字符设备驱动

linux课课程设计字符设备驱动一、教学目标本章节的教学目标是使学生掌握Linux系统中字符设备驱动的基本原理和编程方法。

通过本章节的学习,学生将能够:1.理解字符设备驱动的概念和作用;2.掌握字符设备驱动的原理和编程方法;3.能够编写简单的字符设备驱动程序。

二、教学内容本章节的教学内容主要包括:1.字符设备驱动的概念和作用;2.字符设备驱动的原理和编程方法;3.字符设备驱动的实例分析。

具体的教学大纲如下:1.字符设备驱动的概念和作用:介绍字符设备驱动的基本概念,解释其在Linux系统中的作用;2.字符设备驱动的原理:讲解字符设备驱动的工作原理,包括驱动程序的加载、设备文件的创建和使用;3.字符设备驱动的编程方法:介绍编写字符设备驱动程序的基本步骤和方法,包括文件操作、缓冲区管理和中断处理;4.字符设备驱动的实例分析:分析实际的字符设备驱动程序代码,让学生了解和掌握驱动程序的具体实现方法。

三、教学方法为了达到本章节的教学目标,将采用以下教学方法:1.讲授法:讲解字符设备驱动的基本概念、原理和编程方法;2.案例分析法:分析实际的字符设备驱动程序代码,让学生了解和掌握驱动程序的具体实现方法;3.实验法:让学生动手编写和调试字符设备驱动程序,巩固所学的知识和技能。

四、教学资源为了支持本章节的教学内容和教学方法的实施,将准备以下教学资源:1.教材:《Linux设备驱动程序设计与实现》;2.参考书:《Linux内核设计与实现》;3.多媒体资料:教学PPT、视频教程等;4.实验设备:计算机、开发板等。

五、教学评估为了全面、客观地评估学生在Linux字符设备驱动课程中的学习成果,将采用以下评估方式:1.平时表现:通过课堂参与、提问和讨论等方式评估学生的学习态度和理解程度;2.作业:布置相关的编程练习和理论作业,评估学生对知识的掌握和应用能力;3.考试:进行期中和期末考试,以评估学生对课程内容的整体理解和掌握程度。

一个虚拟的字符驱动程序实验报告

一个虚拟的字符驱动程序实验报告
5. 用命令 insmod char_dev.ko 加载
6. 用命令 lsmod 察看是否成功加载 7. 使用 dmesg 察看主设备号
8. 使用 mknod /dev/char_dev c 249 1 在/dev 目录下创建设备文件 9. 运行 testchardev.c 测试
实验总结:
通过 1. 自定义驱动程序描述符 2. 预订主设备号 3. 初始化自定义描述符 4. 初始化 gendisk 描述符 5. 初始化块设备操作表 6. 分配和初始化请求队列 7. 设置中断处理程序 8. 注册磁盘
}
3.Makefile
ifneq ($(KERNELRELEASE),) obj-m:=char_dev.o else KD ?=/lib/modules/$(shell uname -r)/build PWD :=$(shell pwd) default:
$(MAKE) -C $(KD) M=$(PWD) modules clean:
{ int i; printk("<0>""read char_dev\n"); for(i = 0; i < length; i++) { put_user('a',buffer++); } return (ssize_t)length;
}
static ssize_t device_write(struct file *file, const char *buffer, /* The buffer */ size_t length, /* The length of the buffer */ loff_t *offset) /* Our offset in the file */

字符设备驱动程序实验报告

字符设备驱动程序实验报告

操作系统课程设计报告字符驱动设备程序一、概述1.1课程设计目的设备驱动充当了硬件和应用软件之间的纽带,它使得应用软件只需要调用软件的应用编程接口(API)就可以让硬件去完成要求的工作,在有操作系统的情况下,必须按照相应的架构设计驱动,才能将驱动良好的整合入操作系统的内核。

通过这次课程设计可以了解linux的模块机制,懂得如何加载模块和卸载模块,进一步熟悉模块的相关操作。

加深对驱动程序定义和设计的了解,了解linux驱动的编写过程。

1.2 课程设计内容与要求(1)设计Windows XP或者Linux操作系统下的设备驱动程序;(2)设备类型可以是字符设备、块设备或者网络设备;(3)设备可以是虚拟的也可以是实际设备;1.3设计步骤1)file_operations结构体设计2)模块初始化、模块卸载函数实现3)读写函数的实现4)测试程序编写5)驱动程序编译和加载6)驱动程序测试二、基本概念和原理Linux系统从各异的设备中提取了共性的特征,将设备分为三类:字符设备、块设备和网络设备。

内核针对每一类设备都提供了对应的驱动模型框架,包括基本的内核设施和文件接口。

系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。

设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。

设备驱动程序是内核的一部分,它完成以下的功能:1、对设备初始化和释放;2、把数据从内核传送到硬件和从硬件读取数据;3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据;4、检测和处理设备出现的错误。

字符设备驱动程序:控制长短不一致字符列,应用程序直接呼叫的、没有缓存的设备驱动程序。

字符设备驱动程序所提供的功能是以设备文件的形式提供给用户空间程序使用,应用程序中使用open()、close()、read()、write()等文件处理函数,并且以普通文件方式处理设备文件,从而控制硬件。

第1章 Linux字符设备驱动程序资料

第1章 Linux字符设备驱动程序资料

2018/10/25
嵌入式操作系统
69
BTTC
五、Linux内核中断
2018/10/25
嵌入式操作系统
70
BTTC
五、Linux内核中断
中断处理流程如下:
(1)发生中断时,CPU执行异常向量vector_irq的代码。 (2)在 vector_irq里面,最终会调用中断处理的总入口函数 asm_do_IRQ。 (3) asm_do_IRQ根据中断号调用irq_desc数组项中的 handle_irq。 (4) handle_irq会使用chip成员中的函数来设置硬件,比如清 除中断、禁止中断、重新使能中断等。 (5) handle_irq逐个调用用户在action链表中注册的处理函数。 中断体系结构的初始化就是构造这些数据结构,比如 irq_desc数组项中的handle_irq、chip等成员;用户注册中断 时就是构造action链表;用户卸载中断时就是从action链表中 去除不需要的项。
• 应用——驱动模型
2018/10/25
嵌入式操作系统
40
BTTC
二、字符设备驱动程序
• 4、设备操作实现
2018/10/25
嵌入式操作系统
41
BTTC
二、字符设备驱动程序
• 设备操作
2018/10/25
嵌入式操作系统
42
BTTC
二、字符设备驱动程序
• 设备操作
2018/10/25
嵌入式操作系统
2018/10/25
嵌入式操作系统
73
BTTC
五、Linux内核中断
• 中断注册(参数)
2018/10/25
嵌入式操作系统
74

实验二:字符设备驱动实验

实验二:字符设备驱动实验

实验二:字符设备驱动实验一、实验目的通过本实验的学习,了解Linux操作系统中的字符设备驱动程序结构,并能编写简单的字符设备的驱动程序以及对所编写的设备驱动程序进行测试,最终了解Linux操作系统如何管理字符设备。

二、准备知识字符设备驱动程序主要包括初始化字符设备、字符设备的I/O调用和中断服务程序。

在字符设备驱动程序的file_operations结构中,需要定义字符设备的基本入口点。

open()函数;release()函数read()函数write()函数ioctl()函数select()函数。

另外,注册字符设备驱动程序的函数为register_chrdev()。

register_chrdev() 原型如下:int register_chrdev(unsigned int major, //主设备号const char *name, //设备名称struct file_operations *ops); //指向设备操作函数指针其中major是设备驱动程序向系统申请的主设备号。

如果major为0,则系统为该驱动程序动态分配一个空闲的主设备号。

name是设备名称,ops是指向设备操作函数的指针。

注销字符设备驱动程序的函数是unregister_chrdev(),原型如下:int unregister_chrdev(unsigned int major,const char *name);字符设备注册后,必须在文件系统中为其创建一个设备文件。

该设备文件可以在/dev目录中创建,每个设备文件代表一个具体的设备。

使用mknod命令来创建设备文件。

创建设备文件时需要使用设备的主设备号和从设备号作为参数。

阅读教材相关章节知识,了解字符设备的驱动程序结构。

三、实验内容根据教材提供的实例。

编写一个简单的字符设备驱动程序。

要求该字符设备包括open()、write()、read()、ioctl()和release()五个基本操作,并编写一个测试程序来测试所编写的字符设备驱动程序。

字符设备驱动实验报告(3篇)

字符设备驱动实验报告(3篇)

第1篇一、实验背景与目的随着计算机技术的飞速发展,操作系统对硬件设备的支持越来越丰富。

设备驱动程序作为操作系统与硬件之间的桥梁,扮演着至关重要的角色。

本实验旨在通过学习Linux字符设备驱动的开发,加深对设备驱动程序的理解,提高实践能力。

二、实验环境与工具1. 操作系统:Linux Ubuntu 20.042. 编程语言:C3. 开发工具:gcc、make4. 驱动框架:Linux内核三、实验内容本实验主要完成以下内容:1. 字符设备驱动程序的基本框架2. 字符设备的打开、读取、写入和关闭操作3. 字符设备驱动的注册与注销4. 字符设备驱动的用户空间交互四、实验步骤1. 创建设备文件首先,我们需要在`/dev`目录下创建一个名为`mychar`的字符设备文件。

可以使用以下命令:```bashmknod /dev/mychar c 123 0```其中,`123`是主设备号,`0`是次设备号。

2. 编写字符设备驱动程序创建一个名为`mychar.c`的文件,并编写以下代码:```cinclude <linux/module.h>include <linux/fs.h>include <linux/uaccess.h>static int major = 123; // 设备号static int device_open(struct inode inode, struct file filp);static int device_release(struct inode inode, struct file filp);static ssize_t device_read(struct file filp, char __user buf, size_t count, loff_t pos);static ssize_t device_write(struct file filp, const char __user buf, size_t count, loff_t pos);static struct file_operations fops = {.open = device_open,.release = device_release,.read = device_read,.write = device_write,};static int __init mychar_init(void) {major = register_chrdev(0, "mychar", &fops);if (major < 0) {printk(KERN_ALERT "mychar: can't get major number\n");return major;}printk(KERN_INFO "mychar: registered correctly with major number %d\n", major);return 0;}static void __exit mychar_exit(void) {unregister_chrdev(major, "mychar");printk(KERN_INFO "mychar: Goodbye from the LKM!\n");}static int device_open(struct inode inode, struct file filp) {printk(KERN_INFO "mychar: Device has been opened\n");return 0;}static int device_release(struct inode inode, struct file filp) {printk(KERN_INFO "mychar: Device has been closed\n");return 0;}static ssize_t device_read(struct file filp, char __user buf, size_t count, loff_t pos) {printk(KERN_INFO "mychar: Device has been read\n");return count;}static ssize_t device_write(struct file filp, const char __user buf, size_t count, loff_t pos) {printk(KERN_INFO "mychar: Device has been written\n"); return count;}module_init(mychar_init);module_exit(mychar_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Your Name");MODULE_DESCRIPTION("A simple character device driver");```保存文件,并使用以下命令编译:```bashmake```3. 加载字符设备驱动程序将编译生成的`mychar.ko`文件加载到内核中:```bashinsmod mychar.ko```4. 测试字符设备驱动程序使用以下命令查看`/dev/mychar`设备文件:```bashls -l /dev/mychar```使用`cat`命令测试读取和写入操作:```bashcat /dev/mycharecho "Hello, world!" > /dev/mychar```观察系统日志,确认驱动程序的打开、读取、写入和关闭操作。

基于Linux的字符设备驱动程序的设计

基于Linux的字符设备驱动程序的设计

基于Linux的字符设备驱动程序的设计1 选题意义驱动程序在 Linux 内核里扮演着特殊的角色. 它们是截然不同的"黑盒子", 使硬件的特殊的一部分响应定义好的内部编程接口.它们完全隐藏了设备工作的细节. 用户的活动通过一套标准化的调用来进行,这些调用与特别的驱动是独立的; 设备驱动的角色就是将这些调用映射到作用于实际硬件的和设备相关的操作上. 这个编程接口是这样, 驱动可以与内核的其他部分分开建立, 并在需要的时候在运行时"插入". 这种模块化使得 Linux 驱动易写, 以致于目前有几百个驱动可用.尽管编写设备代码并不一定比编写应用程序更困难,但它需要掌握一些新函数库,并考虑一些新问题,而这些问题是在应用程序空间里不曾遇到的。

在应用程序空间写程序,内核能够为犯的一些错误提供一张安全网,但当我们工作在内核空间时,这张安全网已不复存在。

因为内核代码对计算机有绝对的控制权,它能够阻止其他任何进程的执行,所以编写的设备代码绝对小心不能滥用这种权利。

在 Linux 设备驱动中,字符设备驱动较为基础,所以本次实验设计一个简单的字符设备驱动程序,然后通过模块机制加载该驱动,并通过一个测试程序来检验驱动设计的正确与否,并对出现的问题进行调试解决。

2 技术路线模块实际上是一种目标对象文件(后缀名为ko ),没有链接,不能独立运行,但是其代码可以在运行时链接到系统中作为内核的一部分运行或从内核中取下,从而可以动态扩充内核的功能。

模块有一个入口(init_module())和一个出口(exit_module())函数,分别是模块加载和卸载时执行的操作,加载模块使用insmod命令,卸载使用rmmod命令。

字符设备以字节为单位进行数据处理,一般不适用缓存。

大多数字符设备仅仅是数据通道,只能按照顺序读写。

主设备号表示设备对应的驱动程序,次设备号用来区分具体设备的实例。

LINUX为文件和设备提供一致的用户接口,对用户来说,设备文件与普通文件并无区别,设备文件也可以挂接到任何需要的地方。

(完整word版)简单字符设备驱动程序的设计(word文档良心出品)

(完整word版)简单字符设备驱动程序的设计(word文档良心出品)

实验五:简单字符设备驱动程序的设计实验学时:4实验类型:(设计)一、实验目的1. 理解设备驱动程序的处理过程;2. 掌握Linux设备驱动程序开发的基本过程和设计方法;3. 学会编写简单的字符设备驱动程序。

二、实验条件Linux操作系统gcc三、实验原理及相关知识设备驱动程序是I/O进程与设备控制器之间的通信程序。

驱动程序的功能:⑴接收由设备独立性软件发来的命令和参数,并将命令中的抽象要求转换为具体的要求。

⑵检查用户I/O请求的合法性,了解I/O设备的状态,传递有关参数,设置设备的工作方式。

⑶发出I/O命令。

⑷及时响应由控制器或通道发来的中断请求,并根据其中断类型调用相应的中断处理程序进行处理。

⑸对于设置有通道的计算机系统,驱动程序还应能够根据用户的I/O请求,自动地构建通道程序。

设备驱动程序的处理过程:⑴将抽象要求转换为具体要求⑵检查I/O设备请求的合法性⑶读出和检查设备的状态⑷传送必要的参数⑸工作方式的设置⑹启动I/O设备Linux系统中,设备驱动程序是操作系统内核的重要组成部分,它与硬件设备之间建立了标准的抽象接口。

通过这个接口,用户可以像处理普通文件一样,对硬件设备进行打开(open)、关闭(close)、读写(read/write)等操作。

通常设备驱动程序接口是由结构file_operations结构体向系统说明的,它定义在include/linux/fs.h中。

file_operations的数据结构如下:struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char_user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char _user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, 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);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, struct dentry *, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);...};⑴open 入口点:open函数负责打开设备、准备I/O。

11-2-字符设备驱动程序

11-2-字符设备驱动程序
3. 虚拟文件系统VFS 4. 驱动调试技术 5. 并发控制
67
26
字符设备驱动程序的设计 知识点
1. 设备号 2. 创建设备文件 3. 设备注册 4. 重要的数据结构 5. 设备操作
67
27
主次设备号
67
28
67
29
设备号
• 设备号的用途?
67
30
设备号
67
31
主设备号
67
32
设备号的作用
67
45
结构2:struct file_operations
67
46
例:mem_fops
struct file_operations mem_fops={ .owner=THIS_MODULE; .llseek=mem_seek; .read=mem_read; .write=mem_write; .ioctl=mem_ioctl; .open=mem_open, .release=mem_release, };
67
64
• test.c
测试程序
67
65
阅读程序-1.找到驱动程序入口
67
66
练习与作业题
1. 内核的配置和编译的过程是什么?请简述 2. 内核模块的开发和运行流程是什么? 3. 编写一个内核程序,使其输出hello world 4. 用户如何使用设备驱动程序? 5. 字符设备驱动程序包括哪些部分? 6. 以memdev字符设备驱动程序为例,理解
67
10
驱动程序的安装
1. 模块方式 2. 直接编译进内核
67
11
编译进内核
• 修改两个文件:
67
12
1.创建并编写源文件 touch hello.c gedit hello.c 2.修改 linux-3.0.1/drivers/char/Kconfig linux-3.0.1/drivers/char/Makefile

linux设备驱动第三篇:写一个简单的字符设备驱动

linux设备驱动第三篇:写一个简单的字符设备驱动

linux设备驱动第三篇:写⼀个简单的字符设备驱动在linux设备驱动第⼀篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写⼀个简单的字符设备驱动。

本篇借鉴LDD中的源码,实现⼀个与硬件设备⽆关的字符设备驱动,仅仅操作从内核中分配的⼀些内存。

下⾯就开始学习如何写⼀个简单的字符设备驱动。

⾸先我们来分解⼀下字符设备驱动都有那些结构或者⽅法组成,也就是说实现⼀个可以使⽤的字符设备驱动我们必须做些什么⼯作。

1、主设备号和次设备号对于字符设备的访问是通过⽂件系统中的设备名称进⾏的。

他们通常位于/dev⽬录下。

如下:xxx@ubuntu:~$ ls -l /dev/total 0brw-rw---- 1 root disk 7, 0 3⽉ 25 10:34 loop0brw-rw---- 1 root disk 7, 1 3⽉ 25 10:34 loop1brw-rw---- 1 root disk 7, 2 3⽉ 25 10:34 loop2crw-rw-rw- 1 root tty 5, 0 3⽉ 25 12:48 ttycrw--w---- 1 root tty 4, 0 3⽉ 25 10:34 tty0crw-rw---- 1 root tty 4, 1 3⽉ 25 10:34 tty1crw--w---- 1 root tty 4, 10 3⽉ 25 10:34 tty10其中b代表块设备,c代表字符设备。

对于普通⽂件来说,ls -l会列出⽂件的长度,⽽对于设备⽂件来说,上⾯的7,5,4等代表的是对应设备的主设备号,⽽后⾯的0,1,2,10等则是对应设备的次设备号。

那么主设备号和次设备号分别代表什么意义呢?⼀般情况下,可以这样理解,主设备号标识设备对应的驱动程序,也就是说1个主设备号对应⼀个驱动程序。

当然,现在也有多个驱动程序共享主设备号的情况。

⽽次设备号有内核使⽤,⽤于确定/dev下的设备⽂件对应的具体设备。

Linux字符设备驱动程序实验

Linux字符设备驱动程序实验
} while(strncmp(buff, "quit", 4));
close(fd); exit(0); } 6.首先在虚拟设备驱动源码目录下编译并加载驱动模块 # make clean # make # ./test_drv_load 7.编译并运行测试程序,然后查看测试结果 # gcc -o test test.c # ./test 8.卸载驱动程序 # ./test_drv_unload 9.通过 dmesg 命令可以查看内核打印的信息 实验结果:学会和掌握 Linux 字符设备驱动程序的编写和测试等。
/*打开函数*/ static int test_open(struct inode *inode, struct file *file) {
printk("This is open operation\n"); data = (char*)kmalloc(sizeof(char) * BUFF_SZ, GFP_KERNEL); if (!data) {
} }
/* tests 设备的 file_operations 结构 */ static struct file_operations test_fops = {
.owner = THIS_MODULE, .read = test_read, .write = test_write, .open = test_open, .release = test_release, };
do {
printf("Input some words to kernel(enter 'quit' to exit):"); memset(buff, 0, BUFF_SZ); if (fgets(buff, BUFF_SZ, stdin) == NULL) {

Linux字符设备驱动程序设计

Linux字符设备驱动程序设计

Computer Knowledge and Technology 电脑知识与技术第6卷第15期(2010年5月)Linux 字符设备驱动程序设计付智华(四川大学计算机学院,四川成都610065)摘要:该文主要涉及Linux 字符设备驱动程序设计中的预处理、模块的初始和终止、设备驱动程序的装配和卸载、以及文件操作。

通过代码片断阐述了各个部分的设计要点。

关键词:设备驱动程序;模块中图分类号:TP316文献标识码:A 文章编号:1009-3044(2010)15-3941-04Design of Linux Character Device DriverFU Zhi-hua(College of Computer Science,Sichuan University,Chengdu 610065,China)Abstract:The paper mainly deals with Linux character device driver design that include pretreatment,initializtion and termination of the module,load and unload of the device driver and some file operations.Elaborated various parts of the design features by code fragment.Key words:device driver;module1概述Linux 系统中,设备驱动程序是操作系统内核的重要组成部分,它与硬件设备之间建立了标准的抽象接口。

通过这个接口,用户可以像处理普通文件一样,对硬件设备进行打开(open)、关闭(close)、读写(read/write)等操作。

通过分析和设计设备驱动程序,可以深入理解Linux 系统和进行系统开发。

linux字符设备驱动课程设计报告

linux字符设备驱动课程设计报告

一、课程设计目的Linux 系统的开源性使其在嵌入式系统的开发中得到了越来越广泛的应用,但其本身并没有对种类繁多的硬件设备都提供现成的驱动程序,特别是由于工程应用中的灵活性,其驱动程序更是难以统一,这时就需开发一套适合于自己产品的设备驱动。

对用户而言,设备驱动程序隐藏了设备的具体细节,对各种不同设备提供了一致的接口,一般来说是把设备映射为一个特殊的设备文件,用户程序可以像对其它文件一样对此设备文件进行操作。

通过这次课程设计可以了解linux的模块机制,懂得如何加载模块和卸载模块,进一步熟悉模块的相关操作。

加深对驱动程序定义和设计的了解,了解linux驱动的编写过程,提高自己的动手能力。

二、课程设计内容与要求字符设备驱动程序1、设计目的:掌握设备驱动程序的编写、编译和装载、卸载方法,了解设备文件的创建,并知道如何编写测试程序测试自己的驱动程序是否能够正常工作2、设计要求:1)编写一个简单的字符设备驱动程序,该字符设备包括打开、读、写、I\O控制与释放五个基本操作。

2)编写一个测试程序,测试字符设备驱动程序的正确性。

3)要求在实验报告中列出Linux内核的版本与内核模块加载过程。

三、系统分析与设计1、系统分析系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。

设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。

设备驱动程序是内核的一部分,它完成以下的功能:1、对设备初始化和释放;2、把数据从内核传送到硬件和从硬件读取数据;3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据;4、检测和处理设备出现的错误。

字符设备提供给应用程序的是一个流控制接口,主要包括op e n、clo s e(或r ele as e)、r e ad、w r i t e、i o c t l、p o l l和m m a p等。

虚拟字符驱动设备程序

虚拟字符驱动设备程序

虚拟字符驱动设备一、内容提要:编写一个完整的字符设备驱动程序。

由于这类驱动程序适合于大多数简单的硬件设备,我们首先实现一个字符设备驱动程序。

Linux 下的设备驱动程序被组织为一组完成不同任务的函数的集合,在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作,如open()、close()、read()、write()等。

然后Linux主要将设备分为二类:字符设备和块设备。

字符设备是指设备发送和接收数据以字符的形式进行;而块设备则以整个数据缓冲区的形式进行。

字符设备的驱动相对比较简单。

由于驱动程序是内核的一部分,因此我们需要给其添加模块初始化函数,该函数用来完成对所控设备的初始化工作.并调用register_chrdev()函数注册字符设备:static int__init gobalvar_init(void){if(register_chrdev(MAJOR_NUM,"gobalvar",&gobalvar_fops) ){//…注册失败}else{//…注册成功}}其中,register_chrdev函数中的参数MAJOR_NUM为主设备号,"gobalvar"为设备名,gobalvar_fops为包含基本函数入口点的结构体,类型为file_operations。

当gobalvar模块被加载时,gobalvar_init被执行,它将调用内核函数register_chrdev,把驱动程序的基本入口点指针存放在内核的字符设备地址表中,在用户进程对该设备执行系统调用时提供入口地址。

与模块初始化函数对应的就是模块卸载函数,需要调用register_chrdev()的"反函数"unregister_chrdev():static void__exit gobalvar_exit(void){if(unregister_chrdev(MAJOR_NUM,"gobalvar")){//…卸载失败}else{//…卸载成功}}二、下面是该字符驱动设备的整体函数代码及分步的分析:Test_drv.c:#include <linux/module.h>#include <linux/init.h>#include <linux/fs.h>#include <linux/kernel.h>#include <linux/slab.h>#include <linux/types.h>#include <linux/errno.h>#include <linux/cdev.h>#include <asm/uaccess.h>#define TEST_DEVICE_NAME "test_dev"#define BUFF_SZ 1024/* 全局变量 */static struct cdev test_dev;unsigned int major=0;static char *data=NULL;dev_t dev;/* 读函数 */static ssize_t test_read(struct file *file,char *buf,size_t count,loff_t *f_pos){ /buf指向缓冲区的指针int len;if(count<0) /count读取和写入的字节数{return -EINVAL;}len=strlen(data);count=(len>count) count:len;if(copy_to_user(buf,data,count)) //将内核缓冲的数据复制到用户空间{return -EFAULT;}return count;}/* 写函数 */static ssize_t test_write(struct file *file,const char*buffer,size_t count,loff_t *f_pos){if(count<0){return -EINVAL;}memset(data,0,BUFF_SZ);count=(BUFF_SZ>count) ?count:BUFF_SZ;if(copy_from_user(data,buffer,count)) //将用户缓冲的数据复制到内核空间{return -EFAULT;}return count;}/* 打开函数 */static int test_open(struct inode *inode,struct file *file) {printk("This is open operation.\n");/* 分配并初始化缓冲区 */data=(char*)kmalloc(sizeof(char)*BUFF_SZ,GFP_KERNEL);if(!data){printk("malloc error!");return -ENOMEM;}memset(data,0,BUFF_SZ);return 0;}/* 关闭函数 */static int test_release(struct inode *inode,struct file *file) {printk("This is release operation.\n");if(data){kfree(data); //释放缓冲区data=NULL; //防止出现野指针}return 0;}/* 创建、初始化字符设备,并且注册到系统 */static void test_setup_cdev(struct cdev *cdev,int minor,struct file_operations *fops){int err;cdev_init(cdev,fops);cdev->owner=THIS_MODULE;cdev->ops=fops;err=cdev_add(cdev,dev,1);if(err){printk(KERN_NOTICE"Error %d adding test %d",err,minor);}}/* 虚拟设备的file——operation结构 */static struct file_operations test_fops={.owner=THIS_MODULE,.read=test_read,.write=test_write,.open=test_open,.release=test_release,};/* 模块注册入口 */static int __init init_module(void){int result;dev=MKDEV(major,0);if(major){//静态注册一个设备,设备号事先指定好,用cat/proc/devices来查看result=register_chrdev_region(dev,1,TEST_DEVICE_NAME); }else{result=alloc_chrdev_region(&dev,0,1,TEST_DEVICE_NAME); }if(result<0){printk(KERN_WARNING"Test device:unable to get major %d\n",major);return result;}test_setup_cdev(&test_dev,0,&test_fops);printk("The major of the test device is %d\n",dev); return 0;}/* 卸载模块 */static void __exit cleanup_module(void){cdev_del(&test_dev);unregister_chrdev_region(MKDEV(major,0),1);printk("Test device uninstalled.\n");}module_init(init_module);module_exit(cleanup_module);随着内核不断增加新的功能,file_operations结构体已逐渐变得越来越大,但是大多数的驱动程序只是利用了其中的一部分。

嵌入式第10章 字符设备和驱动程序设计-陈文智.

嵌入式第10章 字符设备和驱动程序设计-陈文智.


在<linux/kdev_t.h>头文件中给出了这些宏的定义
#define MINORBITS #define MINORMASK #define MAJOR(dev) #define MINOR(dev) #define MKDEV(ma,mi) 20 ((1U << MINORBITS) - 1) ((unsigned int) ((dev) >> MINORBITS)) ((unsigned int) ((dev) & MINORMASK)) (((ma) << MINORBITS) | (mi))

register_chrdev_region函数和alloc_chrdev_region函数用于 分配设备号,它们的区别是后者是以动态的方式分配的。 unregister_chrdev_region函数则用于释放设备号。
关键数据结构

多数情况下,基本的驱动程序操作都会涉及到内核提 供的三个关键数据结构,分别是file_operations、file和 inode,它们都在<linux/fs.h>头文件中定义。 ◦ struct file_operations ◦ struct file ◦ struct inode

对字符设备的访问是通过文件系统内的设 备文件进行的,或者称为设备节点。它们 通常位于/dev目录。 表示字符设备的设备文件可以通过“ls -l” 命令输出的第一列中的“c”来识别,而 块设备则用“b”标识。 主设备号用来标识该设备的种类,也标识 了该设备所使用的驱动程序;次设备号由 内核使用,标识使用同一设备驱动程序的 不同硬件设备。

在2.6内核中,可以容纳大量的设备,而不像先前的内核版本最 多只能使用255个主设备号和255个次设备号。

linux字符设备驱动程序设计概述(转)

linux字符设备驱动程序设计概述(转)

linux字符设备驱动程序设计概述(转)linux字符设备驱动程序设计概述(转)字符设备是最基本、最常用的设备。

概括的说,字符设备驱动主要要做三件事:1、定义一个结构体static struct file_operations变量,其内定义一些设备的打开、关闭、读、写、控制函数;2、在结构体外分别实现结构体中定义的这些函数;3、向内核中注册或删除驱动模块。

具体如下:字符设备提供给应用程序流控制接口有:open/close/read/write/ioctl,添加一个字符设备驱动程序,实际上是给上述操作添加对应的代码,Linux对这些操作统一做了抽象struct file_operationsfile_operations结构体的例子如下static struct file_operations myDriver_fops = {owner: THIS_MODULE,write: myDriver_write,read: myDriver_read,ioctl: myDriver_ioctl,open: myDriver_open,release: myDriver_release,};该结构体规定了驱动程序向应用程序提供的操作接口:实现write操作从应用程序接收数据送到硬件。

例:static ssize_t myDriver_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos){size_t fill_size = count;PRINTK("myDriver write called!\n");PRINTK("\tcount=%d, pos=%d\n", count, (int)*f_pos);if(*f_pos >= sizeof(myDriver_Buffer)){PRINTK("[myDriver write]Buffer Overlap\n");*f_pos = sizeof(myDriver_Buffer);return 0;}if((count + *f_pos) > sizeof(myDriver_Buffer)){PRINTK("count + f_pos > sizeof buffer\n");fill_size = sizeof(myDriver_Buffer) - *f_pos;}copy_from_user(&myDriver_Buffer[*f_pos], buf, fill_size);*f_pos += fill_size;return fill_size;}其中的关键函数u_long copy_from_user(void *to, const void *from, u_long len);把用户态的数据拷到内核态,实现数据的传送。

实现字符设备驱动

实现字符设备驱动

/* seek 文件定位函数 */ static loff_t mem_llseek(struct file *filp, loff_t offset, int whence) { loff_t newpos; switch(whence) { case 0: /* SEEK_SET */ /*相对文件开始位置偏移*/ newpos = offset; //* SEEK_CUR */ newpos = filp->f_pos + offset; break; case 2: /* SEEK_END */ newpos = MEMDEV_SIZE -1 + offset; break; default: /* can't happen */ return -EINVAL; } if ((newpos<0) || (newpos>MEMDEV_SIZE)) return -EINVAL; filp->f_pos = newpos; return newpos; } /*文件操作结构体*/ static const struct file_operations mem_fops = { .owner = THIS_MODULE, .llseek = mem_llseek, .read = mem_read, .write = mem_write, .open = mem_open, .release = mem_release, }; /*设备驱动模块加载函数*/ static int memdev_init(void)
} return 0; fail_malloc: unregister_chrdev_region(devno, 1); return result; } /*模块卸载函数*/ static void memdev_exit(void) { cdev_del(&cdev); /*注销设备*/ kfree(mem_devp); /*释放设备结构体内存*/ unregister_chrdev_region(MKDEV(mem_major, 0), 2); /* 释放设备号*/ } MODULE_AUTHOR("David Xie"); MODULE_LICENSE("GPL"); module_init(memdev_init); module_exit(memdev_exit); Makefile: obj-m := memdev.o KERNELBUILD:=/usr/src/linux-headers-4.2.0-18-generic ##/usr/src/linux-source-3.13.0 ##/usr/src/linux-headers-4.2.0-18 default: make -C $(KERNELBUILD) M=$(shell pwd) modules clean: rm -rf *.o *.ko *.mod.c 实验过程设计: 1.编译源文件 global.c,并加载该模块到内核 make 2.安装 global 模块 insmod global.ko 3.查看字符设备号 cat /proc/devices | grep “global”

操作系统实验报告四_字符类型设备的驱动程序

操作系统实验报告四_字符类型设备的驱动程序
Windows XP + VMware Workstation + RedHat Linux
三,实验内容和实验步骤
一,实验内容 编写一个简单的字符设备驱动程序.要求该字符设备包括 scull_open(),scull_write(), scull_read(),scull_ioctl(),scull_realease()5 个基本操作,还应再编写一个测试程序用来测试用 户所编写的字符设备驱动程序. 二,程序代码 scull_open() int scull_open(struct inode *node,struct file *filp) { MOD_INC_USE_COUNT; printk("This chrdev is in open\n"); return 0; } scull_write() int scull_write(struct inode *inode,struct file *filp,const char *buffer,int count;) {
滁州学院实验报告
操作系统
实验名称 课程名称 组 号
操作系统 2
实验报告
字符类型设备的驱动程序

实验时间 同
1, 了解 Linux 操作系统中的设备驱动程序的组成 2, 编写简单的字符设备驱动程序并进行测试; 3, 理解 Linux 操作系统的设备管理机制.
二,实验环境
Write By 吴朔
滁州学院实验报告
if(count < 0) return -EINVAL; if(age || scull.new_msg) return -EBUSY; age = 1; kfree(scull.data); data = kmalloc(sizeof(char)*(count + 1),GFP_KERNEL); if(!scull.data) { return -EMOMEM; } copy_from_user(scull.data,buffer,count + 1); age = 0; scull.new_msg = 1; return count; } scull_read() int scull_read(struct inode *inode,struct file*filp,char*buffer,int count) { int length; if(count < 0) return -EINVAL; if(age) return -EBUSY; age = 1; if(scull.data == 0) return 1; length = strlen(scull.data); if(length < count) count = length; copy_to_user(buf,scull.data,count + 1); scull.new_msg = 0; age = 0; return count; } scull_ioctl() #include<linux/ioctl.h> #define SCULL_MAJOR 0 #define SCULL_MAGIC SUCLL_MAJOR #define SCULL_RESET _IO(SCULL_MAGIC,0) #define SCULL_QUERY_NEW_MSG _IO(SCULL_MAGIC,1) #define SCULL_QUERY_MSG_LENGTH _IO(SCULLL_MAGIC,2) #define IOC_NEW_MSG 1 static int usage,new_msg;
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

字符设备驱动程序课程设计报告中南大学字符设备驱动程序课程设计报告姓名:王学彬专业班级:信安1002班学号:0909103108课程:操作系统安全课程设计指导老师:张士庚一、课程设计目的1.了解Linux字符设备驱动程序的结构;2.掌握Linux字符设备驱动程序常用结构体和操作函数的使用方法;3.初步掌握Linux字符设备驱动程序的编写方法及过程;4.掌握Linux字符设备驱动程序的加载方法及测试方法。

二、课程设计内容5.设计Windows XP或者Linux操作系统下的设备驱动程序;6.掌握虚拟字符设备的设计方法和测试方法;7.编写测试应用程序,测试对该设备的读写等操作。

三、需求分析3.1驱动程序介绍驱动程序负责将应用程序如读、写等操作正确无误的传递给相关的硬件,并使硬件能够做出正确反应的代码。

驱动程序像一个黑盒子,它隐藏了硬件的工作细节,应用程序只需要通过一组标准化的接口实现对硬件的操作。

3.2 Linux设备驱动程序分类Linux设备驱动程序在Linux的内核源代码中占有很大的比例,源代码的长度日益增加,主要是驱动程序的增加。

虽然Linux内核的不断升级,但驱动程序的结构还是相对稳定。

Linux系统的设备分为字符设备(chardevice),块设备(block device)和网络设备(network device)三种。

字符设备是指在存取时没有缓存的设备,而块设备的读写都有缓存来支持,并且块设备必须能够随机存取(random access)。

典型的字符设备包括鼠标,键盘,串行口等。

块设备主要包括硬盘软盘设备,CD-ROM 等。

网络设备在Linux里做专门的处理。

Linux的网络系统主要是基于BSD unix的socket机制。

在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据传递。

系统有支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。

3.3驱动程序的结构驱动程序的结构如图3.1所示,应用程序经过系统调用,进入核心层,内核要控制硬件需要通过驱动程序实现,驱动程序相当于内核与硬件之间的“系统调用”。

图3.1驱动程序的结构3.3.1 内核模块内核模块是Linux内核的重要组成要素,内核模块能在Linux系统启动之后能够动态进行装载和卸载,因此不需对内核进行重新编译或重启系统就可将内核的一部分替换掉,Linux内核的所有设备驱动,文件系统,网络协议等可做成模块的形式来提供。

在所有的模块中需记录编译的内核版本信息,并与当前执行的内核版本一致。

即,模块具有版本依赖性,如果不一样就会出错,当然可以在模块程序中的include<linux/module.h>之前通过宏定义#define__NO_VERSION__表明不定义模块的版本信息。

内核模块程序与一般应用程序之间主要不同之处是,模块程序没有main()函数,模块程序在装载时调用init_module(void)函数添加到内核中,在卸载时调用void cleanup_module( )函数从内核中卸载。

另外一个应用程序从头到尾只执行一个任务,但一个模块可以把响应未来请求的事务登记到内核中,然后等待系统调用,内核模块程序结构如图3.2所示。

init_module( )register_capability( )printk( ). . . . .cleanup_module( )unregister_capability( )insmod rmmod ModuleKernel capabilities[]图3.2内核模块程序结构3.4主、从设备号应用程序通过设备文件系统(devfs )的名字(或节点)访问硬件设备,所有的设备节点在/dev目录下。

利用mknod 命令生成设备文件系统的节点,但只有超级用户才能生成设备文。

Mknod 命令必须要有设备名和设备类型,主设备号(Major Number ),次设备号(Minor Number )等3个参数。

主设备号用于内核区分设备驱动,次设备号用于设备驱动区分设备。

一个设备驱动可能控制多个设备。

新的设备驱动要有新的主设备号。

在内核源代码的Documentation/devices.txt中定义了所有设备的主设备号。

在创建设备的时候不要与常用的设备好冲突。

3.5驱动程序基本框架如果采用模块方式编写设备驱动程序时,通常至少要实现设备初始化模块、设备打开模块、数据读写与控制模块、中断处理模块(有的驱动程序没有)、设备释放模块和、设备卸载模块等几个部分。

3.6重要结构体打开的设备在内核内部由file结构标识,内核使用file_operation结构访问驱动程序函数。

file_operation结构是一个定义在<linux/fs.h>中的函数指针数组。

每个文件都与它自己的函数集相关联。

这个结构中的每一个字段都必须指向驱动程序中实现特定操作的函数。

结构如下,详细内容可查阅相关文档。

structfile_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);unsignedint (*poll) (struct file *, structpoll_table_struct *);int (*ioctl) (structinode *, struct file *, unsigned int, unsigned long);int (*mmap) (struct file *,structvm_area_struct *);int (*open) (structinode *, struct file *);int (*flush) (struct file *);int (*release) (structinode *, struct file *);int (*fsync) (struct file *, structdentry *, intdatasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int,structfile_lock *);ssize_t (*readv) (struct file *, conststructiovec *, unsigned long, loff_t *);ssize_t (*writev) (struct file *, conststructiovec *, unsigned long, loff_t *);ssize_t (*sendpage) (struct file *, structpage *, int, size_t, loff_t *, int);unsigned long(*get_unmapped_area)(struct file *,unsigned long, unsigned long,unsigned long, unsigned long);}四、总体设计1.在对设备驱动的有了充分的学习后,字符设备的驱动程序我们确定采用虚拟设备的驱动程序实现2.实现平台为linux系统,借助linux内核对设备驱动程序的抽象结构体和内核函数3.要明确定义虚拟设备的的设备结构体4.实现模块加载函数和卸载函数5.实现open(),close(),lseek(),write(),read()函数6.因源码包中已包含makefile,故利用make命令交叉编译memdev.c、test.c(已修改)等2个文件7.模块的动态加载,以及/dev/memdev节点的创建8.运行test程序测试,观察结果五、详细设计1.在对设备驱动的有了充分的学习后,字符设备的驱动程序我们确定采用虚拟设备的驱动程序实现,其中确定该设备主要的结构体为:struct mem_dev { char *data;unsigned long size;};2.实现平台为linux系统,借助linux内核对设备驱动程序的抽象结构体和内核函数,要调用的内核抽象体有:struct cdev cdev; //表示一个字符设备的内核设备的抽象体static const struct file_operationsmem_fops ={.owner = THIS_MODULE,.llseek = mem_llseek,.read = mem_read,.write = mem_write,.open = mem_open,.release = mem_release,};3.要明确定义虚拟设备的的设备结构体struct mem_dev{char *data;unsigned long size;};4.实现模块加载函数和卸载函数static int memdev_init(void){}s tatic int memdev_exit(void){}5.实现open(),close(),lseek(),write(),read()函数int mem_open(struct inode *inode, struct file *filp);int mem_release(struct inode *inode, struct file *filp);static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos);static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t*ppos)static loff_t mem_llseek(struct file *filp, loff_t offset, int whence);6.编译模块,模块的动态加载,以及/dev/memdev节点的创建,mknod /dev/memdev 结果如下:模块编译完,后我们需要把内核模块动态加载到内核,用命令insmod memdev.ko加载,用命令lsmod显示加载成功,结果为:7运行test程序测试,观察结果我们看到我们用应用程序成功的写入了”xiaotian!”并出设备中成功的读出,证明我们的驱动程序运行完美。

相关文档
最新文档