Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3962508
  • 博文数量: 366
  • 博客积分: 9916
  • 博客等级: 中将
  • 技术积分: 7195
  • 用 户 组: 普通用户
  • 注册时间: 2011-05-29 23:27
个人简介

简单!

文章分类

全部博文(366)

文章存档

2013年(51)

2012年(269)

2011年(46)

分类: LINUX

2012-03-03 21:13:12

       /proc文件系统是一个特殊的软件创建的文件系统, 内核用来输出消息到外界. /proc 下的每个文件都绑到一个内核函数上, 当文件被读的时候即时产生文件内容. 我们已经见到一些这样的文件起作用; 例如, /proc/modules, 常常返回当前已加载的模块列表.

       /proc 在 Linux 系统中非常多地应用. 很多现代 Linux 发布中的工具, 例如 ps, top, 以及 uptime, 从 /proc 中获取它们的信息. 一些设备驱动也通过 /proc 输出信息. /proc 文件系统是动态的, 因此你的模块可以在任何时候添加或去除条目.

      完全特性的 /proc 条目可能是复杂的; 另外, 它们可写也可读, 但是, 大部分时间, /proc 条目是只读的文件.

1. 在 /proc 里实现文件

       要创建一个只读 /proc 文件, 你的驱动必须实现一个函数来在文件被读时产生数据. 当某个进程读文件时(使用 read 系统调用), 这个请求通过这个函数到达你的模块.

       当一个进程读你的 /proc 文件, 内核分配了一页内存(就是说, PAGE_SIZE 字节), 驱动可以写入数据来返回给用户空间. 那个缓存区传递给你的函数, 是一个称为 read_proc 的方法:

int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data);

page 指针是你写你的数据的缓存区; start 是这个函数用来说有关的数据写在页中哪里(下面更多关于这个); offset 和 count 对于 read 方法有同样的含义. eof 参数指向一个整数, 必须由驱动设置来指示它不再有数据返回, data 是驱动特定的数据指针, 你可以用做内部用途.

       这个函数应当返回实际摆放于 page 缓存区的数据的字节数, 就象 read 方法对别的文件所作一样. 别的输出值是 *eof 和 *start. eof 是一个简单的标志, 但是 start 值的使用有些复杂; 它的目的是帮助实现大的(超过一页) /proc 文件.

       start 参数有些非传统的用法. 它的目的是指示哪里(哪一页)找到返回给用户的数据. 当调用你的 proc_read 方法, *start 将会是 NULL. 如果你保持它为 NULL, 内核假定数据已放进 page 偏移是 0; 换句话说, 它假定一个头脑简单的 proc_read 版本, 它安放虚拟文件的整个内容到 page, 没有注意 offset 参数. 如果, 相反, 你设置 *start 为一个 非NULL 值, 内核认为由 *start 指向的数据考虑了 offset, 并且准备好直接返回给用户. 通常, 返回少量数据的简单 proc_read 方法只是忽略 start. 更复杂的方法设置 *start 为 page 并且只从请求的 offset 那里开始安放数据.

       还有一段距离到 /proc 文件的另一个主要问题, 它也打算解答 start. 有时内核数据结构的 ASCII 表示在连续的 read 调用中改变, 因此读进程可能发现从一个调用到下一个有不一致的数据. 如果 *start 设成一个小的整数值, 调用者用它来递增 filp-

       注意, 有更好的方法实现大的 /proc 文件; 它称为 seq_file, 我们很快会讨论它. 首先, 然而, 是时间举个例子了. 下面是一个简单的(有点丑陋) read_proc 实现, 为 scull 设备:

int scull_read_procmem(char *buf, char **start, off_t offset,int count, int *eof, void *data)
{
int len = 0;

SCULL_DEV *pscull = pscull_dev;

if (down_interruptible(&pscull->sem))
return -ERESTARTSYS;

len += sprintf(buf+len,"\nScull Device: qset %i, quantum %i, size %li\n",pscull->qset, pscull->quantum, pscull->size);

up(&pscull->sem);

*eof = 1;

return len;
}
2. 老接口

如果你阅览内核源码, 你会遇到使用老接口实现 /proc 的代码:

int (*get_info)(char *page, char **start, off_t offset, int count);

