Linux系統(tǒng)中,設(shè)備驅(qū)動程序是操作系統(tǒng)內(nèi)核的重要組成部分,在 與硬件設(shè)備之間
建立了標(biāo)準(zhǔn)的抽象接口。通過這個接口,用戶可以像處理普通文件一樣,對硬件設(shè)
備進(jìn)行打開(open)、關(guān)閉(close)、讀寫(read/write)等操作。通過分析和設(shè)計設(shè)
備驅(qū)動程序,可以深入理解Linux系統(tǒng)和進(jìn)行系統(tǒng)開發(fā)。本文通過一個簡單的例子
來說明設(shè)備驅(qū)動程序的設(shè)計。
1、 程序清單
// MyDev.c 2000年2月7日編寫
#ifndef __KERNEL__
# define __KERNEL__ //按內(nèi)核模塊編譯
#endif
#ifndef MODULE
# define MODULE //設(shè)備驅(qū)動程序模塊編譯
#endif
#define DEVICE_NAME "MyDev"
#define OPENSPK 1
#define CLOSESPK 2
//必要的頭文件
#include <linux/module.h> //同kernel.h,最基本的內(nèi)核模塊頭文件
#include <linux/kernel.h> //同module.h,最基本的內(nèi)核模塊頭文件
#include <linux/sched.h> //這里包含了進(jìn)行正確性檢查的宏
#include <linux/fs.h> //文件系統(tǒng)所必需的頭文件
#include <asm/uaccess.h> //這里包含了內(nèi)核空間與用戶空間進(jìn)行數(shù)據(jù)交換時的
函數(shù)宏
#include <asm/io.h> //I/O訪問
int my_major=0; //主設(shè)備號
static int Device_Open=0;
static char Message[]="This is from device driver";
char *Message_Ptr;
int my_open(struct inode *inode, struct file *file)
{//每當(dāng)應(yīng)用程序用open打開設(shè)備時,此函數(shù)被調(diào)用
printk ("\ndevice_open(%p,%p)\n", inode, file);
if (Device_Open)
return -EBUSY; //同時只能由一個應(yīng)用程序打開
Device_Open++;
MOD_INC_USE_COUNT; //設(shè)備打開期間禁止卸載
return 0;
}
static void my_release(struct inode *inode, struct file *file)
{//每當(dāng)應(yīng)用程序用close關(guān)閉設(shè)備時,此函數(shù)被調(diào)用
printk ("\ndevice_release(%p,%p)\n", inode, file);
Device_Open --;
MOD_DEC_USE_COUNT; //引用計數(shù)減1
}
ssize_t my_read (struct file *f,char *buf,int size,loff_t off)
{//每當(dāng)應(yīng)用程序用read訪問設(shè)備時,此函數(shù)被調(diào)用
int bytes_read=0;
#ifdef DEBUG
printk("\nmy_read is called. User buffer is %p,size is %d\n",buf,size);
#endif
if (verify_area(VERIFY_WRITE,buf,size)==-EFAULT)
return -EFAULT;
Message_Ptr=Message;
while(size && *Message_Ptr)
{
if(put_user(*(Message_Ptr++),buf++)) //寫數(shù)據(jù)到用戶空間
return -EINVAL;
size --;
bytes_read++;
}
return bytes_read;
}
ssize_t my_write (struct file *f,const char *buf, int size,loff_t off)
{//每當(dāng)應(yīng)用程序用write訪問設(shè)備時,此函數(shù)被調(diào)用
int i;
unsigned char uc;
#ifdef DEBUG
printk("\nmy_write is called. User buffer is %p,size is %d\n",buf,size);
#endif
if (verify_area(VERIFY_WRITE,buf,size)==-EFAULT)
return -EFAULT;
printk("\nData below is from user program:\n");
for (i=0;i<size;i++)
if(!get_user(uc,buf++)) //從用戶空間讀數(shù)據(jù)
printk("%02x ",uc);
return size;
}
int my_ioctl(struct inode *inod,struct file *f,unsigned int arg1,
unsigned int arg2)
{//每當(dāng)應(yīng)用程序用ioctl訪問設(shè)備時,此函數(shù)被調(diào)用
#ifdef DEBUG
printk("\nmy_ioctl is called. Parameter is %p,size is %d\n",arg1);
#endif
switch (arg1)
{
case OPENSPK:
printk("\nNow,open PC's speaker.\n");
outb(inb(0x61)|3,0x61); //打開計算機(jī)的揚聲器
break;
case CLOSESPK:
printk("\nNow,close PC's speaker.");
outb(inb(0x61)&0xfc,0x61);//關(guān)閉計算機(jī)的揚聲器
break;
}
}
struct file_operations my_fops = {
NULL, /* lseek */
my_read,
my_write,
NULL,
NULL,
my_ioctl,
NULL,
my_open,
my_release,
/* nothing more, fill with NULLs */
};
int init_module(void)
{//每當(dāng)裝配設(shè)備驅(qū)動程序時,系統(tǒng)自動調(diào)用此函數(shù)
int result;
result = register_chrdev(my_major,DEVICE_NAME,&my_fops);
if (result < 0) return result;
if (my_major == 0)
my_major = result;
printk("\nRegister Ok. major-number=%d\n",result);
return 0;
}
void cleanup_module(void)
{//每當(dāng)卸載設(shè)備驅(qū)動程序時,系統(tǒng)自動調(diào)用此函數(shù)
printk("\nunload\n");
unregister_chrdev(my_major, DEVICE_NAME);
}
2、 設(shè)備 驅(qū)動程序設(shè)計
Linux設(shè)備分為字符設(shè)備、塊設(shè)備和網(wǎng)絡(luò)設(shè)備。字符設(shè)備是不需要緩沖而直接
讀寫的設(shè)備,如串口、鍵盤、鼠標(biāo)等,本例就是字符設(shè)備驅(qū)動程序;塊設(shè)備的訪問
通常需要緩沖來支持,以數(shù)據(jù)塊為單位來讀寫,如磁盤設(shè)備等;網(wǎng)絡(luò)設(shè)備是通過套
接字來訪問的特殊設(shè)備。
1) 設(shè)備驅(qū)動程序和內(nèi)核與應(yīng)用程序的接口
無論哪種類型的設(shè)備,Linux都是通過在內(nèi)核中維護(hù)特殊的設(shè)備控制塊來與設(shè)
備驅(qū)動程序接口的。在字符設(shè)備和塊設(shè)備的控制塊中,有一個重要的數(shù)據(jù)結(jié)構(gòu)
file_operations,該結(jié)構(gòu)中包含了驅(qū)動程序提供給應(yīng)用程序訪問硬件設(shè)備的各種
方法,其定義如下(參見fs.h):
struct file_operations {
loff_t (*llseek) (struct file *, loff_t, int); //響應(yīng)應(yīng)用程序中l(wèi)seek調(diào)
用的函數(shù)指針
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
//響應(yīng)應(yīng)用程序中read調(diào)用的函數(shù)指針
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
//響應(yīng)應(yīng)用程序中write調(diào)用的函數(shù)指針
int (*readdir) (struct file *, void *, filldir_t); //響應(yīng)應(yīng)用程序中
readdir調(diào)用的函數(shù)指針
unsigned int (*poll) (struct file *, struct poll_table_struct *);
//響應(yīng)應(yīng)用程序中select調(diào)用的函數(shù)指針
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned
long);
//響應(yīng)應(yīng)用程序中ioctl調(diào)用的函數(shù)指針
int (*mmap) (struct file *, struct vm_area_struct *);
//響應(yīng)應(yīng)用程序中mmap調(diào)用的函數(shù)指針
int (*open) (struct inode *, struct file *); //響應(yīng)應(yīng)用程序中open調(diào)
用的函數(shù)指針
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *); //響應(yīng)應(yīng)用程序中close
調(diào)用的函數(shù)指針
int (*fsync) (struct file *, struct dentry *);
int (*fasync) (int, struct file *, int);
int (*check_media_change) (kdev_t dev);
int (*revalidate) (kdev_t dev);
int (*lock) (struct file *, int, struct file_lock *);
};
多數(shù)情況下,只需為上面結(jié)構(gòu)中的少數(shù)方法編寫服務(wù)函數(shù),其他均設(shè)為NULL即可。
每一個可裝配的設(shè)備驅(qū)動程序都必須有init_module和cleanup_module兩個函數(shù),
裝載和卸載設(shè)備時內(nèi)核自動調(diào)用這兩個函數(shù)。在init_module中,除了可以對硬件
設(shè)備進(jìn)行檢查和初始化外,還必須調(diào)用register_* 函數(shù)將設(shè)備登記到系統(tǒng)中。本
例中是通過register_chrdev來登記的,如果是塊設(shè)備或網(wǎng)絡(luò)設(shè)備則應(yīng)該用
register_blkdev和register_netdev來登記。Register_chrdev 的主要功能是將設(shè)
備名和結(jié)構(gòu)file_operations登記到系統(tǒng)的設(shè)備控制塊中。
2) 與應(yīng)用程序的數(shù)據(jù)交換
由于設(shè)備驅(qū)動程序工作在內(nèi)核存儲空間,不能簡單地用"="、"memcpy"等方法與應(yīng)
用程序交換數(shù)據(jù)。在頭文件uaccess.h中定義了方法put_user(x, ptr)和
get_user(x, ptr),用于內(nèi)核空間與用戶空間的數(shù)據(jù)交換。值x的類型根據(jù)指針
ptr的類型確定,請參見源代碼中的my_read與my_write函數(shù)。
3) 與硬件設(shè)備的接口
Linux中為設(shè)備驅(qū)動程序訪問I/O端口、硬件中斷和DMA提供了簡便方法,相應(yīng)的頭
文件分別為io.h、irq.h、dma.h。由于篇輻限制,本例中只涉及到I/O端口訪問。
Linux提供的I/O端口訪問方法主要有:inb()、inw()、outb()、outw()、inb_p()
、inw_p()、outb_p()、outw_p()等。要注意的是,設(shè)備驅(qū)動程序在使用端口前,
應(yīng)該先用check_region()檢查該端口的占用情況,如果指定的端口可用,則再用
request_region()向系統(tǒng)登記。說明check_region()、request_region()的頭文件
是ioport.h。
4) 內(nèi)存分配
設(shè)備驅(qū)動程序作為內(nèi)核的一部分,不能使用虛擬內(nèi)存,必須利用內(nèi)核提供的
kmalloc()與kfree()來申請和釋放內(nèi)核存儲空間。Kmalloc()帶兩個參數(shù),第一個
要申請的是內(nèi)存數(shù)量,在早期的版本中,這個數(shù)量必須是2的整數(shù)冪,如128、256
。關(guān)于kmalloc()與kfree()的用法,可參考內(nèi)核源程序中的malloc.h與slab.c程序
。
3、程序的編譯和訪問
本例在Linux 2.2.x.x中可以成功編譯和運行。先用下面的命令行進(jìn)行編譯:
gcc -Wall -O2 -c MyDev.c
該命令行中參數(shù)-Wall告訴編譯程序顯示警告信息;參數(shù)-O2是關(guān)于代碼優(yōu)化的設(shè)置
,注意內(nèi)核模塊必須優(yōu)化;參數(shù)-c規(guī)定只進(jìn)行編譯和匯編,不進(jìn)行連接。
正確編譯后,形成MyDev.o文件??梢暂斎朊頸nsmod MyDev.o來裝載此程序。如
果裝配成功,則顯示信息:Register Ok.major-number=xx。利用命令lsmod可以看
到該模塊被裝配到系統(tǒng)中。
為了訪問該模塊,應(yīng)該用命令mknode來創(chuàng)建設(shè)備文件。下面的應(yīng)用程序可以對創(chuàng)建
的設(shè)備文件進(jìn)行訪問。
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#define DEVICE_NAME MyDev
#define OPENSPK 1
#define CLOSESPK 2
char buf[128];
int main(){
int f=open(DEVICE_NAME,O_RDRW);
if (f==-1) return 1;
printf("\nHit enter key to read device...");
read(f,buf,128); printf(buf);
printf("\nHit enter key to write device ...");
write(f,"test",4);
printf("\nHit enter key to open PC's speaker...");
ioctl(f,OPENSPK);
printf("\nHit enter key to close PC's speaker...");
ioctl(f,CLOSESPK);
close(f);
}