本文转载自:http://blog.csdn.net/coding__madman/article/details/51346532

下面先通过一个编写好的内核驱动模块来体验以下字符设备驱动

可以暂时先忽略下面的代码实现!

memdev.c

  1. #include <linux/module.h>
  2. #include <linux/fs.h>
  3. #include <linux/init.h>
  4. #include <linux/cdev.h>
  5. #include <asm/uaccess.h>
  6. int dev1_registers[5];
  7. int dev2_registers[5];
  8. struct cdev cdev;
  9. dev_t devno;
  10. /*文件打开函数*/
  11. int mem_open(struct inode *inode, struct file *filp)
  12. {
  13. /*获取次设备号*/
  14. int num = MINOR(inode->i_rdev);
  15. if (num==0)
  16. filp->private_data = dev1_registers;
  17. else if(num == 1)
  18. filp->private_data = dev2_registers;
  19. else
  20. return -ENODEV;  //无效的次设备号
  21. return 0;
  22. }
  23. /*文件释放函数*/
  24. int mem_release(struct inode *inode, struct file *filp)
  25. {
  26. return 0;
  27. }
  28. /*读函数*/
  29. static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
  30. {
  31. unsigned long p =  *ppos;
  32. unsigned int count = size;
  33. int ret = 0;
  34. int *register_addr = filp->private_data; /*获取设备的寄存器基地址*/
  35. /*判断读位置是否有效*/
  36. if (p >= 5*sizeof(int))
  37. return 0;
  38. if (count > 5*sizeof(int) - p)
  39. count = 5*sizeof(int) - p;
  40. /*读数据到用户空间*/
  41. if (copy_to_user(buf, register_addr+p, count))
  42. {
  43. ret = -EFAULT;
  44. }
  45. else
  46. {
  47. *ppos += count;
  48. ret = count;
  49. }
  50. return ret;
  51. }
  52. /*写函数*/
  53. static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
  54. {
  55. unsigned long p =  *ppos;
  56. unsigned int count = size;
  57. int ret = 0;
  58. int *register_addr = filp->private_data; /*获取设备的寄存器地址*/
  59. /*分析和获取有效的写长度*/
  60. if (p >= 5*sizeof(int))
  61. return 0;
  62. if (count > 5*sizeof(int) - p)
  63. count = 5*sizeof(int) - p;
  64. /*从用户空间写入数据*/
  65. if (copy_from_user(register_addr + p, buf, count))
  66. ret = -EFAULT;
  67. else
  68. {
  69. *ppos += count;
  70. ret = count;
  71. }
  72. return ret;
  73. }
  74. /* seek文件定位函数 */
  75. static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
  76. {
  77. loff_t newpos;
  78. switch(whence) {
  79. case SEEK_SET:
  80. newpos = offset;
  81. break;
  82. case SEEK_CUR:
  83. newpos = filp->f_pos + offset;
  84. break;
  85. case SEEK_END:
  86. newpos = 5*sizeof(int)-1 + offset;
  87. break;
  88. default:
  89. return -EINVAL;
  90. }
  91. if ((newpos<0) || (newpos>5*sizeof(int)))
  92. return -EINVAL;
  93. filp->f_pos = newpos;
  94. return newpos;
  95. }
  96. /*文件操作结构体*/
  97. static const struct file_operations mem_fops =
  98. {
  99. .llseek = mem_llseek,
  100. .read = mem_read,
  101. .write = mem_write,
  102. .open = mem_open,
  103. .release = mem_release,
  104. };
  105. /*设备驱动模块加载函数*/
  106. static int memdev_init(void)
  107. {
  108. /*初始化cdev结构*/
  109. cdev_init(&cdev, &mem_fops);
  110. /* 注册字符设备 */
  111. alloc_chrdev_region(&devno, 0, 2, "memdev");
  112. cdev_add(&cdev, devno, 2);
  113. }
  114. /*模块卸载函数*/
  115. static void memdev_exit(void)
  116. {
  117. cdev_del(&cdev);   /*注销设备*/
  118. unregister_chrdev_region(devno, 2); /*释放设备号*/
  119. }
  120. MODULE_LICENSE("GPL");
  121. module_init(memdev_init);
  122. module_exit(memdev_exit);

