linux设备驱动--globalmem字符设备框架分析


目录(?)[+]

linux设备驱动--globalmem字符设备框架分析

有不对,不足,需要改正的地方还望大家指出,而且希望结识志同道合的朋友一起学习技术,共同进步.


功能:    1.设备驱动开发详解-globalmem字符设备框架分析(支持2个设备)

 

目录:    1. globalmem流程图

                 2.源码

                 3.分析

1)MKDEV

2)kmalloc和vmalloc的区别

3)module_param

4)file文件结构

5)inode结构

6)container_of()

7)ioctl函数

 

实现:        globalmem流程图

 

源码:

globalmen模块

[cpp]  view plain copy
  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<linux/kdev_t.h>  
  11. #include<linux/device.h>  
  12. #include <asm/io.h>  
  13. #include<asm/system.h>  
  14. #include<asm/uaccess.h>  
  15.    
  16. #define DEVICE_NAME  "globalmem"  
  17. #defineGLOBALMEM_SIZE        0x1000        /*全局内存最大4K字节*/  
  18. #define MEM_CLEAR0x1  /*清0全局内存*/  
  19. #define GLOBALMEM_MAJOR241    /*预设的globalmem的主设备号*/  
  20.    
  21. static intglobalmem_major = GLOBALMEM_MAJOR;  
  22. /*globalmem设备结构体*/  
  23. struct globalmem_dev                                      
  24. {                                                         
  25.   struct cdev cdev; /*cdev结构体*/                        
  26.   unsigned char mem[GLOBALMEM_SIZE];/*全局内存*/         
  27. };  
  28.    
  29. struct globalmem_dev*globalmem_devp; /*设备结构体指针*/  
  30.    
  31. static struct class*globalmem0_class;  
  32. static struct class*globalmem1_class;  
  33.    
  34. /*文件打开函数*/  
  35. int globalmem_open(structinode *inode, struct file *filp)  
  36. {  
  37.   /*将设备结构体指针赋值给文件私有数据指针*/  
  38.   struct globalmem_dev *dev;  
  39.    
  40.   dev = container_of(inode->i_cdev,structglobalmem_dev,cdev);   
  41.   filp->private_data = dev;   
  42.   return 0;  
  43. }  
  44. /*文件释放函数*/  
  45. intglobalmem_release(struct inode *inode, struct file *filp)  
  46. {  
  47.   return 0;  
  48. }  
  49.    
  50. /* ioctl设备控制函数 */  
  51. static intglobalmem_ioctl(struct inode *inodep, struct file *filp, unsigned  
  52.   int cmd, unsigned long arg)  
  53. {  
  54.   struct globalmem_dev *dev =filp->private_data;/*获得设备结构体指针*/  
  55.    
  56.   switch (cmd)  
  57.   {  
  58.     case MEM_CLEAR:  
  59.       memset(dev->mem, 0,GLOBALMEM_SIZE);       
  60.       printk(KERN_INFO "globalmem is setto zero\n");  
  61.       break;  
  62.    
  63.     default:  
  64.       return - EINVAL;  
  65.   }  
  66.   return 0;  
  67. }  
  68.    
  69. /*读函数*/  
  70. static ssize_tglobalmem_read(struct file *filp, char __user *buf, size_t size,  
  71.   loff_t *ppos)  
  72. {  
  73.   unsigned long p =  *ppos;  
  74.   unsigned int count = size;  
  75.   int ret = 0;  
  76.   struct globalmem_dev *dev =filp->private_data; /*获得设备结构体指针*/  
  77.    
  78.   /*分析和获取有效的写长度*/  
  79.   if (p >= GLOBALMEM_SIZE)  
  80.     return count ?  - ENXIO: 0;  
  81.   if (count > GLOBALMEM_SIZE - p)  
  82.     count = GLOBALMEM_SIZE - p;  
  83.    
  84.   /*内核空间->用户空间*/  
  85.   if (copy_to_user(buf, (void*)(dev->mem +p), count))  
  86.   {  
  87.     ret = - EFAULT;  
  88.   }  
  89.   else  
  90.   {  
  91.     *ppos += count;  
  92.     ret = count;  
  93.      
  94.     printk(KERN_INFO "read %d bytes(s)from %d\n", count, p);  
  95.   }  
  96.    
  97.   return ret;  
  98. }  
  99.    
  100. /*写函数*/  
  101. static ssize_tglobalmem_write(struct file *filp, const char __user *buf,  
  102.   size_t size, loff_t *ppos)  
  103. {  
  104.   unsigned long p =  *ppos;  
  105.   unsigned int count = size;  
  106.   int ret = 0;  
  107.   struct globalmem_dev *dev =filp->private_data; /*获得设备结构体指针*/  
  108.    
  109.   /*分析和获取有效的写长度*/  
  110.   if (p >= GLOBALMEM_SIZE)  
  111.     return count ?  - ENXIO: 0;  
  112.   if (count > GLOBALMEM_SIZE - p)  
  113.     count = GLOBALMEM_SIZE - p;  
  114.      
  115.   /*用户空间->内核空间*/  
  116.   if (copy_from_user(dev->mem + p, buf,count))  
  117.     ret = - EFAULT;  
  118.   else  
  119.   {  
  120.     *ppos += count;  
  121.     ret = count;  
  122.      
  123.     printk(KERN_INFO "written %d bytes(s)from %d\n", count, p);  
  124.   }  
  125.    
  126.   return ret;  
  127. }  
  128.    
  129. /* seek文件定位函数 */  
  130. static loff_tglobalmem_llseek(struct file *filp, loff_t offset, int orig)  
  131. {  
  132.   loff_t ret = 0;  
  133.   switch (orig)  
  134.   {  
  135.     case 0:  /*相对文件开始位置偏移*/  
  136.       if (offset < 0)  
  137.       {  
  138.         ret = - EINVAL;  
  139.         break;  
  140.       }  
  141.       if ((unsigned int)offset >GLOBALMEM_SIZE)  
  142.       {  
  143.         ret = - EINVAL;  
  144.         break;  
  145.       }  
  146.       filp->f_pos = (unsigned int)offset;  
  147.       ret = filp->f_pos;  
  148.       break;  
  149.     case 1:  /*相对文件当前位置偏移*/  
  150.       if ((filp->f_pos + offset) >GLOBALMEM_SIZE)  
  151.       {  
  152.         ret = - EINVAL;  
  153.         break;  
  154.       }  
  155.       if ((filp->f_pos + offset) < 0)  
  156.       {  
  157.         ret = - EINVAL;  
  158.         break;  
  159.       }  
  160.       filp->f_pos += offset;  
  161.       ret = filp->f_pos;  
  162.       break;  
  163.     default:  
  164.       ret = - EINVAL;  
  165.       break;  
  166.   }  
  167.   return ret;  
  168. }  
  169.    
  170. /*文件操作结构体*/  
  171. static const structfile_operations globalmem_fops =  
  172. {  
  173.   .owner = THIS_MODULE,  
  174.   .llseek = globalmem_llseek,  
  175.   .read = globalmem_read,  
  176.   .write = globalmem_write,  
  177.   .ioctl = globalmem_ioctl,  
  178.   .open = globalmem_open,  
  179.   .release = globalmem_release,  
  180. };  
  181.    
  182. /*初始化并注册cdev*/  
  183. static voidglobalmem_setup_cdev(struct globalmem_dev *dev, int index)  
  184. {  
  185.   int err, devno = MKDEV(globalmem_major,index);  
  186.    
  187.   cdev_init(&dev->cdev,&globalmem_fops);  
  188.   dev->cdev.owner = THIS_MODULE;  
  189.   dev->cdev.ops = &globalmem_fops;  
  190.   err = cdev_add(&dev->cdev, devno, 1);  
  191.   if (err)  
  192.     printk(KERN_NOTICE "Error %d addingLED%d", err, index);  
  193. }  
  194.    
  195. /*设备驱动模块加载函数*/  
  196. int globalmem_init(void)  
  197. {  
  198.   int result;  
  199.   dev_t devno = MKDEV(globalmem_major, 0);  
  200.    
  201.   /* 申请设备号*/  
  202.   if (globalmem_major)  
  203.     result = register_chrdev_region(devno, 2,DEVICE_NAME);  
  204.   else /* 动态申请设备号 */  
  205.   {  
  206.     result = alloc_chrdev_region(&devno, 0,2, DEVICE_NAME);  
  207.     globalmem_major = MAJOR(devno);  
  208.   }   
  209.   if (result < 0)  
  210.     return result;  
  211.      
  212.   /* 动态申请2个设备结构体的内存*/  
  213.   globalmem_devp = kmalloc(2*sizeof(structglobalmem_dev), GFP_KERNEL);  
  214.   if (!globalmem_devp)    /*申请失败*/  
  215.   {  
  216.     result = - ENOMEM;  
  217.     goto fail_malloc;  
  218.   }  
  219.   memset(globalmem_devp, 0, 2*sizeof(structglobalmem_dev));  
  220.    
  221.   globalmem_setup_cdev(&globalmem_devp[0],0);  
  222.   globalmem_setup_cdev(&globalmem_devp[1],1);  
  223.    
  224.     //注册一个类,使mdev可以在/dev/下面建立设备节点   
  225.     globalmem0_class =class_create(THIS_MODULE, "globalmem0");   
  226.     if( IS_ERR(globalmem0_class) )   
  227.     {   
  228.         printk(KERN_NOTICE, "creatglobalmem0_class failed!");   
  229.         return -1;   
  230.     }   
  231.    
  232.     //创建一个设备节点,节点名字为globalmem0   
  233.     device_create(globalmem0_class, NULL,MKDEV(globalmem_major, 0), NULL, "globalmem0");   
  234.     printk(KERN_NOTICE, "globalmem0initialized!");   
  235.    
  236.     globalmem1_class =class_create(THIS_MODULE, "globalmem1");   
  237.     if( IS_ERR(globalmem1_class) )   
  238.     {   
  239.         printk(KERN_NOTICE, "creatglobalmem1_class failed!");   
  240.         return -1;   
  241.     }   
  242.    
  243.     //创建一个设备节点,节点名字为globalmem1   
  244.     device_create(globalmem1_class, NULL,MKDEV(globalmem_major, 1), NULL, "globalmem1");   
  245.     printk(KERN_NOTICE, "globalmem1initialized!");   
  246.    
  247.   return 0;  
  248.    
  249.   fail_malloc: unregister_chrdev_region(devno,2);  
  250.   return result;  
  251. }  
  252.    
  253. /*模块卸载函数*/  
  254. void globalmem_exit(void)  
  255. {  
  256.   cdev_del(&(globalmem_devp[0].cdev));    
  257.   cdev_del(&(globalmem_devp[1].cdev));   /*注销cdev*/  
  258.   kfree(globalmem_devp);     /*释放设备结构体内存*/  
  259.  unregister_chrdev_region(MKDEV(globalmem_major, 0), 2); /*释放设备号*/  
  260.     class_destroy(globalmem0_class);   
  261.     class_destroy(globalmem1_class);   
  262. }  
  263.    
  264. MODULE_AUTHOR("SongBaohua");  
  265. MODULE_LICENSE("DualBSD/GPL");  
  266.    
  267. module_param(globalmem_major,int, S_IRUGO);  
  268.    
  269. module_init(globalmem_init);  
  270. module_exit(globalmem_exit);  

