Linux內核驅動學習(三)字符型設備驅動之初體驗

      網友投稿 867 2022-05-29

      linux字符型設備驅動之初體驗

      文章目錄

      Linux字符型設備驅動之初體驗

      前言

      框架

      字符型設備

      程序實現

      cdev

      kobj

      owner

      file_operations

      dev_t

      設備注冊過程

      申請設備號

      注冊設備

      register_device

      如何構建

      模塊編譯

      內核編譯

      Makefile

      Kconfig

      總結

      參考

      前言

      驅動總共分為字符型設備驅動,塊設備驅動,網絡設備驅動。對于字符型設備驅動的資料,網上比較多,《Linux Kernel Driver》這本書可以了解一下,對于學習Linux驅動有很大的幫助,當然還有很多優秀的書籍,暫不一一列舉,本文簡單總結了在學習字符型設備驅動的過程中遇到的問題,以及對該類驅動的理解。

      框架

      字符型設備

      什么是字符型設備?字符型以字符(Byte/Char)為單位進行數據傳輸的設備,如鍵盤,串口等等設備,所以Linux環境編程中文件I/O進行操作的系統接口如open,read,write,close等等,在字符型設備驅動中同樣需要支持這些接口。這里會用到file_operations結構體,在后面會講到。

      程序實現

      下面是一個簡單字符型設備驅動程序,可以在系統注冊一個字符型設備驅動,目前未實現open,read,write,close等接口。

      #include #include #include #include #include #define DRIVER_DATA_SIZE 4096 static int major_dev_index = 0; struct cnc_character_st{ struct cdev device; u8 data[DRIVER_DATA_SIZE]; }; static struct cnc_character_st *character_dev; //TODO static ssize_t cnc_character_read (struct file * fd, char __user * data, size_t len, loff_t * offset){ ssize_t ret = 0; printk("%s call\n",__func__); return ret; } //TODO static ssize_t cnc_character_write (struct file * fd, const char __user * data, size_t len, loff_t * offset){ ssize_t ret = 0; return ret; } //TODO static long cnc_character_unlocked_ioctl (struct file * fd, unsigned int data, unsigned long cmd){ long ret = 0; return ret; } //TODO static int cnc_character_open (struct inode * node, struct file * fd){ int ret = 0; return ret; } //TODO static int cnc_character_release (struct inode * node, struct file * fd){ int ret = 0; return ret; } static const struct file_operations cnc_character_ops = { .owner = THIS_MODULE, .read = cnc_character_read, .write = cnc_character_write, .open = cnc_character_open, .unlocked_ioctl = cnc_character_unlocked_ioctl, .release = cnc_character_release, }; static int register_device(struct cnc_character_st *mdev,int major_dev_index,int minor_dev_index){ int ret = 0; int dev_no = MKDEV(major_dev_index, minor_dev_index); // 初始化dev cdev_init(&mdev->device, &cnc_character_ops); mdev->device.owner = THIS_MODULE; ret = cdev_add(&mdev->device,dev_no,1); if(ret){ printk(KERN_ERR "cdev add device failed\n"); } return ret; } static int unregister_device(struct cnc_character_st *mdev){ int ret= 0; kfree(character_dev); return ret; } static int __init cnc_character_init(void){ int ret = 0; dev_t devno = MKDEV(major_dev_index, 0); if(major_dev_index){ ret = register_chrdev_region(devno, 1, "cnc_character"); }else{ ret = alloc_chrdev_region(&devno, 0, 1, "cnc_character"); major_dev_index = MAJOR(devno); } if(ret < 0){ return ret; } character_dev = kmalloc(sizeof(struct cnc_character_st),GFP_KERNEL); if(!character_dev){ printk("%s failed malloc character_dev call\n",__func__); ret = -ENOMEM; goto failed; }else{ printk("%s success malloc character_dev call\n",__func__); } register_device(character_dev,major_dev_index,0); return 0; failed: unregister_chrdev_region(devno, 1); return ret; } module_init(cnc_character_init); static void __exit cnc_character_exit(void){ printk("%s call\n",__func__); unregister_device(character_dev); } module_exit(cnc_character_exit); MODULE_AUTHOR("zhaojunhui@cncgroup.top"); MODULE_VERSION("1.0"); MODULE_LICENSE("GPL");

      1

      2

      3

      4

      5

      6

      Linux內核驅動學習(三)字符型設備驅動之初體驗

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      52

      53

      54

      55

      56

      57

      58

      59

      60

      61

      62

      63

      64

      65

      66

      67

      68

      69

      70

      71

      72

      73

      74

      75

      76

      77

      78

      79

      80

      81

      82

      83

      84

      85

      86

      87

      88

      89

      90

      91

      92

      93

      94

      95

      96

      97

      98

      99

      100

      101

      102

      103

      104

      105

      106

      107

      108

      109

      110

      111

      112

      113

      114

      115

      116

      117

      cdev

      在linux/cdev.h中可以閱讀相關字符型設備驅動的信息,其中包括cdev結構體可以做一下分析,先定位到源碼做一下分析

      #ifndef _LINUX_CDEV_H #define _LINUX_CDEV_H #include #include #include struct file_operations; struct inode; struct module; struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; }; void cdev_init(struct cdev *, const struct file_operations *); struct cdev *cdev_alloc(void); void cdev_put(struct cdev *p); int cdev_add(struct cdev *, dev_t, unsigned); void cdev_del(struct cdev *); void cd_forget(struct inode *); #endif

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      其中包括結構體cdev和cdev的一系列函數接口cdev_init,cdev_alloc,cdev_put,cdev_add,cdev_del,cd_forget。

      kobject是所有設備驅動模型的基類,而cdev可以理解為是它的派生類,這里使用了面向對象的思想,通過訪問cdev中的kobj成員,就能使用kobject中所有功能。關于kobject的詳細內容可以參考內核文檔Documentation/kobject.txt。

      首先明確一點的是owner是struct module的指針變量,owner=THIS_MODULE;,這里將指針指向當前的模塊,關于THIS_MODULE以及struct module的知識可以參考這篇博客。

      這個結構體位于/linux/include/fs.h,代碼如下。

      struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif };

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      在file_operations定義了很多I/O操作接口,這里同樣使用了面向對象編程的思想,每個接口可以在重新定義file_operations結構體變量的時候,重新賦于自定義功能的函數,如下,可以理解read,write,open,unlocked_ioctl,release是對抽象函數的實現。

      static const struct file_operations cnc_character_ops = { .owner = THIS_MODULE, .read = cnc_character_read, .write = cnc_character_write, .open = cnc_character_open, .unlocked_ioctl = cnc_character_unlocked_ioctl, .release = cnc_character_release, };

      1

      2

      3

      4

      5

      6

      7

      8

      設備注冊過程

      設備的初始化在函數cnc_character_init中完成具體的功能實現,主要分為兩個部分,設備號的申請和設備的注冊。其中設備注冊單獨封裝到register_device函數中。

      dev_t devno = MKDEV(major_dev_index, 0); if(major_dev_index){ ret = register_chrdev_region(devno, 1, "cnc_character"); }else{ ret = alloc_chrdev_region(&devno, 0, 1, "cnc_character"); major_dev_index = MAJOR(devno); }

      1

      2

      3

      4

      5

      6

      7

      8

      character_dev = kmalloc(sizeof(struct cnc_character_st),GFP_KERNEL); if(!character_dev){ printk("%s failed malloc character_dev call\n",__func__); ret = -ENOMEM; goto failed; }else{ printk("%s success malloc character_dev call\n",__func__); } register_device(character_dev,major_dev_index,0);

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      在register_device中,主要用到了cdev提供的函數接口。

      cdev_init初始化一個字符型設備并傳入自定義的file_operations類型變量cnc_character_ops。

      cdev_add將初始化的字符型設備添加到內核,并分配已經申請好的設備號。

      static int register_device(struct cnc_character_st *mdev,int major_dev_index,int minor_dev_index){ int ret = 0; int dev_no = MKDEV(major_dev_index, minor_dev_index); // 初始化dev cdev_init(&mdev->device, &cnc_character_ops); mdev->device.owner = THIS_MODULE; ret = cdev_add(&mdev->device,dev_no,1); if(ret){ printk(KERN_ERR "cdev add device failed\n"); } return ret; }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      如何構建

      模塊編譯

      使用這個Makefile

      KVERS = $(shell uname -r) # Kernel modules obj-m += demo_character.o # Specify flags for the module compilation. EXTRA_CFLAGS=-g -O0 build: kernel_modules kernel_modules: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules clean: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      內核編譯

      obj-$(CONFIG_DEMO_CHARACTER_DRIVER) +=demo_character.o

      1

      menuconfig DEMO_DRIVERS tristate "demo drivers" config DEMO_CHARACTER_DRIVER tristate "the most simplest character driver" help character driver endif

      1

      2

      3

      4

      5

      6

      7

      總結

      總體上來說,字符型設備驅動框架還是相對簡單的,通過這次學習加深了對cdev的認識和linux內核源碼中面向對象的設計思想,但是這里還沒有對devfs和sysfs做相應的介紹,后面繼續學習這兩者的區別以及總線驅動模型,總之,加油吧。

      參考

      https://blog.csdn.net/lucky_liuxiang/article/details/83413946

      https://www.cnblogs.com/helloahui/p/3677192.html

      https://blog.csdn.net/jk110333/article/details/8563647

      Linux

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:ElastricSearch第二彈之分片原理
      下一篇:物聯網設備天線設計與選型指南
      相關文章
      亚洲精品乱码久久久久久蜜桃不卡| 亚洲高清成人一区二区三区| 亚洲日本乱码在线观看| 亚洲国产中文字幕在线观看 | 午夜在线a亚洲v天堂网2019| 91亚洲性爱在线视频| 91亚洲性爱在线视频| 亚洲日韩国产精品无码av| 久久综合亚洲色一区二区三区 | 亚洲av日韩精品久久久久久a| 亚洲欧美日韩中文二区| 亚洲aⅴ无码专区在线观看| 亚洲av无码av在线播放| 99亚洲精品卡2卡三卡4卡2卡| 亚洲av第一网站久章草| 九月婷婷亚洲综合在线 | 亚洲综合激情九月婷婷| 亚洲福利秒拍一区二区| 亚洲成AV人片久久| 亚洲天堂2017无码中文| 一本色道久久88亚洲精品综合| 亚洲欧美日韩综合久久久| 亚洲爆乳成av人在线视菜奈实| 成人婷婷网色偷偷亚洲男人的天堂| yy6080亚洲一级理论| 久久久久久亚洲精品不卡| 九月丁香婷婷亚洲综合色| 亚洲精品自产拍在线观看动漫| 亚洲视频在线播放| 亚洲日本视频在线观看| 亚洲 欧洲 自拍 另类 校园| 亚洲性色精品一区二区在线| 青青青亚洲精品国产| 亚洲视频在线免费| 亚洲精品乱码久久久久久自慰| 亚洲第一精品福利| 亚洲人和日本人jizz| 亚洲AV色欲色欲WWW| MM131亚洲国产美女久久| 亚洲成色在线综合网站| 337p欧洲亚洲大胆艺术|