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

首頁 > 學院 > 開發設計 > 正文

Linux 字符設備驅動結構(一)—— cdev 結構體、設備號相關知識解析

2019-11-10 18:34:16
字體:
來源:轉載
供稿:網友

一、字符設備基礎知識

1、設備驅動分類

      linux系統將設備分為3類:字符設備、塊設備、網絡設備。使用驅動程序:

字符設備:是指只能一個字節一個字節讀寫的設備,不能隨機讀取設備內存中的某一數據,讀取數據需要按照先后數據。字符設備是面向流的設備,常見的字符設備有鼠標、鍵盤、串口、控制臺和LED設備等。

塊設備:是指可以從設備的任意位置讀取一定長度數據的設備。塊設備包括硬盤、磁盤、U盤和SD卡等。

每一個字符設備或塊設備都在/dev目錄下對應一個設備文件。linux用戶程序通過設備文件(或稱設備節點)來使用驅動程序操作字符設備和塊設備。

2、字符設備、字符設備驅動與用戶空間訪問該設備的程序三者之間的關系

     如圖,在Linux內核中:

a -- 使用cdev結構體來描述字符設備;

b -- 通過其成員dev_t來定義設備號(分為主、次設備號)以確定字符設備的唯一性;

c -- 通過其成員file_Operations來定義字符設備驅動提供給VFS的接口函數,如常見的open()、read()、write()等;

     在Linux字符設備驅動中:

a -- 模塊加載函數通過 register_chrdev_region( ) 或 alloc_chrdev_region( )來靜態或者動態獲取設備號;

b -- 通過 cdev_init( ) 建立cdev與 file_operations之間的連接,通過 cdev_add( ) 向系統添加一個cdev以完成注冊;

c -- 模塊卸載函數通過cdev_del( )來注銷cdev,通過 unregister_chrdev_region( )來釋放設備號;

     用戶空間訪問該設備的程序:

a -- 通過Linux系統調用,如open( )、read( )、write( ),來“調用”file_operations來定義字符設備驅動提供給VFS的接口函數;

3、字符設備驅動模型

二、cdev 結構體解析

      在Linux內核中,使用cdev結構體來描述一個字符設備,cdev結構體的定義如下:

[cpp] view plain copy 在CODE上查看代碼片<include/linux/cdev.h>    struct cdev {       struct kobject kobj;                  //內嵌的內核對象.      struct module *owner;                 //該字符設備所在的內核模塊的對象指針.      const struct file_operations *ops;    //該結構描述了字符設備所能實現的方法,是極為關鍵的一個結構體.      struct list_head list;                //用來將已經向內核注冊的所有字符設備形成鏈表.      dev_t dev;                            //字符設備的設備號,由主設備號和次設備號構成.      unsigned int count;                   //隸屬于同一主設備號的次設備號的個數.  };  內核給出的操作struct%20cdev結構的接口主要有以下幾個:

a%20--%20void%20cdev_init(struct%20cdev%20*,%20const%20struct%20file_operations%20*);

其源代碼如代碼清單如下:

[cpp]%20view%20plain%20copy%20void cdev_init(struct cdev *cdev, const struct file_operations *fops)  {      memset(cdev, 0, sizeof *cdev);      INIT_LIST_HEAD(&cdev->list);      kobject_init(&cdev->kobj, &ktype_cdev_default);      cdev->ops = fops;  }   %20 %20 %20該函數主要對struct%20cdev結構體做初始化,最重要的就是建立cdev%20和%20file_operations之間的連接:

(1)%20將整個結構體清零;

(2)%20初始化list成員使其指向自身;

(3)%20初始化kobj成員;

(4)%20初始化ops成員;

 b%20--struct%20cdev%20*cdev_alloc(void);

 %20 %20 該函數主要分配一個struct%20cdev結構,動態申請一個cdev內存,并做了cdev_init中所做的前面3步初始化工作(第四步初始化工作需要在調用cdev_alloc后,顯式的做初始化即:%20.ops=xxx_ops).

其源代碼清單如下:

[cpp]%20view%20plain%20copy%20struct cdev *cdev_alloc(void)  {      struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);      if (p) {          INIT_LIST_HEAD(&p->list);          kobject_init(&p->kobj, &ktype_cdev_dynamic);      }      return p;  }  

 %20 %20 在上面的兩個初始化的函數中,我們沒有看到關于owner成員、dev成員、count成員的初始化;其實,owner成員的存在體現了驅動程序與內核模塊間的親密關系,struct%20module是內核對于一個模塊的抽象,該成員在字符設備中可以體現該設備隸屬于哪個模塊,在驅動程序的編寫中一般由用戶顯式的初始化%20.owner%20=%20THIS_MODULE,%20該成員可以防止設備的方法正在被使用時,設備所在模塊被卸載。而dev成員和count成員則在cdev_add中才會賦上有效的值。

 c%20--%20int%20cdev_add(struct%20cdev%20*p,%20dev_t%20dev,%20unsigned%20count);

 %20 %20 %20 該函數向內核注冊一個struct%20cdev結構,即正式通知內核由struct%20cdev%20*p代表的字符設備已經可以使用了。

