Linux系統驅動開發

Linux系統驅動開發

1Linux 設備通常劃分爲三種:字符設備塊設備絡接口設備。

字符設備是指:那些只能一個字節一個字節讀寫數據的設備,不能隨即讀取設備內存中的某一數據。其讀取數據需要按照先後順序,從這點上看,字符設備是面向數據流的設備。常用的字符設備有鼠標、鍵盤、串口、控制檯和LED等設備。

塊設備是指:可以從設備的任意位置讀取一定長度數據的設備。其讀取數據不必按照先後的順序,可以定位到設備的某一具體的位置,讀取數據。常見的塊設備有硬盤,磁盤,U盤和SD卡。

每一個字符設備或者塊設備都在/dev目錄下對應一個設備文件。進入/dev目錄下,執行 ls 命令,以C開頭的是字符設備,以b開頭的是塊設備。

2主設備號次設備號

一個字符設備或者一個塊設備都有一個主設備號和次設備號。主設備號和次設備號統稱爲設備號。主設備號用來表示 一個特定的驅動程序。此設備號用來表示使用該驅動程序的各設備。例如一個嵌入式系統,有兩個LED指示燈,LED燈需要獨立的打開或者關閉。那麼可以寫一個LED燈的字符設備驅動程序,可以將其主設備號註冊成5號設備,次設備號分別爲12.這裏次設備號爲別表示兩個LED燈。

2.1 主設備號和次設備號的表示:

linux內核中,設備號用dev_t類型來表示。在linux 2.6.29.4中,dev_t定義爲一個無符號長整型變量,如下:

typedef  u_long  dev_t 

u_long 32位機中佔4個字節,在64位機中佔8個字節,以32位機爲例,其中12表示主設備號,20表示次設備號。

2.2 動態分配設備號和靜態分配設備號

靜態分配設備號,就是驅動程序開發者靜態的指定一個設備號。對於一部分成用的設備,內核開發者已經爲其分配了設備號,這些設備號可以在內核源碼documentation/divice.txt文件中找到,如果只有開發者自己使用這些設備驅動程序,那麼可以選擇一個尚未使用的一個設備號。在不添加新硬件的時候,這種方式不會產生設備號衝突的問題。但是當添加新硬件的時候,則很可能造成設備號衝突,影響設備的使用。

動態分配設備號,避免了設備號衝突的問題,該函數是alloc_chrdev_region().

2.3 查看設備號

當靜態分配設備號的時候,需要查看系統中已經存在的設備號,從而決定使用哪個新設備號,可以讀取/proc/devices文件獲得設備的設備號,該文件中包含字符設備和塊設備的設備號。

3申請和釋放設備號

3.1:申請設備號

在構建字符設備之前,首先要向系統申請一個或者多個設備號。完成該工作的函數是register_chrdev_region(),該函數在<fs/ char_dev.c>中定義 。

int register_chrdev_region(dev_t  from,  unsigned count, const char *name);

其中,from是 要分配的設備號的起始值,一般只提供from的主設備號,from的此設備號通常被設置成0.  Count是需要申請連續設備號的個數。Name是和該範圍編號關聯的設備名稱,該名稱不能超過64個字節。

register_chrdev_region()函數成功執行返回值爲0, 錯誤時,返回一個負的錯誤碼,並且不能爲字符設備分配設備號。

linux中有非常多的字符設備,在人爲的爲字符設備分配設備號時,很可能發生衝突,linux 內核開發者一直在努力的將設備號變爲動態的。可以使用alloc_chrdev_region()達到這個目的。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char*name);

在上面的函數中,dev作爲輸出參數,在函數成功返回後將保存已經分配好的設備號,函數有可能申請一段連續的設備號,這是dev返回第一個設備號。Baseminor表示要申請的第一個次設備號, 其通常設爲0.

3.2 :釋放設備號

以上兩種方式申請的設備號,都應該在不使用的時候釋放設備號,設備號的統一釋放使用下面的函數:

void unregister_chrdev_regiondev_t from, unsigned  count);

這個函數中的from是要釋放的設備號,count 表示從form開始要釋放的設備號的個數。通常在模塊卸載的函數中,調用unregister_chrdev_region()函數。

4:字符設備結構體cdev

Struct  cdev

{

Struct  kobject  kobj;/*用於內核管理字符設備,驅動開發人員一般不適用該成員*/

Struct  module  *owner;/*指向包含該結構的模塊的指針*/

Const  struct  file_operations *ops;/*指向字符設備操作函數的指針*/

Struct  list_head  list;/*該結構體將使用該驅動的字符設備連成一個結構體,廣泛應用,需要掌握*/

Dev_t  dev;/*該字符設備的起始設備號,一個設備可能有多個設備號*/

Unsigned  int  count;/*使用該字符設備驅動的設備數量*/

}