所有的参数的含义同 read_proc 的相同, 但是没有 eof 和 data 参数. 这个接口仍然支持, 但是将来会消失; 新代码应当使用 read_proc 接口来代替.

3. 创建你的 /proc 文件

一旦你有一个定义好的 read_proc 函数, 你应当连接它到 /proc 层次中的一个入口项. 使用一个 creat_proc_read_entry 调用:

struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode, struct proc_dir_entry *base, read_proc_t *read_proc, void *data);

这里, name 是要创建的文件名子, mod 是文件的保护掩码(缺省系统范围时可以作为 0 传递), base 指出要创建的文件的目录( 如果 base 是 NULL, 文件在 /proc 根下创建 ), read_proc 是实现文件的 read_proc 函数, data 被内核忽略( 但是传递给 read_proc). 这就是 scull 使用的调用, 来使它的 /proc 函数可用做 /proc/scullmem:

create_proc_read_entry("scullmem", 0 /* default mode */, NULL /* parent dir */, scull_read_procmem, NULL /* client data */);

这里, 我们创建了一个名为 scullmem 的文件, 直接在 /proc 下, 带有缺省的, 全局可读的保护.

目录入口指针可用来在 /proc 下创建整个目录层次. 但是, 注意, 一个入口放在 /proc 的子目录下会更容易, 通过简单地给出目录名子作为这个入口名子的一部分 -- 只要这个目录自身已经存在. 例如, 一个(常常被忽略)传统的是 /proc 中与设备驱动相连的入口应当在 driver/ 子目录下; scull 能够安放它的入口在那里, 简单地通过指定它为名子 driver/scullmem.

/proc 中的入口, 当然, 应当在模块卸载后去除. remove_proc_entry 是恢复 create_proc_read_entry 所做的事情的函数:

