Linux驱动开发学习-02.第一个模块HelloWorld

02.第一个模块HelloWorld

2.1 内核划分

2.1.1 根据系统结构划分(5)

  • 进程管理:内核负责进程创建与销毁,处理输入输出,进程间通信,共享CPU控制等
  • 内存管理:内核为每个进程建立虚拟地址空间,内核与内存管理系统交互
  • 文件系统:内核在非结构化的硬件上建立了结构化文件系统
  • 设备控制:除处理器、内存和少见的实体外,其他任何设备控制操作由设备驱动完成
  • 网络:报文是异步事件,在某一进程接手前必须被收集、识别和分发;系统负责在程序和网络间递送报文;所有路由和地址解析问题都在内核中实现
    在这里插入图片描述

2.1.2 根据模块划分

Linux优良特性之一:在系统正在运行时,仍然可以增加或去除内核的部分功能;每块可以在运行时添加到内核的代码即为一个模块;模块并不是一个完成的可执行文件,它需要通过insmod(rmmod)动态链接(移除)到内核中

在这里插入图片描述

2.1.3 设备分类(3)

  • 字符设备:可当做字节流存取的设备(如同一个文件),驱动至少要实现openclosereadwrite系统调用;文本控制台/dev/console和串口/dev/ttyS*是典型实例;字符设备通过文件系统结点存取,如/dev/tty1;与普通文件区别在于,字符设备只能顺序存取
  • 块设备:同字符设备,块设备通过/dev/下的文件系统结点存取;块设备驻有文件系统;大部分Unix系统块设备仅能传送2^n字节的整块,而Linux可传送任意字节;块和字符设备的区别仅仅在于内核在内部管理数据的方式上,以及软件接口上
  • 网络接口:网络接口负责发送和接收报文;非面向流的设备;Unix提供的对接口的存取方式仍然是分配一个名字(如eth*)但这个名字在文件系统中并没有对应入口

除设备驱动外,不论硬件和软件,在内核中都是模块化的(如文件系统)

2. HelloWorld模块

2.2.1 源文件

  • 创建hello.c
#include <linux/init.h>   
#include <linux/module.h>  

MODULE_LICENSE("Dual BSD/GPL");//告知内核自由许可证   

static int hello_init(void)   
{   
    printk(KERN_ALERT "Hello, world\n");//printk内核打印,KERN_ALERT消息优先级  
    return 0;   
}   
static void hello_exit(void)   
{   
    printk(KERN_ALERT "Goodbye, cruel world\n");   
}   

module_init(hello_init);//特别的内核宏   
module_exit(hello_exit);  
  • 创建makefile
obj-m := hello.o   
KERNELDIR := /home/fa/linux-3.4.y  
PWD := $(shell pwd)   

modules:  
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules  
modules_install:  
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install  
clean:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) clean

2.2.2 模块安装与卸载

  • make编译后,使用insmodmodprobermmod加载或卸载模块
  • insmod加载模块的代码段和数据段到内核,加载中有类似ld的函数,它连接模块中的符号到内核的符号表上,内核不修改模块的磁盘文件,而是内存内的拷贝;insmod可接收许多命令行选项
  • insmod依赖kernel/module.c中定义的系统调用,函数sys_init_module分配内核内存来存放模块(vmalloc),它接着拷贝模块的代码段到这块内存区,借助内核符号表解决模块中的内核引用,并且调用模块的初始化函数来启动所有东西
  • sys_前缀的代码对所有系统调用都是成立的
  • 模块加载时,会有vermagic检查,vermagic.o目标文件中包含了目标内核版本、编译器版本,以及许多重要配置变量的设置信息,这些信息会与运行内核进行匹配,若不匹配,模块将不会加载,可通过/var/log/message查看加载日志
  • 模块代码必须要为每个它要连接的内核版本重新编译,模块紧密结合到特殊内核版本的数据结构和函数原型之上

2.2.3 注意

  • 模块没有外部库只能调用内核中函数,因此源文件中也不应包含通常的头文件;大部分头文件位于内核源码include/linux以及include/asm目录下,其余部分已经关联到特定内核子系统内
  • printk不支持浮点
  • 模块加载到内核的过程
    在这里插入图片描述

2.3 带参HelloWorld模块

#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
 
MODULE_LICENSE("Dual BSD/GPL");
static char *whom = "world";
static int howmany = 1;

//insmod hello.ko howmany=10 whom="hello"改变模块参数
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO); 

static int hello_init(void)
{
	int i;
	for (i = 0; i < howmany; i++)
		printk(KERN_ALERT "(%d) Hello, %s\n", i, whom);
	return 0;
}
 
static void hello_exit(void)
{
	printk(KERN_ALERT "Goodbye, cruel world\n");
}
 
module_init(hello_init);
module_exit(hello_exit);
  • 参数用moudle_param宏定义来声明,它定义在moduleparam.h中,module_param使用了3个参数,变量名、类型,以及一个权限掩码用来做一个辅助的sysfs入口
  • 模块参数支持类型:boolinvboolcharpintintlongshortuintulongushort
  • 数组参数使用module_param_array(name,type,num,perm);,perm是权限值,在<linux/stat.h>中定义,S_IRUGOS_IWUSR

2.4 源码说明

2.4.1 Document目录

  • 通过Document/kbuild目录下文件查看模块如何被建立,包括kbuildkconfigmakefilesmodules文件
  • 通过Document/Changes查看各种工具版本

