linux驱动基础知识-白阳(一) 驱动作用及加载卸载

本文目的:编写Linux内核驱动基础知识

1.搭建开发环境

tftp

nfs
minicom
vim+.vimrc插件+.vim插件
ctags/cscope +类似于SI
openssh-server/openssh-client
gcc-arm-linux-androideabi
gcc-arm-linux-gnueabi
gcc-arm-linux-gnueabihf
gcc-arm-none-eabi
u-boot-tools
device-tree-compiler

libncurses5

2.搭建开发板的系统环境

2.1 固化UBOOT

解压源代码,make xxx_config,make ARCH=xxx CROSS_COMPILE=arm-xxxx-即可生成u-boot.bin,

会配置uboot和编译。用来学习提高自己。

2.2 启动kernel

官方提供源代码,
cp arch/xx/configs/xxx_deconfig .config 或者make xxx_defconfig
make menuconfig 修改 ->查看内核是否支持ARM平台、是否支持当前处理器、是否支持当前开发板
system type->
    ARM
    CPU(s5pv210)
    board(cw210)
system type ->
    ->ARM system type(Samsung EXYNOS)
    ->board selection 对应UBOOT id号
make ARCH=xxx CROSS_COMPILE=arm-linux- zImage   -j8

2.3 挂接文件系统

体力活,去多学习,熟练。有机会阅读busybox,这部分代码挺好 init.c开始看起。

2.4 设置启动参数

setenv bootcmd 加载并引导启动内核{网络加载,本地flash,本地sd卡启动等}
setenv bootargs 启动参数,UBOOT告诉内核挂接根文件系统信息
setenv bootcmd tftp 50008000 zImage;bootm 50008000
如nfs启动
setenv bootargs root=/dev/nfs nfsroot=主机ip:主机NFS共享目录
ip=板子IP:主机IP:网关:子网掩码::eth0
init=/linuxrc console=ttySAC0,115200
setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/opt/rootfs init=/linuxrc console=ttySAC0,115200
ip=192.168.1.110:192.168.1.8:192.168.1.1:255.255.255.0::eth0
如果提示:Root-NFS:Server returned error -13 while mounting /opt/rootfs 表示服务没有开启。
ipaddr=xxx 注:这个IP为uboot时使用,bootargs在启动时用

saveenv

注:

这部分好像也可以在内核中配置

 570 CONFIG_CMDLINE="root=/dev/nfs rw nfsroot=192.168.1.222:/home/baiy/train/ITOP-learn/nfs-fs/system ip=192.168.1.230:192.168.1.222:192.168.1.1:255.255.255.0:iTOP-nfs:eth0     :off rootfstype=ext4 init=/linuxrc console=ttySAC2,115200"

2.5 系统起来后,就可以搞驱动和应用了。

案例:现在需要写一个LM77温度传感器的配置信息,在Make menuconfig中找到LM77的配置信息,怎么找源文件?
通过源文件找配置项?这部分必须熟练。 

3.什么是驱动

驱动的本质:1.对硬件工作的封装 2.给用户提供可访问的操作方法

驱动的分类:无操作系统(裸板和单片机),应用程序直接调用函数即可。 驱动就是函数封装。
                           有操作系统(Linux,RTOS), 应用程序通过系统间接调用函数即可。驱动就是系统调用。
                            open->sys_open->drv_open,只需要完成drv_open和open即可。
Linux内核驱动的层次:
                            printf("hello, world\n")--->write------>filesystem---->uart_drivers--->硬件输出
                            

3.1 Linux内核驱动开发方法:

3.1.1 驱动的入口函数 和 出口函数

#include <linux/init.h>
#include <linux/module.h>

static int __init xxx_init(void)
{
printk("xx_init:%s\n",__FUNCTION__);
return 0;//成功返回0,失败返回错误码
}
static void __exit xxx_exit(void)
{
printk("xxx_exit:%s\n",__FUNCTION__);
return;
}
module_init(xxx_init);
module_exit(xxx_exit); //修饰入口和出口函数
MODULE_LICENSE("GPL");
MODULE_AUTHOR("baiy <[email protected]>"); //作者信息,选填
MODULE_DESCRIPTION("This is a test driver"); //描述信息,选填
MODULE_VERSION("1.0.0.0");	//版本描述,选填

