Linux核心裝置驅動學習筆記整理(十)----字元裝置__Linux

來源:互聯網
上載者:User
/********************
 * 字元裝置驅動
 ********************/
(1)字元裝置驅動介紹
字元裝置是指那些按位元組流訪問的裝置,針對字元裝置的驅動稱為字元裝置驅動。
此類驅動適合於大多數簡單的硬體裝置。比如並口印表機,我們通過在/dev下建立一個裝置檔案(如/dev/printer)來訪問它。
使用者應用程式用標準的open函數開啟dev/printer,然後用write向檔案中寫入資料,用read從裡面讀資料。

調用流程:
write(): 使用者空間 -->
sys_write(): VFS -->
f_op->write: 特定裝置的寫方法

所謂驅動,就是提供最後的write函數,通過訪問印表機硬體的寄存器直接和印表機對話

(2)主裝置號和次裝置號

a.裝置編號介紹
對字元裝置的訪問是通過檔案系統內的裝置檔案進行的。這些檔案位於/dev。用"ls -l"查看。

裝置通過裝置號來標識。裝置號分兩部分,主裝置號和次裝置號。
通常,主裝置號標示裝置對應的驅動程式,linux允許多個驅動共用一個主裝置號;
而次裝置號用於確定裝置檔案所指的裝置。

在核心中,用dev_t類型<linux/types.h>儲存裝置編號。
2.4核心中採用16位裝置號(8位主,8位從),而2.6採用32位,12位主,20位從。
在驅動中訪問裝置號應該用<linux/kdev_t.h>中定義的宏。
擷取裝置號:
MAJOR(dev_t dev)
MINOR(dev_t dev)
MKDEV(int major, int minor)

b.分配和釋放裝置編號
在建立一個字元裝置前,驅動需要先獲得裝置編號。
分配:
#include <linux/fs.h>
int register_chrdev_region(dev_t first, unsigned int count, char *name);
first:要分配的裝置編號範圍的起始值(次裝置號常設為0)
count: 所請求的連續編號範圍
name: 和編號關聯的裝置名稱(見/proc/devices)

也可以要求核心動態分配:
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
firstminor: 通常為0
*dev: 存放核心返回的裝置號

釋放:
void unregister_chrdev_region(dev_t first, unsigned int count);
在模組的清除函數中調用

在Documentation/devices.txt中可以找到核心已經分配的裝置號。

c.建立裝置檔案
當裝置驅動模組向系統申請了主裝置號和次裝置號,並且已經通過insmod載入到核心中後,我們就可以通過在/dev下建立裝置檔案來訪問這個裝置了。

字元裝置的建立:
$>mknod /dev/mychar c major minor

我們在驅動中常常採用動態分配主次裝置號的方法,這樣不會和系統中已有的裝置號衝突。
動態分配時,/dev下的裝置檔案也需要通過分析/proc/devices動態建立。
見char_load和char_unload指令碼。

(3)字元裝置的基本資料結構
和字元裝置驅動關係最緊密的3個基本的資料結構是:
file, file_oepeations和inode

a.file_operations資料結構
結構中包含了若干函數指標。這些函數就是實際和硬體打交道的函數。
使用者空間調用的open,write等函數最終會調用這裡面的指標所指向的函數。每個開啟的檔案和一組函數關聯。
見<linux/fs.h>和驅動書的p54

2.6核心結構的初始化:
struct file_operations my_fops = {
.owner = THIS_MODULE,
.llseek = my_llseek,
.read = my_read,
.write = my_write,
.ioctl = my_ioctl,
.open = my_open,
.release = my_release,
}

2.4核心結構的初始化:
struct file_operations my_fops = {
owner: THIS_MODULE,
llseek: my_llseek,
...
}

b.file結構<linux/fs.h>
file是一個核心結構體,實際上和使用者open檔案後返回的檔案描述符fd對應。
file結構代表一個開啟的檔案,系統中每個開啟的檔案在核心空間都有一個對應的file結構。
它由核心在open時建立,並傳遞給在該檔案上進行操作的所有函數,直到最後的close函數,在檔案的所有執行個體都被關閉後,核心會釋放這個結構。

使用者空間進程fork一個新進程後,新老進程會共用開啟的檔案描述符fd,這個操作不會在核心空間建立新的file結構,只會增加已建立file結構的計數。

