编写Linux内核模块/驱动程序,并提供ioctl接口

什么是Linux内核模块、驱动程序和ioctl?

Linux内核模块(Linux Kernel Module)和硬件驱动程序(Hardware Driver)

Linux使用可加载的内核模块(loadable kernel module, LKM)来在运行时动态的添加(或删除)Linux内核的代码。
它有几点好处:
体积:模块化的系统可以减少Linux内核的体积,需要用到的模块用时再加载。
可扩展+安全:内核模块是运行在内核态的,它与用户态应用分离,增强了安全性,又保证了内核的可扩展性。

一个比较典型的Linux可加载内核模块就是Linux中设备的驱动程序。
驱动程序如下图所示,它向下直接与硬件沟通,向上为系统调用提供具体实现。
经过驱动程序的这一层封装,让用户态的程序无需关心硬件运行细节,而直接使用其提供的API,降低了(用户态)程序开发者的开发难度。
Linux kernel space and user space

ioctl

刚才提到,用户态程序是无法直接调用内核态API的,那么应用程序开发者如何与硬件沟通?
Linux就提供了一系列的系统调用,用来让开发者在用户态与系统沟通,进而与硬件沟通,其中就包括了我们常用的open、close、read、write和ioctl。

用户态的ioctl原型为

int ioctl(int fd, int cmd, ...)

而陷入内核之后,ioctl的函数原型为

int (*ioctl)(struct inode *node, struct file *filp, unsigned int cmd, unsigned long arg)

他们之间的关系如下图所示,fd被转换为两个结构体,用来标识操作的设备文件,cmd被原封不动的传入了驱动中。ioctl

写一个简单的内核模块

内核模块HelloWorld

我们结合代码来看一下一个最简单的内核模块的例子

#include <linux/init.h>             // 包含了__init和__exit函数的宏定义
#include <linux/module.h>           // 可加载内核模块的核心的头文件
#include <linux/kernel.h>           // 包含了kernel中的类型、宏和函数等

MODULE_LICENSE("GPL");              //此模块使用的licence,如果不用GPL的话编译时会出warning
MODULE_AUTHOR("StayrealS");      ///模块作者,加载后使用modeinfo可以在系统中看到
MODULE_DESCRIPTION("A simple Linux driver.");  //模块信息,加载后使用modeinfo可以在系统中看到
MODULE_VERSION("0.1");              ///模块版本,加载后使用modeinfo可以在系统中看到


/** @brief Module初始化函数,作为module的入点,使用insmod加载时会调用本函数。
 */
static int __init helloHW_init(void){
   
   printk(KERN_INFO "HW: HelloWorld from the BBB LKM!\n");
   return 0;
}

/** @brief 与init函数相似,在rmmod卸载本模块的时候会调用本函数进行清理等工作。
 */
static void __exit helloHW_exit(void){
   
   printk(KERN_INFO "HW: Goodbye from the HW LKM!\n");
}

/** @brief 一个模块必需使用module_init() 和module_exit() 宏来
 *  定义module的入点和出点
 */
module_init(helloHW_init);
module_exit(helloHW_exit);

内核模块Makefile

另外,还需要Makefile

obj-m+=hello.o

all:
	make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules # 使用M=本文件夹的方式编译本文件夹下的.o为内核模块.ko
clean:
	make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean

其中$shell uname -r用来打印本机linux 内核的版本信息,用于找寻linux内核关于编译module用到的工具。接下来,只需要使用make就可以将hello world编译成为后缀名为.ko的可加载module。
接下来,使用

insmod hello.ko # 加载模块
lsmod # 查看已加载的模块
rmmod hello.ko  # 卸载模块

来加载或卸载编写好的模块。

刚才在编写模块的时候打印是使用了printk,它是printf的内核态版本。
printk的输出不会打印在linux终端上,但是会打印到内核缓冲区中,可以使用

dmesg # 查看内核缓冲区
dmesg -c # 清空内核缓冲区

编写一个提供ioctl接口的设备驱动程序(内核模块)

如第一部分中所说,设备驱动程序是内核模块的一个应用案例。本节我们将编写一个提供ioctl接口的设备驱动程序。

Linux的设备

设备的分类

Linux中I/O设备分为两类:字符设备和块设备。两种设备本身没有严格限制,但是,基于不同的功能进行了分类。

  • 字符设备:提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类设备支持按字节/字符来读写数据。举例来说,键盘、串口、调制解调器都是典型的字符设备。
  • 块设备:应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。硬盘、软盘、CD-ROM驱动器和闪存都是典型的块设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块(通常是512B)的倍数进行。与字符设备不同,块设备并不支持基于字符的寻址。

这两种设备都可以通过访问文件系统中的设备文件(在/dev下)来访问。例如,接下来我们要编写的一个虚拟字符设备,就被挂载在/dev/test处。

设备的主编号(major number)与副编号(minor number)

设备的主编号是为了让操作系统区分不同

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值