3.1.2 入口函数和出口函数什么时候被执行

当内核初始化时或者动态加载驱动模块时,执行驱动入口函数。 初始化工作。
当系统复位或者卸载驱动模块时调用驱动出口函数。扫尾工作。
注释:可通过request_module(module_name);函数去加载模块

3.2 Linux内核驱动模块编译

3.2.1 静态编译:

将驱动和内核绑定在一起,写入到Makefile中,将驱动和内核都压缩在zImage中

做法:
1.Kconfig和Makefile
    Kconfig中找到配置项 config SENSORS_LM77。
    通过此信息得到Makefile使用的配置项:CONFIG_SENSORS_LM77。
    找到对应的配置项存在的内容,也就找到对应的源文件。
2.将驱动放在内核源代码中, cp xxx.c driver/char/
    修改Kconfig和Makefile

    Kconfig中加入

   config HELLO_WORLD
   #depend on //依赖某个驱动
   default y
   help
       This is first drive

Makefile中加入
obj-${CONFIG_HELLO_WORLD} += hello.o
make zImage
查看.config和xxx.o文件是否生成。
重启

3.2.2 动态编译1:内核模块。

配置项写为三态,选为模块即可,最后make modules
make menuconfig 将*变成M
make zImage 去除方法1中的静态编译
make modules 编译模块,生成.ko文件,不产生依赖
make modules_install INSTALL_MOD_PATH=/opt/ //安装模块,并产生依赖关系,生成一个lib目录,包含了之间依赖关系。
cp /opt/lib/modules   /rootfs/lib/ -frd 即可。 //安装模块后会生成/opt/lib/modules  文件,拷贝到/lib/下边即可

烧录zImage重启。

静态编译每次修改编译必须重新编译内核,重新启动系统,耗时较长,适用于产品发布阶段。
动态编译1每次修改只需要 make modules 重新编译,将对应.ko拷贝到设备上加载即可。不需要重新启动系统。

共性:都得去修改Kconfig和Makefile,且放到内核编译。

3.2.3 动态编译2:内核模块,将驱动源码和内核源码进行分离。

前两种方式:代码都在内核目录下,都需要修改Kconfig和Makefile
内核目录
driver/
    char

        helloworld.c 在内核代码中,

可以在外部编写,跳转到模块中

#Makefile:
			
# If KERNELRELEASE is defined, we've been invoked from the
# kernel build system and can use its language.
ifneq ($(KERNELRELEASE),)
    #这部分第二次执行		
    obj-m := hello.o			
    # Otherwise we were called directly from the command
    # line; invoke the kernel build system.			
else				
#这部分第一次执行
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build				
    PWD := $(shell pwd)			
default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules			
    #install: //加不加伪目标都可以。这里可以加入mod_install信息
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install INSTALL_MOD_PATH=/opt/			
endif			
clean:			   
    rm -rf *.ko .*.ko.cmd  *.mod.c  *.mod.o .*.mod.o.cmd *.o .*.o.cmd modules.order  Module.symvers .tmp_versions

  KERNELRELEASE 是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义,所以make将读取执行else之后的内容。
