分享
 
 
 

LINUX下的设备驱动程序

王朝system·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

三、UNIX系统下的设备驱动程序

3.1、UNIX下设备驱动程序的基本结构

在UNIX系统里,对用户程序而言,设备驱动程序隐藏了设备的具体细节,

对各种不同设备提供了一致的接口,一般来说是把设备映射为一个特殊的设备文

件,用户程序可以象对其它文件一样对此设备文件进行操作。UNIX对硬件设备

支持两个标准接口:块特别设备文件和字符特别设备文件,通过块(字符)特别

设备文件存取的设备称为块(字符)设备或具有块(字符)设备接口。

块设备接口仅支持面向块的I/O操作,所有I/O操作都通过在内核地址空间

中的I/O缓冲区进行,它可以支持几乎任意长度和任意位置上的I/O请求,即提

供随机存取的功能。

字符设备接口支持面向字符的I/O操作,它不经过系统的快速缓存,所以它

们负责管理自己的缓冲区结构。字符设备接口只支持顺序存取的功能,一般不能

进行任意长度的I/O请求,而是限制I/O请求的长度必须是设备要求的基本块长

的倍数。显然,本程序所驱动的串行卡只能提供顺序存取的功能,属于是字符设

备,因此后面的讨论在两种设备有所区别时都只涉及字符型设备接口。

设备由一个主设备号和一个次设备号标识。主设备号唯一标识了设备类型,

即设备驱动程序类型,它是块设备表或字符设备表中设备表项的索引。次设备号

仅由设备驱动程序解释,一般用于识别在若干可能的硬件设备中,I/O请求所涉

及到的那个设备。

设备驱动程序可以分为三个主要组成部分:

(1) 自动配置和初始化子程序,负责检测所要驱动的硬件设备是否存在和是否

能正常工作。如果该设备正常,则对这个设备及其相关的、设备驱动程序

需要的软件状态进行初始化。这部分驱动程序仅在初始化的时候被调用一

次。

(2) 服务于I/O请求的子程序,又称为驱动程序的上半部分。调用这部分是由

于系统调用的结果。这部分程序在执行的时候,系统仍认为是和进行调用

的进程属于同一个进程,只是由用户态变成了核心态,具有进行此系统调

用的用户程序的运行环境,因此可以在其中调用sleep()等与进程运行环

境有关的函数。

(3) 中断服务子程序,又称为驱动程序的下半部分。在UNIX系统中,并不是

直接从中断向量表中调用设备驱动程序的中断服务子程序,而是由UNIX

系统来接收硬件中断,再由系统调用中断服务子程序。中断可以产生在任

何一个进程运行的时候,因此在中断服务程序被调用的时候,不能依赖于

任何进程的状态,也就不能调用任何与进程运行环境有关的函数。因为设

备驱动程序一般支持同一类型的若干设备,所以一般在系统调用中断服务

子程序的时候,都带有一个或多个参数,以唯一标识请求服务的设备。

在系统内部,I/O设备的存取通过一组固定的入口点来进行,这组入口点是

由每个设备的设备驱动程序提供的。一般来说,字符型设备驱动程序能够提供如

下几个入口点:

(1) open入口点。打开设备准备I/O操作。对字符特别设备文件进行打开操

作,都会调用设备的open入口点。open子程序必须对将要进行的I/O

操作做好必要的准备工作,如清除缓冲区等。如果设备是独占的,即同一

时刻只能有一个程序访问此设备,则open子程序必须设置一些标志以表

示设备处于忙状态。

(2) close入口点。关闭一个设备。当最后一次使用设备终结后,调用close

子程序。独占设备必须标记设备可再次使用。

(3) read入口点。从设备上读数据。对于有缓冲区的I/O操作,一般是从缓

冲区里读数据。对字符特别设备文件进行读操作将调用read子程序。

(4) write入口点。往设备上写数据。对于有缓冲区的I/O操作,一般是把数

据写入缓冲区里。对字符特别设备文件进行写操作将调用write子程序。