测试程序: 

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <time.h>  
  4. #include <unistd.h>  
  5. #include<linux/fcntl.h>  
  6.    
  7. int main()  
  8. {  
  9.  int fd;  
  10.  char buf1[]="testglobalmen data!";//写入memdev设备的内容  
  11.  char buf_read[4096]; //memdev设备的内容读入到该buf中  
  12.    
  13.  if((fd=open("/dev/globalmem0",O_RDWR))==-1)//打开memdev设备  
  14. {  
  15.   printf("open globalmem0 WRONG!\n");  
  16. return 0;  
  17. }  
  18.    
  19. int count = 0;  
  20. while(count<=100)  
  21. {  
  22. printf("openglobalmem0 SUCCESS!\n");  
  23.  printf("buf is %s\n",buf1);  
  24.  write(fd,buf1,sizeof(buf1));//把buf中的内容写入memdev设备  
  25.  lseek(fd,0,SEEK_SET); //把文件指针重新定位到文件开始的位置  
  26.  read(fd,buf_read,sizeof(buf1));//把memdev设备中的内容读入到buf_read中  
  27.  printf("buf_read is %s\n",buf_read);  
  28. count++;  
  29. }  
  30.    
  31.  close(fd);  
  32.  return 0;  
  33. }  
  34.    