当make的目标为all时,-C $(KDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。
当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容,其为kbuild语法的语句, 指明模块源码中各文件的依赖关系,以及要生成的目标模块名。

所以也可以跳转到内核,然后make M=/home/baiy/code/driver-test/test01 modules,一样。

注:如果这里模块有多个文件,可以:

如何将多个.c生成一个.ko?
a.c和b.c生成d.ko
Makefile中
    obj-m := d.o
    d-objs = a.o b.o 
或者
    obj-m := ${MODNAME}.ko
    ${MODNAME}-objs += helloworld1.o
    ${MODNAME}-objs += helloworld2.o
因为同一个驱动,所以无依赖关系了

模块间的依赖关系

baiy@baiy-ThinkPad-X230:iTop4412_Kernel_3.0$ make modules_install INSTALL_MOD_PATH=/home/baiy/code/mod_install/
  INSTALL crypto/ansi_cprng.ko
  INSTALL drivers/char/baiyang_hello1.ko
  INSTALL drivers/media/video/gspca/gspca_main.ko
  INSTALL drivers/mtk_wcn_combo/common/mtk_hif_sdio.ko
  INSTALL drivers/mtk_wcn_combo/common/mtk_stp_bt.ko
  INSTALL drivers/mtk_wcn_combo/common/mtk_stp_gps.ko
  INSTALL drivers/mtk_wcn_combo/common/mtk_stp_uart.ko
  INSTALL drivers/mtk_wcn_combo/common/mtk_stp_wmt.ko
  INSTALL drivers/mtk_wcn_combo/common/mtk_wmt_wifi.ko
  INSTALL drivers/mtk_wcn_combo/drv_bt/hci_stp.ko
  INSTALL drivers/mtk_wcn_combo/drv_fm/private/mtk_fm_priv.ko
  INSTALL drivers/mtk_wcn_combo/drv_fm/public/mt6620_fm_drv.ko
  INSTALL drivers/mtk_wcn_combo/drv_wlan/p2p/p2p.ko
  INSTALL drivers/mtk_wcn_combo/drv_wlan/wlan/wlan.ko
  INSTALL drivers/scsi/scsi_wait_scan.ko
  INSTALL net/bluetooth/hidp/hidp.ko
  DEPMOD  3.0.15						#生成模块依赖关系
Warning: you may need to install module-init-tools
See http://www.codemonkey.org.uk/docs/post-halloween-2.6.txt

baiy@baiy-ThinkPad-X230:iTop4412_Kernel_3.0$ tree ../mod_install/lib/
../mod_install/lib/
└── modules
    └── 3.0.15
        ├── build -> /home/baiy/code/iTop4412_Kernel_3.0
        ├── kernel
        │   ├── drivers
        │   │   ├── char
        │   │   │   └── baiyang_hello1.ko
		。。。。。。
        ├── modules.alias
        ├── modules.alias.bin
        ├── modules.builtin
        ├── modules.builtin.bin
        ├── modules.dep 			#依赖信息
        ├── modules.dep.bin
        ├── modules.devname
        ├── modules.order
        ├── modules.softdep
        ├── modules.symbols
        ├── modules.symbols.bin
        └── source -> /home/baiy/code/iTop4412_Kernel_3.0

  注:如果内核有需要的驱动,则直接编译选项选中即可。

 

3.3 内核模块动态加载

3.3.1 加载模块

modprobe xxx 会默认到/lib/目录下寻找对应.ko文件,并且检测xxx.ko依赖关系,将所有依赖的.ko加载完成后,在加载xxx.ko。
注:
insmod ....ko 这里必须要写.ko的路径,且不会检查依赖关系。如所依赖的模块未加载,则直接出错。

3.3.2 卸载模块

modprobe -r xxx 这个卸载会将依赖一起卸载
rmmod xxx 这个删除模块只删除指定模块,不删除依赖

3.3.3 查看模块

lsmod
modinfo xxx.ko 即可。
注::这几个命令必须会

注:modprobe 加载和卸载时不需要加 .ko,且编译完内核必须进行模块安装且拷贝到/lib目录下

		modprobe(选项)(参数)
		选项
		-a或--all:载入全部的模块;
		-c或--show-conf:显示所有模块的设置信息;
		-d或--debug:使用排错模式;
		-l或--list:显示可用的模块;
		-r或--remove:模块闲置不用时,即自动卸载模块;
		-t或--type:指定模块类型;
		-v或--verbose:执行时显示详细的信息;
		-V或--version:显示版本信息;
		-help:显示帮助。		
		参数: 加载或移除的模块名
	
		modinfo [-adhpV][模块文件]
		参数:
		-a或--author  显示模块开发人员。
		-d或--description  显示模块的说明。
		-h或--help  显示modinfo的参数使用方法。
		-p或--parameters  显示模块所支持的参数。
		-V或--version  显示版本信息。

			
		insmod(选项)(参数)
		选项
		-f:不检查目前kernel版本与模块编译时的kernel版本是否一致,强制将模块载入;
		-k:将模块设置为自动卸除;
		-m:输出模块的载入信息;
		-o<模块名称>:指定模块的名称,可使用模块文件的文件名;
		-p:测试模块是否能正确地载入kernel;
		-s:将所有信息记录在系统记录文件中;
		-v:执行时显示详细的信息;
		-x:不要汇出模块的外部符号;
		-X:汇出模块所有的外部符号,此为预设置。

1.insmod一次只能加载特定的一个设备驱动,且需要驱动的具体地址。写法为:insmod drv.ko
2.modprobe则可以一次将有依赖关系的驱动全部加载到内核。不加驱动的具体地址,但需要在安装文件
系统时是按照make modues_install的方式安装驱动模块的。驱动被安装在/lib/modules/$(uname -r)/...下。写法为:
modprob driver_name
3.modprobe可以解决load module时的依赖关系,比如load moudleA就必须先load mouduleB之类的,它是通过/lib/modules//modules.dep文件来查找依赖关系的。而insmod不能解决依赖问题。
4.modprobe默认会去/lib/下面查找module,而insmod只在给它的参数中去找module(默认在当前目录找)。
这样,有时insmod也有它的有用之处,举个例子吧:有/root/my-mod.ko这个module,cd /root/,然后用insmod my-mod.ko(insmod /root/my-mod.ko)就可以insert这个module了,
 

#经过测试,发现,modprobe默认会去/lib, /lib/modules/ ,  指定路径,这三个地方去找对应的xxx.ko文件

#在下一章会发现,modprobe寻找依赖时,会先尝试将当前驱动加载(此时加载不上),然后将依赖加载,在将当前驱动加载

lsmod 中used by说明此驱动被哪些驱动依赖,需要调用这个驱动中的函数等;这个命令读取 cat /proc/modules 来显示;

驱动中需要关心 /sys/modules/$modulename/相关部分

/sys/module/hello/
├── coresize
├── holders             #持有人,被哪些驱动引用
│   └── param -> ../../param
├── initsize
├── initstate           #live 状态
├── notes
├── refcnt              #引用计数,当为0时可被卸载
├── sections
│   ├── __ksymtab_gpl
│   ├── __ksymtab_strings
│   └── __mcount_loc
├── srcversion
├── taint
├── uevent
└── version
root@baiy-ThinkPad-T480:/home/baiy/workspace/testcode/driver/test01# cat  /sys/module/hello/refcnt 
0
root@baiy-ThinkPad-T480:/home/baiy/workspace/testcode/driver/test01# cat  /sys/module/hello/initstate 
live
 

#以下是转载的,找不到原创了
在搞Linux驱动移植/开发的时候,对于编译出来的驱动可以选择手动insmod,但是感觉很土:1. 需要指定路径; 2. 如果碰到存在依赖的,就丑陋不堪了。

但是modprobe可以很优雅的解决:直接$ modprobe XX_DRIVER_XX即可。

那么问题来了:modprobe自动加载的时候,如何知道驱动的路径和信息呢?以及,我自己编译的驱动,又如何能够modprobe,而不是山寨的insmod呢?



1. modprobe的信息依据

modprobe依赖于/lib/modules/$(uname -r)/modules.dep。而且该文件而不需要手动修改,而使用depmod即可实现自动化操作。

2. depmod
depmod执行不依赖于目录,效果是自动查找/lib/modules/$(uname -r)/下的驱动文件,以及分析彼此的依赖关系。

因此,如果使modprobe找得到的话:

STEP 1:添加驱动至/lib/modules/$(uname -r)/XX_PATH_XX
STEP 2:$ depmod //更新modules.dep信息

注意:以及,加载驱动时: $modprobe XX_DRIVER_XX 而不是 XX_DRIVER_XX.ko

猜你喜欢

转载自blog.csdn.net/sven0223/article/details/81011328
今日推荐