基于mini6410的linux驱动学习总结(五 字符设备驱动程序实例分析(虚拟设备驱动))

1、 程序要实现的功能

           使用一个虚拟的设备来模拟一个字符设备,该虚拟设备为一块内存。在内存中开辟一块4K的空间,将4K的内存看作一个字符设备,编写一个字符设备驱动程序来操作这块内存。

2、实验环境

       内核版本:linux-2.6.35-32

3、字符设备驱动程序设计步骤

      Step1:设备注册

           在linux 2.6内核中,字符设备使用struct cdev来描述。字符设备的注册可分为如下3个步骤:

           1.分配cdev

           2.初始化cdev

           3.添加cdev

  step2:实现设备所支持的操作

           1.int (*open)(struct inode *, struct file *)

             在设备文件上的第一个操作,并不要求驱动程序一定要实现这个方法。如果该项为NULL,设备的打开操作永远成功。

           2.void (*release)(struct inode *, struct file *)当设备文件被关闭时调用这个操作。与open相仿,release也可以没有。

           3.ssize_t (*read) (struct file *, char __user *, size_t, loff_t *)从设备中读取数据。

           4. ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *)向设备发送数据。

           5.off_t (*llseek) (struct file *, loff_t, int)修改文件的当前读写位置,并将新位置作为返回值。

  step3:设备注消

           字符设备的注销使用cdev_del函数来完成。

           int cdev_del(struct cdev *p)

           参数:

                p:要注销的字符设备结构


4、程序源码如下所示

 

4.1 memdev.h 源代码

  1 #ifndef _MEMDEV_H_
  2 #define _MEMDEV_H_
  3
  4 #ifndef MEMDEV_MAJOR
  5 #define MEMDEV_MAJOR 260 /*预设的mem的主设备号*/
  6 #endif
  7
  8 #ifndef MEMDEV_NR_DEVS
  9 #define MEMDEV_NR_DEVS 2 /*设备数*/
 10 #endif
 11
 12 #ifndef MEMDEV_SIZE
 13 #define MEMDEV_SIZE 4096
 14 #endif
 15
 16 /*mem设备描述结构体*/
 17 struct mem_dev
 18 {
 19         char *data;                      //该指针模拟设备的地址
 20         unsigned long size;      //模拟设备的大小
 21 };
 22
 23 #endif /*_MEMDEV_H_*/


4.2 memdev.c 源代码

1 #include<linux/module.h>
  2 #include<linux/types.h>
  3 #include<linux/fs.h>
  4 #include<linux/errno.h>
  5 #include<linux/mm.h>
  6 #include<linux/sched.h>
  7 #include<linux/init.h>
  8 #include<linux/cdev.h>
  9 #include<linux/slab.h>
 10 #include<asm/io.h>
 11 #include<asm/system.h>
 12 #include<asm/uaccess.h>
 13 #include "memdev.h"
 14
 15 static mem_major = MEMDEV_MAJOR;
 16
 17 module_param(mem_major, int, S_IRUGO);
 18
 19 struct mem_dev *mem_devp;/*设备描述结构体指针,清查看mem_dev.h中的定义*/
 20
 21
/*静态分配字符设备描述符cdev结构*/
 22 struct cdev cdev;
 23
 24 /*文件打开函数*/
 25 int mem_open(struct inode *inode, struct file *filp)
 26 {
 27         struct mem_dev *dev;
 28
 29         /*获取次设备号*/
 30         int num = MINOR(inode->i_rdev);
 31         if(num >= MEMDEV_NR_DEVS)
 32                 return -ENODEV;
 33         dev = &mem_devp[num];
 34
 35         /*将设备描述结构指针复制给文件私有数据指针*/
 36         filp->private_data = dev;
 37         return 0;
 38 }
 39
 40 /*文件释放函数*/
 41 int mem_release(struct inode *inode, struct file *filp)
 42 {
 43         return 0;
 44 }

