一.設計原理
Linux內核中的設備驅動程序是一組常駐內存的具有特權的共享庫,是低級硬件處理例程。每個文件都有兩個設備號,第一個是主設備號,標識驅動程序,第二個是從設備號,標識使用同一個設備驅動程序的不同的硬件設備.設備文件的的主設備號必須與設備驅動程序在登記時申請的主設備號一致,否則用戶進程將無法訪問到驅動程序.
Linux支持3種設備:字符設備、塊設備和網絡設備。字符設備和塊設備的主要區別是:在對字符設備發出讀/寫請求時,實際的硬件I/O一般就緊接著發生了,塊設備則不然,它利用一塊系統內存作緩沖區,當用戶進程對設備請求能滿足用戶的要求,就返回請求的數據,如果不能,就調用請求函數來進行實際的I/O操作.塊設備是主要針對磁盤等慢速設備設計的,以免耗費過多的CPU時間來等待.
一個典型的驅動程序,大體上可以分為這麽幾個部分:
1. 註冊設備
在系統初啟,或者模塊加載時候,必須將設備登記到相應的設備數組,並返回設備的主驅動號。
2. 定義功能函數
對於每一個驅動函數來說,都有一些和此設備密切相關的功能函數。拿最常用的塊設備或者字符設備來說,都存在著諸如 open()、read()這一類的操作。當系統調用這些調用時,將自動的使用驅動函數中特定的模塊。來實現具體的操作。
3. 卸載設備
在不用這個設備時,可以將它卸載,主要是從/proc 中取消這個設備的特殊文件。
二.模塊
linux中的大部分驅動程序,是以模塊的形式編寫的,可以像內核模塊一樣在需要的時候動態加載 ,不使用時卸載。
模塊的加載方式有兩種。首先一種是使用insmod命令手工加載模塊。另外一種則是在需要時加載模塊,如常用的mount命令。
通過rmmod命令來刪除模塊。
模塊的實現機制:
對於每一個內核模塊來說,必定包含兩個函數:
int init_module() 這個函數在插入內核時啟動,在內核中註冊一定的功能函數。
int cleanup_module() 當內核模塊卸載時,調用它將模塊從內核中清除。
註冊設備:register_chrdev(…)在init_module()中調用此函數用來註冊設備。
卸載設備:unregister_chrdev(…)
在cleanup_module()中調用此函數用來卸載設備。
在實際應用中,為外部設備編寫驅動程序是一件很復雜的工作,需要解決的問題很多,如註冊設備、為設備分配端口、響應中斷、對設備狀態進行控制、防止緩沖區溢出等等,這裏既有軟件方面的,也有硬件方面的。在設計時沒有具體的設備對象,只好將內存中的一段看成設備,並針對它編寫驅動程序,因此程序實現的功能非常簡單,不必考慮上述問題,設備的操作也只是對內存的讀和寫。
三.準備過程
由於之前我對設備驅動的編寫完全不熟悉,所以最初我嘗試編寫了一個最簡單的字符設備驅動,它只是實現了簡單的讀寫操作。雖然這個驅動程序並沒有什麽實用價值,但是我通過它對一個驅動程序的編寫,特別事字符設備驅動程序有一定的認識。
具體步驟如下:
1.這個設備驅動程序提供給文件系統的接口
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};
這是在fs.h頭文件中定義的一個接口。我們編寫驅動程序,實際上就是要通過系統調用來實現對設備的操作。在最初的簡單字符設備驅動程序中,我們只使用了open(),release(),read(),write()四個系統調用。
2.頭文件、宏定義和全局變量
一個典型的設備驅動程序一般都包含一個專用頭文件,這個頭文件中包含一些系統函數的聲明、設備寄存器的地址、寄存器狀態位和控制位的定義以及用於次設備驅動程序的全局變量的定義。
3.Open()函數
功能:無論一個進程何時試圖去打開這個設備都會調用這個函數。
4.Release()函數
功能:當一個進程試圖關閉這個設備特殊文件的時候調用這個函數
5.Read()函數
功能:當一個進程已經打開次設備文件以後並且試圖去讀它的時候調用這個函數
6.Write()函數
功能:當試圖將數據寫入這個設備文件的時候,這個函數被調用
7.模塊的初始化和模塊的卸載
主要事init_module()和 cleanup_module()函數的調用
具體源程序如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define _NO_VERSION_
#define SUCCESS 0
#define DEVICE_NAME "char_dev"
#define BUF_LEN 50
MODULE_LICENSE("GPL");
char kernel_version[]=UTS_RELEASE;
static int Device_Open=0;
static int test_major=0;
static char Message[BUF_LEN]="this is a simple program about device driver.";
static char *Message_Ptr;
static int device_open(struct inode *ind,struct file *fip)
{
printk("<1>device open:%d,%d\n",ind->i_rdev>>8,ind->i_rdev&0xFF);
if(Device_Open)
return -EBUSY;
Device_Open++;
Message_Ptr=Message;
MOD_INC_USE_COUNT;
return SUCCESS;
}
static int device_release(struct inode *ind,struct file *fip)
{
printk("<1>device release:%d,%d.\n",ind->i_rdev>>8,ind->i_rdev&0xFF);
Device_Open--;
MOD_DEC_USE_COUNT;
return 0;
}
static ssize_t device_read(struct file *fip,char *buffer,
size_t length,loff_t *offset)
{
int bytes_read=0;
if(*Message_Ptr==0)
return 0;
printk("<1>The message is \"%s\"\n",Message);
while(length&&*Message_Ptr){
put_user(*(Message_Ptr++),buffer++);
length--;
bytes_read++;
}
return bytes_read;
}
static ssize_t device_write(struct file *fip,const char *buffer,
size_t length,loff_t *offset)
{
int i;
printk("<1>Befor write test,the message is \"%s\"\n",Message);
for(i=0;i
get_user(Message[i],buffer+i);
printk("<1>After write test,the message is \"%s\"\n",Message);
Message_Ptr=Message;
return i;
}
struct file_operations fops={
NULL,
NULL,
read:device_read,
write:device_write,
NULL,
NULL,
NULL,
NULL,
open:device_open,
NULL,
release:device_release,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
};
int init_module()
{
int result;
result=register_chrdev(0,DEVICE_NAME,&fops);
if(result<0){
printk("<1>%s device failed with %d\n","sorry,registering the chardev",
result);
return result;
}
printk("<1>%s The major device number is %d.\n",
"Registeration is a success.",result);
if(test_major==0) test_major=result;
return 0;
}
void cleanup_module()
{
int ret;
ret=unregister_chrdev(test_major,DEVICE_NAME);
printk("<1>The device %d was unregistered.\n",test_major);
if(ret<0)
printk("<1>Error in unregister_chrdev:%d\n",ret);
return;
}
編譯過程如下:
gcc -O2 -D__KERNEL__ -DMODULE -I /usr/src/linux-2.4.20-8/include -o device.o -c device.c生成可加載模塊device.o,然後將模塊加載到內核中(insmod vprinter.o),運行cat /proc/devices,查得設備vprinter分得的主設備號是254。最後根據主設備號創建設備文件mknod /dev/vprinter c 254 0,至此設備驅動程序完成。寫了一個簡單的程序進行測試,先向設備中寫入數據,然後讀出數據,測試通過,寫到系統日誌裏的信息為:
Registeration is a success. The major device number is 254.
device open:254,0
The message is " this is a simple program about device driver."
device release:254,0.
四.程序設計
有了上面的基礎,我們開始了一個稍微復雜一些的驅動程序的設計。
該程序模擬一個打印機的等待隊列,采用隊列的數據結構。當向設備寫數據時,將數據分成大小相等塊,插入隊列中。當從設備讀取數據時,按指定數據長度計算所需的塊數,在從設備的等待隊列讀出。
源程序如下所示:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define _NO_VERSION_
#define SUCCESS 0
#define DEVICE_NAME "vprinter" /*vprinter是設備名,它將出現在/proc/devices中*/
MODULE_LICENSE("GPL");
#define SIZE 5
char kernel_version[]=UTS_RELEASE;
static int Device_Open=0;
/*為了防止不同的進程在同一個時間使用此設備,定義此靜態變量跟蹤設備的狀態*/
static int test_major=0; /*主設備號*/
typedef struct wait_q_node {
char buf[6];
struct wait_q_node *next;
}wait_q_node,*wait_q_ptr; /*等待隊列每個節點的結構定義*/
wait_q_ptr f;
typedef struct{
wait_q_ptr head;
wait_q_ptr tail;
}LinkQueue; /*等待隊列的頭指針和尾指針*/
LinkQueue Q,*Q0;
static int vprinter_open(struct inode *ind,struct file *fip)
{
printk("<1>device open:%d,%d\n",ind->i_rdev>>8,ind->i_rdev&0xFF);
/*printk()是在內核代碼中使用的與printf相似的函數。ind->i_rdev>>8是主設備號,ind->i_rdev&0xFF是次設備號*/
if(Device_Open==1)
return -EBUSY; /*設備正在使用中,返回-EBUSY(-1)*/
Device_Open++;
Q0=(LinkQueue *)kmalloc(sizeof(LinkQueue),GFP_KERNEL);
if(Q0==NULL) return(-1);
Q=*Q0;
Q.head=Q.tail=(wait_q_ptr)kmalloc(sizeof(wait_q_node),GFP_KERNEL);
if(Q.head==NULL) return(-1);
Q.head->next=NULL; /*構造一個空的等待隊列*/
MOD_INC_USE_COUNT;
/*當這個設備文件被打開的時候,我們必須確認該模塊還沒有被移走並且增加此模塊的用戶數目(在移走一個模塊的時候會根據這個數字來決定可否移去,如果不是0則表示還有進程正在使用這個模塊,不能移走)*/
return SUCCESS; /*打開設備成功*/
}
static int vprinter_release(struct inode *ind,struct file *fip)
{
printk("<1>device release:%d,%d.\n",ind->i_rdev>>8,ind->i_rdev&0xFF);
Device_Open--; /*為了下一個使用這個設備的進程做準備*/
MOD_DEC_USE_COUNT;
/*減少這個模塊使用者的數目,否則一旦你打開這個模塊以後,你永遠都不能釋放掉它*/
kfree(Q.head);
kfree(Q0);
return 0;
}
static ssize_t vprinter_read(struct file *fip,
char *buffer,/*把讀出的數據放到這個緩沖區*/
size_t length,/*讀出數據的長度*/
loff_t *offset)/*文件中的偏移*/
{
int i=0,m,n;
m=length/SIZE;/*讀取數據所需的塊數*/
n=length%SIZE; /*非整數塊時的字節數*/
/*從設備(即等待隊列中)讀取所需內容到緩沖區中*/
for(;m>0;m--,i+=SIZE,buffer+=i) {
copy_to_user(buffer++,Q.head->next->buf,SIZE);
/*把內核空間裏的內容送到緩沖區,這是kernel提供的一個函數,用於向用戶傳送數據*/
printk("<1>The message is \"%s\"\n",Q.head->next->buf);
f=Q.head->next;
Q.head->next=Q.head->next->next;
kfree(f);
}
if(n) {
copy_to_user(buffer++,Q.head->next->buf,n);
printk("<1>The message is \"%s\"\n",Q.head->next->buf);
f=Q.head->next;
Q.head->next=Q.head->next->next;
kfree(f);
}
return length;
}
static ssize_t vprinter_write(struct file *fip,const char *buffer,
size_t length,loff_t *offset)
{
int i=0,m,n;
m=length/SIZE;
n=length%SIZE;
/*把緩沖區中的內容寫到設備上(即添加到等待隊列中)*/
for(;m>0;m--,i+=SIZE){
wait_q_ptr q=(wait_q_ptr)kmalloc(sizeof(wait_q_node),GFP_KERNEL);
if(q==NULL) return(-1);
/*把緩沖區裏的內容寫入內核*/
copy_from_user(q->buf,buffer+i,SIZE);
copy_from_user(&(q->buf[5]),&(buffer[20]),1);
Q.tail->next=q;
Q.tail=q;
}
if(!n){
wait_q_ptr q=(wait_q_ptr)kmalloc(sizeof(wait_q_node),GFP_KERNEL);
if(q==NULL) return(-1);
/*把緩沖區裏的內容寫入內核*/
copy_from_user(q->buf,buffer+i,n);
copy_from_user(&(q->buf[5]),&(buffer[20]),1);
printk("%s",q->buf);
Q.tail->next=q;
Q.tail=q;
}
if(Q.head!=Q.tail) {
wait_q_ptr p;
p=Q.head->next;
do{
printk("<1>Now the content of the queue is \"%s\"\n",p->buf); /*顯示隊列內容*/
p=p->next;
}while(p!=NULL);
}
return i;
}
struct file_operations fops={
NULL,
NULL,
read:vprinter_read,
write:vprinter_write,
NULL,
NULL,
NULL,
NULL,
open:vprinter_open,
NULL,
release:vprinter_release,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
};
/*設備驅動程序提供給文件系統的接口.它的指針保存在設備表中,
在init_module()中被傳遞給操作系統.這個結構的每一個成員的名字都對應著
一個系統調用.用戶進程利用系統調用在對設備文件進行諸如read/write操作時,
系統調用通過設備文件的主設備號找到相應的設備驅動程序,然後讀取這個數
據結構相應的函數指針,接著把控制權交給該函數.這是linux的設備驅動程序工
作的基本原理.*/
int init_module()
{
int result;
result=register_chrdev(0,DEVICE_NAME,&fops);
if(result<0){
printk("<1>%s device failed with %d\n","sorry,registering the chardev",result);
return result;/*註冊失敗*/
}
printk("<1>%s The major device number is %d.\n","Registeration is a success.",result);
if(test_major==0) test_major=result;
return 0;
}
/*在用insmod命令將編譯好的模塊調入內存時,init_module 函數被調用。在
這裏,init_module只做了一件事,就是向系統的字符設備表登記了一個字符
設備。register_chrdev需要三個參數,參數一是希望獲得的設備號,如果是
零的話,系統將選擇一個沒有被占用的設備號返回。參數二是設備文件名,
參數三用來登記驅動程序實際執行操作的函數的指針。如果登記成功,
返回設備的主設備號,不成功,返回一個負值。*/
void cleanup_module()
{
int ret;
ret=unregister_chrdev(test_major,DEVICE_NAME);/*卸載設備*/
printk("<1>The device %d was unregistered.\n",test_major);
if(ret<0)
printk("<1>Error in unregister_chrdev:%d\n",ret);
return;
}
/*在用rmmod卸載模塊時,cleanup_module函數被調用,它釋放字符設備
在系統字符設備表中占有的表項,即從/proc中取消註冊的設備特殊文件.
unregister_chrdev需要兩個參數,參數一是獲得的設備號,參數二是設備文件名.
*/
編譯時使用命令gcc -O2 -D__KERNEL__ -DMODULE -I /usr/src/linux-2.4.20-8/include -o vprinter.o -c vprinter.c,生成可加載模塊vprinter.o,然後將模塊加載到內核中(insmod vprinter.o),運行cat /proc/devices,查得設備vprinter分得的主設備號是254。最後根據主設備號創建設備文件mknod /dev/vprinter c 254 0,至此設備驅動程序完成。寫了一個簡單的程序進行測試,先向設備中寫入數據“This is a vitual pri“ ,將該數據分成大小相等的塊,每塊為5Byte,建立一個等待隊列。然後將其讀出並打印輸出,測試通過,寫到系統日誌裏的信息為:
Registeration is a success. The major device number is 254.
device open:254,0
Now the content of the queue is "This "
Now the content of the queue is "is a "
Now the content of the queue is "vitua"
Now the content of the queue is "l pri"
Now the content of the queue is ""
The message is "This "
The message is "is a "
The message is "vitua"
The message is "l pri"
device release:254,0.
測試程序:
#include
#include
#include
#include
#include
#include
main()
{
int testdev=0;
int length;
char buf[21]="This is a vitual pri";
testdev = open("/dev/vprinter",O_RDWR);
printf("%d\n",testdev);
if(testdev == -1)
{
printf("Cann't open the device!\n");
exit(0);
}
write(testdev,buf,20);
read(testdev,buf,20);
close(testdev);
return testdev;
}
文章標籤
全站熱搜