2.4.2 与模块相关的头文件

  • module.h中包含了大量加载模块需要的函数和符号的定义,init.h指定初始化和清理函数,moduleparam.h在模块加载时传递参数
  • 可以在模块中包含其他描述性定义,如MODULE_AUTHORMODULE_DESCRIPIONMODULE_VERSIONMODULE_ALIASMODULE_DEVICE_TABLE(告知用户空间模块支持那些设备),惯例这些声明置于文件末尾
  • struct task_struct *current;(#include <linux/sched.h>,包含很多驱动使用的内核API的定义)
  • module_param(variable, type, perm);(#include <linux/moduleparam.h>)
  • int printk(const char * fmt, ...);(#include <linux/kernel.h>)

2.4.3 初始化&退出

  • 模块初始化函数注册模块提供的任何功能
  • 初始化函数应声明为static,因为它们不会在特定文件之外可见
  • __init标志给内核暗示,给定的函数只是在初始化使用,__initdata给只在初始化时用的数据
  • __exit__exitdata
  • 使用moudle_init是强制的,它表明增加了特别的段到模块目标代码中,表明在哪里找到模块的初始化函数
  • 模块可以注册许多的不同设施,包括不同类型的设备、文件系统、加密转换等;对每一个设施,有一个特定的内核函数来完成这个注册,传给内核注册函数的参数常常是一些数据结构的指针,描述新设施以及要注册的新设施的名字,数据结构常常包含模块函数指针
static int __init initialization_function(void)
{
/* Initialization code here */
}
module_init(initialization_function);
static void __exit cleanup_function(void)
{
/* Cleanup code here */
}
module_exit(cleanup_function);

2.5 内核空间与用户空间

  • 模块在内核空间运行,应用程序在用户空间运行
  • Unix下,内核在最高级操作形态运行,允许任何事情,而应用程序在最低级运行,这里处理器控制了对硬件的直接存取以及对内存的非法存取;内核空间和用户空间都有各自的内存映射(自己的地址空间)
  • Unix 从用户空间转换执行到内核空间,无论何时一个应用程序发出一个系统调用或者被硬件中断挂起时,执行系统调用的内核代码在进程的上下文中工作(它代表调用进程并且可以存取该进程的地址空间),即处理中断的代码对进程来说是异步的,不和任何特别的进程有关
  • 模块通常的任务,一是作为系统调用的一部分执行,二是负责中断处理
  • Linux内核代码包括驱动代码,必须是可重入
    在这里插入图片描述

2.6 在用户空间编写驱动

  • 用户空间设备驱动优点:可使用C库,可运行常用调试器、可杀掉挂起的用户空间驱动、用户内存可交换、允许对设备并行存取
  • 用户空间设备驱动缺点:无法使用中断、只能通过内存映射/dev/mem使用DMA、存取I/O端口只能在调用iopermiopl之后、响应时间慢、最重要的设备不能在用户空间处理

2.7 实操

  • 开发板:NanoPi-NEO-Air
  • 内核源码版本:4.14.52 (从源码顶层Makefile的首部可以看到源码的版本号,开发板中使用uname -r可以看到系统运行的版本号
VERSION = 4
PATCHLEVEL = 14
SUBLEVEL = 52
  • 使用make编译hello.c时,报错
./arch/x86/include/asm/arch_hweight.h:55:42: error: expected ‘:’ or ‘)’ before ‘POPCNT64’
./arch/x86/include/asm/atomic64_64.h:22:22: error: request for member ‘counter$
./arch/x86/include/asm/atomic64_64.h:22:22: error: request for member ‘counter$
...

这是由于make是没有指定编译平台和编译工具链所致,使用make ARCH=arm CROSS_COMPILE=arm-linux-即可解决

  • 使用sudo insmod hello.ko时报错
insmod: ERROR: could not insert module hello.ko: Unknown symbol in module

通过dmesg|tail查看日志

[  116.880560] hello: loading out-of-tree module taints kernel.
[  116.880736] hello: Unknown symbol __gnu_mcount_nc (err 0)

百度搜索其原因,Unknown symbol __gnu_mcount_nc (err 0) ,是顶层Makefile开启了gprof(并不知道是什么东西),只需要把CFLAGS-pg-p参数去掉即可,即注释掉第763

763 #CC_FLAGS_FTRACE := -pg 

再次编译、insmod成功,通过dmesg|tail可以查看到

[  272.028920] hello: loading out-of-tree module taints kernel.
[  272.030778] Hello, world
  • 使用sudo rmmod -f hello卸载模块时报错
rmmod: ERROR: ../libkmod/libkmod-module.c:793 kmod_module_remove_module() could not remove 'hello': Device or resource busy
rmmod: ERROR: could not remove module hello: Device or resource busy

百度后,这种无法卸载模块的原因有:设备号冲突(hello模块并无设备号)、别的进程正在使用等,Linux强制卸载内核模块(由于驱动异常导致rmmod不能卸载,该文描述的很清楚,但这里仍旧没找到无法卸载的原因,日后再说!

  • 调试工具
lsmod#查看已安装的模块
cat /proc/modules#查看设备详细信息(模块名字 内存大小 load次数 依赖 状态 内存偏移位置)
dmesg#查看内核日志(-T)
cat /proc/devices#查看设备以及设备号
modinfo hello.ko#查看模块信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值