框架

static int __init chrdev init(void)
{printk(KERN_DEBUG "chrdev_init");return 0;
}static void __exit chrdev_exit(void)
{printk(KERN_DEBUG ""chrdev_exit);
}module_init(chrdev_init);
module_exit(chrdev_exit);MODULE_LICENSE("GPL");              // 描述模块的许可证
MODULE_AUTHOR("aston");               // 描述模块的作者
MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

insmod与module_init宏。模块源代码中用module_init宏声明了一个函数(在我们这个例子里是chrdev_init函数),作用就是指定chrdev_init这个函数和insmod命令绑定起来,也就是说当我们insmod module_test.ko时,insmod命令内部实际执行的操作就是帮我们调用chrdev_init函数。

printk相比printf来说还多了个:打印级别的设置。printk的打印级别是用来控制printk打印的这条信息是否在终端上显示的。应用程序中的调试信息要么全部打开要么全部关闭,一般用条件编译来实现(DEBUG宏),但是在内核中,因为内核非常庞大,打印信息非常多,有时候整体调试内核时打印信息要么太多找不到想要的要么一个没有没法调试。所以才有了打印级别这个概念。
操作系统的命令行中也有一个打印信息级别属性,值为0-7。当前操作系统中执行printk的时候会去对比printk中的打印级别和我的命令行中设置的打印级别,小于我的命令行设置级别的信息会被放行打印出来,大于的就被拦截的。譬如我的ubuntu中的打印级别默认是4,那么printk中设置的级别比4小的就能打印出来,比4大的就不能打印出来。ubuntu中这个printk的打印级别控制没法实践,ubuntu中不管你把级别怎么设置都不能直接打印出来,必须dmesg命令去查看。

照此分析,那insmod时就应该能看到chrdev_init中使用printk打印出来的一个chrdev_init字符串,但是实际没看到。原因是ubuntu中拦截了,要怎么才能看到呢?在ubuntu中使用dmesg命令就可以看到了。

定义一个struct file_operation 结构体

file_operations结构体
(1)元素主要是函数指针,用来挂接实体函数地址,驱动源码中提供真正的open、read、write、close等函数实体
(2)每个设备驱动都需要一个该结构体类型的变量
(3)设备驱动向内核注册时提供该结构体类型的变量

注册驱动程序register_chrdev   (#include <linux/fs.h>)

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)

(1)作用,驱动向内核注册自己的file_operations
(2)参数
(3)inline和static

内核如何管理字符设备驱动
(1)内核中有一个数组用来存储注册的字符设备驱动
(2)register_chrdev内部将我们要注册的驱动的信息(主要是 )存储在数组中相应的位置
(3)cat /proc/devices查看内核中已经注册过的字符设备驱动(和块设备驱动)
(4)好好理解主设备号(major)的概念

注册字符设备驱动
(1)为何要注册驱动
(2)谁去负责注册
(3)向谁注册
(4)注册函数从哪里来
(5)注册前怎样?注册后怎样?注册产生什么结果?

谁来调用驱动,驱动入口函数 first_drv_init()

修饰入口函数module_init(first_drv_init)

自定义一个file_operation结构体变量,并且去填充

static const struct file_operations test_fops = {.ower    = THIS_MODULE,.open    = test_chrdev_open,.release = test_chrdev_release,
};

原型int (*open) (struct inode *, struct file *);

写test_chrdev_open函数

static int test_chrdev_open(struct inode *inode,struct file *file)
{printk(KERN_INFO "test_chrdev_open\n");return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{printk(KERN_INFO "test_chredev_open\n");return 0;
}

在module_init 宏调用的函数中去注册字符设备驱动register_chrdev

#define MYMAJOR    200

#define MYNAME      "testchar"

原型:static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)

//模块安装函数
static int __init chrdev_init(void)
{int ret = -1;printk(KERN INFO "chrdev init helloworld init\n");//在module_init宏调用的函数中去注册字符设备驱动ret = register_chrdev(MYMAJOR, MYNAME, &test_fops);if(ret){printk(KERN_ERR "register_chrdev fail\n");return -EINVAL;}printk(KERN_INFO "register_chrdev success\n");return 0;
}

