五、字符设备驱动
5.1 Linux 字符设备驱动结构
5.1.1 cdev 结构体
在 Linux 内核中,使用 cdev 结构体描述一个字符设备。
使用宏可以从 dev_t 中获取主设备号和次设备号,同时也可以通过主设备号和次设备号生成 dev_t
cdev 的初始化:
cdev_alloc:
cdev_add:
cdev_del:
5.1.2 分配和释放设备号
在使用 cdev_add 函数向系统注册字符设备之前,应先调用函数来向系统申请设备号,完成此功能函数有两个:
在调用 cdev_del 之前,需要完成设备号的注销,注销函数如下:
5.1.3 file_operations 结构体
此结构体是字符设备驱动程序设计的主体内容,此结构体的成员函数都对应着系统调用中 open,close 等函数。
5.1.4 字符设备驱动的组成
- 字符设备驱动由以下几个部分组成:
- 字符设备驱动模块加载和卸载函数
- 加载函数中实现设备号的申请和 cdev 的注册
- 卸载函数中实现设备号的释放和 cdev 的注销
- 字符设备驱动的 file_operations 结构体的成员函数
- 此结构中的成员函数是字符设备驱动和内核虚拟文件系统的接口,是用户空间对 Linux 进行系统调用的最终落实者。
- copy_from_user() 函数完成用户空间缓冲区到内核空间的复制
- copy_to_user() 函数完成内核空间到用户空间缓冲区的复制
- 上面两个函数均返回不能被复制的字节数,若完成复制成功,返回0,复制失败,返回负值
- 若复制的内存是简单类型,如 char,long,int 等,可使用简单的 put_user 和 get_user 函数
- 内核空间虽然可以访问用户空间的缓冲区,但是在访问之前,一般需要先检查其合法性,通过 access_ok(type, addr, size) 进行判断,以确定缓冲区的确属于用户空间。
- 字符设备驱动模块加载和卸载函数
字符设备驱动的结构、字符设备驱动与字符设备以及字符设备驱动与用户空间访问该设备的程序之间的关系图如下:
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 的时候自动去删除。