(5) ioctl入口点。执行读、写之外的操作。

(6) select入口点。检查设备,看数据是否可读或设备是否可用于写数据。

select系统调用在检查与设备特别文件相关的文件描述符时使用select入口点。

如果设备驱动程序没有提供上述入口点中的某一个,系统会用缺省的子程序

来代替。对于不同的系统,也还有一些其它的入口点。

3.2、LINUX系统下的设备驱动程序

具体到LINUX系统里,设备驱动程序所提供的这组入口点由一个结构来向系

统进行说明,此结构定义为:

#include <linux/fs.h>

struct file_operations {

int (*lseek)(struct inode *inode,struct file *filp,

off_t off,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);

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);

};

其中,struct inode提供了关于特别设备文件/dev/driver(假设此设备名

为driver)的信息,它的定义为:

#include <linux/fs.h>

struct inode {

dev_t i_dev;

unsigned long i_ino; /* Inode number */

umode_t i_mode; /* Mode of the file */

nlink_t i_nlink;

uid_t i_uid;

gid_t i_gid;

dev_t i_rdev; /* Device major and minor numbers*/

off_t i_size;

time_t i_atime;

time_t i_mtime;

time_t i_ctime;

unsigned long i_blksize;

unsigned long i_blocks;

struct inode_operations * i_op;

struct super_block * i_sb;

struct wait_queue * i_wait;

struct file_lock * i_flock;

struct vm_area_struct * i_mmap;

struct inode * i_next, * i_prev;

struct inode * i_hash_next, * i_hash_prev;

struct inode * i_bound_to, * i_bound_by;

unsigned short i_count;

unsigned short i_flags; /* Mount flags (see fs.h) */

unsigned char i_lock;

unsigned char i_dirt;

unsigned char i_pipe;

unsigned char i_mount;

unsigned char i_seek;

unsigned char i_update;

union {

struct pipe_inode_info pipe_i;

struct minix_inode_info minix_i;

struct ext_inode_info ext_i;

struct msdos_inode_info msdos_i;

struct iso_inode_info isofs_i;

struct nfs_inode_info nfs_i;

} u;

};

struct file主要用于与文件系统对应的设备驱动程序使用。当然,其它设

备驱动程序也可以使用它。它提供关于被打开的文件的信息,定义为:

#include <linux/fs.h>

struct file {

mode_t f_mode;

dev_t f_rdev; /* needed for /dev/tty */

off_t f_pos; /* Curr. posn in file */

unsigned short f_flags; /* The flags arg passed to open */

unsigned short f_count; /* Number of opens on this file */

unsigned short f_reada;

struct inode *f_inode; /* pointer to the inode struct */

struct file_operations *f_op;/* pointer to the fops struct*/

};

在结构file_operations里,指出了设备驱动程序所提供的入口点位置,分

别是:

(1) lseek,移动文件指针的位置,显然只能用于可以随机存取的设备。

(2) read,进行读操作,参数buf为存放读取结果的缓冲区,count为所要

读取的数据长度。返回值为负表示读取操作发生错误,否则返回实际读取

的字节数。对于字符型,要求读取的字节数和返回的实际读取字节数都必

须是inode->i_blksize的的倍数。

(3) write,进行写操作,与read类似。

(4) readdir,取得下一个目录入口点,只有与文件系统相关的设备驱动程序

才使用。

(5) selec,进行选择操作,如果驱动程序没有提供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 <linux/fs.h>

#include <linux/errno.h>

int register_chrdev(unsigned int major, const char *name,

struct file_operations *fops);

其中,major是为设备驱动程序向系统申请的主设备号,如果为0则系统为此

驱动程序动态地分配一个主设备号。name是设备名。fops就是前面所说的对各个

调用的入口点的说明。此函数返回0表示成功。返回-EINVAL表示申请的主设备号

非法,一般来说是主设备号大于系统所允许的最大设备号。返回-EBUSY表示所申

请的主设备号正在被其它设备驱动程序使用。如果是动态分配主设备号成功,此

