Chinaunix首页 | 论坛 | 博客
  • 博客访问: 512640
  • 博文数量: 68
  • 博客积分: 5011
  • 博客等级: 大校
  • 技术积分: 806
  • 用 户 组: 普通用户
  • 注册时间: 2007-11-30 22:06
文章分类
文章存档

2011年(1)

2009年(8)

2008年(59)

我的朋友

分类: LINUX

2008-09-06 08:49:31

1.Linux驱动程序概述
在操作系统里,对用户而言,设备驱动程序隐藏了设备的具体细节。对于各种不同的设备,如调制解调器,USB扫描仪,打印机等,从用户的角度看,它们都是特殊化的文件,以用同样的Read,Write等操作,完成对不同设备的访问。而驱动程序在特殊的设备和一般的用户间起了桥梁的作用。

用户进程的下层是设备无关的软件,在Linux中设备无关软件的大部分功能由文件系统去完成,执行适用于所有设备的常用I/O功能,向用户进程提供一个统一的接口。当用户进程发出I/O请求时,Linux把请求的处理权限放在文件系统,文件系统通过设备驱动程序提供的接口再把任务下放到驱动程序,驱动程序根据需要对设备控制器进行操作,设备控制器再去控制设备自身。这样通过逐层隔离,Linux对用户进程基本上屏蔽掉了设备的各种硬件特性。设备控制器对设备自身的控制是电气工程师的职责范围,操作系统对I/O设备的管理只是通过文件系统和设备驱动程序来完成。在Linux中,设备驱动程序应完成的主要功能为:对设备进行初始化;设备使用完成后对设备进行相应清理工作;从设备接收数据并将之送回内核;将数据从内核传送至设备;检测和处理设备出现的错误。

通常LINUX驱动程序接口分为如下四层:
1)应用程序进程与内核的接口;
2)内核与文件系统的接口;
3)文件系统与设备驱动程序的接口;
4)设备驱动程序与硬件设备的接口。
Linux内核需要访问两类主要设备:字符设备和块设备。与此相关主要有两类设备驱动程序,字符设备驱动程序和块设备驱动程序。字符设备是最简单的,如鼠标、键盘、甚至自己设计的I/O卡等。块设备的读写一般要缓存支持,并可随机存取(字符设备则不需要),它主要包括硬盘、光驱等存贮设备。两者的主要差异是:与字符设备有关的系统调用几乎直接和驱动程序的内部功能结合在一起。而读写块设备则主要和快速缓冲存储区打交道。只有需要完成实际的输入/输出时,才用到块设备驱动程序。
Linux的设备由一个主设备号(major number)和一个次设备号(minor number)标识。主设备号唯一标识了设备类型,即设备驱动程序类型,它是块设备表或字符设备表中设备表项的索引。次设备号仅由设备驱动程序解释,用于识别同类设备中,I/O请求所涉及到的那个设备。
2.Linux驱动简介
当引导系统时,内核调用每一个驱动程序的初始化函数。它的任务之一是将这一设备驱动程序使用的主设备号通知内核。同时,初始化函数还将驱动程序中的函数地址结构的指针送给内核。
内核中有两张表。一张表用于字符设备驱动程序,另一张用于块设备驱动程序。这两张表用来保存指向file_operations结构的指针,设备驱动程序内部的函数地址就保存在这一结构中。内核用主设备号作为索引访问file_operations结构,因而能访问驱动程序内的子程序。
对于字符设备来说,file_operations{}是唯一的函数接口。

此结构定义为:
#include
struct file_operations{
int (*lseek) (struct inode *inode,struct file *filp,off_to ff,int pos);  /*设备定位*/
int (*read) (struct inode *inode,struct file *filp,char *buf,int count);  /*设备读*/
int (*write) (struct inode *inode,struct file *filp,char *buf,int count);  /*设备写*/
int (*readdir) (struct inode *inode,struct file *filp,struct dirent *dirent,int count);
int (*select) (struct inode *inode,struct file *filp,int sel_type,select_table *wait);
int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned int arg);
 /*I/O控制*/
int (*mmap) (void);
int (*open) (struct inode *inode,struct file *filp);  /*设备打开*/
void (*release) (struct inode *inode,struct file *filp);  /*设备关闭*/
int (*fsync) (struct inode *inode,struct file *filp);
};

在结构file_operations里,指出了设备驱动程序所提供的入口点位置,分别是:
(1)lseek,移动文件指针的位置,显然只能用于可以随机存取的设备。
(2)read,进行读操作,参数buf为存放读取结果的缓冲区,count为所要读取的数据长度。返回值为负表示读取操作发生错误,否则返回实际读取的字节数。
(3)write,进行写操作,与read类似。
(4)readdir,取得下一个目录入口点,只有与文件系统相关的设备驱动程序才使用。
(5)select,进行选择操作,如果驱动程序没有提供select入口,select操作将会认为设备已经准备好进行任何的I/O操作。
(6)ioctl,进行读、写以外的其它操作,参数cmd为自定义的的命令。
(7)mmap,用于把设备的内容映射到地址空间,一般只有块设备驱动程序使用。
(8)open,打开设备准备进行I/O操作。返回0表示打开成功,返回负数表示失败。如果驱动程序没有提供open入口,则只要/dev/driver文件存在就认为打开成功。
(9)release,即close操作。设备驱动程序所提供的入口点,在设备驱动程序初始化的时候向系统进行登记,以便系统在适当的时候调用。

