五、字符设备驱动

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 字符设备驱动的组成

  •  字符设备驱动由以下几个部分组成:
    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) 进行判断,以确定缓冲区的确属于用户空间。

  字符设备驱动的结构、字符设备驱动与字符设备以及字符设备驱动与用户空间访问该设备的程序之间的关系图如下:

  

 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
");
 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
", 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
", 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
", 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 的时候自动去删除。

原文地址:https://www.cnblogs.com/kele-dad/p/11655872.html