當然這里還需提供兩個參數:

(1)第一個設備號%20dev,

(2)和該設備關聯的設備編號的數量。

這兩個參數直接賦值給struct%20cdev%20的dev成員和count成員。

d%20--%20void%20cdev_del(struct%20cdev%20*p);

 %20 %20 該函數向內核注銷一個struct%20cdev結構,即正式通知內核由struct%20cdev%20*p代表的字符設備已經不可以使用了。

 %20 %20 從上述的接口討論中,我們發現對于struct%20cdev的初始化和注冊的過程中,我們需要提供幾個東西

(1)%20struct%20file_operations結構指針;

(2)%20dev設備號;

(3)%20count次設備號個數。

但是我們依舊不明白這幾個值到底代表著什么,而我們又該如何去構造這些值!

三、設備號相應操作

1%20--%20主設備號和次設備號(二者一起為設備號):

 %20 %20 %20一個字符設備或塊設備都有一個主設備號和一個次設備號。主設備號用來標識與設備文件相連的驅動程序,用來反映設備類型。次設備號被驅動程序用來辨別操作的是哪個設備,用來區分同類型的設備。

  linux內核中,設備號用dev_t來描述,2.6.28中定義如下:

  typedef%20u_long%20dev_t;

  在32位機中是4個字節,高12位表示主設備號,低20位表示次設備號。

內核也為我們提供了幾個方便操作的宏實現dev_t:

1)%20-- 從設備號中提取major和minor

MAJOR(dev_t%20dev);                              

MINOR(dev_t%20dev);

2)%20-- 通過major和minor構建設備號

MKDEV(int%20major,int%20minor);

注:這只是構建設備號。并未注冊,需要調用 register_chrdev_region 靜態申請;

[cpp]%20view%20plain%20copy%20//宏定義:  #define MINORBITS    20  #define MINORMASK    ((1U << MINORBITS) - 1)  #define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))  #define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))  #define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))</span>  

2、分配設備號(兩種方法):

a%20--%20靜態申請

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

其源代碼清單如下:

[cpp]%20view%20plain%20copy%20int register_chrdev_region(dev_t from, unsigned count, const char *name)  {      struct char_device_struct *cd;      dev_t to = from + count;      dev_t n, next;        for (n = from; n < to; n = next) {          next = MKDEV(MAJOR(n)+1, 0);          if (next > to)              next = to;          cd = __register_chrdev_region(MAJOR(n), MINOR(n),                     next - n, name);          if (IS_ERR(cd))              goto fail;      }      return 0;  fail:      to = n;      for (n = from; n < to; n = next) {          next = MKDEV(MAJOR(n)+1, 0);          kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));      }      return PTR_ERR(cd);  }  b%20--%20動態分配:

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

