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

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

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

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

一、字符設備基礎知識

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
福利一区福利二区微拍刺激| 97视频com| 中文字幕精品一区二区精品| 欧美视频专区一二在线观看| 91午夜在线播放| 久久久视频在线| 亚洲成年人在线播放| 亚洲久久久久久久久久| 日韩精品免费在线播放| 国产热re99久久6国产精品| 成人黄色av网站| 日韩亚洲国产中文字幕| 久久综合国产精品台湾中文娱乐网| 欧美色xxxx| 欧美日韩亚洲天堂| 国产欧美日韩专区发布| 91精品国产九九九久久久亚洲| 日韩中文字幕免费看| 亚洲第一福利网站| 午夜精品久久久久久久久久久久久| 国产精品免费视频久久久| 夜夜狂射影院欧美极品| 亚洲欧美国产一本综合首页| 成人激情视频小说免费下载| 欧美性高跟鞋xxxxhd| 久久久精品电影| 一区二区三区动漫| 91在线免费视频| 91高清免费在线观看| 日本老师69xxx| 韩剧1988免费观看全集| 亚洲国产日韩欧美在线图片| 久久精品国产亚洲7777| 热久久99这里有精品| 国产综合香蕉五月婷在线| 一区二区三区无码高清视频| 在线视频中文亚洲| 91色琪琪电影亚洲精品久久| 日韩中文字幕视频在线| 性欧美在线看片a免费观看| 久久久久久久电影一区| 综合136福利视频在线| 亚洲欧美国产精品久久久久久久| 深夜福利亚洲导航| 欧美最顶级丰满的aⅴ艳星| 中文字幕亚洲精品| 国产精品露脸av在线| 日韩在线精品一区| 国语自产精品视频在免费| 国内精品美女av在线播放| 欧美黑人狂野猛交老妇| 国内精品视频一区| 51午夜精品视频| 亚洲美女av在线| 国产精品偷伦视频免费观看国产| 欧美日韩中文在线| 国产精品视频一区二区高潮| 亚洲综合在线做性| 亚洲天堂av在线免费观看| 日本一区二区三区四区视频| 91精品中国老女人| 国产视频亚洲视频| 久久最新资源网| 欧美日韩精品在线| www.国产精品一二区| 国产噜噜噜噜久久久久久久久| 美日韩精品免费观看视频| 亚洲第一精品福利| 97婷婷大伊香蕉精品视频| 一区二区三区久久精品| 亚洲女人天堂色在线7777| 色在人av网站天堂精品| 亚洲乱码一区av黑人高潮| 亚洲成人av在线播放| 亚洲石原莉奈一区二区在线观看| 国产一区二区成人| 在线视频日韩精品| 国产精品久久久久久久久久三级| 亚洲成人精品视频| 欧美在线视频网| 欧美精品在线极品| 精品国产成人在线| 亚洲黄色www网站| 日韩h在线观看| 成人激情在线播放| 欧美高清在线视频观看不卡| 久久综合久中文字幕青草| 欧美精品电影免费在线观看| 亚洲摸下面视频| 国产亚洲视频中文字幕视频| 欧美久久精品午夜青青大伊人| 日本不卡免费高清视频| 欧美日韩国产123| 国产精品女人久久久久久| 国产精品久久一区主播| 欧美xxxwww| 久久久av亚洲男天堂| 国产男女猛烈无遮挡91| 亚洲精品av在线播放| 中文字幕亚洲欧美日韩2019| 色偷偷av一区二区三区| 亚洲成人动漫在线播放| 国产在线视频不卡| 成人免费网站在线看| 最近中文字幕mv在线一区二区三区四区| 久久影院在线观看| 欧美综合国产精品久久丁香| 日韩中文视频免费在线观看| 中文字幕亚洲欧美一区二区三区| 4438全国亚洲精品在线观看视频| 色吧影院999| 久久久久九九九九| 国产精品自产拍在线观看| 日韩久久精品成人| 国产精品白丝av嫩草影院| 国产精品99久久久久久www| 成人黄色影片在线| 欧美床上激情在线观看| 色偷偷噜噜噜亚洲男人| 国产精品第3页| 亚洲a一级视频| 欧美亚州一区二区三区| 97热在线精品视频在线观看| 欧美日韩在线免费| 亚洲国产成人精品电影| 国产精品视频一区二区高潮| 国产精品美女呻吟| 国产精品都在这里| 日韩不卡中文字幕| 国产精品一区二区性色av| 日韩中文视频免费在线观看| 亚洲精品国产欧美| 最近的2019中文字幕免费一页| 欧美激情精品久久久久| 在线观看成人黄色| 久久男人资源视频| 亚洲一区二区在线播放| 亚洲欧洲成视频免费观看| 日韩成人在线观看| 久久夜色精品亚洲噜噜国产mv| 色阁综合伊人av| 欧美一区二区大胆人体摄影专业网站| 欧美国产日产韩国视频| 久久久亚洲国产| 欧美电影免费观看高清完整| 欧美性猛交视频| 成人日韩在线电影| 亚洲欧美第一页| 欧美激情第1页| 久久久91精品| 日韩电影免费在线观看中文字幕| 久久99热精品这里久久精品| 久久久久这里只有精品| 久久久久久久999精品视频| 91高潮在线观看| 久久躁狠狠躁夜夜爽| 亚洲欧洲偷拍精品| 亚洲精品电影久久久| 亚洲美女免费精品视频在线观看| 欧美电影在线观看| 国产一区视频在线播放| 欧美裸体男粗大视频在线观看| 欧美激情欧美激情| 2019日本中文字幕|