remove_proc_entry("scullmem", NULL /* parent dir */);
  1. #ifndef __KERNEL__
  2. # define __KERNEL__
  3. #endif
  4. #ifndef MODULE
  5. # define MODULE
  6. #endif

  7. #include <linux/init.h>
  8. #include <linux/module.h>
  9. #include <linux/cdev.h>
  10. #include <linux/slab.h>
  11. #include <linux/kernel.h> /* printk() */
  12. #include <linux/fs.h> /* everything... */
  13. #include <linux/errno.h> /* error codes */
  14. #include <linux/types.h> /* size_t */
  15. #include <linux/proc_fs.h>
  16. #include <linux/fcntl.h> /* O_ACCMODE */
  17. #include <asm/system.h> /* cli(), *_flags */
  18. #include <asm/uaccess.h>

  19. #include "scull.h" /* local definitions */


  20. int scull_major = SCULL_MAJOR;
  21. int scull_minor    = SCULL_MINOR;
  22. int scull_mn_dev = 1;
  23. int scull_quantum = 4000;    /*每个指针所指向存储单元的大小*/
  24. int scull_qset = 4000;    /*指针数组的大小*/
  25. static pSCULL_DEV *pscull_dev = NULL;



  26. int scull_read_procmem(char *buf, char **start, off_t offset,int count, int *eof, void *data)
  27. {
  28.     int len = 0;

  29.     SCULL_DEV *pscull = pscull_dev;

  30.     if (down_interruptible(&pscull->sem))
  31.         return -ERESTARTSYS;

  32.     len += sprintf(buf+len,"\nScull Device: qset %i, quantum %i, size %li\n",pscull->qset, pscull->quantum, pscull->size);

  33.     up(&pscull->sem);

  34.     *eof = 1;

  35.     return len;
  36. }

  37. static void scull_create_proc(void)
  38. {
  39.     create_proc_read_entry("scullmem", 0,NULL, scull_read_procmem,NULL);
  40. }

  41. static void scull_remove_proc(void)
  42. {
  43.     remove_proc_entry("scullmem", NULL);
  44. }

  45. int scull_trim(SCULL_DEV *pscull)
  46. {
  47.     int i = 0;
  48.     int qset = pscull->qset;

  49.     if (pscull->data)
  50.     {
  51.         for(i = 0; i < qset; i++)
  52.         {
  53.             if (pscull->data[i])
  54.                 kfree(pscull->data[i]);
  55.         }

  56.         kfree(pscull->data);
  57.         pscull->data=NULL;
  58.     }

  59.     pscull->size = 0;
  60.     pscull->quantum = scull_quantum;
  61.     pscull->qset = scull_qset;
  62.  
  63.     return 0;
  64. }

  65. /*open方法提供给驱动程序以初始化的能力。它应完成如下工作:1、检查设备特定的错误;2、如果设备是首次打开,则对其进行初始化;3、如有必要,更新f_op指针;4、分配并填写置于filp->private_data里的数据结构。*/
  66. int scull_open(struct inode *inode, struct file *filp)
  67. {
  68.     SCULL_DEV *pscull = NULL;

  69.     /*获取我们自定义结构的指针*/
  70.     pscull = container_of(inode->i_cdev,pSCULL_DEV,cdev);
  71.     /*将自定义结构的指针保存到file结构的private_data字段中,这样可以方便以后对该指针的访问*/
  72.     filp->private_data = pscull;
  73.     if ( (filp->f_flags & O_ACCMODE) == O_WRONLY)
  74.     {
  75.         scull_trim(pscull);
  76.     }

  77.     return 0;
  78. }

  79. /*release主要的工作是:1、释放由open分配的、保存在filp->private_data中的所有内容;2、在最后一次关闭操作时关闭设备。*/
  80. int scull_release(struct inode *inode, struct file *filp)
  81. {
  82.     return 0;
  83. }

  84. /*
  85.  *调用程序对read的返回值解释如下:
  86.  *1、如果返回值等于传递给read系统调用的count参数,则说明所请求的字节数传输成功完成了。
  87.  *2、如果返回值是正的,但是比count小,则说明只有部分数据成功传送。这种情况因为设备的不同可能有许多原因。大部分情况下,程序会重新读数据。例如,如果用fread函数读数据,这个库函数就会不断调用系统调用,直至所请求的数据传输完毕为止。
  88.  *3、如果返回值为0,则表示已经到达了文件尾。
  89.  *4、负值意味着发生了错误,该值指明了发生什么错误,错误码在<linux/errno.h>中定义
  90. */
  91. ssize_t scull_read(struct file *filp, char *buf, size_t count,loff_t *f_pos)
  92. {
  93.     SCULL_DEV *pscull = filp->private_data;
  94.     int quantum = pscull->quantum;
  95.     int qset = pscull->qset;
  96.     int s_pos = 0, q_pos = 0;
  97.     ssize_t ret = 0;

  98.     if(down_interruptible(&pscull->sem))
  99.         return -ERESTARTSYS;
  100.     
  101.     if(*f_pos >= pscull->size)
  102.         goto out;
  103.     
  104.     if(*f_pos + count > pscull->size)
  105.         count = pscull->size - *f_pos;
  106.     
  107.     s_pos = (long)*f_pos/quantum;
  108.     q_pos = (long)*f_pos%quantum;

  109.     if(s_pos > qset)
  110.         goto out;
  111.     
  112.     if(!pscull->data)
  113.         goto out;
  114.     
  115.     if(!pscull->data[s_pos])
  116.         goto out;
  117.    
  118.     if(count > quantum-q_pos)
  119.         count = quantum-q_pos;

  120.     /*将内核空间的数据拷贝到用户空间*/
  121.     if(copy_to_user(buf, pscull->data[s_pos]+q_pos, count))
  122.     {
  123.         ret = -EFAULT;
  124.         goto out;
  125.     }

  126.     *f_pos += count;
  127.     ret = count;

  128. out:
  129.     up(&pscull->sem);

  130.     return ret;    
  131. }

  132. /*
  133.  *调用程序对write的返回值解释如下:
  134.  *1、如果返回值等于count,则完成了所请求数目的字节传送。
  135.  *2、如果返回值是正的,但小于count,则只传送了部分数据。程序很可能再次试图写入余下的数据。
  136.  *3、如果值为0,意味着什么也没有写入。这个结果不是错误,而且也没有理由返回一个错误码。
  137.  *4、负值意味着发生了错误。
  138. */
  139. ssize_t scull_write(struct file *filp, const char *buf, size_t count,loff_t *f_pos)
  140. {
  141.     SCULL_DEV *pscull = filp->private_data;
  142.     int quantum = pscull->quantum;
  143.     int qset = pscull->qset;
  144.     int s_pos,q_pos;
  145.     ssize_t ret = -ENOMEM;

  146.     if(down_interruptible(&pscull->sem))
  147.         return -ERESTARTSYS;

  148.     s_pos = (long)*f_pos/quantum;
  149.     q_pos = (long)*f_pos%quantum;

  150.     if(!pscull->data)
  151.     {
  152.         pscull->data = kmalloc(qset*sizeof(char *), GFP_KERNEL);
  153.         if (!pscull->data)
  154.             goto out;
  155.         memset(pscull->data, 0, qset*sizeof(char *));
  156.     }

  157.     if(!pscull->data[s_pos])
  158.     {
  159.         pscull->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
  160.         if (!pscull->data[s_pos])
  161.             goto out;
  162.         memset(pscull->data[s_pos],0,quantum);
  163.     }

  164.     if(count > quantum-q_pos)
  165.         count = quantum-q_pos;

  166.     /*将用户空间的数据拷贝到内核空间*/
  167.     if(copy_from_user(pscull->data[s_pos]+q_pos, buf, count))    
  168.     {
  169.         ret = -EFAULT;
  170.         goto out;
  171.     }

  172.     *f_pos += count;
  173.     ret = count;

  174.     if(pscull->size < *f_pos)
  175.         pscull->size = *f_pos;
  176.     
  177. out:
  178.     up(&pscull->sem);

  179.     return ret;
  180. }

  181. int scull_ioctl(struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg)
  182. {
  183.     return 0;
  184. }

  185. loff_t scull_llseek(struct file *filp, loff_t off, int whence)
  186. {
  187.     return 0;
  188. }

  189. struct file_operations scull_fops = {
  190.     .owner     =    THIS_MODULE,
  191.     .llseek    =    scull_llseek,
  192.     .read    =    scull_read,
  193.     .write    =    scull_write,
  194.     .ioctl    =    scull_ioctl,
  195.     .open    =    scull_open,
  196.     .release =    scull_release,
  197. };

  198. /*模块的清除函数*/
  199. static void scull_cleanup_module(void)
  200. {
  201.     dev_t dev;

  202.     scull_remove_proc();
  203.     /*获取起始设备号*/
  204.     dev = MKDEV(scull_major,scull_minor);
  205.     /*从系统中移除一个字符设备*/
  206.     cdev_del(&pscull_dev->cdev);
  207.     /*释放不再使用的设备编号,第一个参数是起始设备编号,第二个参数是需要释放的连续编号数*/
  208.     unregister_chrdev_region(dev,scull_mn_dev);
  209.     scull_trim(pscull_dev);
  210.     kfree(pscull_dev);
  211.     pscull_dev = NULL;    

  212.     return;
  213. }

  214. /*模块的初始化函数*/
  215. static int scull_init_module(void)
  216. {
  217.     /*在内核的2.6.0版本中,dev_t是一个32位的数,其中12位用来表示主设备号,而其余20位用来表示次设备号*/
  218.     dev_t dev;
  219.     int result = -1;

  220.     pscull_dev = kmalloc(sizeof(SCULL_DEV),GFP_KERNEL);
  221.     if(pscull_dev == NULL)
  222.     {
  223.         printk(KERN_WARNING"scull: can't kmalloc memory\n");
  224.         result = -ENOMEM;
  225.         return result;
  226.     }
  227.     memset(pscull_dev,0,sizeof(SCULL_DEV));
  228.     pscull_dev->data = NULL;
  229.     pscull_dev->quantum = scull_quantum;
  230.     pscull_dev->qset = scull_qset;

  231.     /*创建信号量,并将信号量的初始值赋值为1*/
  232.     sema_init(&pscull_dev->sem,1);

  233.     /*用来获取主设备号,可由程序指定(或在insmod的时候指定相应值)或采用内核动态分配的方式*/
  234.     if(scull_major)
  235.     {
  236.         /*将主设备号和次设备号转换成dev_t*/
  237.         dev = MKDEV(scull_major,scull_minor);    
  238.         /*获得设备的一个或多个编号,该种方式是由程序指定设备号,函数的第一个参数是设备号的起始值(通常次设备号都被设置成0),第二个参数表示所请求的连续设备编号的个数,第三个参数是和该编号范围关联的设备名称,它将出现在/proc/devices和sysfs中*/
  239.         result = register_chrdev_region(dev,scull_mn_dev,"scull");    
  240.     }
  241.     else
  242.     {
  243.         /*通常情况下,我们都不知道使用哪个设备号。使用以下的函数,内核将恰当的为我们分配所需的主设备号,第一个参数仅用于输出的参数,在成功完成调用后将保存已分配范围的第一个编号,第二个参数是要使用的被请求的第一个次设备号,第三个参数表示所请求的连续设备编号的个数,第四个参数是和该编号范围关联的设备名称,它将出现在/proc/devices和sysfs中*/
  244.         result = alloc_chrdev_region(&dev,scull_minor,scull_mn_dev,"scull");
  245.         /*获取主设备号,采用MINOR(dev)可以获取到次设备号*/
  246.         scull_major = MAJOR(dev);    
  247.     }

  248.     if(result < 0)
  249.     {
  250.         printk(KERN_WARNING"scull: can't get major %d\n",scull_major);
  251.         return result;
  252.     }

  253.     /*在内核内部,使用struct cdev结构来表示字符设备,以下函数可以初始化已分配到的cdev结构。因为本例中我们将cdev结构嵌入到自己的特定设备结构中,所以采用下面的函数。也可以使用cdev_alloc()来分配一个cdev字符设备结构*/
  254.     cdev_init(&pscull_dev->cdev,&scull_fops);
  255.     /*初始化cdev结构的所有者字段*/
  256.     pscull_dev->cdev.owner = THIS_MODULE;
  257.     /*初始化设备的文件操作接口结构的指针*/
  258.     pscull_dev->cdev.ops = &scull_fops;
  259.     /*将cdev结构告诉内核,第一个参数是struct cdev机构,第二个是该设备对应的第一个设备编号,第三个参数是和该设备关联的设备编号的数量。在使用cdev_add时,需要注意:首先,这个调用可能会失败。如果它返回一个负的错误码,则设备不会被添加到系统中。但这个调用总是会成功返回。只要cdev_add成功返回了,设备就启动了,它的操作就会被内核调用。因此,在驱动程序还没有完全准备好处理设备上的操作时,就不能调用cdev_add。*/
  260.     result = cdev_add(&pscull_dev->cdev,dev,1);
  261.     if(result)
  262.     {
  263.         printk(KERN_NOTICE"Error %d add scull device\n",result);
  264.         goto fail;
  265.     }

  266.     scull_create_proc();

  267.     return 0;

  268. fail:
  269.     scull_cleanup_module();

  270.     return result;
  271. }



  272. /*用于指定模块的初始化和清除函数的宏*/
  273. module_init(scull_init_module);
  274. module_exit(scull_cleanup_module);

  275. /*在目标文件中添加关于模块的文档信息*/
  276. MODULE_AUTHOR("txgcwm");
  277. MODULE_VERSION("scull_v1.0");
  278. MODULE_LICENSE("GPL");

当运行我们之前写的测试程序的时候,可以从proc目录下抓取到如下的信息:
  1. Scull Device: qset 4000, q 4000, sz 0
  2. root@txgcwm:/proc# cat scullmem

  3. Scull Device: qset 4000, q 4000, sz 216
  4. root@txgcwm:/proc# cat scullmem

  5. Scull Device: qset 4000, q 4000, sz 312
  6. root@txgcwm:/proc# cat scullmem

  7. Scull Device: qset 4000, q 4000, sz 360
  8. root@txgcwm:/proc# cat scullmem

  9. Scull Device: qset 4000, q 4000, sz 408
  10. root@txgcwm:/proc# cat scullmem

  11. Scull Device: qset 4000, q 4000, sz 456
  12. root@txgcwm:/proc# cat scullmem

  13. Scull Device: qset 4000, q 4000, sz 576

阅读(2165) | 评论(1) | 转发(1) |
给主人留下些什么吧!~~

txgc_wm2012-03-04 18:57:10

http://blog.chinaunix.net/link.php?url=http://www.embeddedlinux.org.cn%2Fldd3%2Findex.html
chinaunix中《Linux设备驱动程序第三版》中文地址