亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 服務器 > Linux服務器 > 正文

Linux內核設備驅動之字符設備驅動筆記整理

2024-09-05 23:05:27
字體:
來源:轉載
供稿:網友
/******************** * 字符設備驅動 ********************/

(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時對這個指針賦值??梢栽隍寗拥膐pen方法中根據次設備號賦予不同的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, constvoid *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;}

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對VEVB武林網的支持。


注:相關教程知識閱讀請移步到服務器教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产午夜精品视频免费不卡69堂| 91精品国产自产在线老师啪| 91美女片黄在线观| 亚洲精品99久久久久| 欧美性69xxxx肥| 精品偷拍一区二区三区在线看| 尤物九九久久国产精品的特点| 久久久久久久香蕉网| 欧美人与物videos| 国产99视频精品免视看7| 欧美精品国产精品日韩精品| 国产一区二区精品丝袜| 午夜精品国产精品大乳美女| 欧美在线视频一区二区| 日韩免费在线免费观看| 久久人人爽人人爽人人片亚洲| 日韩精品亚洲元码| 国产亚洲视频中文字幕视频| 午夜精品久久久久久久久久久久| 国产亚洲成av人片在线观看桃| 国产黑人绿帽在线第一区| 日韩av日韩在线观看| 九九久久国产精品| 亚洲成人激情在线观看| 国产精品成人免费视频| 久久人人爽人人爽爽久久| 成人啪啪免费看| 欧美久久精品一级黑人c片| 久久国产精品久久久久久久久久| 欧美午夜精品久久久久久浪潮| 亚洲第一中文字幕在线观看| 伊人成人开心激情综合网| 久久久久久久影院| 国产激情综合五月久久| 91久久久久久国产精品| 在线成人激情黄色| 国产精品香蕉在线观看| 性色av一区二区三区红粉影视| 不卡av电影院| 欧美性猛交丰臀xxxxx网站| 国产一区二区三区在线观看视频| 成人免费网站在线看| 欧美日韩亚洲一区二区| 亚洲va码欧洲m码| 亚洲自拍偷拍区| 爽爽爽爽爽爽爽成人免费观看| 丝袜情趣国产精品| 日韩视频在线免费| 97久久久免费福利网址| 国产精品视频一区国模私拍| 国内成人精品一区| 91免费的视频在线播放| 2021国产精品视频| 久久久久久久久久国产| 一个色综合导航| 国产日韩在线免费| 亚洲香蕉在线观看| 日韩av免费看| 91国产美女在线观看| 法国裸体一区二区| 欧美精品激情在线| 亚洲欧美在线一区| 亚洲国内精品视频| 成人精品视频久久久久| 国产中文字幕91| 国产欧美日韩丝袜精品一区| 国产精品自拍网| 国模吧一区二区| 日本久久亚洲电影| 国产成人综合av| 麻豆一区二区在线观看| 欧美成人精品三级在线观看| 欧美国产日韩一区二区| 国产999精品视频| 狠狠色狠狠色综合日日小说| 亚洲欧洲一区二区三区在线观看| 98视频在线噜噜噜国产| 欧美另类极品videosbest最新版本| 91久久久久久久久久久| 亚洲精品一区久久久久久| 欧美电影免费观看网站| 69影院欧美专区视频| 91精品国产乱码久久久久久蜜臀| 亚洲欧美国产va在线影院| 国产精品精品久久久久久| 91免费看国产| 欧美成人精品不卡视频在线观看| 欧美大成色www永久网站婷| 成人精品视频久久久久| 亚洲电影成人av99爱色| 精品国模在线视频| 欧美成在线观看| 欧美日韩亚洲一区二区三区| 久久久久久久久久久人体| 中文字幕欧美国内| 亚洲精品福利在线观看| 欧美夫妻性生活xx| 中文字幕成人在线| 成人情趣片在线观看免费| 日本不卡高字幕在线2019| 日韩中文字幕在线精品| 全亚洲最色的网站在线观看| 深夜福利一区二区| 国产欧美亚洲视频| 精品福利视频导航| 成人有码在线视频| 亚洲精品电影久久久| 欧美精品久久久久久久久| 国产成人91久久精品| 久久久久久久一区二区| 国产精品久久久久影院日本| 欧美日韩性视频在线| 最近2019中文字幕第三页视频| 久久精品欧美视频| 国产一区二区三区三区在线观看| 欧美亚洲另类视频| 91国偷自产一区二区三区的观看方式| 日韩精品福利在线| 9.1国产丝袜在线观看| 国产不卡视频在线| 亚洲淫片在线视频| 亚洲人成网站777色婷婷| 欧美成人国产va精品日本一级| 激情懂色av一区av二区av| 亚洲护士老师的毛茸茸最新章节| 国产日韩欧美在线观看| 成人网中文字幕| 国产精品69av| 日韩在线视频线视频免费网站| 北条麻妃一区二区在线观看| 精品视频在线导航| 亚洲女人天堂视频| 亚洲在线视频福利| 一个人www欧美| 欧美日韩亚洲一区二区三区| 国产精品狼人色视频一区| 亚洲欧美中文字幕| 日韩在线观看成人| 亚洲国产精品va| 日韩欧美在线观看视频| 亚洲欧美中文日韩在线| 夜夜狂射影院欧美极品| 欧美与欧洲交xxxx免费观看| 国产97色在线| 精品成人久久av| 中文字幕欧美亚洲| 亚洲午夜性刺激影院| 97国产精品视频人人做人人爱| 日本高清视频精品| 日韩激情av在线免费观看| 欧美大码xxxx| 亚洲的天堂在线中文字幕| 国产区精品在线观看| 中文字幕亚洲一区在线观看| 91美女片黄在线观看游戏| 九九热99久久久国产盗摄| 91影院在线免费观看视频| 亚洲欧美国产制服动漫| 中文字幕欧美在线| 久久视频在线看| 亚洲性夜色噜噜噜7777| 国产精品白嫩美女在线观看| 中文字幕亚洲二区| 国产欧美va欧美va香蕉在|