1. 编译/安装驱动:在Linux系统中,驱动程序通常采用内核模块的程序结构来进行编码,因此编译、安装一个驱动程序,其实质就是编译/安装一个内核模块。

2. 创建设备文件

应用程序如何通过字符设备文件来访问设备驱动接口,即字符设备驱动程序访问大揭秘,下面先来看个应用程序的小例子:(应用程序是如何通过系统调用找到设备驱动程序入口的然后让设备驱动程序work)

read-mem.c

  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. int main()
  6. {
  7. int fd = 0;
  8. int dst = 0;
  9. /*打开设备文件*/
  10. fd = open("/dev/memdev0",O_RDWR);
  11. /*写入数据*/
  12. read(fd, &dst, sizeof(int));
  13. printf("dst is %d\n",dst);
  14. /*关闭设备*/
  15. close(fd);
  16. return 0;
  17. }

在linux下对上面的文件进行静态编译(考虑到前面开发板上移植的某些库还没有添加进去)生成read-mem目标文件,然后进行反汇编并将反汇编生成的文件导入到当前目录下的dump上去。

VIM打开dump文件,搜索/main 可以看到这一段汇编代码

可以看到 应用程序中的read()函数实际是调用了__libc_read函数, 然后继续在dump中搜索__libc_read函数

这里红箭头指向的两行是比较重要的两行,将3传给r7,然后使用了SVC系统调用指令,这时PC指针会从用户空间进入到内核空间(通过一个固定的入口),第二步会取r7寄存器里面的值3, 然后根据这个值查一个表确定要调用那个系统调用(即对于3的系统调用内核代码)。这里打开内核源码工程,打开一个文件:

entry-comon.S(/arch/arm/kernel目录下)

上面的内核代码部分vector_swi就是那个固定的入口,第二个箭头部分就是上面所说的第二步,第三步在这部分的代码下面这里截图截不了这么多(也就是根据number查表)

  1. enable_irq
  2. get_thread_info tsk
  3. adr tbl, <span style="color:#ff0000;">sys_call_table</span>     @ load syscall table pointer

搜索sys_call_table,来看看这张表是什么?


查看calls.S文件

系统就是通过固定入口进入内核空间,然后取出系统调用编号,在利用编号查找上面的这张表,然后取出内核中对于上面用户空间的read的实现函数!(这里就分析了用户空间的read是如何找到内核空间的sys_read的过程)

这里顺便看一下sys_read的内核代码实现:/fs 目录下的 Read_write.c文件中

  1. SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
  2. {
  3. struct file *file;
  4. ssize_t ret = -EBADF;
  5. int fput_needed;
  6. file = fget_light(fd, &fput_needed);
  7. if (file) {
  8. loff_t pos = file_pos_read(file);
  9. ret = vfs_read(file, buf, count, &pos);
  10. file_pos_write(file, pos);
  11. fput_light(file, fput_needed);
  12. }
  13. return ret;
  14. }

每个打开的文件都会有一个struct file与之对应!从上面的函数可以看到通过传入的fd参数 能找到与之对应的struct file.

然后通过file 调用了vfs_read()函数.下面先看看该函数的内部实现。

红色箭头部分!f_op部分是驱动程序里面的自定义结构,通过f_op结构找到设备读取方法!

就是这个:

  1. /*文件操作结构体*/
  2. static const struct file_operations mem_fops =
  3. {
  4. .llseek = mem_llseek,
  5. .read = mem_read,
  6. .write = mem_write,
  7. .open = mem_open,
  8. .release = mem_release,
  9. };