分析:

一.MKDEV

[cpp]  view plain copy
  1. /linux/kdev_t.h  
  2. #define _LINUX_KDEV_T_H  
  3.    3#ifdef __KERNEL__  
  4.    4#define MINORBITS       20  
  5.    5#define MINORMASK       ((1U << MINORBITS) - 1)  
  6.    6  
  7.    7#define MAJOR(dev)      ((unsigned int) ((dev) >>MINORBITS))  
  8.    8#define MINOR(dev)      ((unsigned int) ((dev) & MINORMASK))  
  9.    9#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))  

 

当编译内核的时候,__KERNEL__在命令行被定义

When you compile your kernel, __KERNEL__ isdefined on the command line.

User-space programs need access tothe kernel headers, but some of the info in kernel headers is intended only forthe kernel. Wrapping some statements in an #ifdef __KERNEL__/#endif blockensures that user-space programs don't see those statements.

主设备号高12位,次设备号低20位

 

二.kmalloc和vmalloc的区别

1.kmalloc

1>kmalloc内存分配和malloc相似,除非被阻塞否则他执行的速度非常快,而且不对获得空间清零.在用kmalloc申请函数后,要对起清零.用memset()函数对申请的内存进行清零。

2>kamlloc函数原型:

#include<linux/slab.h>

Void *kmalloc(size_t size, intflags);

(1)第一个参数是要分配的块的大小

(2)第二个参数是分配标志(flags),他提供了多种kmalloc的行为。

(3)第三个最常用的GFP_KERNEL;

A.表示内存分配(最终总是调用get_free_pages来实现实际的分配;这就是GFP前缀的由来)是代表运行在内核空间的进程执行的。使用GFP_KERNEL容许kmalloc在分配空闲内存时候如果内存不足容许把当前进程睡眠以等待。因此这时分配函数必须是可重入的。如果在进程上下文之外如:中断处理程序、tasklet以及内核定时器中这种情况下current进程不该睡眠,驱动程序该使用GFP_ATOMIC.

B.GFP_ATOMIC

用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠.

C.GFP_KERNEL

内核内存的正常分配. 可能睡眠.

D.GFP_USER

用来为用户空间页来分配内存; 它可能睡眠.

E.GFP_HIGHUSER

如同 GFP_USER, 但是从高端内存分配, 如果有. 高端内存在下一个子节描述.

F.GFP_NOFS,GFP_NOIO

这个标志功能如同 GFP_KERNEL, 但是它们增加限制到内核能做的来满足请求. 一个 GFP_NOFS 分配不允许进行任何文件系统调用, 而 GFP_NOIO 根本不允许任何 I/O 初始化. 它们主要地用在文件系统和虚拟内存代码, 那里允许一个分配睡眠, 但是递归的文件系统调用会是一个坏注意.

上面列出的这些分配标志可以是下列标志的相或来作为参数, 这些标志改变这些分配如何进行:

__GFP_DMA

这个标志要求分配在能够 DMA 的内存区. 确切的含义是平台依赖的并且在下面章节来解释.

__GFP_HIGHMEM

这个标志指示分配的内存可以位于高端内存.

__GFP_COLD

正常地, 内存分配器尽力返回"缓冲热"的页 -- 可能在处理器缓冲中找到的页. 相反, 这个标志请求一个"冷"页, 它在一段时间没被使用. 它对分配页作 DMA 读是有用的, 此时在处理器缓冲中出现是无用的. 一个完整的对如何分配 DMA 缓存的讨论看"直接内存存取"一节在第 1 章.

__GFP_NOWARN

这个很少用到的标志阻止内核来发出警告(使用 printk), 当一个分配无法满足.

__GFP_HIGH

这个标志标识了一个高优先级请求, 它被允许来消耗甚至被内核保留给紧急状况的最后的内存页.

Ø  __GFP_REPEAT

__GFP_NOFAIL

__GFP_NORETRY

这些标志修改分配器如何动作, 当它有困难满足一个分配. __GFP_REPEAT 意思是" 更尽力些尝试" 通过重复尝试 -- 但是分配可能仍然失败. __GFP_NOFAIL 标志告诉分配器不要失败; 它尽最大努力来满足要求. 使用__GFP_NOFAIL 是强烈不推荐的; 可能从不会有有效的理由在一个设备驱动中使用它. 最后, __GFP_NORETRY告知分配器立即放弃如果得不到请求的内存.