45
 46 /*读函数*/
 47 static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
 48 {
 49         unsigned long p = *ppos;
 50         unsigned int count = size;
 51         int ret = 0;
 52         /*获得设备结构体指针*/
 53         struct mem_dev *dev = filp->private_data;
 54
 55         /*判断读位置是否有效*/
 56         if(p >= MEMDEV_SIZE)
 57                 return 0;
 58         if(count > MEMDEV_SIZE - p)
 59                 count = MEMDEV_SIZE - p;
 60
 61         /*读数据到用户空间*/
 62         if(copy_to_user(buf, (void*)(dev->data + p), count))
 63         {
 64                 ret = -EFAULT;
 65         }
 66         else
 67         {
 68                 *ppos += count;
 69                 ret = count;
 70
 71                 printk(KERN_INFO "read %d btyes(s) from %d\n", count, p);
 72         }
 73         return ret;
 74 }
 75
 76 /*写函数*/
 77  static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *p    pos)
 78 {
 79         unsigned long p = *ppos;
 80         unsigned int count = size;
 81         int ret = 0;
 82         /*获得设备结构体*/
 83         struct mem_dev *dev = filp->private_data;
 84
 85         /*分析和获取有效的写长度*/
 86         if(p >= MEMDEV_SIZE)
 87                 return 0;
 88         if(count > MEMDEV_SIZE - p)
89                 count = MEMDEV_SIZE - p;
 90
 91         /*从用户空间写入数据*/
 92         if(copy_from_user(dev->data + p, buf, count))
 93                 ret = -EFAULT;
 94         else
 95         {
 96                 *ppos += count;
 97                 ret = count;
 98
 99                 printk(KERN_INFO "write %d btyes(s) from %d\n", count, p);
100         }
101         return ret;
102
103 }
104
105 /*seek 文件定位函数*/
106 static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
107 {
108         loff_t newpos;
109         switch(whence)
110         {
111                 case 0: /*SEEK_SET*/
112                         newpos = offset;
113                         break;
114
115                 case 1: /*SEEK_CUR*/
116                         newpos = filp->f_pos + offset;
117                         break;
118
119                 case 2: /*SEEK_END*/
120                         newpos = MEMDEV_SIZE -1 + offset;
121                         break;
122
123                 default: /* can't happen */
124                         return -EINVAL;
125         }
126         if((newpos < 0) || (newpos > MEMDEV_SIZE))
127                 return -EINVAL;
128
129         filp->f_pos = newpos;
130         return newpos;
131 }

132
133 /*文件操作结构体*/
134 static const struct file_operations mem_fops =
135 {
136         .owner = THIS_MODULE,
137         .llseek = mem_llseek,
138         .read = mem_read,
139         .write = mem_write,
140         .open = mem_open,
141         .release = mem_release,
142 };
143
144 /*设备驱动模块加载函数*/
145 static int memdev_init(void)
146 {
147         int result;
148         int i;
149
150         dev_t devno = MKDEV(mem_major, 0);
151
152         /*静态申请设备号*/
153         if (mem_major)
154                 result = register_chrdev_region(devno, 2, "memdev");
155         else /*动态分配设备号*/
156         {
157                 result = alloc_chrdev_region(&devno, 0, 2, "memdev");
158                 mem_major = MAJOR(devno);
159         }
160
161         if(result < 0)
162                 return result;
163
164         /*程序开始处已静态分配了cdev结构*/
165         /*初始化cdev结构*/
166         cdev_init(&cdev, &mem_fops);
167         cdev.owner = THIS_MODULE;
168         cdev.ops = &mem_fops;
169
170         /*注册字符设备*/
171         cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
172
173         /*为设备描述结构分配内存*/
174         mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
175         if(!mem_devp) /*申请失败*/
176         {
177                 result = -ENOMEM;
178                 goto fail_malloc;
179         }
180         memset(mem_devp, 0, sizeof(struct mem_dev));
181
182         /*为设备分配内存*/
183         for(i>0; i < MEMDEV_NR_DEVS; i++)
184         {
185                 mem_devp[i].size = MEMDEV_SIZE;
186                 mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
187                 memset(mem_devp[i].data, 0, MEMDEV_SIZE);
188         }
189
190         return 0;
191
192 fail_malloc:
193         unregister_chrdev_region(devno, 1); /*注销设备号*/
194 }
195
196 /*模块卸载函数*/
197 static void memdev_exit(void)
198 {
199         cdev_del(&cdev); /*注销设备*/
200         kfree(mem_devp); /*释放设备结构体内存*/
201         unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
202 }
203
204 MODULE_AUTHOR("yinjiabin");
205 MODULE_LICENSE("GPL");
206
207 module_init(memdev_init);
208 module_exit(memdev_exit);

 