5 file_operatins結構體和其成員函數

static const struct file_operations  xxx_fops

{

.owner = THIS_MODULE ,    /*模塊引用,任何時候都賦值THIS_MODULE*/

.read = xxx_read, /*指定設備的讀函數*/

.write = xxx_write, /*指定設備的寫函數*/

.ioctol = xxx_ioctol, /*指定設備的控制函數*/

};

/*讀函數*/

static ssize_t  xxx_read(struct  file * filp,  char  __user * buf,  ssize_t  size,  loff_t *opps)

{

If( size > 8)

Copy_to_user(buf,  ,  ); /*當數據較大時,使用copy_to_user(), 效率較高;

Else 

put_user(,  buf);/*當數據較小時, 使用put_user(),效率較高。

}

/*寫函數*/

Static ssize_t  xxx_write(struct  file * filp,  char  __user * buf,  ssize_t  size,  loff_t *opps)

{

If( size > 8)

Copy_from_user(buf,  ,  ); /*當數據較大時,使用copy_form_user(), 效率較高;

else 

get_user(,  buf); /*當數據較小時, 使用get_user(),效率較高。

}

 /* seek文件定位函數 */  

 static loff_t VirtualDisk_llseek(struct file *filp, loff_t offset, int  orig)  

 {  

  loff_t ret = 0;                               /*返回的位置偏移*/  

   switch (orig)  

   {  

     case SEEK_SET:                              /*相對文件開始位置偏移*/  

       if (offset < 0)                           /*offset不合法*/  

        {  

          ret =  - EINVAL;                            /*無效的指針*/  

         break;  

       }  

        if ((unsigned int)offset > VIRTUALDISK_SIZE)  

                                                    /*偏移大於設備內存*/  

         {  

          ret =  - EINVAL;                            /*無效的指針*/  

          break;  

        }  

        filp->f_pos = (unsigned int)offset;       /*更新文件指針位置*/  

       ret = filp->f_pos;                            /*返回的位置偏移*/  

        break;  

      case SEEK_CUR:                              /*相對文件當前位置偏移*/  

        if ((filp->f_pos + offset) > VIRTUALDISK_SIZE)  

                                                    /*偏移大於設備內存*/  

        {  

         ret =  - EINVAL;                            /*無效的指針*/  

          break;  

        }  

       if ((filp->f_pos + offset) < 0)           /*指針不合法*/  

       {  

         ret =  - EINVAL;                            /*無效的指針*/  

         break;  

       }  

       filp->f_pos += offset;                        /*更新文件指針位置*/  

        ret = filp->f_pos;                            /*返回的位置偏移*/  

        break;  

     default:  

       ret =  - EINVAL;                          /*無效的指針*/  

        break;  

    }  

    return ret;  

  } 

/*ioctol設備控制函數*/

static  long  ioctol static file  *file,  unsigned int  cmd, unsigned long  arg

{

Switch( cmd )

{

Case  xxx_cmd1:

/*命令執行的操作*/

Break;

Case  xxx_cmd2:

/*命令執行的操作*/

Break;

Default:

Return  -EINVAL;/*內核和驅動程序都不支持該命令時,返回無效的命令*/

}

Return  0;

}

驅動程序與應用程序的數據交換

驅動程序和應用程序的數據交換是非常重要的。

File_operations中的read()和write()函數就是用來在驅動程序和應用程序間進行數據交換的。通過數據交換,驅動程序和應用程序可以彼此瞭解對方。但是驅動程序和應用程序屬於不同的地址空間,驅動程序不能直接訪問應用程序的地址空間;同樣,應用程序也不能直接訪問驅動程序的地址空間,否則會破壞彼此空間的數據,從而造成系統崩潰,或者數據紊亂。

安全的方法是:使用內核提供的專用函數,完成數據在應用程序空間和驅動程序空間的交換。這些函數對用戶程序傳過來的指針進行了嚴格的檢查和必要的轉換,從而保證了用戶程序和驅動程序數據交換的安全性。這些函數有:

unsigned  long  copy_to_uservoid  __user * to,  const  void *form,  unsigned long n;

unsigned  long  copy_from_uservoid  __user * to,  const  void *form,  unsigned long n;

put_user()

get_user();

8--字符設備小結:

字符設備的驅動程序完成的主要工作是初始化,添加和刪除cdev結構體,申請和釋放設備號,以及填充file_operations結構體中的read(),write()ioctl()等重要函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章