Linux 驱动开发之内核模块开发 (一)—— 内核模块机制基础

本文介绍了Linux内核模块的基本概念,包括模块的动态加载特性、加载作用、使用的特定内核函数,以及内核模块与应用程序的区别。文章强调内核模块运行在内核空间,使用内核导出的函数,并以module_init()和module_exit()作为模块的加载和卸载入口。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、内核模块的概念

1、什么是模块?

       内核模块是一些可以让操作系统内核在需要时载入和执行的代码,同时在不需要的时候可以卸载。这是一个好的功能,扩展了操作系统的内核功能,却不需要重新启动系统,是一种动态加载的技术。

特点:动态加载,随时载入,随时卸载,扩展功能


2、内核模块的加载作用

      内核模块只是向linux内核预先注册自己,以便于将来的请求使用;由目标代码组成,没有形成完整的可执行程序。只是告诉内核,它有了新增的功能,而并不马上使用(执行),只是等待应用程序的调用;而应用程序在加载后就开始执行。

     

3、内核模块所用函数

      内核模块代码编写没有外部的函数库可以用,只能使用内核导出的函数。而应用程序习惯于使用外部的库函数,在编译时将程序与库函数链接在一起。例如对比printf( ) and printk( )。

      驱动所用头文件均来自内核源代码,应用程序所用头文件来自库函数


4、内核模块代码运行空间

      内核代码运行在内核空间,而应用程序在用户空间。应用程序的运行会形成新的进程,而内核模块一般不会。每当应用程序执行系统调用时,linux执行模式从用户空间切换到内核空间。


二、linux内核模块的框架

        最少两个入口点

*模块加载函数   module_init()

*模块卸载函数   module_exit()

      module_init() and module_exit()两个宏定义声明模块的加载函数和卸载函数,这个定义在linux3.14/include/linux/init.h中。内容为:

#define module_init(x) __initcall(x)

//在内核启动或模块加载时执行

#define module_exit(x) __exitcall(x)

//在模块卸载时执行

每一个模块只能有一个module_init 和一个module_exit


下面我们对比一下应用程序,看看应用程序与内核模块的区别

#include <stdio.h>

int main()
{
     printf("Hello World!\n");

     
买书时赠送的电子稿,代码示例丰富,非常不错!全书分三个文件打包。 linuxdriver_code_tool |-- 03 | `-- 2.6内核升级工具 | |-- device-mapper-1.00.19-2.i386.rpm | |-- lvm2-2.00.25-1.01.i386.rpm | |-- mkinitrd-4.2.0.3.tar.tar | |-- module-init-tools-3.2.2.tar.bz2 | `-- modutils-2.4.5-1.src.rpm |-- 04 | |-- 内核模块参数范例 | | `-- book.c | |-- 内核模块导出符号 | | `-- export_symb.c | `-- 最简单的内核模块 | `-- hello.c |-- 05 | `-- udev源代码 | `-- udev-114.tar.gz |-- 06 | |-- globalmem驱动 | | `-- globalmem.c | `-- 包含2个globalmem设备的驱动 | `-- globalmem_two.c |-- 07 | `-- 含并发控制的globalmem驱动 | `-- globalmem_lock.c |-- 08 | |-- globalfifo驱动 | | `-- globalfifo.c | `-- poll应用程序范例 | `-- pollmonitor.c |-- 09 | |-- 异步通知应用程序范例 | | `-- asyncmonitor.c | `-- 支持异步通知的globalfifo | `-- globalfifo_async.c |-- 10 | |-- S3C2410实时钟驱动 | | `-- s3c2410-rtc.c | `-- 秒设备驱动与应用程序 | |-- second.c | `-- second_test.c |-- 11 | |-- DMA范例 | | |-- 3c505.c | | |-- 3c505.h | | `-- dma.h | `-- 静态映射范例 | `-- mach-smdk2440.c |-- 12 | |-- NVRAM驱动 | | `-- generic_nvram.c | |-- 触摸屏驱动 | | |-- 作为input设备 | | | |-- s3c2410_ts.c | | | `-- s3c2410_ts.h | | `-- 作为普通字符设备 | | `-- s3c2410-ts.c | |-- 看门狗驱动 | | `-- s3c2410_wdt.c | `-- 平台设备 | `-- devs.c |-- 13 | |-- IDE驱动 | | |-- ide-disk.c | | `-- ide-h8300.c | `-- RAMDISK驱动 | `-- rd.c |-- 14 | |-- S3C2410串口驱动 | | |-- regs-gpio.h | | |-- regs-serial.h | | `-- s3c2410.c | `-- 串口核心层 | |-- serial_core.c | `-- serial_core.h |-- 15 | |-- S3C2410 I2C主机驱动 | | |-- i2c-s3c2410.c | | |-- iic.h | | |-- regs-gpio.h | | `-- regs-iic.h | `-- SAA711x I2C设备驱动 | `-- saa711x.c |-- 16 | `-- CS8900以太网设备驱动 | |-- cs89x0.c | `-- cs89x0.h |-- 17 | |-- ALSA工具及库 | | |-- alsa-driver-1.0.15.tar.bz2 | | |-- alsa-firmware-1.0.15.tar.bz2 | | |-- alsa-lib-1.0.15.tar.bz2 | | |--
### Linux 驱动开发:编写内核模块教程 #### 创建项目结构 为了便于管理和编译,建议创建个专门用于存储驱动代码的工作空间。按照惯例,在此工作空间下建立`linux_driver/module/hellomodule/`文件夹,并将所需的源码解压缩至该位置[^1]。 #### 准备Makefile 构建Linux内核模块的关键在于配置恰当的Makefile。典型的Makefile会指定要编译的目标以及链接到哪个版本的内核头文件。下面是个简单的例子: ```makefile obj-m += hellomodule.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean ``` 这段脚本定义了个名为hellomodule的对象文件作为最终产物;当执行`make all`时它会在当前路径下调用系统的标准内核构建环境来处理我们的模块;而`make clean`则负责清理临时生成物以便重新开始新的编译过程。 #### 编写基本的HelloWorld模块 接下来就是实际编码部分了。这里给出段非常基础但也十分重要的hello world风格的内核模块实现方式: ```c #include <linux/init.h> #include <linux/module.h> static int __init hello_init(void){ printk(KERN_INFO "Hello, Kernel!\n"); return 0; } static void __exit hello_exit(void){ printk(KERN_INFO "Goodbye, Kernel.\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple Hello World module."); ``` 上述代码展示了怎样向系统注册入口函数(`__init`)和出口函数(`__exit`),并通过调用printk()打印调试信息给dmesg日志缓冲区。最后几行宏用来描述这个模块的些元数据,比如许可证声明、作者姓名等。 #### 加载与卸载模块 完成以上准备工作之后就可以尝试着把新写的模块加载入正在运行中的kernel里去了。这步骤需要用到两个命令——个是`insmod`用于安装未压缩过的`.ko`二进制镜像;另个则是`rmmod`可以安全地移除不再需要使用的模块实例[^3]。 #### 测试验证 旦成功装载上了自定义的驱动组件,可以通过读取/sys/kernel/debug/dri目录下的相应条目或者查看/var/log/messages里的记录确认其行为是否符合预期效果。另外还可以利用cat /proc/devices这样的指令查询已知字符型设备列表看看有没有新增加进来的东西。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值