其源代碼清單如下:

[cpp]%20view%20plain%20copy%20int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,              const char *name)  {      struct char_device_struct *cd;      cd = __register_chrdev_region(0, baseminor, count, name);      if (IS_ERR(cd))          return PTR_ERR(cd);      *dev = MKDEV(cd->major, cd->baseminor);      return 0;  }  

可以看到二者都是調用了__register_chrdev_region%20函數,其源代碼如下:

[cpp]%20view%20plain%20copy%20static struct char_device_struct *  __register_chrdev_region(unsigned int major, unsigned int baseminor,                 int minorct, const char *name)  {      struct char_device_struct *cd, **cp;      int ret = 0;      int i;        cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);      if (cd == NULL)          return ERR_PTR(-ENOMEM);        mutex_lock(&chrdevs_lock);        /* temporary */      if (major == 0) {          for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {              if (chrdevs[i] == NULL)                  break;          }            if (i == 0) {              ret = -EBUSY;              goto out;          }          major = i;          ret = major;      }        cd->major = major;      cd->baseminor = baseminor;      cd->minorct = minorct;      strlcpy(cd->name, name, sizeof(cd->name));        i = major_to_index(major);        for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)          if ((*cp)->major > major ||              ((*cp)->major == major &&               (((*cp)->baseminor >= baseminor) ||                ((*cp)->baseminor + (*cp)->minorct > baseminor))))              break;        /* Check for overlapping minor ranges.  */      if (*cp && (*cp)->major == major) {          int old_min = (*cp)->baseminor;          int old_max = (*cp)->baseminor + (*cp)->minorct - 1;          int new_min = baseminor;          int new_max = baseminor + minorct - 1;            /* New driver overlaps from the left.  */          if (new_max >= old_min && new_max <= old_max) {              ret = -EBUSY;              goto out;          }            /* New driver overlaps from the right.  */          if (new_min <= old_max && new_min >= old_min) {              ret = -EBUSY;              goto out;          }      }        cd->next = *cp;      *cp = cd;      mutex_unlock(&chrdevs_lock);      return cd;  out:      mutex_unlock(&chrdevs_lock);      kfree(cd);      return ERR_PTR(ret);  }   通過這個函數可以看出 register_chrdev_region alloc_chrdev_region%20的區別,register_chrdev_region直接將Major%20注冊進入,而 alloc_chrdev_region從Major%20=%200%20開始,逐個查找設備號,直到找到一個閑置的設備號,并將其注冊進去;

二者應用可以簡單總結如下:

 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 register_chrdev_region  %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 %20 alloc_chrdev_region 

   %20devno = MKDEV(major,minor);    ret = register_chrdev_region(devno, 1, "hello");     cdev_init(&cdev,&hello_ops);    ret = cdev_add(&cdev,devno,1); %20  alloc_chrdev_region(&devno, minor, 1, "hello"); %20 %20major = MAJOR(devno); %20 %20cdev_init(&cdev,&hello_ops); %20 %20ret = cdev_add(&cdev,devno,1)register_chrdev(major,"hello",&hello

 %20 %20 可以看到,除了前面兩個函數,還加了一個register_chrdev%20函數,可以發現這個函數的應用非常簡單,只要一句就可以搞定前面函數所做之事;

下面分析一下register_chrdev%20函數,其源代碼定義如下:

[cpp]%20view%20plain%20copy%20static inline int register_chrdev(unsigned int major, const char *name,                    const struct file_operations *fops)  {      return __register_chrdev(major, 0, 256, name, fops);  }  調用了 __register_chrdev(major,%200,%20256,%20name,%20fops)%20函數:[cpp]%20view%20plain%20copy%20int __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;  }  可以看到這個函數不只幫我們注冊了設備號,還幫我們做了cdev%20的初始化以及cdev%20的注冊;

3、注銷設備號:

void%20unregister_chrdev_region(dev_t%20from,%20unsigned%20count);

4、創建設備文件:

 %20 %20 利用cat%20/#include <linux/module.h>  #include <linux/fs.h>  #include <linux/cdev.h>  static int major = 250;  static int minor = 0;  static dev_t devno;  static struct cdev cdev;  static int hello_open (struct inode *inode, struct file *filep)  {      printk("hello_open /n");      return 0;  }  static struct file_operations hello_ops=  {      .open = hello_open,           };    static int hello_init(void)  {      int ret;          printk("hello_init");      devno = MKDEV(major,minor);      ret = register_chrdev_region(devno, 1, "hello");      if(ret < 0)      {          printk("register_chrdev_region fail /n");          return ret;      }      cdev_init(&cdev,&hello_ops);      ret = cdev_add(&cdev,devno,1);      if(ret < 0)      {          printk("cdev_add fail /n");          return ret;      }         return 0;  }  static void hello_exit(void)  {      cdev_del(&cdev);      unregister_chrdev_region(devno,1);      printk("hello_exit /n");  }  MODULE_LICENSE("GPL");  module_init(hello_init);  module_exit(hello_exit);  

測試程序%20test.c

[cpp]%20view%20plain%20copy%20#include <sys/types.h>  #include <sys/stat.h>  #include <fcntl.h>  #include <stdio.h>    main()  {      int fd;        fd = open("/dev/hello",O_RDWR);      if(fd<0)      {          perror("open fail /n");          return ;      }        close(fd);  }  makefile:[cpp]%20view%20plain%20copy%20派生到我的代碼片ifneq  ($(KERNELRELEASE),)  obj-m:=hello.o  $(info "2nd")  else  KDIR := /lib/modules/$(shell uname -r)/build  PWD:=$(shell pwd)  all:      $(info "1st")      make -C $(KDIR) M=$(PWD) modules  clean:      rm -f *.ko *.o *.symvers *.mod.c *.mod.o *.order  endif  

編譯成功后,使用 insmod 命令加載:

