在 Linux 內核中,使用 cdev 結構體描述一個字符設備。node
使用宏能夠從 dev_t 中獲取主設備號和次設備號,同時也能夠經過主設備號和次設備號生成 dev_tlinux
cdev 的初始化:shell
cdev_alloc:函數
cdev_add:spa
cdev_del:設計
在使用 cdev_add 函數向系統註冊字符設備以前,應先調用函數來向系統申請設備號,完成此功能函數有兩個:3d
在調用 cdev_del 以前,須要完成設備號的註銷,註銷函數以下:指針
此結構體是字符設備驅動程序設計的主體內容,此結構體的成員函數都對應着系統調用中 open,close 等函數。code
字符設備驅動的結構、字符設備驅動與字符設備以及字符設備驅動與用戶空間訪問該設備的程序之間的關係圖以下:blog
實現 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 的時候自動去刪除。