Ø  内存区段

__GFP_DMA和__GFP_HIGHMEM的使用与平台相关,Linux把内存分成3个区段:可用于DMA的内存、常规内存、以及高端内存。X86平台上ISA设备DMA区段是内存的前16MB,而PCI设备无此限制。

内存区后面的机制在 mm/page_alloc.c 中实现, 而内存区的初始化在平台特定的文件中, 常常在 arch 目录树的 mm/init.c。

3>kamlloc的使用方法:

Linux 处理内存分配通过创建一套固定大小的内存对象池. 分配请求被这样来处理, 进入一个持有足够大的对象的池子并且将整个内存块递交给请求者. 驱动开发者应当记住的一件事情是, 内核只能分配某些预定义的, 固定大小的字节数组.

如果你请求一个任意数量内存, 你可能得到稍微多于你请求的, 至多是 2 倍数量. 同样, 程序员应当记住kmalloc 能够处理的最小分配是 32 或者 64 字节, 依赖系统的体系所使用的页大小. kmalloc 能够分配的内存块的大小有一个上限. 这个限制随着体系和内核配置选项而变化. 如果你的代码是要完全可移植, 它不能指望可以分配任何大于 128 KB. 如果你需要多于几个 KB, 但是, 有个比 kmalloc 更好的方法来获得内存。在设备驱动程序或者内核模块中动态开辟内存,不是用malloc,而是kmalloc ,vmalloc,或者用get_free_pages直接申请页。释放内存用的是kfree,vfree,或free_pages. kmalloc函数返回的是虚拟地址(线性地址). kmalloc特殊之处在于它分配的内存是物理上连续的,这对于要进行DMA的设备十分重要. 而用vmalloc分配的内存只是线性地址连续,物理地址不一定连续,不能直接用于DMA.

注意kmalloc最大只能开辟128k-16,16个字节是被页描述符结构占用了。kmalloc用法参见khg.

内存映射的I/O口,寄存器或者是硬件设备的RAM(如显存)一般占用F0000000以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得重新映射以后的地址。

另外,很多硬件需要一块比较大的连续内存用作DMA传送。这块内存需要一直驻留在内存,不能被交换到文件中去。但是kmalloc最多只能开辟大小为32XPAGE_SIZE的内存,一般的PAGE_SIZE=4kB,也就是128kB的大小的内存。

3.kmalloc和vmalloc的区别

• vmalloc()与 kmalloc()都可用于分配内存

• kmalloc()分配的内存处于3GB~high_memory之 间,这段内核空间与物理内存的映射一一对应

•vmalloc()分配的内存在 VMALLOC_START~4GB之间,这段非连续内 存区映射到物理内存也可能是非连续的

• 在内核空间中调用kmalloc()分配连续物理空间,而调用vmalloc()分配非物理连续空间。

• 把kmalloc()所分配内核空间中的地址称为内核逻辑地址

• 把vmalloc()分配的内核空间中的地址称 为内核虚拟地址

• vmalloc()在分配过程中须更新内核页表

总结:

1.kmalloc和vmalloc分配的是内核的内存,malloc分配的是用户的内存

2.kmalloc保证分配的内存在物理上是连续的,kmalloc()分配的内存在0xBFFFFFFF-0xFFFFFFFF以上的内存中,driver一般是用它来完成对DS的分配,更适合于类似设备驱动的程序来使用;

3.vmalloc保证的是在虚拟地址空间上的连续,vmalloc()则是位于物理地址非连续,虚地址连续区,起始位置由VMALLOL_START来决定,一般作为交换区、模块的分配。

3.kmalloc能分配的大小有限,vmalloc和malloc能分配的大小相对较大(因为vmalloc还可以处理交换空间)。

4.内存只有在要被DMA访问的时候才需要物理上连续,vmalloc比kmalloc要慢

5.vmalloc使用的正确场合是分配一大块,连续的,只在软件中存在的,用于缓冲的内存区域。不能在微处理器之外使用。

6.vmalloc 中调用了 kmalloc (GFP—KERNEL),因此也不能应用于原子上下文。

7.kmalloc和 kfree管理内核段内分配的内存,这是真实地址已知的实际物理内存块。

8.vmalloc对应于vfree,分配连续的虚拟内存,但是物理上不一定连续。

9.kmalloc分配内存是基于slab,因此slab的一些特性包括着色,对齐等都具备,性能较好。物理地址和逻辑地址都是连续的

 

三.module_param

1.为什么引入 

 在用户态下编程可以通过main()来传递命令行参数,而编写一个内核模块则可通过module_param()来传递命令行参数. 

2.module_param宏是Linux 2.6内核中新增的,该宏被定义在include/linux/moduleparam.h文件中,具体定义如下:

 