函数将返回所分配的主设备号。如果register_chrdev操作成功,设备名就会出

现在/proc/devices文件里。

初始化部分一般还负责给设备驱动程序申请系统资源,包括内存、中断、时

钟、I/O端口等,这些资源也可以在open子程序或别的地方申请。在这些资源不

用的时候,应该释放它们,以利于资源的共享。

在UNIX系统里,对中断的处理是属于系统核心的部分,因此如果设备与系

统之间以中断方式进行数据交换的话,就必须把该设备的驱动程序作为系统核心

的一部分。设备驱动程序通过调用request_irq函数来申请中断,通过free_irq

来释放中断。它们的定义为:

#include <linux/sched.h>

int request_irq(unsigned int irq,

void (*handler)(int irq,void dev_id,struct pt_regs *regs),

unsigned long flags,

const char *device,

void *dev_id);

void free_irq(unsigned int irq, void *dev_id);

参数irq表示所要申请的硬件中断号。handler为向系统登记的中断处理子

程序,中断产生时由系统来调用,调用时所带参数irq为中断号,dev_id为申

请时告诉系统的设备标识,regs为中断发生时寄存器内容。device为设备名,

将会出现在/proc/interrupts文件里。flag是申请时的选项,它决定中断处理

程序的一些特性,其中最重要的是中断处理程序是快速处理程序(flag里设置

了SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT),快速处理程序

运行时,所有中断都被屏蔽,而慢速处理程序运行时,除了正在处理的中断外,

其它中断都没有被屏蔽。在LINUX系统中,中断可以被不同的中断处理程序共享,

这要求每一个共享此中断的处理程序在申请中断时在flags里设置SA_SHIRQ,

这些处理程序之间以dev_id来区分。如果中断由某个处理程序独占,则dev_id

可以为NULL。request_irq返回0表示成功,返回-INVAL表示irq>15或

handler==NULL,返回-EBUSY表示中断已经被占用且不能共享。

作为系统核心的一部分,设备驱动程序在申请和释放内存时不是调用malloc

和free,而代之以调用kmalloc和kfree,它们被定义为:

#include <linux/kernel.h>

void * kmalloc(unsigned int len, int priority);

void kfree(void * obj);

参数len为希望申请的字节数,obj为要释放的内存指针。priority为分配内存操

作的优先级,即在没有足够空闲内存时如何操作,一般用GFP_KERNEL。

与中断和内存不同,使用一个没有申请的I/O端口不会使CPU产生异常,也

就不会导致诸如“segmentation fault"一类的错误发生。任何进程都可以访问

任何一个I/O端口。此时系统无法保证对I/O端口的操作不会发生冲突,甚至会

因此而使系统崩溃。因此,在使用I/O端口前,也应该检查此I/O端口是否已有

别的程序在使用,若没有,再把此端口标记为正在使用,在使用完以后释放它。

这样需要用到如下几个函数:

int check_region(unsigned int from, unsigned int extent);

void request_region(unsigned int from, unsigned int extent,

const char *name);

void release_region(unsigned int from, unsigned int extent);

调用这些函数时的参数为:from表示所申请的I/O端口的起始地址;

extent为所要申请的从from开始的端口数;name为设备名,将会出现在

/proc/ioports文件里。check_region返回0表示I/O端口空闲,否则为正在

被使用。

在申请了I/O端口之后,就可以如下几个函数来访问I/O端口:

#include <asm/io.h>

inline unsigned int inb(unsigned short port);

inline unsigned int inb_p(unsigned short port);

inline void outb(char value, unsigned short port);

inline void outb_p(char value, unsigned short port);

其中inb_p和outb_p插入了一定的延时以适应某些慢的I/O端口。

在设备驱动程序里,一般都需要用到计时机制。在LINUX系统中,时钟是由

系统接管,设备驱动程序可以向系统申请时钟。与时钟有关的系统调用有:

#include <asm/param.h>

#include <linux/timer.h>

void add_timer(struct timer_list * timer);

