linux驱动开发

注:本文是对朱老师linux驱动开发课程的备忘引导性笔记,主要是为了能够在学完后快速回忆起相关内容。本文主要记录了一些关键易忘性知识点并包含少量理解性内容,遵循尽量精简的原则,以尽量少的篇幅概括整个课程的知识点,便于后期能够快速定位知识点。故本文不包含具体的命令及函数使用方法等,同时不要太纠结于字面表达,这些只是为了能够快速回忆起相关知识点,具体的准确描述以及用法等需参考具体的文章。

(未完待续)

驱动应该怎么学

linux体系架构
(1)分层思想
(2)驱动的上面是系统调用API
(3)驱动的下面是硬件
(4)驱动自己本身也是分层的

微内核和宏内核
(1)宏内核(又称为单内核):将内核从整体上作为一个大过程实现,并同时运行在一个单独的地址空间。所有的内核服务都在一个地址空间运行,相互之间直接调用函数,简单高效。
(2)微内核:功能被划分成独立的过程,过程间通过IPC进行通信。模块化程度高,一个服务失效不会影响另外一个服务。典型如windows 

linux本质上是宏内核,但是又吸收了微内核的模块化特性,提现在2个层面
(1)静态模块化:在编译时实现可裁剪,特征是想要功能裁剪改变必须重新编译
(2)动态模块化:zImage可以不重新编译烧录,甚至可以不关机重启就实现模块的安装和卸载。 

驱动分类
(1)分3类:字符设备驱动、块设备驱动、网络设备驱动
(2)分类原则:设备本身读写操作的特征差异 

三类驱动程序详细对比分析
(1)字符设备,准确的说应该叫“字节设备”,软件操作设备时是以字节为单位进行的。典型的如LCD、串口、LED、蜂鸣器、触摸屏······
(2)块设备,块设备是相对于字符设备定义的,块设备被软件操作时是以块(多个字节构成的一个单位)为单位的。设备的块大小是设备本身设计时定义好的,软件是不能去更改的,不同设备的块大小可以不一样。常见的块设备都是存储类设备,如:硬盘、NandFlash、iNand、SD····
(3)网络设备,网络设备是专为网卡设计的驱动模型,linux中网络设备驱动主要目的是为了支持API中socket相关的那些函数工作。 

字符设备驱动基础

驱动开发要点

(1)驱动开发时,需要保证烧录到开发板中的zImage的源码树和编译驱动用到的源码树是一样的。这样驱动安装时内核版本校验才不会出错。

(2)最好使用tftp的方式加载内核,便于重新编译后更改内核。

set bootcmd 'tftp 0x30008000 zImage;bootm 0x30008000'

(3)使用nfs方式挂载跟文件系统,方便将编译好的驱动模块由主机转移到开发板的跟文件系统中。(内核配置记得打开使能nfs形式的rootfs)

setenv bootargs root=/dev/nfs nfsroot=192.168.1.141:/root/porting_x210/rootfs/rootfs ip=192.168.1.10:192.168.1.141:192.168.1.1:255.255.255.0::eth0:off  init=/linuxrc console=ttySAC2,115200 

 

常用的模块操作命令

(1)lsmod(list module,将模块列表显示),功能是打印出当前内核中已经安装的模块列表

(2)insmod(install module,安装模块),功能是向当前内核中去安装一个模块,用法是insmod xxx.ko

(3)modinfo(module information,模块信息),功能是打印出一个内核模块的自带信息。,用法是modinfo xxx.ko

(4)rmmod(remove module,卸载模块),功能是从当前内核中卸载一个已经安装了的模块,用法是rmmod xxx(注意卸载模块时只需要输入模块名即可,不能加.ko后缀)

(5)modprobe,也是安装模块,insmod 命令不能解决模块的依赖关系,而该命令比较智能,可以分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中。

(6)depmod,检测模块的相依性,供modprobe在安装模块时使用

insmod与module_init宏

模块源代码中用module_init宏声明了一个函数,作用就是指定这个函数和insmod命令绑定起来,也就是说当我们insmod一个模块时,insmod命令内部实际执行的操作就是帮我们调用该函数。该函数被调用后除了我们代码中编写的指令会被执行外,内核还会做一些额外的使用,例如lsmod查看时会多出刚刚安装的模块。

ubuntu中会拦截模块安装等操作时打印出的信息,故需要在ubuntu中使用dmesg命令就可以看到模块安装时打印出的所有信息。  

 模块的版本信息

(1)使用modinfo查看模块的版本信息

