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中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分 ...
随机推荐
- js 进度条,可实现结束和重新开始
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- Runtime Error---Description: An application error occurred on the server....
[原]Runtime Error---Description: An application error occurred on the server.... 2010-1-7阅读2010 评论3 D ...
- 导出所选行为excle
要实现的是将所选行导出.例如勾选这两条
- 为什么使用ConcurrentHashMap
ConcurrentHashMap是有Segment数组结构和HashEntry数组结构组成. Segment是一种可重入锁(ReentrantLock),在ConcurrentHashMap里扮演锁 ...
- JS读写cookie以及中文乱码解决
本文地址:http://www.cnblogs.com/PiaoMiaoGongZi/p/4092489.html 转载请注明. Js获取所有的cookie信息: var cookiename = d ...
- nginx https ssl 设置受信任证书[原创]
1. 安装nginx 支持ssl模块 http://nginx.org/en/docs/configure.html yum -y install openssh openssh-devel (htt ...
- Indexing and Hashing
DATABASE SYSTEM CONCEPTS, SIXTH EDITION11.1 Basic ConceptsAn index for a file in a database system wo ...
- JS 的trim()
去除字符串左右两端的空格,在vbscript里 可 用 trim.ltrim 或 rtrim,但 js 却没有这 3个 内置方法,需 手工编写.下面的实现方法 用到 正则表达式,效率不错, 把 三 ...
- Markdown资源 markd
markdown是将某些格式的文本,翻译成HTML的一个库,非常精妙! https://www.npmjs.com/package/markedhttps://github.com/adam-p/ma ...
- AngularJS 实现简单购物车
使用AngularJS实现一个简单的购物车,主要感受强大的双向绑定和只关注对象不关注界面特性. 先看看界面: 点击+-操作和删除: 这些全部只需要操作数据源就行,不需要关注界面. 实现过程: 一.使用 ...