<script>function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}</script>
作者:华清远见
在华清远见的嵌入式培训课程中,linux自负设备驱动程序的编写是嵌入式学院<嵌入式工程师职业培训班>三期课程中嵌入式linux驱动开发方面的重要内容,这里我们先对QT做一下简单介绍。
一、Linux device driver 的概念
系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:
1、对设备初始化和释放;
2、把数据从内核传送到硬件和从硬件读取数据;
3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据;
4、检测和处理设备出现的错误。
在Linux操作系统下有三类主要的设备文件类型,一是字符设备,二是块设备,三是网络设备。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。
已经提到,用户进程是通过设备文件来与实际的硬件打交道。每个设备文件都都有其文件属性(c/b),表示是字符设备还是块设备?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们。设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序。
最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作。如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck。
二、实例剖析
我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理。把下面的C代码输入机器,你就会获得一个真正的设备驱动程序。
由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就是一些系统调用,如 open,read,write,close…, 注意,不是fopen, fread,但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据结构:
struct file_operations {
int (*seek) (struct inode * ,struct file *, off_t ,int);
int (*read) (struct inode * ,struct file *, char ,int);
int (*write) (struct inode * ,struct file *, off_t ,int);
int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);
int (*select) (struct inode * ,struct file *, int ,select_table *);
int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);
int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);
int (*open) (struct inode * ,struct file *);
int (*release) (struct inode * ,struct file *);
int (*fsync) (struct inode * ,struct file *);
int (*fasync) (struct inode * ,struct file *,int);
int (*check_media_change) (struct inode * ,struct file *);
int (*revalidate) (dev_t dev);
}
这个结构的每一个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。这是linux的设备驱动程序工作的基本原理。既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域。
下面就开始写子程序。
#include <linux/types.h> 基本的类型定义
#include <linux/fs.h> 文件系统使用相关的头文件
#include <linux/mm.h>
#include <linux/errno.h>
#include <asm/segment.h>
unsigned int test_major = 0;
static int read_test(struct inode *inode,struct file *file,char *buf,int count)
{
int left; 用户空间和内核空间
if (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )
return -EFAULT;
for(left = count ; left > 0 ; left--)
{
__put_user(1,buf,1);
buf++;
}
return count;
}
这个函数是为read调用准备的。当调用read时,read_test()被调用,它把用户的缓冲区全部写1。buf 是read调用的一个参数。它是用户进程空间的一个地址。但是在read_test被调用时,系统进入核心态。所以不能使用buf这个地址,必须用__put_user(),这是kernel提供的一个函数,用于向用户传送数据。另外还有很多类似功能的函数。请参考,在向用户空间拷贝数据之前,必须验证buf是否可用。这就用到函数verify_area。为了验证BUF是否可以用。
static int write_test(struct inode *inode,struct file *file,const char *buf,int count)
{
return count;
}
static int open_test(struct inode *inode,struct file *file )
{
MOD_INC_USE_COUNT; 模块计数加以,表示当前内核有个设备加载内核当中去
return 0;
}
static void release_test(struct inode *inode,struct file *file )
{
MOD_DEC_USE_COUNT;
}
这几个函数都是空操作。实际调用发生时什么也不做,他们仅仅为下面的结构提供函数指针。
struct file_operations test_fops = {?
read_test,
write_test,
open_test,
release_test,
};
设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(modules),如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐使用模块方式。
int init_module(void)
{
int result;
result = register_chrdev(0, "test", &test_fops); 对设备操作的整个接口
if (result < 0) {
printk(KERN_INFO "test: can't get major number\n");
return result;
}
if (test_major == 0) test_major = result; /* dynamic */
return 0;
}
在用insmod命令将编译好的模块调入内存时,init_module 函数被调用。在这里,init_module只做了一件事,就是向系统的字符设备表登记了一个字符设备。register_chrdev需要三个参数,参数一是希望获得的设备号,如果是零的话,系统将选择一个没有被占用的设备号返回。参数二是设备文件名,参数三用来登记驱动程序实际执行操作的函数的指针。
如果登记成功,返回设备的主设备号,不成功,返回一个负值。
void cleanup_module(void)
{
unregister_chrdev(test_major,"test");
}
在用rmmod卸载模块时,cleanup_module函数被调用,它释放字符设备test在系统字符设备表中占有的表项。
一个极其简单的字符设备可以说写好了,文件名就叫test.c吧。
下面编译 :
$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c –c表示输出制定名,自动生成.o文件
得到文件test.o就是一个设备驱动程序。
如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后
ld ?-r ?file1.o ?file2.o ?-o ?modulename。
驱动程序已经编译好了,现在把它安装到系统中去。
$ insmod ?–f ?test.o
如果安装成功,在/proc/devices文件中就可以看到设备test,并可以看到它的主设备号。要卸载的话,运行 :
$ rmmod test
下一步要创建设备文件。
mknod /dev/test c major minor
c 是指字符设备,major是主设备号,就是在/proc/devices里看到的。
用shell命令
$ cat /proc/devices
就可以获得主设备号,可以把上面的命令行加入你的shell script中去。
minor是从设备号,设置成0就可以了。
我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
main()
{
int testdev;
int i;
char buf[10];
testdev = open("/dev/test",O_RDWR);
if ( testdev == -1 )
{
printf("Cann't open file \n");
exit(0);
}
read(testdev,buf,10);
for (i = 0; i < 10;i++)
printf("%d\n",buf[i]);
close(testdev);
}
编译运行,看看是不是打印出全1 ?
以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,DMA,I/O port等问题。这些才是真正的难点。上述给出了一个简单的字符设备驱动编写的框架和原理,更为复杂的编写需要去认真研究LINUX内核的运行机制和具体的设备运行的机制等等。希望大家好好掌握LINUX设备驱动程序编写的方法。
分享到:
相关推荐
这本书可能涵盖了如何编写和管理Linux设备驱动程序的基础知识,包括驱动模型、I/O操作、中断处理、DMA(直接内存访问)、设备树等概念。通过学习,开发者可以掌握如何为不同的硬件组件如GPIO、SPI、I2C、UART等编写...
### Linux设备驱动程序框架详解 #### 一、Linux设备驱动程序与外界的接口 Linux设备驱动程序作为连接硬件和操作系统的桥梁,其设计遵循一定的框架和原则,以便于更好地管理和控制硬件资源。根据Linux设备驱动程序...
本文主要探讨嵌入式Linux字符设备驱动程序的设计,包括相关概念、设计框架和开发流程。 1. 设备驱动程序的概念 设备驱动程序是一种特殊的程序,它实现了操作系统内核与硬件设备之间的交互。它并不独立运行,而是...
通过上述示例,我们可以看到一个基本的Linux字符设备驱动程序的结构。虽然示例中并未包含具体的业务逻辑,但已经足够展示设备驱动程序的基本框架和关键组件。理解这些基础知识对于深入学习更复杂的驱动程序开发至关...
### 嵌入式Linux字符设备驱动的设计与应用 #### 摘要 本文主要探讨了基于嵌入式Linux环境下的字符设备驱动程序的设计方法及其实际应用案例。通过对PXA255开发板上几种常见字符设备(如电机、数码管、串口和mini...
本文将详细讲解Linux字符设备驱动的基本框架,包括混杂设备、外部中断处理以及输入子系统的使用。 首先,让我们关注混杂设备。混杂设备是一种特殊的字符设备,它的主设备号是固定的,通常为10。与常规字符设备不同...
《Linux设备驱动程序(2)-字符设备驱动程序》将介绍如何实现基本的字符设备驱动,包括设备节点创建、打开、关闭、读写等操作。 3. **高级字符驱动程序操作**:在《Linux设备驱动程序(7)-高级字符驱动程序操作2》...
总结来说,Linux字符设备驱动程序的编写框架涉及设备驱动的概念、设备分类、文件操作结构体以及驱动程序的基本组件。通过理解和掌握这些基础知识,开发者能有效地为特定硬件编写驱动程序,实现应用程序与硬件的无缝...
编写Linux字符设备驱动程序的基本流程包含以下环节: 1. 编写头文件,这些通常包含了定义版本信息和必要的内核头文件。 2. 定义关键数据结构struct file_operations。这个结构体中的每个成员对应着内核的系统调用,...
对于嵌入式开发人员和系统管理员来说,掌握Linux设备驱动程序的编写是必不可少的技能。通过学习这本书,不仅可以提升硬件级别的控制能力,还能深入了解Linux内核的工作原理,对于解决硬件相关问题和优化系统性能...
《Linux设备驱动程序》(第三版)是一本深入解析Linux内核与设备驱动程序的经典著作。这本书详尽地介绍了如何为各种硬件设备编写驱动程序,包括字符设备、块设备、网络设备、输入设备等,旨在帮助读者理解Linux系统中...
《Linux设备驱动程序第三版》是一本深入探讨Linux操作系统下设备驱动程序开发的权威书籍,中文CHM版的提供使得国内读者能够更加便捷地学习这一领域的重要知识。这本书旨在帮助开发者理解和编写各种类型的Linux设备...
在"Simple Linux Driver"这个示例中,我们可能会看到一个简单的字符设备驱动程序的实现。源代码通常包含以下几个部分: - `driver.c`:驱动的主要实现,包括设备结构体定义、操作函数实现和模块初始化/清理函数。 -...
### Linux设备驱动程序知识点概述 #### 一、Linux设备驱动基础概念 - **定义与作用**:Linux设备驱动是一种特殊的程序,它作为操作系统的一部分运行在内核空间,用于控制计算机硬件并与用户空间应用程序进行交互。...
对于想对Linux驱动开发入门的同学来说,理解和掌握设备驱动程序的工作原理以及如何编写它们是至关重要的。 在Linux系统中,设备驱动程序可以分为字符设备、块设备和网络设备等类型。字符设备处理单个数据流,如键盘...
《Linux设备驱动程序》第三版(通常简称ldd3)是Linux系统开发领域的经典之作,由Robert Love编写,深入浅出地介绍了Linux内核设备驱动的编写与理解。该书的源码包“ldd3_examples”包含了书中所涉及的所有示例代码...
Linux设备驱动程序通常可以分为几种类型,包括字符设备驱动、块设备驱动和网络设备驱动等。字符设备是顺序访问的数据流,比如键盘、鼠标等。块设备则支持随机访问,例如硬盘驱动器和USB驱动器。网络设备驱动则用于...
字符设备驱动实例包括字符设备驱动程序的编写、编译、加载、卸载等过程。字符设备驱动程序需要使用Linux内核提供的设备驱动框架,例如character device driver framework。 九、字符设备驱动中的并发控制 字符设备...
本文将深入探讨字符驱动程序的框架以及如何编写应用测试程序。 首先,我们要理解字符驱动的基本结构。字符驱动程序通常包含以下关键部分: 1. **初始化和退出函数**:在驱动加载时执行初始化操作,如设置设备文件...