[cpp]  view plain copy
  1. /* Helper functions: type is byte, short, ushort, int, uint, long, 
  2.   ulong,charp, bool or invbool, or XXX if you define param_get_XXX, 
  3.  param_set_XXX and param_check_XXX. 
  4.    */  
  5.  #define module_param_named(name, value, type,perm)                 
  6.   param_check_##type(name,&(value));                     
  7.   module_param_call(name, param_set_##type, param_get_##type, &value,perm);   
  8.   __MODULE_PARM_TYPE(name, #type)  
  9.   #define module_param(name, type,perm)                  
  10.   module_param_named(name, name, type, perm)  

由此可知module_param的实现是通过module_param_named(name, name, type, perm)的。 

3.module_param使用了3个参数:变量名,它的类型,以及一个权限掩码用来做一个辅助的sysfs入口。 

这个宏定义应当放在任何函数之外,典型地是出现在源文件的前面。

eg:static char *whom="world"

   static int    tige=1;

   module_param(tiger,int,S_IRUGO);

   module_param(whom,charp,S_IRUGO); 

4.模块参数支持许多类型: 

bool

invbool

一个布尔型(true 或者 false)值(相关的变量应当是 int 类型). invbool 类型颠倒了值, 所以真值变成 false, 反之亦然.

charp:一个字符指针值. 内存为用户提供的字串分配, 指针因此设置.

int

long

short

uint

ulong

ushort

基本的变长整型值.以 u 开头的是无符号值. 

5.数组参数,用逗号间隔的列表提供的值, 模块加载者也支持。

声明一个数组参数,使用:

module_param_array(name,type,num,perm);

这里 name是你的数组的名子(也是参数名),

type是数组元素的类型,

num是一个整型变量,

perm是通常的权限值.

如果数组参数在加载时设置,num 被设置成提供的数的个数. 模块加载者拒绝比数组能放下的多的值.

Tiger-John说明:

perm参数的作用是什么?

最后的module_param 字段是一个权限值,表示此参数在sysfs文件系统中所对应的文件节点的属性。你应当使用 <linux/stat.h>中定义的值. 这个值控制谁可以存取这些模块参数在 sysfs 中的表示.当perm为0时,表示此参数不存在 sysfs文件系统下对应的文件节点。 否则,模块被加载后,在/sys/module/ 目录下将出现以此模块名命名的目录, 带有给定的权限.。

权限在include/linux/stat.h中有定义

比如:

#defineS_IRWXU 00700

#defineS_IRUSR 00400

#defineS_IWUSR 00200

#defineS_IXUSR 00100

#defineS_IRWXG 00070

#defineS_IRGRP 00040

#defineS_IWGRP 00020

#defineS_IXGRP 00010

#defineS_IRWXO 00007

#defineS_IROTH 00004

#defineS_IWOTH 00002

#defineS_IXOTH 00001

使用S_IRUGO 参数可以被所有人读取, 但是不能改变; S_IRUGO|S_IWUSR 允许 root 来改变参数. 注意, 如果一个参数被 sysfs修改, 你的模块看到的参数值也改变了, 但是你的模块没有任何其他的通知. 你应当不要使模块参数可写, 除非你准备好检测这个改变并且因而作出反应.

 

四.file文件结构

struct file, 定义于<linux/fs.h>, 是设备驱动中第二个最重要的数据结构. 注意 file 与用户空间程序的 FILE 指针没有任何关系. 一个 FILE定义在 C 库中, 从不出现在内核代码中. 一个 struct file, 另一方面, 是一个内核结构, 从不出现在用户程序中.

文件结构代表一个打开的文件.(它不特定给设备驱动; 系统中每个打开的文件有一个关联的 struct file 在内核空间). 它由内核在 open 时创建,并传递给在文件上操作的任何函数, 直到最后的关闭. 在文件的所有实例都关闭后, 内核释放这个数据结构.

在内核源码中, struct file的指针常常称为 file 或者 filp("file pointer"). 我们将一直称这个指针为 filp 以避免和结构自身混淆.因此, file 指的是结构, 而 filp 是结构指针.

struct file 的最重要成员在这展示.如同在前一节, 第一次阅读可以跳过这个列表. 但是, 在本章后面, 当我们面对一些真实 C 代码时, 我们将更详细讨论这些成员.

mode_tf_mode;

文件模式确定文件是可读的或者是可写的(或者都是),通过位 FMODE_READ 和 FMODE_WRITE. 你可能想在你的 open 或者 ioctl 函数中检查这个成员的读写许可,但是你不需要检查读写许可, 因为内核在调用你的方法之前检查. 当文件还没有为那种存取而打开时读或写的企图被拒绝, 驱动甚至不知道这个情况.

loff_tf_pos;

当前读写位置.loff_t 在所有平台都是 64 位( 在 gcc 术语里是 long long ). 驱动可以读这个值, 如果它需要知道文件中的当前位置,但是正常地不应该改变它; 读和写应当使用它们作为最后参数而收到的指针来更新一个位置, 代替直接作用于 filp->f_pos. 这个规则的一个例外是在llseek 方法中, 它的目的就是改变文件位置.

unsignedint f_flags;

这些是文件标志,例如 O_RDONLY, O_NONBLOCK, 和 O_SYNC. 驱动应当检查 O_NONBLOCK 标志来看是否是请求非阻塞操作(我们在第一章的"阻塞和非阻塞操作"一节中讨论非阻塞 I/O ); 其他标志很少使用. 特别地, 应当检查读/写许可, 使用 f_mode而不是 f_flags. 所有的标志在头文件 <linux/fcntl.h> 中定义.

structfile_operations *f_op;

和文件关联的操作.内核安排指针作为它的 open 实现的一部分, 接着读取它当它需要分派任何的操作时. filp->f_op 中的值从不由内核保存为后面的引用;这意味着你可改变你的文件关联的文件操作, 在你返回调用者之后新方法会起作用. 例如, 关联到主编号 1 (/dev/null, /dev/zero, 等等)的open 代码根据打开的次编号来替代 filp->f_op 中的操作. 这个做法允许实现几种行为, 在同一个主编号下而不必在每个系统调用中引入开销.替换文件操作的能力是面向对象编程的"方法重载"的内核对等体.

void*private_data;

open系统调用设置这个指针为 NULL, 在为驱动调用 open 方法之前. 你可自由使用这个成员或者忽略它; 你可以使用这个成员来指向分配的数据,但是接着你必须记住在内核销毁文件结构之前, 在 release 方法中释放那个内存. private_data 是一个有用的资源, 在系统调用间保留状态信息,我们大部分例子模块都使用它.

structdentry *f_dentry;

关联到文件的目录入口(dentry )结构. 设备驱动编写者正常地不需要关心 dentry 结构, 除了作为 filp->f_dentry->d_inode 存取inode 结构.

真实结构有多几个成员,但是它们对设备驱动没有用处. 我们可以安全地忽略这些成员, 因为驱动从不创建文件结构; 它们真实存取别处创建的结构.

 

五.inode结构

inode 结构由内核在内部用来表示文件. 因此, 它和代表打开文件描述符的文件结构是不同的.可能有代表单个文件的多个打开描述符的许多文件结构, 但是它们都指向一个单个 inode 结构.

inode 结构包含大量关于文件的信息. 作为一个通用的规则, 这个结构只有 2 个成员对于编写驱动代码有用:

dev_ti_rdev;

对于代表设备文件的节点,这个成员包含实际的设备编号.

structcdev *i_cdev;

structcdev 是内核的内部结构, 代表字符设备; 这个成员包含一个指针, 指向这个结构, 当节点指的是一个字符设备文件时.

i_rdev 类型在 2.5 开发系列中改变了, 破坏了大量的驱动. 作为一个鼓励更可移植编程的方法, 内核开发者已经增加了 2 个宏,可用来从一个 inode 中获取主次编号:

unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);

为了不要被下一次改动抓住, 应当使用这些宏代替直接操作 i_rdev.

 

六.container_of()

container_of()的作用是通过结构体成员的指针找到对应

结构体的指针,这个技巧在Linux内核编程中十分常用。在container_of

(inode->i_cdev,structglobalmem_dev,cdev)语句中,传给container_of()的第1个参数是结

构体成员的指针,第2个参数为整个结构体的类型,第3 个参数为传入的第1 个参数即

结构体成员的类型,container_of()返回值为整个结构体的指针。


七.ioctl函数

首先说明在2.6.36以后ioctl函数已经不再存在了,而是用unlocked_ioctl和compat_ioctl两个函数实现以前版本的ioctl函数。同时在参数方面也发生了一定程度的改变,去除了原来ioctl中的struct inode参数,同时改变了返回值。

但是驱动设计过程中存在的问题变化并不是很大,同样在应用程序设计中我们还是采用ioctl实现访问,而并不是unlocked_ioctl函数,因此我们还可以称之为ioctl函数的实现。

ioctl函数的实现主要是用来实现具体的硬件控制,采用相应的命令控制硬件的具体操作,这样就能使得硬件的操作不再是单调的读写操作。使得硬件的使用更加的方便。

ioctl函数实现主要包括两个部分,首先是命令的定义,然后才是ioctl函数的实现,命令的定义是采用一定的规则。

ioctl的命令主要用于应用程序通过该命令操作具体的硬件设备,实现具体的操作,在驱动中主要是对命令进行解析,通过switch-case语句实现不同命令的控制,进而实现不同的硬件操作。

  • ioctl函数的命令定义方法:

    int (*unlocked_ioctl)(struct file*filp,unsigned int cmd,unsigned long arg)

    虽然其中没有指针的参数,但是通常采用arg传递指针参数。cmd是一个命令。每一个命令由一个整形数据构成(32bits),将一个命令分成四部分,每一部分实现具体的配置,设备类型(幻数)8bits,方向2bits,序号8bits,数据大小13/14bits。命令的实现实质上就是通过简单的移位操作,将各个部分组合起来而已。

    一个命令的分布的大概情况如下:

    |---方向位(31-30)|----数据长度(29-16)----------------|---------设备类型(15-8)------|----------序号(7-0)----------|

    |----------------------------------------------------------------------------------------------------------------------------------------|

    其中方向位主要是表示对设备的操作,比如读设备,写设备等操作以及读写设备等都具有一定的方向,2个bits只有4种方向。

    数据长度表示每一次操作(读、写)数据的大小,一般而已每一个命令对应的数据大小都是一个固定的值,不会经常改变,14bits说明可以选择的数据长度最大为16k。

    设备类型类似于主设备号(由于8bits,刚好组成一个字节,因此经常采用字符作为幻数,表示某一类设备的命令),用来区别不同的命令类型,也就是特定的设备类型对应特定的设备。序号主要是这一类命令中的具体某一个,类似于次设备号(256个命令),也就是一个设备支持的命令多达256个。

    同时在内核中也存在具体的宏用来定义命令以及解析命令。

    但是大部分的宏都只是定义具体的方向,其他的都需要设计者定义。

    主要的宏如下:

    [cpp]  view plain copy
    1. #include<linux/ioctl.h>  
    2. _IO(type,nr)                    表示定义一个没有方向的命令,  
    3. _IOR(type,nr,size)            表示定义一个类型为type,序号为nr,数据大小为size的读命令  
    4. _IOW(type,nr,size)           表示定义一个类型为type,序号为nr,数据大小为size的写命令  
    5. _IOWR(type,nr,size)         表示定义一个类型为type,序号为nr,数据大小为size的写读命令  

    通常的type可采用某一个字母或者数字作为设备命令类型。

    是实际运用中通常采用如下的方法定义一个具体的命令:

    [cpp]  view plain copy
    1. //头文件  
    2. #include<linux/ioctl.h>  
    3. /*定义一系列的命令*/  
    4. /*幻数,主要用于表示类型*/  
    5. #define MAGIC_NUM 'k'  
    6. /*打印命令*/  
    7. #define MEMDEV_PRINTF _IO(MAGIC_NUM,1)  
    8. /*从设备读一个int数据*/  
    9. #define MEMDEV_READ _IOR(MAGIC_NUM,2,int)  
    10. /*往设备写一个int数据*/  
    11. #define MEMDEV_WRITE _IOW(MAGIC_NUM,3,int)  
    12. /*最大的序列号*/  
    13. #define MEM_MAX_CMD 3  

    还有对命令进行解析的宏,用来确定具体命令的四个部分(方向,大小,类型,序号)具体如下所示:

    [cpp]  view plain copy
    1. /*确定命令的方向*/  
    2. _IOC_DIR(nr)                      
    3. /*确定命令的类型*/  
    4. _IOC_TYPE(nr)                       
    5. /*确定命令的序号*/  
    6. _IOC_NR(nr)                             
    7. /*确定命令的大小*/  
    8. _IOC_SIZE(nr)      

    上面的几个宏可以用来命令,实现命令正确性的检查。

    ioctl的实现过程主要包括如下的过程:

    1、命令的检测

    2、指针参数的检测

    3、命令的控制switch-case语句

    1、命令的检测主要包括类型的检查,数据大小,序号的检测,通过结合上面的命令解析宏可以快速的确定。

    [cpp]  view plain copy
    1. /*检查类型,幻数是否正确*/  
    2. if(_IOC_TYPE(cmd)!=MAGIC_NUM)  
    3.         return -EINVAL;  
    4. /*检测命令序号是否大于允许的最大序号*/  
    5. if(_IOC_NR(cmd)> MEM_MAX_CMD)  
    6.         return -EINVAL;  

    2、主要是指针参数的检测。指针参数主要是因为内核空间和用户空间的差异性导致的,因此需要来自用户空间指针的有效性。使用copy_from_user,copy_to_user,get_user,put_user之类的函数时,由于函数会实现指针参量的检测,因此可以省略,但是采用__get_user(),__put_user()之类的函数时一定要进行检测。具体的检测方法如下所示:

    [cpp]  view plain copy
    1. if(_IOC_DIR(cmd) & _IOC_READ)  
    2.         err = !access_ok(VERIFY_WRITE,(void *)args,_IOC_SIZE(cmd));  
    3. else if(_IOC_DIR(cmd) & _IOC_WRITE)  
    4.         err = !access_ok(VERIFY_READ,(void *)args,_IOC_SIZE(cmd));  
    5. if(err)/*返回错误*/  
    6.         return -EFAULT;  

    当方向是读时,说明是从设备读数据到用户空间,因此要检测用户空间的指针是否可写,采用VERIFY_WRITE,而当方向是写时,说明是往设备中写数据,因此需要检测用户空间中的指针的可读性VERIFY_READ。检查通常采用access_ok()实现检测,第一个参数为读写,第二个为检测的指针,第三个为数据的大小。

    3、命名的控制:

    命令的控制主要是采用switch和case相结合实现的,这于window编程中的检测各种消息的实现方式是相同的。

    [cpp]  view plain copy
    1. /*根据命令执行相应的操作*/  
    2.         switch(cmd)  
    3.         {  
    4.                 case MEMDEV_PRINTF:  
    5.                         printk("<--------CMD MEMDEV_PRINTF Done------------>\n\n");  
    6.                         ...  
    7.                         break;  
    8.                 case MEMDEV_READ:  
    9.                         ioarg = &mem_devp->data;  
    10.                         ...  
    11.                         ret = __put_user(ioarg,(int *)args);  
    12.                         ioarg = 0;  
    13.                         ...  
    14.                         break;  
    15.                 case MEMDEV_WRITE:  
    16.                         ...  
    17.                         ret = __get_user(ioarg,(int *)args);  
    18.                         printk("<--------CMD MEMDEV_WRITE Done ioarg = %d--------->\n\n",ioarg);   
    19.                         ioarg = 0;  
    20.                         ...  
    21.                         break;  
    22.                 default:  
    23.                         ret = -EINVAL;  
    24.                         printk("<-------INVAL CMD--------->\n\n");  
    25.                         break;  
    26.         }  

    这只是基本的框架结构,实际中根据具体的情况进行修改。这样就实现了基本的命令控制。

    文件操作支持的集合如下:

    [cpp]  view plain copy
    1. /*添加该模块的基本文件操作支持*/  
    2. static const struct file_operations mem_fops =  
    3. {  
    4.         /*结尾不是分号,注意其中的差别*/  
    5.         .owner = THIS_MODULE,  
    6.         .llseek = mem_llseek,  
    7.         .read = mem_read,  
    8.         .write = mem_write,  
    9.         .open = mem_open,  
    10.         .release = mem_release,  
    11.         /*添加新的操作支持*/  
    12.         .unlocked_ioctl = mem_ioctl,  
    13. };  

    需要注意不是ioctl,而是unlocked_ioctl。

 

参考链接:

http://blog.csdn.net/tigerjb/article/details/6412881

http://blog.csdn.net/lixuyuan/article/details/6246893

http://blog.chinaunix.net/space.php?uid=20937170&do=blog&id=3033633



以下是自己已编译OK 的代码

Description:
    This is a simple Character Device Drives with globalmem.

Author/Created Date:
    Lumi-liu, Nov19'14

Modification History:


Note:


=====================================*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/kdev_t.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include <linux/slab.h>


MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Lumi-liu");


#define DEVICE_NAME "globalmem"
#define GLOBALMEM_SIZE 0x1000
#define MEM_CLEAR 0x1
//#define GLOBALMEM_MAJOR 255  //指定主设备号
#define GLOBALMEM_MAJOR 0  //动态申请主设备号

static int globalmem_major = GLOBALMEM_MAJOR;


static struct class *globalmem_class;

//设备结构体
struct globalmem_dev
{
    struct cdev cdev;
    unsigned char mem[GLOBALMEM_SIZE];
};

//设备结构体指针
struct globalmem_dev *globalmem_devp;

//设备文件打开
int globalmem_open(struct inode *inode,struct file *filp)
{
    filp->private_data = globalmem_devp;
    return 0;
}

//设备文件释放
int globalmem_release(struct inode *inode, struct file *filp)
{
    return 0;
}

//设备文件控制
static long globalmem_ioctl(/*struct inode *inodep, */struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct globalmem_dev *dev = filp->private_data;

    switch (cmd)
    {
        case MEM_CLEAR:
            memset(dev->mem,0,GLOBALMEM_SIZE);
            printk(KERN_INFO "globalmem is set to zero\n");
            break;

        default:
            return -EINVAL;
    }
    return 0;
}

//设备文件读
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;

    struct globalmem_dev *dev = filp->private_data;

    if (p > GLOBALMEM_SIZE) 
        return count ? -ENXIO: 0;
    //    return 0;
    if (count > GLOBALMEM_SIZE - p)
        count = GLOBALMEM_SIZE - p;

    if (copy_to_user(buf, (void*)(dev->mem + p), count))
    {
        ret = -EFAULT;
    }
    else
    {
        *ppos += count;
        ret = count;

        printk(KERN_INFO "read %u bytes(s) from %lu\n",count,p);
    }

    return ret;
}

