20150518 字符设备驱动
2015-05-18 Lover雪儿
经过这两个月的学习,相信对设备驱动的编写已经有一个大概的了解了,温故而知新,此处我们再一次的系统性的复习一下字符设备驱动,然后,我们来尝试着自己从零实战写一个AD采集的字符设备驱动.
以前学习使用的是老方法来注册字符设备驱动,此处我们使用字符设备的新方法来学习.
本文参考:华清远见的Linux 设备驱动开发详解-字符设备驱动,具体还请看作者原书
一.cdev结构体
1 struct cdev{ 2 struct kobject kobj; /* 内嵌的 kobject 对象 */ 3 struct module *owner; 4 /*所属模块*/ 5 struct file_operations *ops; /*文件操作结构体*/ 6 struct list_head list; 7 dev_t dev; /*设备号*/ 8 unsigned int count; 9 };
cdev结构体的dev_t成员定义了设备号,为32位,其中高12位为主设备号,低20位为次设备号.
使用MAJOR(dev_t dev)获得主设备号和MINOR(dev_t dev)获得次设备号;
而使用MKDEV(int major, int minor)又可以生成dev_t.
cdev 结构体的另一个重要成员 file_operations 定义了字符设备驱动提供给虚拟文件系统的接口函数。
Linux 2.6 内核提供了一组函数用于操作 cdev 结构体,如下所示:
1 void cdev_init(struct cdev *, struct file_operations *); 2 //初始化 cdev 的成员,并建立 cdev 和 file_operations 之间的连接 3 struct cdev *cdev_alloc(void); 4 //用于动态申请一个 cdev 内存 5 void cdev_put(struct cdev *p); 6 int cdev_add(struct cdev *, dev_t, unsigned); 7 void cdev_del(struct cdev *); 8 //cdev_add()函数和 cdev_del()函数分别向系统添加和删除一个 cdev, 完成字符设备的注册和注销
二.分配和释放设备号
在调用cdev_add()函数向系统注册字符设备之前,应首先调用register_chrdev_region()或者alloc_chrdev_region()函数向系统申请设备号.
相反,在调用cdev_del()函数从系统注销字符设备之后,unregister_chrdev_region()应该被调用以释放原先申请的设备号
int register_chrdev_region(dev_t from, unsigned count, const char*name); //用已知起始设备的设备号 int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);//用于设备号未知 void unregister_chrdev_region(dev_t from, unsigned count); //卸载
三.file_operations 结构体
file_operations 结构体中的成员函数是字符设备驱动程序设计的主体内容, 这些函数实际会在应用程序进行 Linux 的 open()、write()、read()、close()等系统调用时最终被调用.
struct file_operations{ struct module *owner; // 拥有该结构的模块的指针,一般为 THIS_MODULES loff_t(*llseek)(struct file *, loff_t, int); // 用来修改文件当前的读写位置,并将新位置返回,在出错时,这个函数返回一个负值。 ssize_t(*read)(struct file *, char _ _user *, size_t, loff_t*); // 从设备中同步读取数据,成功时函数返回读取的字节数,出错时返回一个负值。 ssize_t(*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t); // 初始化一个异步的读取操作,设备实现这两个函数后,用户空间可以对该设备文件描述符调用aio_read()、aio_write()等系统调用进行读写。 ssize_t(*write)(struct file *, const char _ _user *, size_t,loff_t*); // 向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被实现,当用户进行 write()系统调用时,将得到-EINVAL 返回值。 ssize_t(*aio_write)(struct kiocb *, const char _ _user *, size_t,loff_t); // 初始化一个异步的写入操作 int(*readdir)(struct file *, void *, filldir_t); // 仅用于读取目录,对于设备文件,该字段为 NULL,设备节点不需要实现它。 unsigned int(*poll)(struct file *, struct poll_table_struct*); // 轮询函数,判断目前是否可以进行非阻塞的读取或写入,当询问的条件未触发时,用户空间进行 select()和 poll()系统调用将引起进程的阻塞。 int(*ioctl)(struct inode *, struct file *, unsigned int, unsignedlong); // 执行设备 I/O 控制命令,当调用成功时,返回给调用程序一个非负值 long(*unlocked_ioctl)(struct file *, unsigned int, unsigned long); // 不使用 BLK 文件系统,将使用此种函数指针代替 ioctl long(*compat_ioctl)(struct file *, unsigned int, unsigned long); // 在 64 位系统上,32 位的 ioctl 调用将使用此函数指针代替 int(*mmap)(struct file *, struct vm_area_struct*); // 用于请求将设备内存映射到进程地址空间,如果设备驱动未实现此函数,用户进行 mmap()系统调用时将获得-ENODEV 返回值 int(*open)(struct inode *, struct file*); // 打开,驱动程序可以不实现这个函数,在这种情况下,设备的打开操作永远成功。 int(*flush)(struct file*); int(*release)(struct inode *, struct file*); // 关闭 int(*synch)(struct file *, struct dentry *, int datasync); // 刷新待处理的数据 int(*aio_fsync)(struct kiocb *, int datasync); // 异步 fsync int(*fasync)(int, struct file *, int); // 通知设备 FASYNC 标志发生变化 int(*lock)(struct file *, int, struct file_lock*); ssize_t(*readv)(struct file *, const struct iovec *, unsigned long,loff_t*); ssize_t(*writev)(struct file *, const struct iovec *, unsigned long,loff_t*); // readv 和 writev:分散/聚集型的读写操作 ssize_t(*sendfile)(struct file *, loff_t *, size_t, read_actor_t,void*); // 通常为 NULL ssize_t(*sendpage)(struct file *, struct page *, int, size_t,loff_t *, int); // 通常为 NULL unsigned long(*get_unmapped_area)(struct file *,unsigned long,unsigned long,unsigned long, unsigned long); // 在进程地址空间找到一个将底层设备中的内存段映射的位置 int(*check_flags)(int); // 允许模块检查传递给 fcntl(F_SETEL...)调用的标志 int(*dir_notify)(struct file *filp, unsigned long arg); // 仅对文件系统有效,驱动程序不必实现 int(*flock)(struct file *, int, struct file_lock*); };
四.字符设备组成
1.系统加载与卸载函数
1 //设备结构体 2 struct xxx_dev_t{ 3 struct cdev cdev; 4 ... 5 } xxx_dev; 6 //设备驱动模块加载函数 7 static int _ _init xxx_init(void){ 8 ... 9 cdev_init(&xxx_dev.cdev, &xxx_fops); //初始化 cdev 10 xxx_dev.cdev.owner = THIS_MODULE; 11 //获取字符设备号 12 if (xxx_major){ 13 register_chrdev_region(xxx_dev_no, 1, DEV_NAME); 14 }else{ 15 alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME); 16 } 17 ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); //注册设备 18 … 19 } 20 /*设备驱动模块卸载函数*/ 21 static void _ _exit xxx_exit(void){ 22 unregister_chrdev_region(xxx_dev_no, 1); //释放占用的设备号 23 cdev_del(&xxx_dev.cdev); //注销设备 24 ... 25 }
2.file_operations结构体
1 /* 读设备*/ 2 ssize_t xxx_read(struct file *filp, char _ _user *buf, size_t count,loff_t*f_pos){ 3 ... 4 copy_to_user(buf, ..., ...); 5 ... 6 } 7 /* 写设备*/ 8 ssize_t xxx_write(struct file *filp, const char _ _user *buf, size_tloff_t *f_pos){ 9 ... 10 copy_from_user(..., buf, ...); 11 ... 12 } 13 /* ioctl 函数 */ 14 int xxx_ioctl(struct inode *inode, struct file *filp, unsigned intunsigned long arg){ 15 ... 16 switch (cmd){ 17 case XXX_CMD1: … break; 18 case XXX_CMD2: … break; 19 default:/* 不能支持的命令 */ return - ENOTTY; 20 } 21 return 0; 22 } 23 struct file_operations xxx_fops ={ 24 .owner = THIS_MODULE, 25 .read = xxx_read, 26 .write = xxx_write, 27 .ioctl = xxx_ioctl, 28 ... 29 }; 30 31 //由于内核空间与用户空间的内存不能直接互访,因此借助函数 copy_from_user()完成用户空间到内核空间的复制 32 unsigned long copy_from_user(void *to, const void _ _user *from, unsignedlong count); 33 unsigned long copy_to_user(void _ _user *to, const void *from, unsignedlong count); 34 //读和写函数中的_ _user 是一个宏,表明其后的指针指向用户空间 35 //上述函数均返回不能被复制的字节数,因此,如果完全复制成功,返回值为 0。 36 //如果要复制的内存是简单类型, 如 char、 int、 long 等, 则可以使用简单的 put_user()和 get_user(),如下所示: 37 int val; //内核空间整型变量 38 ... 39 get_user(val, (int *) arg); //用户空间到内核空间,arg 是用户空间的地址 40 ... 41 put_user(val, (int *) arg); //内核空间到用户空间,arg 是用户空间的地址
五.globalmem虚拟设备实例描述
1.ioctl()命令
linux系统建议以下表格所示的方式定义ioctl()命令:
设备类型 |
序列号 |
方向 |
数据尺寸 |
8bit |
8bit |
2bit |
13/14bit |
命令码的设备类型字段为一个“幻数” ,可以是 0~0xff 之间的值,内核中的ioctl-number.txt 给出了一些推荐的和已经被使用的“幻数”,新设备驱动定义“幻数”的时候要避免与其冲突.
命令码的方向字段为 2 位,该字段表示数据传送的方向, 可能的值是_IOC_NONE(无数据传输)、_IOC_READ(读)、_IOC_WRITE(写)和_IOC_READ|_IOC_WRITE(双向)。数据传送的方向是从应用程序的角度来看的。
命令码的数据长度字段表示涉及的用户数据的大小,这个成员的宽度依赖于体系结构,通常是 13 位或者 14 位。
内核还定义了_IO()、_IOR()、_IOW()和_IOWR()这 4 个宏来辅助生成命令
1 #define _IO(type,nr) 2 #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),\ 3 (_IOC_TYPECHECK(size))) 4 #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),\ 5 (_IOC_TYPECHECK(size))) 6 #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr), \ 7 (_IOC_TYPECHECK(size))) 8 /*_IO、 _IOR 等使用的 _IOC 宏*/ 9 #define _IOC(dir,type,nr,size) \ 10 (((dir) << _IOC_DIRSHIFT) | \ 11 ((type) << _IOC_TYPESHIFT) | \ 12 ((nr) << _IOC_NRSHIFT) | \ 13 ((size) << _IOC_SIZESHIFT)) 14 //这几个宏的作用是根据传入的 type (设备类型字段)、 nr(序列号字段)和 size(数据长度字段)和宏名隐含的方向字段移位组合生成命令码 15 16 //由于 globalmem 的 MEM_CLEAR 命令不涉及数据传输,因此它可以定义如下: 17 #define GLOBALMEM_MAGIC ... 18 #define MEM_CLEAR _IO(GLOBALMEM_MAGIC,0)
2.预定义命令
内核中预定义了一些 I/O 控制命令,如果某设备驱动中包含了与预定义命令一样的命令,这些命令会被当作预定义命令被内核处理而不是被设备驱动处理,预定义命令有如下 4 种。
①FIOCLEX:即 File IOctl Close on Exec,对文件设置专用标志,通知内核当exec()系统调用发生时自动关闭打开的文件。
②FIONCLEX:即 File IOctl Not CLose on Exec,与 FIOCLEX 标志相反,清除由 FIOCLEX 命令设置的标志。
③FIOQSIZE:获得一个文件或者目录的大小,当用于设备文件时,返回一个ENOTTY 错误。
④FIONBIO:即 File IOctl Non-Blocking I/O,这个调用修改在 filp->f_flags 中的 O_NONBLOCK 标志。FIOCLEX、FIONCLEX、FIOQSIZE 和 FIONBIO 这些宏的定义如下:
#define FIONCLEX 0x5450 #define FIOCLEX 0x5451 #define FIOQSIZE 0x5460 #define FIONBIO 0x5421 由以上定义可以看出,FIOCLEX、FIONCLEX、FIOQSIZE 和 FIONBIO 的幻数为“T”。
3.globalmem代码:
1 #include <linux/module.h> 2 #include <linux/types.h> 3 #include <linux/fs.h> 4 #include <linux/kernel.h> 5 #include <linux/device.h> 6 #include <linux/errno.h> 7 #include <linux/mm.h> 8 #include <linux/sched.h> 9 #include <linux/init.h> 10 #include <linux/cdev.h> 11 #include <asm/io.h> 12 #include <asm/system.h> 13 #include <asm/uaccess.h> 14 15 #define GLOBALMEM_SIZE 0x1000 /*全局内存大小:4KB*/ 16 #define MEM_CLEAR 0x1 /*清零全局内存*/ 17 #define GLOBALMEM_MAJOR 0 /*预设的 globalmem 的主设备号*/ 18 19 20 static int globalmem_major = GLOBALMEM_MAJOR; 21 /*globalmem 设备结构体*/ 22 struct globalmem_dev 23 { 24 struct cdev cdev; /*cdev 结构体*/ 25 unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/ 26 }; 27 28 struct globalmem_dev *globalmem_devp; /*设备结构体实例*/ 29 30 //自动添加设备节点 31 static struct class *cls = NULL; 32 33 34 /*文件打开函数*/ 35 static int globalmem_open(struct inode *inode, struct file *filp){ 36 /*将设备结构体指针赋值给文件私有数据指针*/ 37 filp->private_data = globalmem_devp; 38 return 0; 39 } 40 //释放函数 41 static int globalmem_release(struct inode *inode, struct file *filp){ 42 return 0; 43 } 44 //文件读函数 45 static ssize_t globalmem_read(struct file *filp, char __user *buf,size_t count,loff_t *ppos) 46 { 47 unsigned long p = *ppos; 48 int ret = 0; 49 struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/ 50 /*分析和获取有效的读长度*/ 51 if (p >= GLOBALMEM_SIZE) //要读的偏移位置越界 52 return count ? - ENXIO: 0; 53 if (count > GLOBALMEM_SIZE - p)//要读的字节数太大 54 count = GLOBALMEM_SIZE - p; 55 /*内核空间→用户空间*/ 56 if (copy_to_user(buf, (void*)(dev->mem + p), count)) 57 { 58 ret = - EFAULT; 59 }else{ 60 *ppos += count; 61 ret = count; 62 printk(KERN_INFO "read %d bytes(s) from %ld\n", count, p); 63 } 64 return ret; 65 } 66 //文件写函数 67 static ssize_t globalmem_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos) 68 { 69 unsigned long p = *ppos; 70 int ret = 0; 71 struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/ 72 /*分析和获取有效的写长度*/ 73 if (p >= GLOBALMEM_SIZE) //要写的偏移位置越界 74 return count ? - ENXIO: 0; 75 if (count > GLOBALMEM_SIZE - p) //要写的字节数太多 76 count = GLOBALMEM_SIZE - p; 77 78 /*用户空间→内核空间*/ 79 if (copy_from_user(dev->mem + p, buf, count)){ 80 ret = - EFAULT; 81 }else{ 82 *ppos += count; 83 ret = count; 84 printk(KERN_INFO "written %d bytes(s) from %ld\n", count, p); 85 } 86 return ret; 87 } 88 //文件定位函数 89 static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig) 90 { 91 loff_t ret; 92 switch (orig){ 93 case 0: /*从文件开头开始偏移*/ 94 if (offset < 0){ 95 ret = - EINVAL; 96 break; 97 } 98 if ((unsigned int)offset > GLOBALMEM_SIZE) //偏移越界 99 { 100 ret = - EINVAL; 101 break; 102 } 103 filp->f_pos = (unsigned int)offset; 104 ret = filp->f_pos; 105 break; 106 107 case 1: /*从当前位置开始偏移*/ 108 if ((filp->f_pos + offset) > GLOBALMEM_SIZE) //偏移越界 109 { 110 ret = - EINVAL; 111 break; 112 } 113 if ((filp->f_pos + offset) < 0) 114 { 115 ret = - EINVAL; 116 break; 117 } 118 filp->f_pos += offset; 119 ret = filp->f_pos; 120 break; 121 default: ret = - EINVAL; break; 122 } 123 return ret; 124 } 125 // 126 static int globalmem_ioctl(struct inode *inodep, struct file *filp,unsigned int cmd, unsigned long arg) 127 { 128 struct globalmem_dev *dev = filp->private_data; 129 switch (cmd){ 130 case MEM_CLEAR: //清除全局内存 131 memset(dev->mem, 0, GLOBALMEM_SIZE); 132 printk(KERN_INFO "globalmem is set to zero\n"); 133 break; 134 default: 135 return - EINVAL; //其他不支持的命令 136 break; 137 } 138 return 0; 139 } 140 //定义file_operatetiont 141 static const struct file_operations globalmem_fops = 142 { 143 .owner = THIS_MODULE, 144 .llseek = globalmem_llseek, 145 .read = globalmem_read, 146 .write = globalmem_write, 147 .ioctl = globalmem_ioctl, 148 .open = globalmem_open, 149 .release = globalmem_release, 150 }; 151 152 /*globalmem 设备驱动模块加载函数*/ 153 static int globalmem_init(void){ 154 int result,err; 155 dev_t devno = MKDEV(globalmem_major, 0); 156 /* 申请字符设备驱动区域*/ 157 if (globalmem_major){ 158 result = register_chrdev_region(devno, 1, "globalmem"); 159 }else{ 160 /* 动态获得主设备号 */ 161 result = alloc_chrdev_region(&devno, 0, 1, "globalmem"); 162 globalmem_major = MAJOR(devno); 163 } 164 if (result < 0) 165 return result; 166 printk("request major %d,minor %d\n",globalmem_major,MINOR(devno)); 167 168 /*动态申请设备结构体的内存*/ 169 globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL); 170 if(!globalmem_devp){ /* 申请失败 */ 171 result = -ENOMEM; 172 goto fail_malloc; 173 } 174 memset(globalmem_devp, 0, sizeof(struct globalmem_dev)); 175 176 /*初始化设备结构体*/ 177 cdev_init(&globalmem_devp->cdev, &globalmem_fops); 178 globalmem_devp->cdev.owner = THIS_MODULE; 179 globalmem_devp->cdev.ops = &globalmem_fops; 180 /* 注册cdev */ 181 err = cdev_add(&globalmem_devp->cdev, devno, 1); 182 if (err) 183 printk(KERN_NOTICE "Error %d adding globalmem", err); 184 185 //自动创建设备节点 186 cls = class_create(THIS_MODULE,"globalmem"); 187 device_create(cls, NULL, devno,NULL,"globalmem"); 188 189 return 0; 190 fail_malloc: unregister_chrdev_region(devno,1); 191 return result; 192 } 193 /*globalmem 设备驱动模块卸载函数*/ 194 static void globalmem_exit(void) 195 { 196 dev_t devno = MKDEV(globalmem_major, 0); 197 198 device_destroy(cls,devno); 199 class_destroy(cls); 200 cdev_del(&globalmem_devp->cdev); /*删除 cdev 结构*/ 201 kfree(globalmem_devp); /* 释放设备结构体内存 */ 202 unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);/*注销设备*/ 203 } 204 205 module_init(globalmem_init); 206 module_exit(globalmem_exit); 207 MODULE_LICENSE("GPL");
测试:
root@EasyARM-iMX257 /mnt/nfs/module/47_globalmem# insmod globalmem.ko request major 249,minor 0 root@EasyARM-iMX257 /mnt/nfs/module/47_globalmem# ls /dev/globalmem /dev/globalmem root@EasyARM-iMX257 /mnt/nfs/module/47_globalmem# echo "hello" > /dev/globalmem written 6 bytes(s) from 0 root@EasyARM-iMX257 /mnt/nfs/module/47_globalmem# cat /dev/globalmem read 4096 bytes(s) from 0 hello
4.[改进]支持两个globalmem设备的驱动
附程序:
1 #include <linux/module.h> 2 #include <linux/types.h> 3 #include <linux/fs.h> 4 #include <linux/kernel.h> 5 #include <linux/device.h> 6 #include <linux/errno.h> 7 #include <linux/mm.h> 8 #include <linux/sched.h> 9 #include <linux/init.h> 10 #include <linux/cdev.h> 11 #include <asm/io.h> 12 #include <asm/system.h> 13 #include <asm/uaccess.h> 14 15 #define GLOBALMEM_SIZE 0x1000 /*全局内存大小:4KB*/ 16 #define MEM_CLEAR 0x1 /*清零全局内存*/ 17 #define GLOBALMEM_MAJOR 0 /*预设的 globalmem 的主设备号*/ 18 19 20 static int globalmem_major = GLOBALMEM_MAJOR; 21 /*globalmem 设备结构体*/ 22 struct globalmem_dev 23 { 24 struct cdev cdev; /*cdev 结构体*/ 25 unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/ 26 }; 27 28 struct globalmem_dev *globalmem_devp; /*设备结构体实例*/ 29 30 //自动添加设备节点 31 static struct class *cls = NULL; 32 33 34 /*文件打开函数*/ 35 static int globalmem_open(struct inode *inode, struct file *filp){ 36 /*将设备结构体指针赋值给文件私有数据指针*/ 37 filp->private_data = globalmem_devp; 38 return 0; 39 } 40 //释放函数 41 static int globalmem_release(struct inode *inode, struct file *filp){ 42 return 0; 43 } 44 //文件读函数 45 static ssize_t globalmem_read(struct file *filp, char __user *buf,size_t count,loff_t *ppos) 46 { 47 unsigned long p = *ppos; 48 int ret = 0; 49 struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/ 50 /*分析和获取有效的读长度*/ 51 if (p >= GLOBALMEM_SIZE) //要读的偏移位置越界 52 return count ? - ENXIO: 0; 53 if (count > GLOBALMEM_SIZE - p)//要读的字节数太大 54 count = GLOBALMEM_SIZE - p; 55 /*内核空间→用户空间*/ 56 if (copy_to_user(buf, (void*)(dev->mem + p), count)) 57 { 58 ret = - EFAULT; 59 }else{ 60 *ppos += count; 61 ret = count; 62 printk(KERN_INFO "read %d bytes(s) from %ld\n", count, p); 63 } 64 return ret; 65 } 66 //文件写函数 67 static ssize_t globalmem_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos) 68 { 69 unsigned long p = *ppos; 70 int ret = 0; 71 struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/ 72 /*分析和获取有效的写长度*/ 73 if (p >= GLOBALMEM_SIZE) //要写的偏移位置越界 74 return count ? - ENXIO: 0; 75 if (count > GLOBALMEM_SIZE - p) //要写的字节数太多 76 count = GLOBALMEM_SIZE - p; 77 78 /*用户空间→内核空间*/ 79 if (copy_from_user(dev->mem + p, buf, count)){ 80 ret = - EFAULT; 81 }else{ 82 *ppos += count; 83 ret = count; 84 printk(KERN_INFO "written %d bytes(s) from %ld\n", count, p); 85 } 86 return ret; 87 } 88 //文件定位函数 89 static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig) 90 { 91 loff_t ret; 92 switch (orig){ 93 case 0: /*从文件开头开始偏移*/ 94 if (offset < 0){ 95 ret = - EINVAL; 96 break; 97 } 98 if ((unsigned int)offset > GLOBALMEM_SIZE) //偏移越界 99 { 100 ret = - EINVAL; 101 break; 102 } 103 filp->f_pos = (unsigned int)offset; 104 ret = filp->f_pos; 105 break; 106 107 case 1: /*从当前位置开始偏移*/ 108 if ((filp->f_pos + offset) > GLOBALMEM_SIZE) //偏移越界 109 { 110 ret = - EINVAL; 111 break; 112 } 113 if ((filp->f_pos + offset) < 0) 114 { 115 ret = - EINVAL; 116 break; 117 } 118 filp->f_pos += offset; 119 ret = filp->f_pos; 120 break; 121 default: ret = - EINVAL; break; 122 } 123 return ret; 124 } 125 // 126 static int globalmem_ioctl(struct inode *inodep, struct file *filp,unsigned int cmd, unsigned long arg) 127 { 128 struct globalmem_dev *dev = filp->private_data; 129 switch (cmd){ 130 case MEM_CLEAR: //清除全局内存 131 memset(dev->mem, 0, GLOBALMEM_SIZE); 132 printk(KERN_INFO "globalmem is set to zero\n"); 133 break; 134 default: 135 return - EINVAL; //其他不支持的命令 136 break; 137 } 138 return 0; 139 } 140 //定义file_operatetiont 141 static const struct file_operations globalmem_fops = 142 { 143 .owner = THIS_MODULE, 144 .llseek = globalmem_llseek, 145 .read = globalmem_read, 146 .write = globalmem_write, 147 .ioctl = globalmem_ioctl, 148 .open = globalmem_open, 149 .release = globalmem_release, 150 }; 151 152 /*globalmem 设备驱动模块加载函数*/ 153 static int globalmem_init(void){ 154 int result,err; 155 dev_t devno = MKDEV(globalmem_major, 0); 156 /* 申请字符设备驱动区域*/ 157 if (globalmem_major){ 158 result = register_chrdev_region(devno, 2, "globalmem"); 159 }else{ 160 /* 动态获得主设备号 */ 161 result = alloc_chrdev_region(&devno, 0, 2, "globalmem"); 162 globalmem_major = MAJOR(devno); 163 } 164 if (result < 0) 165 return result; 166 printk("request major %d,minor %d\n",globalmem_major,MINOR(devno)); 167 168 /*动态申请设备结构体的内存*/ 169 globalmem_devp = kmalloc(2 * sizeof(struct globalmem_dev), GFP_KERNEL); 170 if(!globalmem_devp){ /* 申请失败 */ 171 result = -ENOMEM; 172 goto fail_malloc; 173 } 174 memset(globalmem_devp, 0, 2 * sizeof(struct globalmem_dev)); 175 176 /*初始化设备结构体*/ 177 cdev_init(&globalmem_devp[0].cdev, &globalmem_fops); 178 globalmem_devp[0].cdev.owner = THIS_MODULE; 179 globalmem_devp[0].cdev.ops = &globalmem_fops; 180 /* 注册cdev */ 181 err = cdev_add(&globalmem_devp[0].cdev, devno, 1); 182 if (err) 183 printk(KERN_NOTICE "Error %d adding globalmem", err); 184 /*初始化设备结构体*/ 185 cdev_init(&globalmem_devp[1].cdev, &globalmem_fops); 186 globalmem_devp[1].cdev.owner = THIS_MODULE; 187 globalmem_devp[1].cdev.ops = &globalmem_fops; 188 /* 注册cdev */ 189 err = cdev_add(&globalmem_devp[1].cdev, devno, 1); 190 if (err) 191 printk(KERN_NOTICE "Error %d adding globalmem", err); 192 193 //自动创建设备节点 194 cls = class_create(THIS_MODULE,"globalmem"); 195 device_create(cls, NULL, MKDEV(globalmem_major,0),NULL,"globalmem0"); 196 device_create(cls, NULL, MKDEV(globalmem_major,1),NULL,"globalmem1"); 197 198 return 0; 199 fail_malloc: unregister_chrdev_region(devno,1); 200 return result; 201 } 202 /*globalmem 设备驱动模块卸载函数*/ 203 static void globalmem_exit(void) 204 { 205 device_destroy(cls,MKDEV(globalmem_major, 0)); 206 device_destroy(cls,MKDEV(globalmem_major, 1)); 207 class_destroy(cls); 208 cdev_del(&globalmem_devp->cdev); /*删除 cdev 结构*/ 209 kfree(globalmem_devp); /* 释放设备结构体内存 */ 210 unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);/*注销设备*/ 211 } 212 213 module_init(globalmem_init); 214 module_exit(globalmem_exit); 215 MODULE_LICENSE("GPL");
测试:
root@EasyARM-iMX257 /mnt/nfs/module/47_globalmem# insmod globalmem.ko request major 247,minor 0 root@EasyARM-iMX257 /mnt/nfs/module/47_globalmem# ls /dev/globalmem* /dev/globalmem0 /dev/globalmem1 root@EasyARM-iMX257 /mnt/nfs/module/47_globalmem# root@EasyARM-iMX257 /mnt/nfs/module/47_globalmem# echo "hello" > /dev/globalmem0 written 6 bytes(s) from 0 root@EasyARM-iMX257 /mnt/nfs/module/47_globalmem# cat /dev/globalmem0 read 4096 bytes(s) from 0 hello