4.3 对应的Makefile文件

ifneq ($(KERNELRELEASE),)

obj-m := memdev.o

else

KDIR := /lib/modules/2.6.35-32-generic/build(指定自己的内核所在路径)
all:
        make -C $(KDIR) M=$(PWD) modules
clean:
        rm -f *.ko *.o *.mod *.mod.c *.symvers

endif

4.4 应用程序app-mem.c源码

#include<stdio.h>
#include<string.h>

int main()
{
        FILE *fp0 = NULL;
        char Buf[4096];

        /*初始化Buf*/
        strcpy(Buf,"Mem is char dev!");
        printf("BUF: %s\n",Buf);

        /*打开设备文件*/
        fp0 = fopen("/dev/memdev0","r+");
        if(fp0 == NULL)
        {
                printf("Open Memdev0 Error!\n");
                return -1;
        }

        /*写入设备*/
        fwrite(Buf, sizeof(Buf), 1, fp0);

        /*重新定位文件位置*/
        fseek(fp0, 0, SEEK_SET);

        /*清除Buf*/
        strcpy(Buf,"Buf is NULL!");
        printf("BUF: %s\n",Buf);

        /*读出设备*/
        fread(Buf, sizeof(Buf), 1, fp0);

        /*检测结果*/
        printf("BUF: %s\n",Buf);
        return 0;
}
~                                     

5、代码分析:

      该程序采用的是内核模块编写方式,对应上面提到的字符设备驱动设计步骤来分析该代码。

        5.1 首先分析memdev_init()设备模块加载函数,因为它是第一个运行的函数。该函数主要功能是完成设计字符设备驱动的Step1:设备注册

  1. 分配cdev

   2. 初始化cdev

   3. 添加cdev

   因为我用的是虚拟设备,所以还要为设备描述结构和设备分配内存。


static int memdev_init(void)
146 {
147         int result;
148         int i;
149         /*定义一个设备号,并利用主、次设备号构造该设备号*/
150         dev_t devno = MKDEV(mem_major, 0);     // MKDEV函数实现构造设备号。 mem_major对应宏MEMDEV_MAJOR 254,在memdev.h中定义
151
152         /*静态申请设备号,mem_major>0*/
153         if (mem_major)
154                 result = register_chrdev_region(devno, 2, "memdev");
155         else /*动态分配设备号mem_major==0*/
156         {
157                 result = alloc_chrdev_region(&devno, 0, 2, "memdev");  //将申请的主设备号存放在devno中
158                 mem_major = MAJOR(devno);    //从devno中提取主设备号
159         }
160
161         if(result < 0)
162                 return result;
163
164        
/*(分配cdev)程序开始处已静态分配了cdev结构,查看mem_dev.c的第22行*/
165        
/*(初始化cdev)初始化字符设备描述符cdev结构*/
166         cdev_init(&cdev, &mem_fops);
167         cdev.owner = THIS_MODULE;
168         cdev.ops = &mem_fops;
169
170        
/*(添加cdev)注册字符设备*/
171         cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);  //cdev_add()执行后,驱动程序就完成了在内核中的注册
172
173        
/*为设备描述结构分配内存*/
174         mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
175         if(!mem_devp) /*申请失败*/
176         {
177                 result = -ENOMEM;
178                 goto fail_malloc;
179         }
180         memset(mem_devp, 0, sizeof(struct mem_dev));
181
182        
/*为设备分配内存*/
183         for(i>0; i < MEMDEV_NR_DEVS; i++)
184         {
185                 mem_devp[i].size = MEMDEV_SIZE;
186                 mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
187                 memset(mem_devp[i].data, 0, MEMDEV_SIZE);
188         }
189
190         return 0;
191
192 fail_malloc:
193         unregister_chrdev_region(devno, 1); /*注销设备号*/
194 }


 5.2 分析file_operation,该函数的主要功能是实现设计字符设备驱动的step2:实现设备所支持的操作

 文件操作结构体