見<linux/fs.h>
mode_t f_mode;
通過FMODE_READ和FMODE_WRITE標示檔案是否可讀或可寫。

loff_t f_pos;
當前的讀寫位置,loff_t為64位

unsigned int f_flags;
檔案標誌,如O_RDONLY, O_NONBLOCK, O_SYNC。標誌都定義在<linux/fcntl.h>

struct file_operations *f_op;
與檔案相關的操作。核心在執行open時對這個指標賦值。可以在驅動的open方法中根據次裝置號賦予不同的f_op

void *private;
通常將表示硬體裝置的結構體賦給private.

struct dentry *f_dentry;
檔案對應的目錄項(dentry)結構。可通過filp->f_dentry->d_inode訪問索引節點。

file中其他的內容和驅動關係不大。

c.inode結構
核心用inode結構表示一個實際的檔案,可以是一個普通的檔案,也可以是一個裝置檔案。
每個檔案只有一個inode結構,而和檔案描述符對應的file結構可以有多個(多次進行open調用)。這些file都指向同一個inode。

inode定義在<linux/fs.h>
dev_t i_rdev;
對於表示裝置檔案的inode結構,i_rdev裡包含了真正的裝置編號

struct cdev *i_cdev
cdev是表示字元裝置的核心的內部結構。當inode表示一個字元裝置時,i_cdev指向核心中的struct cdev.

其他結構和裝置驅動關係不大。

用如下宏從inode擷取裝置號:
unsigned int iminor(struct inode *inode)
unsigned int imajor(struct inode *inode)

(4)字元裝置的註冊
核心內部使用struct cdev結構來表示一個字元裝置。
我們的驅動要把自己的cdev註冊到核心中去。見 <linux/cdev.h>


a.通常在裝置的結構中加入cdev
struct scull_dev{
...
struct cdev cdev; /* 字元裝置結構 */
}


b.初始化
void cdev_init(struct cdev *cdev, struct file_operations *fops)


c.設定cdev中的內容
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;

d.向核心添加設定好的cdev
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
num: 裝置對應的第一個編號
count: 和裝置關聯的裝置編號的數量,常取1
一旦cdev_add返回,核心就認為裝置可以使用了,所以要在調用之前完成裝置的硬體初始化。

(5)老式的註冊函數
2.4中的老式註冊函數仍然在驅動函數中大量存在,但新的代碼不應該使用這些代碼。
註冊:
int register_chrdev(unsigned int major,
    const char *name,
    struct file_operations *fops);
為給定的主裝置號註冊0~255作為次裝置號,並為每個裝置建立一個對應的預設cdev結構

登出:
int unregister_chrdev(unsigned int major,
    const char *name);

(6)open和release
a.open
在驅動的open方法中完成裝置的初始化工作,open完成後,硬體就可以使用,使用者程式可以通過write等訪問裝置,open的工作有:
*檢查裝置的特定錯誤
*如果裝置首次開啟,則對其進行初始化(有可能多次調用open)
*如有必要,更新f_op指標
*分配並填寫置於filp->private_data中的資料

open原型;
int (*open) (struct inode *inode, struct file *filp);

在open中通過inode獲得dev指標,並將其賦給file->private_data
struct scull_dev *dev;
dev = contain_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev;
(如果dev是靜態分配的,則在open或write等方法中可以直接存取dev,但如果dev是在module_init時動態分配的,則只能通過上面的方法獲得其指標)

b.release
並不是每個close調用都會引起對release方法的調用,只有當file的計數器歸零時,才會調用release,從而釋放dev結構)

(7)read和write
read和write的工作是從使用者空間拷貝資料到核心,或是將核心資料拷貝到使用者空間。
其原型為:
ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);
ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);
buff: 使用者空間的緩衝區指標
offp: 使用者在檔案中進行存取操作的位置
在read和write中,拷貝完資料後,應該更新offp,並將實際完成的拷貝位元組數返回。

(8)和使用者空間交換資料
read和write中的__user *buff 是使用者空間的指標,核心不能直接引用其中的內容(也就是不能直接對buff進行取值操作),需要通過核心提供的函數進行資料拷貝。
其原因是:
a.在不同架構下,在核心模式中運行時,使用者空間的指標可能是無效的。
b.使用者空間的記憶體是分頁的,系統調用執行時,buff指向的記憶體可能根本不在RAM中(被交換到磁碟中了)
c.這可能是個無效或者惡意指標(比如指向核心空間)