(2)内核zImage中也有一个确定的版本信息,使用uname -r进行查看

(3)insmod时模块的vermagic必须和内核的相同,否则不能安装,报错信息为:insmod: ERROR: could not insert module module_test.ko: Invalid module format

(4)模块的版本信息是为了保证模块和内核的兼容性,是一种安全措施

(5)上面说到,要保证烧录到开发板中的zImage的源码树和编译驱动用到的源码树是一样的,这里就是为了保证两者的版本相同

rmmod与module_exit宏 

模块中常用宏
(1)MODULE_LICENSE,模块的许可证。一般声明为GPL许可证,而且最好不要少,否则可能会出现莫名其妙的错误(譬如一些明显存在的函数提升找不到)。
(2)MODULE_AUTHOR,描述模块的作者
(3)MODULE_DESCRIPTION,描述模块的介绍信息
(4)MODULE_ALIAS ,描述模块的别名信息

函数修饰符
(1)__init,本质上是个宏定义,在内核源代码中就有#define __init xxxx。这个__init的作用就是将被他修饰的函数放入.init.text段中去(本来默认情况下函数是被放入.text段中)。
整个内核中的所有的这类函数都会被链接器链接放入.init.text段中,所以所有的内核模块的__init修饰的函数其实是被统一放在一起的。内核启动时统一会加载.init.text段中的这些模块安装函数,加载完后就会把这个段给释放掉以节省内存。
(2)__exit 

printk函数详解

(1)printk在内核源码中用来打印信息的函数,用法和printf非常相似。

(2)printk和printf最大的差别:printf是C库函数,是在应用层编程中使用的,不能在linux内核源代码中使用;printk是linux内核源代码中自己封装出来的一个打印函数,是内核源码中的一个普通函数,只能在内核源码范围内使用,不能在应用编程中使用。

(3)printk相比printf来说还多了个:打印级别的设置。printk的打印级别是用来控制printk打印的这条信息是否在终端上显示的。应用程序中的调试信息要么全部打开要么全部关闭,一般用条件编译来实现(DEBUG宏),但是在内核中,因为内核非常庞大,打印信息非常多,有时候整体调试内核时打印信息要么太多找不到想要的要么一个没有没法调试。所以才有了打印级别这个概念。

(4)操作系统的命令行中也有一个打印信息级别属性,值为0-7。当前操作系统中执行printk的时候会去对比printk中的打印级别和我的命令行中设置的打印级别,小于我的命令行设置级别的信息会被放行打印出来,大于的就被拦截的。譬如我的ubuntu中的打印级别默认是4,那么printk中设置的级别比4小的就能打印出来,比4大的就不能打印出来。

(5)ubuntu中这个printk的打印级别控制没法实践,ubuntu中不管你把级别怎么设置都不能直接打印出来,必须dmesg命令去查看。 

关于驱动模块中的头文件

驱动源代码中包含的头文件和原来应用编程程序中包含的头文件不是一回事。应用编程中包含的头文件是应用层的头文件,是应用程序的编译器带来的(譬如gcc的头文件路径在 /usr/include下,这些东西是和操作系统无关的)。驱动源码属于内核源码的一部分,驱动源码中的头文件其实就是内核源代码目录下的include目录下的头文件。 

驱动开发模板和Makefile

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

内核如何管理字符设备驱动

(1)内核中有一个数组用来存储注册的字符设备驱动

(2)register_chrdev内部将我们要注册的驱动的信息(主要是 )存储在数组中相应的位置

(3)cat /proc/devices查看内核中已经注册过的字符设备驱动(和块设备驱动)

(4)数组的下标就是主设备号(major)

(5)linux/major.h中定义了内核中默认分配给某种设备的主设备号

(6)在include/linux/fs.h文件中定义了#define CHRDEV_MAJOR_HASH_SIZE    255 ,该值与最大支持主设备号有关,具体需分析register_chrdev实现,得到最大为254(内核版本2.6.35.7)

旧的字符设备注册/注销函数

(1)register_chrdev。注册字符设备驱动程序和字符设备主设备号。他只分配主设备号,从设备号在mknod的时候指定

  • 参数:主设备号(为0时,由内核自动分配) 
  • 参数:名字 (/proc/devices)
  • 参数:file_operations结构体变量指针
  • 返回值:第一个参数非零时,正确返回0,错误返回负数;第一个参数为零时,正确返回主设备号,错误返回负数。

(2)unregister_chrdev。注销字符设备驱动程序和字符设备主设备号。

  • 参数:主设备号
  • 参数:名字 (/proc/devices)
  • 返回值:正常返回0,错误返回负数。