LINUX系统里,通过调用register_chrdev向系统注册字符型设备驱动程序。register_chrdev定义为:
#include
#include
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops);
在register_chrdev()函数中,如果参数major为0,则系统动态为其分配一个设备号。如果major大于0则按照major给出的主设备号给其分配一个设备号。设备号分配成功后,register_chrdev()调用cdev_alloc()分配一个cdev结构,并进行初始化,建立cdev和file_operations之间的连接。之后调用cdev_add()向系统注册字符设备。对于它的返回值,在注册成功时,如果参数major为0则返回动态申请的设备号,否则返回0。在注册失败时返回错误码

初始化部分一般还负责给设备驱动程序申请系统资源,包括内存、中断、时钟、I/O端口等,这些资源也可以在open子程序或别的地方申请。在这些资源不用的时候,应该释放它们,以利于资源的共享。

在Linux系统里,对中断的处理是属于系统核心的部分,因此如果设备与系统之间以中断方式进行数据交换的话,就必须把该设备的驱动程序作为系统核心的一部分。设备驱动程序通过调用request_irq函数来申请中断,通过free_irq来释放中断。
它们的定义为:
#include
int request_irq (unsigned int irq,void (*handler) (int irq,void dev_id,structpt_regs *regs),
unsigned long irqflags,const char *devname,void *dev_id);
void free_irq (unsigned int irq,void *dev_id);

参数irq表示所要申请的硬件中断号。handler为向系统登记的中断处理子程序,中断产生时由系统来调用,调用时所带参数irq为中断号,dev_id为申请时告诉系统的设备标识,regs为中断发生时寄存器内容。devname为设备名,将会出现在/dev文件里。irqflag是申请时的选项,它决定中断处理程序的一些特性,其中最重要的是中断处理程序是快速处理程序(irqflag里设置了SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT),快速处理程序运行时,所有中断都被屏蔽,而慢速处理程序运行时,除了正在处理的中断外,其它中断都没有被屏蔽。在LINUX系统中,中断可以被不同的中断处理程序共享,这要求每一个共享此中断的处理程序在申请中断时在irqflags里设置SA_SHIRQ,这些处理程序之间以dev_id来区分。如果中断由某个处理程序独占,则dev_id可以为NULL。request_irq返回0表示成功,返回-INVAL表示irq无效或handler=NULL,返回-EBUSY表示中断已经被占用且不能共享。

和中断有关的数据结构:
struct irqaction {
    irq_handler_t handler;   /* 指向中断服务程序 */
    unsigned long flags;     /* 中断标志 */
    unsigned long mask;      /* 中断掩码 */
    const char *name;        /* I/O设备名
    void *dev_id;            /* 设备标识 */
    struct irqaction *next;  /* 指向下一个描述符 */
    int irq;                 /* IRQ线 */
    struct proc_dir_entry *dir; /* 指向IRQn相关的/proc/irq/n目录的描述符 */
};

作为系统核心的一部分,设备驱动程序在申请和释放内存时不是调用malloc和free,而代之以调用kmalloc和kfree,它们被定义为:
#include
void *kmalloc (unsigned int len,intpriority);
void kfree (void *obj);

参数len为希望申请的字节数,obj为要释放的内存指针。priority为分配内存操作的优先级,即在没有足够空闲内存时如何操作,一般用GFP_KERNEL。

与中断和内存不同,使用一个没有申请的I/O端口不会使CPU产生异常,也就不会导致诸如“segment ation fault"一类的错误发生。任何进程都可以访问任何一个I/O端口。此时系统无法保证对I/O端口的操作不会发生冲突,甚至会因此而使系统崩溃。因此,在使用I/O端口前,也应该检查此I/O端口是否已有别的程序在使用,若没有,再把此端口标记为正在使用,在使用完以后释放它。
这样需要用到如下几个函数:
int check_region (unsigned int start,unsigned long n);

void request_region (unsigned int start,unsigned long n);
void release_region (unsigned int start,unsigned long n);

调用这些函数时的参数为:start表示所申请的I/O端口的起始地址;n为所要申请的从from开始的端口数;
check_region返回0表示I/O端口空闲,否则为正在被使用。
request_region申请I/O端口使用完成后,使用release_region 函数将他们归还给系统。
在申请了I/O端口之后,就可以如下几个函数来访问I/O端口:
#include
inline unsigned int inb(unsigned short port);
inline unsigned int inb_p(unsigned short port);
inline void outb(char value,unsigned shortport);
inline void outb_p(char value,unsigned short port);
其中inb_p和outb_p插入了一定的延时以适应某些慢的I/O端口。


注:以上内容是摘录网上一遍文章,我做了相应的修改。
       Linux字符驱动程序的设计(下)主要以实例说明
阅读(3211) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~