5、字符設備驅動

5.1 Linux 字符設備驅動結構

5.1.1 cdev 結構體

  在 Linux 內核中,使用 cdev 結構體描述一個字符設備。node

  

 

   使用宏能夠從 dev_t 中獲取主設備號和次設備號,同時也能夠經過主設備號和次設備號生成 dev_tlinux

  

 

   cdev 的初始化:shell

  

  

 

   cdev_alloc:函數

  

 

   cdev_add:spa

  

 

   cdev_del:設計

  

 

5.1.2 分配和釋放設備號

  在使用 cdev_add 函數向系統註冊字符設備以前,應先調用函數來向系統申請設備號,完成此功能函數有兩個:3d

  

 

   

 

   在調用 cdev_del 以前,須要完成設備號的註銷,註銷函數以下:指針

  

5.1.3 file_operations 結構體

  此結構體是字符設備驅動程序設計的主體內容,此結構體的成員函數都對應着系統調用中 open,close 等函數。code

  

5.1.4 字符設備驅動的組成

  •  字符設備驅動由如下幾個部分組成:
    1. 字符設備驅動模塊加載和卸載函數
      • 加載函數中實現設備號的申請和 cdev 的註冊
      • 卸載函數中實現設備號的釋放和 cdev 的註銷
    2. 字符設備驅動的 file_operations 結構體的成員函數
      • 此結構中的成員函數是字符設備驅動和內核虛擬文件系統的接口,是用戶空間對 Linux 進行系統調用的最終落實者。
      • copy_from_user() 函數完成用戶空間緩衝區到內核空間的複製
      • copy_to_user() 函數完成內核空間到用戶空間緩衝區的複製
      • 上面兩個函數均返回不能被複制的字節數,若完成複製成功,返回0,複製失敗,返回負值
      • 若複製的內存是簡單類型,如 char,long,int 等,可以使用簡單的 put_user 和 get_user 函數
      • 內核空間雖然能夠訪問用戶空間的緩衝區,可是在訪問以前,通常須要先檢查其合法性,經過 access_ok(type, addr, size) 進行判斷,以肯定緩衝區的確屬於用戶空間。

  字符設備驅動的結構、字符設備驅動與字符設備以及字符設備驅動與用戶空間訪問該設備的程序之間的關係圖以下:blog

  

 5.2 globalmem 程序

  實現 globalmem 全局內存字符設備驅動。

  globalmem.c

  1 #include <linux/module.h>
  2 #include <linux/fs.h>
  3 #include <linux/init.h>
  4 #include <linux/cdev.h>
  5 #include <linux/slab.h>
  6 #include <linux/uaccess.h>
  7 
  8 
  9 #define GLOBALMEM_SIZE      0x1000
 10 //#define MEM_CLEAR           0X1
 11 #define GLOBALMEM_MAGIC     'g'
 12 #define MEM_CLEAR           _IO(GLOBALMEM_MAGIC, 0)
 13 #define GLOBALMEM_MAJOR     230
 14 #define DEVICE_NUMBER       10
 15 
 16 static int globalmem_major = GLOBALMEM_MAJOR;
 17 module_param(globalmem_major, int, S_IRUGO);
 18 
 19 struct globalmem_dev {
 20     struct cdev cdev;
 21     unsigned char mem[GLOBALMEM_SIZE];
 22 };
 23 
 24 struct globalmem_dev *globalmem_devp;
 25 
 26 /** 
 27  * 這裏涉及到私有數據的定義,大多數遵循將文件私有數據 pirvate_data 指向設備結構體,
 28  * 再用 read write llseek ioctl 等函數經過 private_data 訪問設備結構體。
 29  * 對於此驅動而言,私有數據的設置是在 open 函數中完成的
 30  */
 31 static int globalmem_open(struct inode *inode, struct file *filp)
 32 {
 33     /**
 34      *  NOTA: 
 35      *      container_of 的做用是經過結構體成員的指針找到對應結構體的指針。
 36      *      第一個參數是結構體成員的指針
 37      *      第二個參數是整個結構體的類型
 38      *      第三個參數爲傳入的第一個參數(即結構體成員)的類型
 39      *      container_of 返回值爲整個結構體指針
 40      */ 
 41     struct globalmem_dev *dev = container_of(inode->i_cdev, struct globalmem_dev, cdev);
 42     filp->private_data = dev;
 43     return 0;
 44 }
 45 
 46 static int globalmem_release(struct inode *inode, struct file *filp)
 47 {
 48     return 0;
 49 }
 50 
 51 static long globalmem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 52 {
 53     struct globalmem_dev *dev = filp->private_data;
 54 
 55     switch(cmd){
 56     case MEM_CLEAR:
 57         memset(dev->mem, 0, GLOBALMEM_SIZE);
 58         printk(KERN_INFO "globalmem is set to zero\n");
 59         break;
 60     default:
 61         return -EINVAL;
 62     }
 63 
 64     return 0;
 65 }
 66 
 67 static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
 68 {
 69     loff_t ret = 0;
 70     switch(orig) {
 71     case 0: /** 從文件開頭位置 seek */
 72         if(offset < 0){
 73             ret = -EINVAL;
 74             break;
 75         }
 76         if((unsigned int)offset > GLOBALMEM_SIZE){
 77             ret = -EINVAL;
 78             break;
 79         }
 80         filp->f_pos = (unsigned int)offset;
 81         ret = filp->f_pos;
 82         break;
 83     case 1: /** 從文件當前位置開始 seek */
 84         if((filp->f_pos + offset) > GLOBALMEM_SIZE){
 85             ret = -EINVAL;
 86             break;
 87         }
 88         if((filp->f_pos + offset) < 0){
 89             ret = -EINVAL;
 90             break;
 91         }
 92         filp->f_pos += offset;
 93         ret = filp->f_pos;
 94         break;
 95     default:
 96         ret = -EINVAL;
 97         break;
 98     }
 99 
100     return ret;
101 }
102 
103 static ssize_t globalmem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
104 {
105     unsigned long p = *ppos;
106     unsigned int count = size;
107     int ret = 0;
108     struct globalmem_dev *dev = filp->private_data;
109 
110     if(p >= GLOBALMEM_SIZE)
111         return 0;
112     if(count > GLOBALMEM_SIZE - p)
113         count = GLOBALMEM_SIZE - p;
114 
115     if(copy_from_user(dev->mem + p, buf, count))
116         ret = -EFAULT;
117     else {
118 
119         *ppos += count;
120         ret = count;
121         printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
122     }
123 
124     return ret;
125 }
126 
127 /**
128  * *ppos 是要讀的位置相對於文件開頭的偏移,若是該偏移大於或等於 GLOBALMEM_SIZE,意味着已經獨到文件末尾
129  */
130 static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
131 {
132     unsigned long p = *ppos;
133     unsigned int count = size;
134     int ret = 0;
135     struct globalmem_dev *dev = filp->private_data;
136 
137     if(p >= GLOBALMEM_SIZE)
138         return 0;
139     if(count > GLOBALMEM_SIZE - p)
140         count = GLOBALMEM_SIZE - p;
141 
142     if(copy_to_user(buf, dev->mem + p, count)) {
143         ret = -EFAULT;
144     } else {
145         *ppos += count;
146         ret = count;
147         printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
148     }
149 
150     return ret;
151 }
152 
153 static const struct file_operations globalmem_fops = {
154     .owner = THIS_MODULE,
155     .llseek = globalmem_llseek,
156     .read = globalmem_read,
157     .write = globalmem_write,
158     .unlocked_ioctl = globalmem_ioctl,
159     .open = globalmem_open,
160     .release = globalmem_release,
161 };
162 
163 
164 /**
165  * @brief  globalmem_setup_cdev     
166  *
167  * @param  dev
168  * @param  index    次設備號
169  */
170 static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
171 {
172     int err;
173     int devno = MKDEV(globalmem_major, index);
174 
175     /** 使用 cdev_init 便是靜態初始化了 cdev */
176     cdev_init(&dev->cdev, &globalmem_fops);
177     dev->cdev.owner = THIS_MODULE;
178 
179     /** 設備編號範圍設置爲1,表示咱們只申請了一個設備 */
180     err = cdev_add(&dev->cdev, devno, 1);
181     if(err)
182         printk(KERN_NOTICE "Error %d adding globalmem%d\n", err, index);
183 }
184 
185 static int __init globalmem_init(void)
186 {
187     int ret;
188     int i;
189     dev_t devno = MKDEV(globalmem_major, 0);
190 
191     if(globalmem_major)
192         ret = register_chrdev_region(devno, DEVICE_NUMBER, "globalmem");
193     else {
194         ret = alloc_chrdev_region(&devno, 0, DEVICE_NUMBER, "gobalmem");
195         globalmem_major = MAJOR(devno);
196     }
197 
198     if(ret < 0)
199         return ret;
200 
201     globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
202     if(!globalmem_devp){
203         ret = -ENOMEM;
204         goto fail_malloc;
205     }
206 
207     for(i = 0; i < DEVICE_NUMBER; i++){
208         globalmem_setup_cdev(globalmem_devp + i, i);
209     }
210 
211 fail_malloc:
212     unregister_chrdev_region(devno, 1);
213     return ret;
214 }
215 
216 static void __exit globalmem_exit(void)
217 {
218     int i;
219     for(i = 0; i < DEVICE_NUMBER; i++) {
220         cdev_del(&(globalmem_devp + i)->cdev);
221     }
222     kfree(globalmem_devp);
223     unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
224 }
225 
226 module_init(globalmem_init);
227 module_exit(globalmem_exit);

  Makefile

 1 .PHONY:all clean
 2 ifneq ($(KERNELRELEASE),)
 3 
 4 obj-m := globalmem.o
 5 
 6 else
 7 LINUX_KERNEL := $(shell uname -r)
 8 LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)
 9 CURRENT_PATH  := $(shell pwd)
10 EXTRA_CFLAGS += -DDEBUG
11 
12 all:
13     make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
14 clean:
15     rm -fr *.ko *.o *.mod.o *.mod.c *.symvers *.order .*.ko .tmp_versions
16 
17 endif

   驗證:

  加載

  

  cat /proc/devices

  

 

   建立節點:

  

 

   

 

   

 

   在建立了一個節點以後,一樣能夠再建立另一個節點,進行讀寫。

  刪除模塊:

  

 

   刪除模塊後,/sys/module 下的 globalmem 文件夾會自動刪除,可是再 /dev/ 下建立的 globalmem 節點須要使用 rm 命令手動刪除,或者也能夠將刪除命令寫入 Makefile 中,make clean 的時候自動去刪除。

相關文章
相關標籤/搜索