Linux应用程序访问字符设备驱动详细过程【转】
本文转载自:http://blog.csdn.net/coding__madman/article/details/51346532
下面先通过一个编写好的内核驱动模块来体验以下字符设备驱动
可以暂时先忽略下面的代码实现!
memdev.c
- #include <linux/module.h>
 - #include <linux/fs.h>
 - #include <linux/init.h>
 - #include <linux/cdev.h>
 - #include <asm/uaccess.h>
 - int dev1_registers[5];
 - int dev2_registers[5];
 - struct cdev cdev;
 - dev_t devno;
 - /*文件打开函数*/
 - int mem_open(struct inode *inode, struct file *filp)
 - {
 - /*获取次设备号*/
 - int num = MINOR(inode->i_rdev);
 - if (num==0)
 - filp->private_data = dev1_registers;
 - else if(num == 1)
 - filp->private_data = dev2_registers;
 - else
 - return -ENODEV; //无效的次设备号
 - return 0;
 - }
 - /*文件释放函数*/
 - int mem_release(struct inode *inode, struct file *filp)
 - {
 - return 0;
 - }
 - /*读函数*/
 - static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
 - {
 - unsigned long p = *ppos;
 - unsigned int count = size;
 - int ret = 0;
 - int *register_addr = filp->private_data; /*获取设备的寄存器基地址*/
 - /*判断读位置是否有效*/
 - if (p >= 5*sizeof(int))
 - return 0;
 - if (count > 5*sizeof(int) - p)
 - count = 5*sizeof(int) - p;
 - /*读数据到用户空间*/
 - if (copy_to_user(buf, register_addr+p, count))
 - {
 - ret = -EFAULT;
 - }
 - else
 - {
 - *ppos += count;
 - ret = count;
 - }
 - return ret;
 - }
 - /*写函数*/
 - static ssize_t mem_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;
 - int *register_addr = filp->private_data; /*获取设备的寄存器地址*/
 - /*分析和获取有效的写长度*/
 - if (p >= 5*sizeof(int))
 - return 0;
 - if (count > 5*sizeof(int) - p)
 - count = 5*sizeof(int) - p;
 - /*从用户空间写入数据*/
 - if (copy_from_user(register_addr + p, buf, count))
 - ret = -EFAULT;
 - else
 - {
 - *ppos += count;
 - ret = count;
 - }
 - return ret;
 - }
 - /* seek文件定位函数 */
 - static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
 - {
 - loff_t newpos;
 - switch(whence) {
 - case SEEK_SET:
 - newpos = offset;
 - break;
 - case SEEK_CUR:
 - newpos = filp->f_pos + offset;
 - break;
 - case SEEK_END:
 - newpos = 5*sizeof(int)-1 + offset;
 - break;
 - default:
 - return -EINVAL;
 - }
 - if ((newpos<0) || (newpos>5*sizeof(int)))
 - return -EINVAL;
 - filp->f_pos = newpos;
 - return newpos;
 - }
 - /*文件操作结构体*/
 - static const struct file_operations mem_fops =
 - {
 - .llseek = mem_llseek,
 - .read = mem_read,
 - .write = mem_write,
 - .open = mem_open,
 - .release = mem_release,
 - };
 - /*设备驱动模块加载函数*/
 - static int memdev_init(void)
 - {
 - /*初始化cdev结构*/
 - cdev_init(&cdev, &mem_fops);
 - /* 注册字符设备 */
 - alloc_chrdev_region(&devno, 0, 2, "memdev");
 - cdev_add(&cdev, devno, 2);
 - }
 - /*模块卸载函数*/
 - static void memdev_exit(void)
 - {
 - cdev_del(&cdev); /*注销设备*/
 - unregister_chrdev_region(devno, 2); /*释放设备号*/
 - }
 - MODULE_LICENSE("GPL");
 - module_init(memdev_init);
 - module_exit(memdev_exit);
 
1. 编译/安装驱动:在Linux系统中,驱动程序通常采用内核模块的程序结构来进行编码,因此编译、安装一个驱动程序,其实质就是编译/安装一个内核模块。
2. 创建设备文件
应用程序如何通过字符设备文件来访问设备驱动接口,即字符设备驱动程序访问大揭秘,下面先来看个应用程序的小例子:(应用程序是如何通过系统调用找到设备驱动程序入口的然后让设备驱动程序work)
read-mem.c
- #include <stdio.h>
 - #include <sys/types.h>
 - #include <sys/stat.h>
 - #include <fcntl.h>
 - int main()
 - {
 - int fd = 0;
 - int dst = 0;
 - /*打开设备文件*/
 - fd = open("/dev/memdev0",O_RDWR);
 - /*写入数据*/
 - read(fd, &dst, sizeof(int));
 - printf("dst is %d\n",dst);
 - /*关闭设备*/
 - close(fd);
 - return 0;
 - }
 
在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查表)
- enable_irq
 - get_thread_info tsk
 - 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文件中
- SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
 - {
 - struct file *file;
 - ssize_t ret = -EBADF;
 - int fput_needed;
 - file = fget_light(fd, &fput_needed);
 - if (file) {
 - loff_t pos = file_pos_read(file);
 - ret = vfs_read(file, buf, count, &pos);
 - file_pos_write(file, pos);
 - fput_light(file, fput_needed);
 - }
 - return ret;
 - }
 
每个打开的文件都会有一个struct file与之对应!从上面的函数可以看到通过传入的fd参数 能找到与之对应的struct file.
然后通过file 调用了vfs_read()函数.下面先看看该函数的内部实现。
红色箭头部分!f_op部分是驱动程序里面的自定义结构,通过f_op结构找到设备读取方法!
就是这个:
- /*文件操作结构体*/
 - static const struct file_operations mem_fops =
 - {
 - .llseek = mem_llseek,
 - .read = mem_read,
 - .write = mem_write,
 - .open = mem_open,
 - .release = mem_release,
 - };
 
Linux应用程序访问字符设备驱动详细过程【转】的更多相关文章
- 深入理解Linux字符设备驱动
		
文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次.组成框架和交互.如何编写驱动.设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解.本文整合之前发表的& ...
 - Linux字符设备驱动结构(一)--cdev结构体、设备号相关知识机械【转】
		
本文转载自:http://blog.csdn.net/zqixiao_09/article/details/50839042 一.字符设备基础知识 1.设备驱动分类 linux系统将设备分为3类:字符 ...
 - Linux驱动设计——字符设备驱动(一)
		
Linux字符设别驱动结构 cdev结构体 struct cdev { struct kobject kobj; struct module *owner; const struct file_ope ...
 - Linux字符设备驱动
		
一.字符设备基础 字符设备 二.字符设备驱动与用户空间访问该设备的程序三者之间的关系 三.字符设备模型 1.Linux内核中,使用 struct cdev 来描述一个字符设备 动态申请(构造)cdev ...
 - 【Linux驱动】字符设备驱动
		
一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 1.字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面 ...
 - linux设备驱动第三篇:如何实现一个简单的字符设备驱动
		
在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...
 - linux设备驱动第三篇:如何写一个简单的字符设备驱动?
		
在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...
 - (57)Linux驱动开发之三Linux字符设备驱动
		
1.一般情况下,对每一种设备驱动都会定义一个软件模块,这个工程模块包含.h和.c文件,前者定义该设备驱动的数据结构并声明外部函数,后者进行设备驱动的具体实现. 2.典型的无操作系统下的逻辑开发程序是: ...
 - linux设备驱动第三篇:写一个简单的字符设备驱动
		
在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分 ...
 
随机推荐
- [LintCode] Letter Combinations of a Phone Number 电话号码的字母组合
			
Given a digit string, return all possible letter combinations that the number could represent. A map ...
 - nginx连接php fastcgi配置
			
匹配到php结尾的文件抛到后端 后端php端口9000
 - java开发常用工具类
			
package com.rui.util; import java.text.DateFormat; import java.text.DecimalFormat; import java.text. ...
 - c++ basic 整理2
			
//拷贝函数 //拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量. //不显式指定拷贝函数时,编译器会生成默认拷贝函数. //使用默认拷贝函数 ...
 - 火狐的调试利器-----Firebug
			
什么是Firebug 从事了数年的Web开发工作,越来越觉得现在对WEB开发有了更高的要求.要写出漂亮的HTML代码:要编写精致的CSS样式表展示每个页面模块:要调试javascript给页面增加一些 ...
 - 栈的C++实现(数组)——创建-push-pop-top-清空栈-处理栈
			
今天学习了利用数组方式的栈的C++实现,这种方式跟指针实现有很多不一样的地方: 栈的指针实现,栈的创建申请头结点,push需要申请新的结点,pop释放结点,这些结点都放在第一个位置,top时,S-&g ...
 - Struts2基础学习总结
			
引用自:http://www.cnblogs.com/jbelial/archive/2012/05/10/2486886.html Struts 2是在WebWork2基础发展而来的. 注意:str ...
 - 【iCore3 双核心板_ uC/OS-III】例程十:消息队列
			
实验指导书及代码包下载: http://pan.baidu.com/s/1sleklm1 iCore3 购买链接: https://item.taobao.com/item.htm?id=524229 ...
 - Socket请求和Http请求的各自特点、区别及适用场景
			
Socket实现服务器与客户端之间的物理连接,并进行数据传输.主要有TCP/UDP两个协议.Socket处于网络协议的传输层.TCP:传输控制协议,面向连接的的协议,稳定可靠.当客户和服务器彼此交换数 ...
 - android发送/接收json数据
			
客户端向服务器端发送数据,这里用到了两种,一种是在url中带参数,一种是json数据发送方式: url带参数的写法: url+/?r=m/calendar/contact_list&uid=3 ...