然后用cat /proc/devices 查看,會發現設備號已經申請成功;


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
韩国v欧美v日本v亚洲| 成人午夜高潮视频| 亚洲人成77777在线观看网| 亚洲无限乱码一二三四麻| 亚洲视频网站在线观看| 精品亚洲一区二区三区四区五区| 国产精品高精视频免费| 亚洲夜晚福利在线观看| 久久久久久久国产精品视频| 欧美日韩成人在线播放| 欧美在线观看网站| 亚洲欧洲免费视频| 欧美巨大黑人极品精男| 日韩欧美在线播放| 欧美激情视频网| 一区二区亚洲欧洲国产日韩| 日韩亚洲欧美成人| 日韩成人小视频| 国产成人精品在线观看| 亚洲精品v欧美精品v日韩精品| 日韩av在线免费播放| 亚洲国产精品va在线| 欧美久久精品午夜青青大伊人| 日韩av网站电影| 91精品视频观看| 久久久久国产精品免费网站| 国内精品小视频| 亚洲色图17p| 亚洲国产精品久久91精品| 国产丝袜精品视频| 成年人精品视频| 欧美日韩国产影院| 久久久免费观看视频| 精品人伦一区二区三区蜜桃免费| 亚洲国产精品国自产拍av秋霞| 国产精品久久久久av| 视频在线观看一区二区| 1769国内精品视频在线播放| 国产成人免费91av在线| 理论片在线不卡免费观看| 69久久夜色精品国产7777| 欧美激情精品久久久久| 欧美一区二区大胆人体摄影专业网站| 成人激情在线观看| 亚洲精品久久久久久下一站| 国产精品稀缺呦系列在线| 久国内精品在线| 欧美精品国产精品日韩精品| 91午夜理伦私人影院| 亚洲精品suv精品一区二区| 欧美精品久久久久久久| 欧美www在线| 久久九九精品99国产精品| 久久久久久久97| 国产视频久久久| 伦伦影院午夜日韩欧美限制| 麻豆乱码国产一区二区三区| 国产一区二区丝袜高跟鞋图片| 国产99久久久欧美黑人| 狠狠综合久久av一区二区小说| 久久久成人的性感天堂| 亚洲精品白浆高清久久久久久| 久久亚洲精品小早川怜子66| 日韩精品一二三四区| 成人做爰www免费看视频网站| 国产脚交av在线一区二区| 亚洲欧美日韩爽爽影院| 亚洲性线免费观看视频成熟| 日韩av综合中文字幕| 亚洲欧洲黄色网| 精品久久久久久中文字幕一区奶水| 高清日韩电视剧大全免费播放在线观看| 亚洲精品视频网上网址在线观看| 日韩av免费观影| 九色成人免费视频| 精品呦交小u女在线| 精品国产依人香蕉在线精品| 亚洲人成在线免费观看| 欧美黄网免费在线观看| 日本人成精品视频在线| 国产情人节一区| 欧美成人性生活| 国产亚洲欧美一区| 中文字幕无线精品亚洲乱码一区| 亚洲色图综合网| 久久久久999| 91精品国产综合久久香蕉922| 久久久视频免费观看| 日韩欧美极品在线观看| 成人黄色中文字幕| 久久韩国免费视频| 国产一级揄自揄精品视频| 久久在线免费视频| 久久久亚洲欧洲日产国码aⅴ| 综合136福利视频在线| 国产精品久久久久久久久粉嫩av| 欧美激情成人在线视频| 性欧美xxxx交| 92看片淫黄大片欧美看国产片| 黄色成人在线播放| 亚洲欧美一区二区三区情侣bbw| 国内成人精品视频| 秋霞av国产精品一区| 欧美激情女人20p| 欧美日韩国产精品一区二区不卡中文| 成人女保姆的销魂服务| 55夜色66夜色国产精品视频| 国产精品免费观看在线| 色阁综合伊人av| 久久久av免费| 国产日本欧美一区二区三区在线| 亚洲国产另类 国产精品国产免费| 久久久久久久久电影| 国产精品永久免费视频| 日韩亚洲成人av在线| 91欧美激情另类亚洲| 成人妇女免费播放久久久| 中文字幕无线精品亚洲乱码一区| 亚洲国产一区二区三区在线观看| 日韩在线视频免费观看| 亚洲精品99久久久久中文字幕| 欧美性资源免费| 韩国欧美亚洲国产| 国产精品久久久久久久9999| 国产精品久久久久久久久| 日韩视频中文字幕| 久久手机精品视频| 国产日韩专区在线| 欧美成人午夜免费视在线看片| 亚洲国模精品一区| 欧美怡红院视频一区二区三区| 亚洲а∨天堂久久精品9966| 亚洲曰本av电影| 欧美精品videosex极品1| 国产一区二区三区视频在线观看| 欧美日韩在线视频一区二区| 麻豆精品精华液| 一区二区三区美女xx视频| 欧美大片免费观看| 国产在线久久久| 国产午夜精品全部视频在线播放| 欧美午夜精品久久久久久浪潮| 国产精品久在线观看| 5566日本婷婷色中文字幕97| 欧美一级高清免费| 亚洲精品大尺度| 国产精品女人网站| 国产精品亚洲美女av网站| 欧美激情在线有限公司| 欧洲成人在线视频| 国产mv免费观看入口亚洲| 成人有码在线播放| 55夜色66夜色国产精品视频| 国产精品视频免费观看www| 日韩免费av一区二区| 亚洲精品视频在线播放| 亚洲成人中文字幕| 黄色一区二区三区| 欧美自拍大量在线观看| 欧美黑人极品猛少妇色xxxxx| 欧美刺激性大交免费视频| 日韩精品免费在线| xvideos国产精品| 久久精品91久久香蕉加勒比|