核心和使用者空間交換資料的函數見<asm/uaccess.h>

如:
1. unsigned long copy_to_user(
      void __user *to, 
      const void *from, 
      unsigned long count);
向使用者空間拷貝資料
      
2. unsigned long copy_from_user(
      void *to, 
      const void __user *from, 
      unsigned long count);
從使用者空間獲得資料

3. int put_user(datum, ptr)
向使用者空間拷貝資料。位元組數由sizeof(*ptr)決定
傳回值為0成功,為負錯誤。

4. int get_user(local, ptr);
從使用者空間獲得資料。位元組數由sizeof(*ptr)決定
傳回值和local都是從使用者空間獲得的資料

任何訪問使用者空間的函數都必須是可睡眠的,這些函數需要可重新進入。

copy_to_user等函數如果傳回值不等於0,則read或write應向使用者空間返回-EFAULT

---------------------------------------------------------------------------------------------------------------------------------

主裝置號用來表示裝置驅動, 次裝置號表示使用該驅動的裝置
在核心dev_t 表示裝置號, 裝置號由主裝置號和次裝置號組成
#include <linux/kdev_t.h>
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //根據裝置號擷取主裝置號
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))  //擷取次裝置號
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))          //根據指定的主裝置和次裝置號產生裝置號
#include <linux/fs.h>  
//靜態:申請指定的裝置號, from指裝置號, count指使用該驅動有多少個裝置(次裝置號), 裝置名稱 
int register_chrdev_region(dev_t from, unsigned count, const char *name);
name的長度不能超過64位元組

//動態申請裝置號, 由核心分配沒有使用的主裝置號, 分配好的裝置存在dev, baseminor指次裝置號從多少開始, count指裝置數, name裝置名稱 
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)

//釋放裝置號, from指裝置號, count指裝置數
void unregister_chrdev_region(dev_t from, unsigned count)

//cat /proc/devices 可查看裝置使用方式
在核心源碼的documentations/devices.txt可查看裝置號的靜態分配情況

///核心裡使用struct cdev來描述一個字元裝置驅動 
#include <linux/cdev.h>
struct cdev {
struct kobject kobj;       //核心用於管理字元裝置驅動 
struct module *owner;      //通常設為THIS_MODULE, 用於防止驅動在使用中時卸載驅動模組
const struct file_operations *ops;  //怎樣操作(vfs)
struct list_head list;     //因多個裝置可以使用同一個驅動, 用鏈表來記錄
dev_t dev;                 //裝置號
unsigned int count;        //裝置數
};

////////字元裝置驅動//////////
1. 申請裝置號
2. 定義一個cdev的裝置驅動對象
struct cdev mycdev;
   定義一個file_operations的檔案操作對象
struct file_operations fops = {
.owner = THIS_MODULE,
.read = 讀函數
....
};

3. 把fops對象與mycdev關聯起來
cdev_init(&mycdev, &fops); //mycdev.ops = &fops;
mycdev.owner = THIS_MODULE;

4. 把裝置驅動加入核心裡, 並指定該驅動對應的裝置號
cdev_add(&mycdev, 裝置號, 次裝置號的個數);

5. 卸載模組時, 要把裝置驅動從核心裡移除, 並把裝置號反註冊
cdev_del(&mycdev);

///////////建立裝置檔案
mknod /dev/裝置檔案名稱  c 主裝置號 次裝置號

////////inode節點對象描述一個檔案/裝置檔案, 包括許可權,裝置號等資訊
struct inode {
...
dev_t  i_rdev;     //裝置檔案對應的裝置號
struct cdev *i_cdev; //指向對應的裝置驅動對象的地址
...
};

////file對象描述檔案描述符, 在檔案開啟時建立, 關閉時銷毀
struct file {
...
const struct file_operations *f_op; //對應的檔案操作對象的地址
unsigned int f_flags; //檔案開啟的標誌
fmode_t f_mode;  //許可權
loff_t f_pos;   //檔案描述符的位移
struct fown_struct f_owner; //屬於哪個進程
unsigned int f_uid, f_gid; 
void *private_data; //給驅動程式員使用
...
};
通file裡的成員f_path.dentry->d_inode->i_rdev可以擷取到裝置檔案的裝置號