在module exit宏调用的函数中去注销字符设备驱动

static void __exit chrdev_exit(void)
{printk(KERN_INFO "chrdev_exit helloworld exit\n");//在module_exit 宏调用的函数中去注销字符设备驱动unregister_chrdev(MYMAJOR,MYNAME);
}

驱动设备文件的创建
(1)何为设备文件
(2)设备文件的关键信息是:设备号 = 主设备号 + 次设备号,使用ls -l去查看设备文件,就可以得到这个设备文件对应的主次设备号。
(3)使用mknod创建设备文件:mknod /dev/xxx c 主设备号 次设备号

写应用来测试驱动

#include <stdio.h>#define FILE "/dev/test"      //刚才mknod 创建的设备文件名int main(void)
{int fd = -1;fd = open(FILE, O_RDWR);if(fd < 0){printf("open %s error.\n",FILE);return -1;}printf("open %s success\n",FILE);//读写文件//关闭文件return 0;
}

完整驱动:

#include <linux/module.h>      // module_init  module_exit
#include <linux/init.h>           // __init   __exit
#include <linux/fs.h>#define MYMAJOR      200
#define MYNAME      "testchar"int mymajor;static int test_chrdev_open(struct inode *inode, struct file *file)
{// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。printk(KERN_INFO "test_chrdev_open\n");return 0;
}static int test_chrdev_release(struct inode *inode, struct file *file)
{printk(KERN_INFO "test_chrdev_release\n");return 0;
}// 自定义一个file_operations结构体变量,并且去填充
static const struct file_operations test_fops = {.owner        = THIS_MODULE,             // 惯例,直接写即可.open     = test_chrdev_open,            // 将来应用open打开这个设备时实际调用的.release = test_chrdev_release,     // 就是这个.open对应的函数
};// 模块安装函数
static int __init chrdev_init(void)
{   printk(KERN_INFO "chrdev_init helloworld init\n");// 在module_init宏调用的函数中去注册字符设备驱动// major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号// 内核如果成功分配就会返回分配的主设备好;如果分配失败会返回负数mymajor = register_chrdev(0, MYNAME, &test_fops);if (mymajor < 0){printk(KERN_ERR "register_chrdev fail\n");return -EINVAL;}printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);return 0;
}// 模块下载函数
static void __exit chrdev_exit(void)
{printk(KERN_INFO "chrdev_exit helloworld exit\n");// 在module_exit宏调用的函数中去注销字符设备驱动unregister_chrdev(mymajor, MYNAME);}module_init(chrdev_init);
module_exit(chrdev_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("aston");               // 描述模块的作者
MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

在module_init宏调用的函数中去注册字符设备驱动
major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号
内核如果成功分配就会返回分配的主设备好;如果分配失败会返回负数

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit

原型copy_from_user(void *to, const void __user *from, unsigned long n)

copy_from_user函数的返回值定义,和常规有点不同。返回值如果成功复制则返回0,如果 不成功复制则返回尚未成功复制剩下的字节数。

#include <asm/uaccess.h>                  //copy_to_user       copy_from_user

驱动中如何操控硬件
还是那个硬件
(1)硬件物理原理不变
(2)硬件操作接口(寄存器)不变
(3)硬件操作代码不变
哪里不同了?
(1)寄存器地址不同。原来是直接用物理地址,现在需要用该物理地址在内核虚拟地址空间相对应的虚拟地址。寄存器的物理地址是CPU设计时决定的,从datasheet中查找到的。

(2)编程方法不同。裸机中习惯直接用函数指针操作寄存器地址,而kernel中习惯用封装好的io读写函数来操作寄存器,以实现最大程度可移植性。

内核中有2套虚拟地址映射方法:动态和静态
(1)静态映射方法的特点:
    内核移植时以代码的形式硬编码,如果要更改必须改源代码后重新编译内核
    在内核启动时(开机)建立静态映射表,到内核关机时销毁,中间一直有效
    对于移植好的内核,你用不用他都在那里
(2)动态映射方法的特点:
    驱动程序根据需要随时动态的建立映射、使用、销毁映射
    映射是短期临时的

如何选择虚拟地址映射方法
(1)2种映射并不排他,可以同时使用
(2)静态映射类似于C语言中全局变量,动态方式类似于C语言中malloc堆内存
(3)静态映射的好处是执行效率高,坏处是始终占用虚拟地址空间;动态映射的好处是按需使用虚拟地址空间,坏处是每次使用前后都需要代码去建立映射&销毁映射(还得学会使用那些内核函数的使用)

关于静态映射要说的
(1)不同版本内核中静态映射表位置、文件名可能不同
(2)不同SoC的静态映射表位置、文件名可能不同
(3)所谓映射表其实就是头文件中的宏定义

三星版本内核中的静态映射表
(1)主映射表位于:arch/arm/plat-s5p/include/plat/map-s5p.h
CPU在安排寄存器地址时不是随意乱序分布的,而是按照模块去区分的。每一个模块内部的很多个寄存器的地址是连续的。所以内核在定义寄存器地址时都是先找到基地址,然后再用基地址+偏移量来寻找具体的一个寄存器。
map-s5p.h中定义的就是要用到的几个模块的寄存器基地址。
map-s5p.h中定义的是模块的寄存器基地址的虚拟地址。
(2)虚拟地址基地址定义在:arch/arm/plat-samsung/include/plat/map-base.h
#define S3C_ADDR_BASE    (0xFD000000)        // 三星移植时确定的静态映射表的基地址,表中的所有虚拟地址都是以这个地址+偏移量来指定的
(3)GPIO相关的主映射表位于:arch/arm/mach-s5pv210/include/mach/regs-gpio.h
表中是GPIO的各个端口的基地址的定义
(4)GPIO的具体寄存器定义位于:arch/arm/mach-s5pv210/include/mach/gpio-bank.h

静态映射操作LED2
参考裸机中的操作方法添加LED操作代码
(1)宏定义
(2)在init和exit函数中分别点亮和熄灭LED
实践测试
(1)insmod和rmmod时观察LED亮灭变化
(2)打印出寄存器的值和静态映射表中的分析相对比
5.2.15.3、将代码移动到open和close函数中去

静态映射操作LED3
添加驱动中的写函数
(1)先定义好应用和驱动之间的控制接口,这个是由自己来定义的。譬如定义为:应用向驱动写"on"则驱动让LED亮,应用向驱动写"off",驱动就让LED灭
(2)应用和驱动的接口定义做的尽量简单,譬如用1个字目来表示。譬如定义为:应用写"1"表示灯亮,写"0"表示让灯灭。
5.2.16.2、写应用来测试写函数
5.2.16.3、驱动和应用中来添加读功能

5.2.17.动态映射操作LED
5.2.17.1、如何建立动态映射
(1)request_mem_region,向内核申请(报告)需要映射的内存资源。
(2)ioremap,真正用来实现映射,传给他物理地址他给你映射返回一个虚拟地址
5.2.17.2、如何销毁动态映射
(1)iounmap
(2)release_mem_region
注意:映射建立时,是要先申请再映射;然后使用;使用完要解除映射时要先解除映射再释放申请。

简单字符驱动笔记(朱有鹏)相关推荐

  1. 具有IOctl的简单字符驱动

    http://www.cnblogs.com/geneil/archive/2011/12/04/2275372.html 驱动层 #include <linux/init.h> #inc ...

  2. 字符设备驱动基础-linux驱动开发第2部分-朱有鹏-专题视频课程

    字符设备驱动基础-linux驱动开发第2部分-5673人已学习 课程介绍         本课程是linux驱动开发的第2个课程,从零开始带领大家逐渐熟悉内核模块,并且一步步写出一个字符设备驱动程序来 ...

  3. 字符设备驱动基础篇1——简单的驱动源码分析

    以下内容源于朱有鹏嵌入式课程的学习,如有侵权,请告知删除. 参考资料:http://www.cnblogs.com/biaohc/p/6575074.html module_test.c代码 #inc ...

  4. 上海lin上海linux培训ux,lin教材ux字符驱动设备-学习笔记(最新实例).pdf

    - 1 - 字符驱动开发学习笔记 Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过 这些函数使得Windows 的设备操作犹如文件一般.在应用程序看来,硬件设备只 是一个设备文件, ...

  5. Linux驱动编程 step-by-step (二) 简单字符设备驱动

    简单字符设备驱动 1.主次设备号 主设备号标识设备连接的的驱动,此设备好由内核使用,标识在相应驱动下得对应的设备 在linux中设备号是一个32位的dev_t类型 typedef __u32    _ ...

  6. linux2.6驱动学习笔记之字符驱动

    1.字符驱动组成 1.1字符驱动的模块加载与卸载 //设备结构体模板 struct xxx_dev_t { struct cdev cdev; ...... }xxx_dev; 在字符驱动模块加载函数 ...

  7. 一个简单字符型设备驱动及其测试

    驱动对一些人来说很难,而对一些人来说很容易.窃以为,理解简单设备驱动模型不难,深入理解并与Linux内核设计联系到一起需要花费时间.对于移植者来说,如何将自己自定义的模块天衣无缝放到内核中,是比较重要 ...

  8. [设备驱动] 最简单的内核设备驱动--字符驱动

    [设备驱动] 最简单的内核设备驱动--字符驱动  概要: x86平台上(linux-2.6.34.14;Linux debian 3.2.0-3-686-pae)编写一个256字节的字符驱动程序.在/ ...

  9. 驱动框架入门之LED-linux驱动开发第4部分-朱有鹏-专题视频课程

    驱动框架入门之LED-linux驱动开发第4部分-5199人已学习 课程介绍         本课程是linux驱动开发的第4个课程,主要内容是驱动框架的引入.通过led驱动框架和gpiolib的这两 ...

最新文章

  1. 一篇论文摘要计算机英语,推荐:计算机毕业论文英文摘要的写作方法
  2. 相似三角形_JAVA
  3. char类型的实参与const char类型的形参不兼容_C++干货系列——顶层const和底层const...
  4. Super Jumping! Jumping! Jumping!
  5. C++ dlopen mini HOWTO 一篇非常好的介绍C++ dlopen linux/mac os x 编程的资料
  6. 一起来啃书——PHP看书
  7. SQL Server命令行
  8. Web API 开发入门--基于Visual Studio
  9. 海思3519A上运行yolov3(一)——板卡配置(包括烧写内核、文件系统等)
  10. 帧同步分离逻辑层和渲染层_帧同步如何分离逻辑和渲染?
  11. 《人人都是产品经理》系列图书说明 Late2021
  12. 海思 K3V2的前世今生
  13. 加载脚本依赖发生错误--暴力猴
  14. python下拉菜单_python下拉菜单
  15. java报错Error attempting to get column ‘XXX’ from result set. Cause: java.sql.怎么解决
  16. gps测距+java_GPS测距会高估你的移动距离
  17. 微擎支付返回商户单号_微信小程序支付流程
  18. hexo添加valine评论系统 (yilia主题)
  19. 2020年博客日报第4篇|多数据源管理插件(支持不同数据库)
  20. PHP+PHPQRcode写一个二维码接口api

热门文章

  1. android平台下OpenGL ES 3.0使用GLSurfaceView对相机Camera预览实时处理
  2. Excel-用OFFSET和COUNTA实现动态增加下拉列表
  3. JS for循环实现 My cats are called Bill, Jeff, Pete, Biggles, and Jasmin.
  4. 一款开源的Java在线考试系统项目(附源码)
  5. 火线,零线,地线各自颜色和作用(转载)
  6. 服务器遇到攻击了,有什么好的安全解决方案
  7. 黑马探花交友----5.聊天消息-即时通讯
  8. RPA学习-数组处理
  9. 一文读懂硬件开发EVT/DVT/PVT三大阶段
  10. IT外企那点儿事(20): 程序员的大侠情结