Linux内核分析(五)----字符设备驱动实现
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Linux内核分析(五)----字符设备驱动实现,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含7459字,纯文字阅读大概需要11分钟。
内容图文
昨天我们对 linux 内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷,我们都会以虚拟的设备为例进行学习,所以大家不必害怕没有硬件的问题。
今天我们会分析到以下内容:
1. 字符设备驱动基础
2. 简单字符设备驱动实现
3. 驱动测试
1. 字符设备描述结构
在 linux2.6 内核中,使用 cdev 结构体描述一个字符设备,其定义如下:
1 struct cdev { 2 struct kobject kobj;/*基于kobject*/3struct module *owner; /*所属模块*/4conststruct file_operations *ops; /*设备文件操作函数集*/5struct list_head list; 6 dev_t dev; /*设备号*/7 unsigned int count; /*该种类型设备数目*/8 };
上面结构中需要我们进行初始化的有 ops 和 dev ,下面我们会对这两个成员进行分析。
注: kobject 结构是驱动中很重要的一个结构,由于其复杂性,我们现在不进行介绍,后面会详细介绍。
2. 设备号
1. 何为设备号: cdev 结构体中 dev 成员定义了设备号,而 dev_t 则为 U32 类型的也就是 32 位,其中 12 位为主设备号, 20 位为次设备号。我们执行 ls –l /dev/ 可看到下图,其中左边红框为主设备号,右边为次设备号
2. 何为主设备号: 用来对应该设备为何种类型设备。(比如串口我们用一个数字识别,而串口有好几个)
3. 何为次设备号: 用来对应同一类型设备下的具体设备。(用次设备号来具体区分是哪个串口)
4. 设备号相关操作:
1. 通过主设备号和次设备号获取 dev : dev = MKDEV( 主,次 );
2. 通过 dev 获取主设备号: 主 = MAJOR(dev);
3. 通过 dev 获取次设备号: dev = MINOR(dev);
5. 设备号分配: 设备号的分配有两种方式,一种是静态的,另一种是动态的,下面一一分析
1. 静态分配: 也就是程序员自己指定设备号,通过 register_chrdev_region(); 函数向内核申请,可能会导致和内核已有的冲突,从而失败。
2. 动态分配: 通过 alloc_chrdev_region(); 函数向内核申请设备号。
3. 释放设备号: 通过 unregister_chrdev_region(); 释放申请到的设备号。
3. file_operations 操作函数集
file_operations 结构体中的成员函数在我们驱动开发过程中极为重要,其中的内容相当庞大,下面我们看看其定义:
1 struct file_operations { 2 struct module *owner;/*拥有该结构的模块的指针,一般为THIS_MODULES*/ 3 loff_t (*llseek) (struct file *, loff_t, int); /*用来修改当前文件的读写指针*/ 4 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);/*从设备读取数据*/ 5 ssize_t (*write) (struct file *, constchar __user *, size_t, loff_t *);/*向设备发送数据*/ 6 ssize_t (*aio_read) (struct kiocb *, conststruct iovec *, unsigned long, loff_t); /*初始化一个异步的读取操作*/ 7 ssize_t (*aio_write) (struct kiocb *, conststruct iovec *, unsigned long, loff_t); /*初始化一个异步的写入操作*/ 8int (*readdir) (struct file *, void *, filldir_t); /*只用于读取目录,对于设备文件该字段为NULL*/ 9 unsigned int (*poll) (struct file *, struct poll_table_struct *);/*轮询函数,判断目前是否可以进行非阻塞的读取或写入*/10long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); /* 不用BLK的文件系统,将使用此函数代替ioctl*/11long (*compat_ioctl) (struct file *, unsigned int, unsigned long); /* 代替ioctl*/12int (*mmap) (struct file *, struct vm_area_struct *);/*用于请求将设备内存映射到进程地址空间*/13int (*open) (struct inode *, struct file *);/*打开*/14int (*flush) (struct file *, fl_owner_t id); /*在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作. */15int (*release) (struct inode *, struct file *);/*关闭*/16int (*fsync) (struct file *, int datasync); /*刷新待处理数据*/17int (*aio_fsync) (struct kiocb *, int datasync); /*异步fsync*/18int (*fasync) (int, struct file *, int); /*通知设备FASYNC标志发生变化*/19int (*lock) (struct file *, int, struct file_lock *);/* 实现文件加锁*/20 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); /*通常为NULL*/21 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); /*在当前的进程地址空间找的一个未映射的内存段*/22int (*check_flags)(int); /*法允许模块检查传递给 fnctl(F_SETFL...) 调用的标志*/23int (*flock) (struct file *, int, struct file_lock *);/**/24 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); /*由VFS调用,将管道数据粘贴到文件*/25 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); /*由VFS调用,将文件数据粘贴到管道*/26int (*setlease)(struct file *, long, struct file_lock **);/**/27long (*fallocate)(struct file *file, int mode, loff_t offset, 28 loff_t len); /**/29 };
上面结构体中的函数指针所指向的函数,在我们在进行 open 、 write 、 read 等系统调用的时候最终会被调用到,所以我们的驱动中想为应用层实现那种调用就要在此实现。
4. 字符设备驱动初始化
我们通过上面的分析对设备号和操作函数集有了一定的了解下面我们来看字符设备驱动初始化,其主要步骤如下。
1. 分配 cdev 结构: 有静态(直接定义)动态( cdev_alloc(); )两种方式
2. 初始化 cdev 结构: 使用 cdev_init(struct cdev *cdev, conststruct file_operations *fops) 初始化
3. 驱动注册: 使用 int cdev_add(struct cdev *p, dev_t dev, unsigned count)//count为该种类型的设备个数注册
4. 硬件初始化: 阅读芯片手册进行硬件设备的初始化
5. 完成操作函数集: 实现要用的操作(设备方法)
6. 驱动注销: 使用 void cdev_del(struct cdev *p) 注销
5. 字符设备驱动模型及调用关系
下面我通过一张图将字符设备的驱动结构、以及字符设备驱动与用户空间的调用关系进行展示:
6. 遗漏知识
我们内核空间和用户空间的数据交互要用到下面两个函数:
1 copy_from_user();//从用户空间读2 copy_to_user();//写入用户空间
l 简单字符设备驱动实现
经过上面的分析我们对字符设备有一定了解,下面我们来完成一个最简单的字符设备驱动。我只展示最主要的代码,整个项目工程在 https://github.com/wrjvszq/myblongs.git 欢迎大家关注。
1. 字符设备驱动编写
因为驱动本身就是一个内核模块,下面的字符设备驱动只实现了部分方法,在后面的博客中我们会基于此驱动慢慢修改,希望大家掌握。
1 #include<linux/module.h> 2 #include<linux/init.h> 3 #include<linux/cdev.h> 4 #include<linux/fs.h> 5 #include<asm/uaccess.h> 6 7#define MEM_SIZE 1024 8 9 MODULE_LICENSE("GPL"); 10 11struct mem_dev{ 12struct cdev cdev; 13int mem[MEM_SIZE];//全局内存4k 14 dev_t devno; 15}; 16 17struct mem_dev my_dev; 18 19/*打开设备*/ 20int mem_open(struct inode *inode, struct file *filp){ 21int num = MINOR(inode->i_rdev);/*获取次设备号*/ 22 23if(num == 0){/*判断为那个设备*/ 24 filp -> private_data = my_dev.mem;/*将设备结构体指针复制给文件私有数据指针*/ 25 } 26return0; 27} 28/*文件关闭函数*/ 29int mem_release(struct inode *inode, struct file *filp){ 30return0; 31} 32 33static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos){ 34int * pbase = filp -> private_data;/*获取数据地址*/ 35 unsigned long p = *ppos;/*读的偏移*/ 36 unsigned int count = size;/*读数据的大小*/ 37int ret = 0; 38 39if(p >= MEM_SIZE)/*合法性判断*/ 40return0; 41if(count > MEM_SIZE - p)/*读取大小修正*/ 42 count = MEM_SIZE - p; 43 44if(copy_to_user(buf,pbase + p,size)){ 45 ret = - EFAULT; 46 }else{ 47 *ppos += count; 48 ret = count; 49 } 50 51return ret; 52} 53 54static ssize_t mem_write(struct file *filp, constchar __user *buf, size_t size, loff_t *ppos){ 55 unsigned long p = *ppos; 56 unsigned int count = size; 57int ret = 0; 58int *pbase = filp -> private_data; 59 60if(p >= MEM_SIZE) 61return0; 62if(count > MEM_SIZE - p) 63 count = MEM_SIZE - p; 64 65if(copy_from_user(pbase + p,buf,count)){ 66 ret = - EFAULT; 67 }else{ 68 *ppos += count; 69 ret = count; 70 } 71return ret; 72} 73 74/*seek文件定位函数*/ 75static loff_t mem_llseek(struct file *filp, loff_t offset, int whence){ 76 77 loff_t newpos; 78 79switch(whence) { 80case SEEK_SET:/*从文件头开始定位*/ 81 newpos = offset; 82break; 83case SEEK_CUR:/*从当前位置开始定位*/ 84 newpos = filp->f_pos + offset; 85break; 86case SEEK_END: 87 newpos = MEM_SIZE * sizeof(int)-1 + offset;/*从文件尾开始定位*/ 88break; 89default: 90return -EINVAL; 91 } 92 93if ((newpos<0) || (newpos>MEM_SIZE * sizeof(int)))/*检查文件指针移动后位置是否正确*/ 94return -EINVAL; 95 96 filp->f_pos = newpos; 97return newpos; 98 99} 100101conststruct file_operations mem_ops = { 102 .llseek = mem_llseek, 103 .open = mem_open, 104 .read = mem_read, 105 .write = mem_write, 106 .release = mem_release, 107}; 108109staticint memdev_init(void){ 110int ret = -1; 111112/*动态分配设备号*/113 ret = alloc_chrdev_region(&my_dev.devno,0,1,"memdev"); 114if (ret >= 0){ 115 cdev_init(&my_dev.cdev,&mem_ops);/*初始化字符设备*/116 cdev_add(&my_dev.cdev,my_dev.devno,1);/*添加字符设备*/117 } 118119return ret; 120} 121122staticvoid memdev_exit(void){ 123 cdev_del(&my_dev.cdev); 124 unregister_chrdev_region(my_dev.devno,1); 125126} 127128module_init(memdev_init); 129 module_exit(memdev_exit);
l 驱动测试
经过上面的代码我们已经实现了一个简单的字符设备驱动,我们下面进行测试。(应用程序在 https://github.com/wrjvszq/myblongs.git 上)
1. 加载内核模块
我们使用 insmod memdev.ko 命令加载内核模块
2. 获取设备号
我们的设备号是动态申请到的,所以我们要通过下面的命令查看设备号
cat /proc/devices
找到我们的设备 memdev 的设备号
3. 建立设备文件
使用如下命令建立设备文件
mknod /dev/文件名 c 主设备号次设备号
上面命令中文件名为我们在应用程序中打开的文件名
c 代表字符设备
主设备号 为上一步找到的,我的位 249
次设备号 非负即可,但不能超过自己所创建的设备数。
比如我的就是 mknod /dev/memdev0 c 2490
4. 编译应用程序并测试
使用 gcc 对应用程序进行编译,然后先使用 write 对设备进行写入,在使用 read 对设备读取,完成测试。
原文:http://www.cnblogs.com/wrjvszq/p/4272211.html
内容总结
以上是互联网集市为您收集整理的Linux内核分析(五)----字符设备驱动实现全部内容,希望文章能够帮你解决Linux内核分析(五)----字符设备驱动实现所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。