设备文件

(1)设备文件的关键信息是:设备号 = 主设备号 + 次设备号,使用ls -l去查看设备文件,就可以得到这个设备文件对应的主次设备号。

(2)使用mknod创建设备文件:mknod /dev/xxx c 主设备号 次设备号 

应用和驱动之间的数据交换

(1)copy_from_user,将数据从用户空间复制到内核空间,copy_from_user函数的返回值定义,和常规有点不同。返回值如果成功复制则返回0,如果 不成功复制则返回尚未成功复制剩下的字节数。

(2)copy_to_user,将数据从内核空间复制到用户空间

如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数

静态映射方法的特点:
    内核移植时以代码的形式硬编码,如果要更改必须改源代码后重新编译内核
    在内核启动时建立静态映射表,到内核关机时销毁,中间一直有效
    对于移植好的内核,你用不用他都在那里

动态映射方法的特点:
    驱动程序根据需要随时动态的建立映射、使用、销毁映射
    映射是短期临时的 

静态映射

(1)不同版本内核中静态映射表位置、文件名可能不同

(2)不同SoC的静态映射表位置、文件名可能不同

(3)所谓映射表其实就是头文件中的宏定义 

(4)虚拟地址基地址定义在:arch/arm/plat-samsung/include/plat/map-base.h

  • #define S3C_ADDR_BASE    (0xFD000000)        // 三星移植时确定的静态映射表的基地址,表中的所有虚拟地址都是以这个地址+偏移量来指定的

(5)主映射表位于:arch/arm/plat-s5p/include/plat/map-s5p.h

  • CPU在安排寄存器地址时不是随意乱序分布的,而是按照模块去区分的。每一个模块内部的很多个寄存器的地址是连续的。所以内核在定义寄存器地址时都是先找到基地址,然后再用基地址+偏移量来寻找具体的一个寄存器。
  • map-s5p.h中定义的就是要用到的几个模块的寄存器虚拟基地址。

(6)GPIO相关的主映射表位于:arch/arm/mach-s5pv210/include/mach/regs-gpio.h,表中是GPIO的各个端口的基地址的定义

(7)GPIO的具体寄存器定义位于:arch/arm/mach-s5pv210/include/mach/gpio-bank.h

(8)静态映射的使用方法和裸机中直接操作寄存器地址没有什么太大区别,只是把之前的物理地址替换为对应的虚拟地址即可。

动态映射

(1)建立静态映射

  •  request_mem_region,向内核申请(报告)需要映射的内存资源。
  • ioremap,真正用来实现映射,传给他物理地址他给你映射返回一个虚拟地址

(2)销毁静态映射

  • iounmap
  • release_mem_region

(3)注意:映射建立时,是要先申请再映射;然后使用;使用完要解除映射时要先解除映射再释放申请。

(4)动态映射使用例程

字符设备驱动高级

主次设备号与dev_t类型

(1)MKDEV分别通过主设备号和次设备号得到整合的dev_t类型的设备号

(2)MAJOR通过整合的dev_t类型的设备号得到主设备号

(3)MINOR通过整合的dev_t类型的设备号得到次设备号 

新接口

(1)register_chrdev_region注册主次设备号

  • 参数:dev_t类型的设备号,表示主设备号下对应的从设备号范围的起始
  • 参数:从设备号的连续编号范围
  • 参数:名字 (/proc/devices)
  • 返回值:正确返回0,错误返回负数

(2)alloc_chrdev_region分配主次设备号

(3)unregister_chrdev_region注销申请的主次设备号

(4)cdev_init初始化驱动变量

  • 参数:输出型参数,struct cdev类型的驱动变量
  • 参数:file_operations结构体变量指针

(5)cdev_alloc申请驱动变量空间

(6)cdev_add关联驱动变量和主次设备号

(7)cdev_del注销字符设备驱动

新接口使用例程  

字符设备注册详解 

udev(嵌入式中用的是mdev)自动创建字符设备驱动的设备文件

(1)udev是应用层的一个应用程序

(2)内核驱动和应用层udev之间有一套信息传输机制(netlink协议)

(3)应用层启用udev,内核驱动中使用相应接口

(4)驱动注册和注销时信息会被传给udev,由udev在应用层进行设备文件的创建和删除

sys文件系统简介
(1)sys文件系统的设计思想
(2)设备类的概念
(3)/sys/class/xxx/中的文件的作用 

内核驱动设备类相关函数

class_create

class_destroy

device_create

device_destroy

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值