//设备文件写
static ssize_t globalmem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
    struct globalmem_dev *dev = filp->private_data;

    if (p > GLOBALMEM_SIZE)
        return count ? -ENXIO: 0;
    //    return 0;
    if (count > GLOBALMEM_SIZE - p)
        count = GLOBALMEM_SIZE - p;

    if (copy_from_user(dev->mem + p, buf, count))
    {
            ret = -EFAULT;
    }
    else
    {
        *ppos += count;
        ret = count;

        printk(KERN_INFO "written %u bytes(s) from %lu\n",count, p);
    }
    return ret;
}

/*
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
    loff_t ret = 0;
    switch (orig)
    {
        case 0:
            if (offset < 0)
            {
                ret = -EINVAL;
                break;
            }
            if ((unsigned int)offset > GLOBALMEM_SIZE)
            {
                ret = -EINVAL;
                break;
            }
            filp->f_pos = (unsigned int)offset;
            ret = filp->f_pos;
            break;
        case 1:
            if ((filp->f_pos + offset) > GLOBALMEM_SIZE)
            {
                ret = -EINVAL;
                break;
            }
            if ((filp->f_pos + offset) < 0)
            {
                    ret = -EINVAL;
                    break;
            }
            filp->f_pos += offset;
            ret = filp->f_pos;
            break;
        default:
            ret = -EINVAL;
            break;
    }
    return ret;
}
*/

