noneLinux

© / Posted in Linux / May 29, 2008

——————
在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
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
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
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,用于把设备的内容映射到地址空间,一般只有块设备驱动程序使用。
( 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,
struct file_operations *fops);
  其中,major是为设备驱动程序向系统申请的主设备号,如果为0则系统为此 驱动程序动态地分配一个主设备号。name是设备名。fops就是前面所说的对各个 调用的入口点的说明。此函数返回0表示成功。返回-EINVAL表示申请的主设备号 非法,一般来说是主设备号大于系统所允许的最大设备号。返回-EBUSY表示所申 请的主设备号正在被其它设备驱动程序使用。如果是动态分配主设备号成功,此函数将返回所分配的主设备号。如果register_chrdev操作成功,设备名就会出现在/proc/devices文件里。
  初始化部分一般还负责给设备驱动程序申请系统资源,包括内存、中断、时 钟、I/O端口等,这些资源也可以在open子程序或别的地方申请。在这些资源不 用的时候,应该释放它们,以利于资源的共享。 在UNIX系统里,对中断的处理是属于系统核心的部分,因此如果设备与系
统之间以中断方式进行数据交换的话,就必须把该设备的驱动程序作为系统核心的一部分。设备驱动程序通过调用request_irq函数来申请中断,通过free_irq 来释放中断。它们的定义为:
#include
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
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
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
#include
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_timerjiffies=JIFFIES+num,表示在num个 系统最小时间间隔后执行function。系统最小时间间隔与所用的硬件平台有关, 在核心里定义了常数HZ表示一秒内最小时间间隔的数目,则num*HZ表示num 秒。系统计时到预定时间就调用function,并把此子程序从定时队列里删除, 因此如果想要每隔一定时间间隔执行一次的话,就必须在function里再一次调 用add_timer。function的参数d即为timer里面的data项。
  在设备驱动程序里,还可能会用到如下的一些系统函数:
#include
#define cli() __asm__ __volatile__ (”cli”:)
#define sti() __asm__ __volatile__ (”sti”:)
这两个函数负责打开和关闭中断允许。
#include
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。由于在不链接时, gcc只允许一个输入文件,因此一个模块的所有部分都必须在一个文件里实现。编译好的模块*.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. welcome to our burberry outlet store ,there are many fashion and hight quality burberry bags wait for

      you!
      do you want to buy burberry bags for yourslf or your beat friends in sometimes?

      please come to our burberry outlet store.
      our burberry outlet store offer many new style burberry watches for you.do

      not miss the fashion burbery Burberry T-shirts.
      In burberry sunglasses stores it really is typically some sort of

      most effective style which precisely the top notch and several middle-class are typically in a region to afford
      Fouthy-six Putting on a new burberry watches for men and relish the

      summertime Does one prefers to invest in Burberry bags?


      as we all know the burberry brand is very famouse in the world.in burberry bags outlet store you

      will have a good time!
      there is new store,the name is burberry outlet !you are interested in new store,you will have a good

      time in there.
      If you use burberry bags ,you will feel very Summer feeling, so many girls put it

      down.
      evey women love the burberry shirts in life,the burberry can give you more

      confidence in sometimes.
      Jack bought the burberry sunglasses and the next day he came to the

      interview in the Burberry cheak shirt.


      Join forums that areparticularlymeant to talk about watches. If you are searching for burberry sale for women, then

      obviouslycheck out women's forum.
      One of the most popular British exports is the burberry outlet online .
      The particular burberry bags typically reveal the actual Burberry renowned examine layout

      which has been made popular by the garment range.
      The color of burberry ties are bright but with the characters of mature and steady
      the burberry shoes collection had occipied a large markey share and enjoys a massive

      fan following.
      I believe one's destiny of your burberry wallets long decrease jackets are

      often more large-scale progress.

    添加新评论 ↑↑