第三章 OS Environment包装Linux驱动程序的方法分析
1. Linux内核为驱动程序提供的支持
为了让驱动程序能够正常的工作,操作系统内核必须要为驱动程序提供一系列的支持,这些支持包括许多方面。举例来说,驱动程序需要像内核申请使用系统内存,驱动程序需要向内核使用系统硬件资源(如端口、DMA控制器),驱动程序需要向内核注册自己,尤其是那些占用中断资源的驱动程序。
要将其它的系统的驱动程序包装进来,就首先要了解其它的系统的内核是如何为自己系统的驱动程序提供支持的,然后通过OS Environment为这些驱动提供支持就可以了。由于我们是在Linux上使用OSKit的,所以就顺便选择了分析一下Linux大概通过哪些函数为内核的驱动程序提供支持。我们并没有将所有的支持函数都找出来,只是找了一些比较重要比较典型的,为的是学习OSKit中包装别人的驱动程序的方法。
Linux中这些函数很多在kernel目录下,在这个目录下的dma.c、resource.c、printk.c中,分配内存的函数在mm目录下。下面我们大概看一下Linux中的情况。
1.1 内存分配函数
在Linux中,驱动程序通常使用kmalloc函数申请内存,当然,也可以使用其它的函数申请。但因为kmalloc函数使用的比较多,我们重点说一下它。
void *kmalloc(size_t size, int flags)
kmalloc函数和malloc函数相似,它有两个参数,一个参数是size,即申请内存块的大小,这个参数比较简单,就像malloc中的参数一样。第二个参数是一个标志,在里面可以指定优先权之类的信息。在Linux中,有以下的一些优先权:
GFP_KERNEL,它的意思是该内存分配是由运行在内核模式的进程调用的,即当内存低于min_free_pages的时候可以让该进程进入睡眠;
GFP_ATOMIC,原子性的内存分配允许在实际内存低于min_free_pages时继续分配内存给进程。
GFP_DMA:此标志位需要和GFP_KERNEL、GFP_ATOMIC等一起使用,用来申请用于直接内存访问的内存页。
1.2 DMA
Linux的设备驱动程序在使用DMA通道以前必须申请,在使用之后必须要释放,这样从理论上讲,就允许几个设备同时使用一个DMA通道了,当然,很多设备是无法这样使用的。
Linux中申请和释放DMA通道的函数是request_dma( )和free_dma( )。
1.3 I/O端口
Linux中,系统维护着一个输入输出端口的设备注册表,设备驱动程序在使用输入输出端口前,也必须要进行申请。只有申请成功,才能够使用相应的端口。Linux内核提供了三个函数用于申请、释放端口及检查端口是否被占用。这三个函数是:
void request_region(unsigned long from,
unsigned long num, const char *name)
这个函数用来申请一块输入输出区域。
void release_region(unsigned long from, unsigned long num)
这个函数用来释放一块输入输出区域。
int check_region(unsigned long from, unsigned long num)
这个函数用来检测一块输入输出区域是否被占用。
1.4 中断号的申请
中断是非常宝贵的系统资源,在Linux中,系统维护着一个象输入输出设备注册表一样的中断资源注册表。任何驱动程序在使用一个中断,即作为中断处理程序之前,要申请一个中断通道,并且,在处理完成之后,可以释放它。在Linux中,对于i386平台,这些函数定义在arch/i386/kernel/irq.c中,申请和释放一个中断通道的函数是:
int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *),
unsigned long irqflags,
const char * devname,
void *dev_id)
void free_irq(unsigned int irq, void *dev_id)
其中各个参数的含义如下:
irq: 要申请的中断资源的中断号。
handler: 指向要安装的中断处理函数的指针。
irqflags: 这是一个与中断管理有关的各种选项的字节掩码。此项可选值如下:SA_INTERRUPT、SA_SHIRQ、SA_SAMPLE_RANDOM。如果不置SA_INTERRUPT,则标明这个中断处理程序时一个"慢速"的中断处理程序,如果置了这一位,这个处理程序就是一个"快速"的中断处理程序。
devname:传递给request_irq的字符串,可以通过Linux的proc文件系统显示中断地拥有者的名字。
dev_id:这个参数用于共享中断号,驱动程序可以自由的任意的使用dev_id,除非强制使用中断共享,dev_id通常被置为NULL。
1.5 打印函数
内核程序为了输出一些信息,就需要一个内核打印函数,Linux中的在内核中使用的打印函数是printk,这个函数的参数同普通的printf是一样的。
2. OSKit替换Linux内核-驱动界面的方法
大致了解了Linux内核为其驱动程序提供的支持之后,我们就可以来看一看OSKit究竟是采用什么方法来使用Linux的驱动程序的源码的。OSKit使用Linux驱动程序的方法,并不是去修改Linux驱动程序的源代码。
看了上面Linux内核所提供的部分支持,再看看前面OS Environment所提供的支持,不难发现它们有着很多的相似之处。Linux的驱动程序要使用内核提供的支持,就必须要包含内核的头文件,因此,只要根据OSKit的接口所提供的功能,重写一下Linux驱动程序需要的那些应当由内核提供的支持函数,就可以让Linux的驱动程序作为OSKit的一部分进行编译了。
当然,仅仅能够编译还是不够的,还必须要让驱动程序能够正常的工作,这一方面要在包装原有的代码上面下功夫,比如要能够正确的处理函数传递过来的各种参数,尤其是各种标志掩码。另一方面,还要为驱动程序建立一个可以运行的环境,这一部分内容就是OSKit整体设计的问题了,在前面的OSKit介绍中已经大概讲过了。当真正要设计一个目标系统的时候,这一点就是必须要考虑得非常仔细了。
3. OSKit包装Linux驱动程序的实际例子
上面的内容,完全是对OSKit包装Linux驱动程序的方法的理论上的叙述,下面我们具体看两个OSKit中的例子,这样就可以对这种方法有更深刻的了解。
3.1 DMA
OSKit为Linux的驱动程序提供了支持,提供了Linux驱动程序需要的两个函数,request_dma和free_dma,下面是这两个函数在OSKit中的实现。此段代码在linux/dev/dma.c中。
/*
* Allocate a DMA channel.
*/
int
request_dma(unsigned int drq, const char *name)
{
struct task_struct *cur = current;
int chan;
chan = osenv_isadma_alloc(drq);
current = cur;
return chan;
}
/*
* Free a DMA channel.
*/
void
free_dma(unsigned int drq)
{
struct task_struct *cur = current;
osenv_isadma_free(drq);
current = cur;
}
从上面的两个程序,我们可以看出,OSKit所提供的request_dma( )函数实际上是调用了OS Environment所提供的osenv_isadma_alloc( )函数,而free_dma( )则是调用了osenv_isadma_free( )函数。
DMA的例子时非常简单的,下面我们看一个比较复杂一点的例子。
3.2 内存管理
void *
kmalloc(unsigned int size, int priority)
{
int flags;
void *p;
flags = KMALLOC_FLAGS;
if (priority & GFP_ATOMIC)
flags |= OSENV_NONBLOCKING;
if (priority & GFP_DMA)
flags |= OSENV_ISADMA_MEM;
p = oskit_linux_mem_alloc(size, flags, 0);
return (p);
}
上面的程序段,就是OSKit提供的由Linux驱动程序使用的用来进行内存分配的函数。这个函数并没有直接调用OS Environment中的osenv_mem_alloc函数,这是有原因的,我们会在后面介绍。
这里值得注意的是OSKit用自己的标志代替了Linux驱动程序所使用的标志。比如,如果Linux驱动程序给出了标志GFP_ATOMIC,则用相应的OSENV_NONBLOCKING来代替,而GFP_DMA则用相应的OSENV_ISADMA_MEM来代替。
下面我们来看一下上面的程序段中用到的oskit_linux_mem_alloc函数。
void *
oskit_linux_mem_alloc(oskit_size_t size, osenv_memflags_t flags, unsigned align)
{
struct task_struct *cur = current;
void *mem;
mem = osenv_mem_alloc(size, flags, align);
current = cur;
return mem;
}
这个函数,实际调用了OSKit所提供的内存分配函数,在调用osenv_mem_alloc之前,保存了当前进程的进程控制块的指针,在内存申请之后再恢复,这是因为内存申请的请求有可能被阻塞。
另外还有一个原因,就是Linux实际提供的内存分配函数并不止kmalloc一个,还有get_free_pages和vmalloc等多个,然而在OSKit的缺省的实现中,只是通过osenv_mem_alloc来进行内存分配的。
OSKit为Linux所提供的内存分配函数是在linux/share/kmem.c中定义的。
4. 小结
通过上面的两个例子,不难看出OSKit包装Linux驱动程序的基本方法,输入输出端口、中断和内核打印输出等部分和这些部分实现的基本原理是相同的,当然随着支持的不同以及OSKit同Linux所提供的底层支持之间的差异,程序的写法是不同的。我们所分析的这几个例子是比较简单的,同时也并没有把Linux的所有函数都列出来,我们分析这些例子,目的是学到一种方法及思想。
OSKit除了使用了Linux的设备驱动程序,还是用了FreeBSD的设备驱动程序、Linux的网络设备驱动程序等等很多其它操作系统中的模块,我们选择Linux的设备驱动程序进行分析是因为这一段的程序代码相对比较清晰简单,那些部分同这个部分所使用的思想和方法是大致相同的,只是接口不同、数据结构不同而已。