//文件操作结构体
static const struct file_operations globalmem_fops =
{
    .owner = THIS_MODULE,
//    .llseek = globalmem_llseek,
    .read = globalmem_read,
    .write = globalmem_write,
//    .ioctl = globalmem_ioctl,
    .compat_ioctl = globalmem_ioctl,
//    .unlocked_ioctl = globalmem_ioctl,
    .open = globalmem_open,
    .release = globalmem_release,
};

//初始化并注册设备
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
    int err;
    int devno = MKDEV(globalmem_major,index);

    cdev_init(&dev->cdev, &globalmem_fops);
    dev->cdev.owner = THIS_MODULE;
    dev->cdev.ops = &globalmem_fops;
    err = cdev_add(&dev->cdev, devno, 1);
    if (err)
        printk(KERN_NOTICE "Error %d adding GLOBALMEM %d",err, index);
}


//设备模块加载
int globalmem_init(void)
{
    int result;

    dev_t devno = MKDEV(globalmem_major, 0);

    if (globalmem_major)
        result = register_chrdev_region(devno, 1, "globalmem");
    else
    {
        result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
        globalmem_major = MAJOR(devno);
    }
    if (result < 0)
        return result;
    //动态申请结构体内存
    globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
    if (!globalmem_devp)
    {
            result = -ENOMEM;
            goto fail_malloc;
    }
    memset(globalmem_devp, 0, sizeof(struct globalmem_dev));

    globalmem_setup_cdev(globalmem_devp, 0);


    globalmem_class =class_create(THIS_MODULE, "globalmem");   
    if( IS_ERR(globalmem_class) )   
    {   
        printk(KERN_NOTICE "creatglobalmem_class failed!");   
        return -1;   
    }   
   
    //创建一个设备节点,节点名字为globalmem   
    device_create(globalmem_class, NULL,MKDEV(globalmem_major, 0), 
                    NULL, "globalmem");   
    printk(KERN_NOTICE "globalmem initialized!");


    return 0;
    
fail_malloc: 
    unregister_chrdev_region(devno, 1);
    return result;
}

void globalmem_exit(void)
{
    dev_t devno = MKDEV(globalmem_major, 0);

    device_destroy(globalmem_class,devno);
    class_destroy(globalmem_class);

    //kfree(globalmem_devp);
    cdev_del(&globalmem_devp->cdev);
    kfree(globalmem_devp);
    unregister_chrdev_region(devno,/*MKDEV(globalmem_major, 0),*/ 1);
    printk(KERN_NOTICE "globalmem goodbye!");
}


module_param(globalmem_major, int, S_IRUGO);

module_init(globalmem_init);
module_exit(globalmem_exit);









Makefile文件



obj-m := globalmem.o
KERNEL_DIR := /lib/modules/$(shell uname -r)/build
#PWD := $(shell pwd)
all:
    make -C $(KERNEL_DIR) SUBDIRS=$(shell pwd) modules
clean:
    rm -rf *.o *.ko *.mod.c *.mod.o *.symvers *.order .*.ko.cmd .*.o.cmd .*.mod.o.cmd .*.swp .tmp_versions


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值