static const struct file_operations mem_fops =
 {
         .owner = THIS_MODULE,
         .llseek = mem_llseek,
        .read = mem_read,
         .write = mem_write,
        .open = mem_open,
         .release = mem_release,
 };

       文件操作结构体,他其实就是一张对应关系表,把应用程序中对设备文件的操作转化成驱动对应的操作函数。
 5.2.1 首先分析open()函数 
 25 int mem_open(struct inode *inode, struct file *filp)
 26 {
 27         struct mem_dev *dev;
 28
 29         /*获取次设备号*/
 30         int num = MINOR(inode->i_rdev);
 31         if(num >= MEMDEV_NR_DEVS)
 32                 return -ENODEV;
 33         dev = &mem_devp[num];//根据次设备号获取设备描述结构指针
 34
 35         /*将设备描述结构指针复制给文件私有数据指针*/
 36         filp->private_data = dev;
 37         return 0;
 38 }
 39 
      注意考虑: 为什么不能在read()函数中直接获取设备结构体指针(&mem_dev[num])?

      因为read()函数没有inode参数,所以只能通过open()函数传递设备结构体指针。

5.2.2 分析read()函数 

46 /*读函数*/
 47 static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
 48 {
 49         unsigned long p = *ppos;
 50         unsigned int count = size;
 51         int ret = 0;
 52         /*获得设备结构体指针*/
 53         struct mem_dev *dev = filp->private_data;
 54
 55         /*判断读位置是否有效*/
 56         if(p >= MEMDEV_SIZE)
 57                 return 0;
 58         if(count > MEMDEV_SIZE - p)
 59                 count = MEMDEV_SIZE - p;
 60
 61         /*读数据到用户空间*/
 62         if(copy_to_user(buf, (void*)(dev->data + p), count))
 63         {
 64                 ret = -EFAULT;
 65         }
 66         else
 67         {
 68                 *ppos += count;
 69                 ret = count;
 70
 71                 printk(KERN_INFO "read %d btyes(s) from %d\n", count, p);
 72         }
 73         return ret;
 74 }
 75

5.2.3 分析写函数
 76 /*写函数*/
 77  static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *p    pos)
 78 {
 79         unsigned long p = *ppos;
 80         unsigned int count = size;
 81         int ret = 0;
 82         /*获得设备结构体*/
 83         struct mem_dev *dev = filp->private_data;
 84
 85         /*分析和获取有效的写长度*/
 86         if(p >= MEMDEV_SIZE)
 87                 return 0;
 88         if(count > MEMDEV_SIZE - p)
89                 count = MEMDEV_SIZE - p;
 90
 91         /*从用户空间写入数据*/
 92         if(copy_from_user(dev->data + p, buf, count))
 93                 ret = -EFAULT;
 94         else
 95         {
 96                 *ppos += count;
 97                 ret = count;
 98
 99                 printk(KERN_INFO "write %d btyes(s) from %d\n", count, p);
100         }
101         return ret;
102
103 }
5.2.4 分析文件定位函数
105 /*seek 文件定位函数*/
106 static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
107 {
108         loff_t newpos;
109         switch(whence)
110         {
111                 case 0: /*SEEK_SET*/
112                         newpos = offset;
113                         break;
114
115                 case 1: /*SEEK_CUR*/
116                         newpos = filp->f_pos + offset;
117                         break;
118
119                 case 2: /*SEEK_END*/
120                         newpos = MEMDEV_SIZE -1 + offset;
121                         break;
122
123                 default: /* can't happen */
124                         return -EINVAL;
125         }
126         if((newpos < 0) || (newpos > MEMDEV_SIZE))
127                 return -EINVAL;
128
129         filp->f_pos = newpos;
130         return newpos;
131 }

5.3 分析memdev_exit()模块卸载函数,对应设计字符设备驱动的step3:设备注消

196 /*模块卸载函数*/
197 static void memdev_exit(void)
198 {
199         cdev_del(&cdev); /*注销设备*/
200         kfree(mem_devp); /*释放设备结构体内存*/
201         unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
202 }

 










 

你可能感兴趣的:(linux,struct,File,Module,user,FP)