///錯誤碼在<asm/errno.h> ////

/////////struct file_operations ////
     inode表示應用程式開啟的檔案的節點對象,  file表示開啟檔案擷取到的檔案描述符
     成功返回0, 失敗返回錯誤碼
int (*open) (struct inode *, struct file *);
     
     buf指向使用者進程裡的緩衝區, len表示buf的大小(由使用者調用read時傳進來的)
     off表示fl檔案描述符的操作位移, 傳回值為實際給使用者的資料位元組數.
ssize_t (*read) (struct file *fl, char __user *buf, size_t len, loff_t *off);

     使用者進程把資料給驅動
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

////////////////
to指使用者進程的緩衝區, from指驅動裡裝資料的緩衝區, n多少位元組, 傳回值是0
extern inline long copy_to_user(void __user *to, const void *from, long n)
to指驅動的...   from使用者...    n多少位元組, ....

static inline unsigned long __must_check copy_to_user(void __user *to, const
void *from, unsigned long n)
{
if (access_ok(VERIFY_WRITE, to, n))
n = __copy_to_user(to, from, n);
return n; //傳回值為剩下多少位元組沒拷貝
}
///////

extern inline long copy_from_user(void *to, const void __user *from, long n)

如果與使用者進程互動的資料是1,2,4,8位元組的話, 可用
  put_user(x,p) //x為值, p為地址

如果從使用者進程擷取1,2,4位元組的話, 可用
  get_user(x,p) 

///////////

///動態申請記憶體, 並清零. size為申請多大(不要超過128K),
//flags為標誌(常為GFP_KERNEL). 成功返回地址, 失敗返回NULL
// GFP_ATOMIC, 使用系統的記憶體緊急池

void *kmalloc(size_t size, gfp_t flags);//申請後要記憶體要清零
void *kzalloc(size_t size, gfp_t flags); //申請出來的記憶體已清零
void kfree(const void *objp); //回收kmalloc/kzalloc的記憶體

void *vmalloc(unsigned long size); //申請大記憶體空間
void vfree(const void *addr); //回收vmalloc的記憶體

// kmalloc申請出來的記憶體是物理地址連續的, vmalloc不一定是連續的

///// container_of(ptr, type, member) type包括member成員的結構體,
//ptr是type類型 結構體的member成員的地址.
//此宏根據結構體成員的地址擷取結構體變數的首地址

#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

 15 typedef struct led_dev_t {
 16         dev_t mydevid;
 17         unsigned int *rLEDCON;
 18         unsigned int *rLEDDAT;
 19         struct cdev mycdev;
 20 }LED_DEV;

 LED_DEV myled;

 ind->i_cdev是指向myled.mycdev成員的地址
 結構體變數myled首地址可由container_of(ind->i_cdev, LED_DEV, mycdev)擷取;

/////// 自動建立裝置檔案 ////
#include <linux/device.h>
1.  struct class *cl; 
    cl = class_create(owner, name) ; //owner指屬於哪個模組, name類名
//建立出來後可以查看  /sys/class/類名
    void class_destroy(struct class *cls); //用於銷毀建立出來的類

2. 建立裝置檔案
struct device *device_create(struct class *cls, struct device *parent,
   dev_t devt, void *drvdata,
   const char *fmt, ...)
   __attribute__((format(printf, 5, 6)));

 device_create(所屬的類, NULL, 裝置號, NULL, "mydev%d", 88); //在/dev/目錄下產生名字為mydev88的裝置檔案
 void device_destroy(struct class *cls, dev_t devt); //用於銷毀建立出來的裝置檔案

////////
int register_chrdev(unsigned int major, const char *name,
   const struct file_operations *fops) ; //註冊裝置號並建立驅動對象

void unregister_chrdev(unsigned int major, const char *name); //反註冊裝置號並刪除驅動對象

static inline int register_chrdev(unsigned int major, const char *name,
 const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
int __register_chrdev(unsigned int major, unsigned int baseminor,
     unsigned int count, const char *name,
     const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
int err = -ENOMEM;


cd = __register_chrdev_region(major, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);

cdev = cdev_alloc();
if (!cdev)
goto out2;


cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);

err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
if (err)
goto out;

cd->cdev = cdev;

return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, baseminor, count));
return err;
}

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.