int del_timer(struct timer_list * timer);

inline void init_timer(struct timer_list * timer);

struct timer_list的定义为:

struct timer_list {

struct timer_list *next;

struct timer_list *prev;

unsigned long expires;

unsigned long data;

void (*function)(unsigned long d);

};

其中expires是要执行function的时间。系统核心有一个全局变量JIFFIES

表示当前时间,一般在调用add_timer时jiffies=JIFFIES+num,表示在num个

系统最小时间间隔后执行function。系统最小时间间隔与所用的硬件平台有关,

在核心里定义了常数HZ表示一秒内最小时间间隔的数目,则num*HZ表示num

秒。系统计时到预定时间就调用function,并把此子程序从定时队列里删除,

因此如果想要每隔一定时间间隔执行一次的话,就必须在function里再一次调

用add_timer。function的参数d即为timer里面的data项。

在设备驱动程序里,还可能会用到如下的一些系统函数:

#include <asm/system.h>

#define cli() __asm__ __volatile__ ("cli"::)

#define sti() __asm__ __volatile__ ("sti"::)

这两个函数负责打开和关闭中断允许。

#include <asm/segment.h>

void memcpy_fromfs(void * to,const void * from,unsigned long n);

void memcpy_tofs(void * to,const void * from,unsigned long n);

在用户程序调用read 、write时,因为进程的运行状态由用户态变为核心

态,地址空间也变为核心地址空间。而read、write中参数buf是指向用户程

序的私有地址空间的,所以不能直接访问,必须通过上述两个系统函数来访问用

户程序的私有地址空间。memcpy_fromfs由用户程序地址空间往核心地址空间

复制,memcpy_tofs则反之。参数to为复制的目的指针,from为源指针,n

为要复制的字节数。

在设备驱动程序里,可以调用printk来打印一些调试信息,用法与printf

类似。printk打印的信息不仅出现在屏幕上,同时还记录在文件syslog里。

3.3、LINUX系统下的具体实现

在LINUX里,除了直接修改系统核心的源代码,把设备驱动程序加进核心里

以外,还可以把设备驱动程序作为可加载的模块,由系统管理员动态地加载它,

使之成为核心地一部分。也可以由系统管理员把已加载地模块动态地卸载下来。

LINUX中,模块可以用C语言编写,用gcc编译成目标文件(不进行链接,作

为*.o文件存在),为此需要在gcc命令行里加上-c的参数。在编译时,还应该在

gcc的命令行里加上这样的参数:-D__KERNEL__ -DMODULE。由于在不链接时,g

cc只允许一个输入文件,因此一个模块的所有部分都必须在一个文件里实现。

编译好的模块*.o放在/lib/modules/xxxx/misc下(xxxx表示核心版本,如

在核心版本为2.0.30时应该为/lib/modules/2.0.30/misc),然后用depmod -a

使此模块成为可加载模块。模块用insmod命令加载,用rmmod命令来卸载,并可

以用lsmod命令来查看所有已加载的模块的状态。

编写模块程序的时候,必须提供两个函数,一个是int init_module(void),

供insmod在加载此模块的时候自动调用,负责进行设备驱动程序的初始化工作。

init_module返回0以表示初始化成功,返回负数表示失败。另一个函数是void

cleanup_module (void),在模块被卸载时调用,负责进行设备驱动程序的清除

工作。

在成功的向系统注册了设备驱动程序后(调用register_chrdev成功后),

就可以用mknod命令来把设备映射为一个特别文件,其它程序使用这个设备的时

候,只要对此特别文件进行操作就行了。

附录:参考文献

1、 《UNIX操作系统设计与实现》

陈华瑛、李建国主编

电子工业出版社出版

2、 《Linux Kernel Hacker's Guide》

作者:Michael K. Johnson

3、 《Kernel Jorn》

作者:Alessandro Rubini & Georg Zezchwitz

连载于《Linux Journal》1996年36期

4、 Linux核心源代码(核心版本2.0.30)

5、 Linux-HOWTO

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有