天天看点

基于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

  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

 17 struct mem_dev

 18 {

 19         char *data;                      //该指针模拟设备的地址

 20         unsigned long size;      //模拟设备的大小

 21 };

 22

 23 #endif

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;

 20

 21

 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

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:

112                         newpos = offset;

113                         break;

114

115                 case 1:

116                         newpos = filp->f_pos + offset;

117                         break;

118

119                 case 2:

120                         newpos = MEMDEV_SIZE -1 + offset;

121                         break;

122

123                 default:

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        

165        

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];

        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);

        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        

153         if (mem_major)

154                 result = register_chrdev_region(devno, 2, "memdev");

155         else

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        

165        

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);  //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

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:

112                         newpos = offset;

113                         break;

114

115                 case 1:

116                         newpos = filp->f_pos + offset;

117                         break;

118

119                 case 2:

120                         newpos = MEMDEV_SIZE -1 + offset;

121                         break;

122

123                 default:

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 }

继续阅读