Linux应用程序访问字符设备驱动详细过程【转】的更多相关文章

  1. 深入理解Linux字符设备驱动

    文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次.组成框架和交互.如何编写驱动.设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解.本文整合之前发表的& ...

  2. Linux字符设备驱动结构(一)--cdev结构体、设备号相关知识机械【转】

    本文转载自:http://blog.csdn.net/zqixiao_09/article/details/50839042 一.字符设备基础知识 1.设备驱动分类 linux系统将设备分为3类:字符 ...

  3. Linux驱动设计——字符设备驱动(一)

    Linux字符设别驱动结构 cdev结构体 struct cdev { struct kobject kobj; struct module *owner; const struct file_ope ...

  4. Linux字符设备驱动

    一.字符设备基础 字符设备 二.字符设备驱动与用户空间访问该设备的程序三者之间的关系 三.字符设备模型 1.Linux内核中,使用 struct cdev 来描述一个字符设备 动态申请(构造)cdev ...

  5. 【Linux驱动】字符设备驱动

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 1.字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面 ...

  6. linux设备驱动第三篇:如何实现一个简单的字符设备驱动

    在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...

  7. linux设备驱动第三篇:如何写一个简单的字符设备驱动?

    在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...

  8. (57)Linux驱动开发之三Linux字符设备驱动

    1.一般情况下,对每一种设备驱动都会定义一个软件模块,这个工程模块包含.h和.c文件,前者定义该设备驱动的数据结构并声明外部函数,后者进行设备驱动的具体实现. 2.典型的无操作系统下的逻辑开发程序是: ...

  9. linux设备驱动第三篇:写一个简单的字符设备驱动

          在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分 ...

随机推荐

  1. hadoop Error: JAVA_HOME is not set and could not be found.

    Hadoop安装完后,启动时报Error: JAVA_HOME is not set and could not be found.解决办法:        修改/etc/hadoop/hadoop- ...

  2. MarkMan – 马克鳗,让设计更有爱!

    scavin(Google+) on 2010.11.16. MarkMan – 马克鳗 是一款方便高效的标注工具,极大节省设计师在设计稿上添加和修改标注的时间,让设计更有爱.Adobe AIR 平台 ...

  3. OSI七层&TCP&IP协议

    OSI七层: OSI七层与ICP/IP概念层的对应: ICP/IP概念层上的网络设备: IP(Internet Protocol网际协议):计算机之间的通信 IP(网络协议)位于网络层,作用是把各种数 ...

  4. php引用&符号详解——————给变量起小名

    学习了这篇博客[http://blog.csdn.net/jiedushi/article/details/6428585] php中引用采用的是“写时拷贝”的原理,就是除非发生写操作,指向同一个地址 ...

  5. PHP后门新玩法:一款猥琐的PHP后门分析

    0x00 背景 近日,360网站卫士安全团队近期捕获一个基于PHP实现的webshell样本,其巧妙的代码动态生成方式,猥琐的自身页面伪装手法,让我们在分析这个样本的过程中感受到相当多的乐趣.接下来就 ...

  6. IEnumerable和IQueryable区别、优缺点

    转自 http://www.cnblogs.com/fly_dragon/archive/2011/02/21/1959933.html IEnumerable接口 公开枚举器,该枚举器支持在指定类型 ...

  7. .NET 页面间传值的几种方法

    1. QueryString 这是最简单的传值方式,但缺点是传的值会显示在浏览器的地址栏中且不能传递对象,只适用于传递简单的且安全性要求不高的数值. 传递: location.href="W ...

  8. linux下mysql的忘记root密码的解决办法

    因为放寒假家里没有宽带,便很少上网,前几天用手机进入自己的个人博客时竟然返回数据库不能连接的错误,吓我一跳,网站肯定被人黑了,但转头一想我的博客就几篇破文章,谁这么无聊要黑,我并没有立刻去网上找解决的 ...

  9. php——用for循环打印半金字塔、金字塔、正方形、倒金字塔、菱形、空心图形等

    1.半金字塔 $n=5; //控制层数 for($i=1;$i<=$n;$i++){ //控制每层的 “*” 数 for($j=1;$j<=$i;$j++){ echo  "*& ...

  10. Kmeans方法

    基本Kmeans算法介绍及其实现 http://blog.csdn.net/qll125596718/article/details/8243404/ kmeans++ http://www.52ml ...