Linux:模块加载与参数传递

Linux:模块加载与参数传递

Abstract

Linux内核是模块化的,由一个尽可能小的基本内核,和一堆实现进阶功能的内核模块组成。

支持模块的好处有三个,一是让基本内核非常精简,二是允许在运行时添加功能,三是支持设备的热插拔,因为设备驱动也是以内核模块的形式实现的。大多数情况下,可以认为模块的功能就是注册和删除设备驱动。

模块和应用程序的功能是不同的,它并不负责完成工作,而是将完成工作所需要的函数,结构体,变量等注册到内核,并在设备不再需要的时候清除内核。

编写内核通常由四步组成

  1. 编写设备功能
  2. 编写初始化/退出函数
  3. 编译模块
  4. 装载/卸载模块

设备功能

Linux设备通常分为三类

  1. 字符设备(PCI设备,USB设备等)
  2. 块设备(磁盘等)
  3. 网络设备

对设备的划分纯粹是功能性的,换句话说,字符设备共享一套编程接口(尽管它们的功能不尽相同),块设备共享一套,网络设备则是另一套。

拿字符设备来说,具体的编程接口由三个结构体定义:

  1. file_operations,定义对设备的操作
  2. file,设备的运行时标识
  3. inode,设备在系统中的标识

file_operation中,定义了很多对设备的操作函数,open,release,read,write等,它们的参数和返回值是由内核统一规定的,这使得内核能够以统一的接口处理不同的设备。

在定义自己的设备时,需要提供一个file_operations结构体,其中自己定义的函数以下图的方式映射,未定义的函数则为NULL。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3eAfM15y-1583671464004)(F:\Users\iSIka\AppData\Roaming\Typora\typora-user-images\image-20200308162942375.png)]

关于file_operation的具体定义,以及它是如何使用file和inode的,这是另一个有趣的故事,目前,就让我们使用一个toy example,file_operations是一个空结构体,该设备的每个函数都为空1

/*Demo设备向系统注册操作功能,声明设备操作的功能接口函数*/
struct file_operations Demo_ops={
};

编写初始化/退出函数

假设已经编写好了关于设备功能的函数,下一步就是编写该模块被注册时调用的函数。

初始化函数一般被声明为static,这使得它只在内部可见,被其他模块调用时需要借助于称为导出的技术,__init标记是另外一个可选标记,它使得该函数在初始化之后从内核中清除出去。

/*系统初始化*/
static int __init Demo_Init(void)
{
	int ret = register_chrdev(DEMO_MAJOR, DEVICE_NAME, &Demo_ops);
	if(ret){	
		printk("Init Failed");		
	}	
	else{
		printk("Init Successed!");	
    }
    return ret;
}

在初始化函数内部,使用register_chrdev(Major,Name,file_operations)注册字符设备,Major是主设备号,在0~255之间,name是设备名,在/proc/devices中使用,Demo_ops就是前面建立的file_operations结构体。

与初始化函数相对的,模块必须编写退出函数,在模块被卸载时调用以释放资源。

static void __exit Demo_Exit(void)
{	
	unregister_chrdev(DEMO_MAJOR, DEVICE_NAME);
	printk("Bye~\n");	
}

注意register_chrdev和unregister_chrdev都是在2.6版本之前使用的函数,在2.6版本之后,一般使用cdev注册。

在编写完这两个函数之后,需要将其与模块初始化与卸载绑定。

/*内核模块入口,相当于main()函数,完成模块初始化*/
module_init(Demo_Init);
/*卸载时调用的函数入口,完成模块卸载*/
module_exit(Demo_Exit);

其中module_init和module_exit都是预先定义好的宏,不可改变。

最后为模块设定许可证,表示其开源协议即可,如果没有显式设定许可证,则会被认为是“Proprietary(专有)”。

MODULE_LICENSE("GPL");

编译模块

和任何一个C语言程序一样,模块在实际使用之前需要经过编译,编译在linux内核中一般通过make命令和makefile完成。

例如:

$:make a

linux会在当前目录下查找Makefile,并在其中寻找a:something项,并将其执行。

例如:

a:cat Makefile

此时:make a 将Makefile打印到终端

$:make a

在编译模块时,我们使用的Makefile一般如下:

obj-m := demo_drv.o
KERNELDIR := /lib/modules/3.2.14/build
PWD := $(shell pwd)

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

modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:	
	rm -f *.ko *.mod.c *.o ~core.depend

当在终端执行:

$ make

会找到Makefile里的default项,执行

make -C $(KERNELDIR) M=$(PWD) modules

其中,-C表明对module的编译规则需要到KERNELDIR目录下的Makefile中寻找,M=PWD表示最终要编译的参数在当前目录,而在Makefile开头声明的obj-m:=demo_drv.o,则指示了make的目标:demo_drv.c会先被编译为demo_drv.o,然后得到demo_drv.ko2

同理,make modules_install和make clean也会执行对应的功能。

装载/卸载 模块

模块编译之后,得到.ko文件,这就是我们要装载的模块,语法是简单的:

sudo insmod mymodule.ko
sudo rmmod mymodule

代码和结果

#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/cdev.h>

#define DEMO_MAJOR 98
#define DEMO_MINOR 0
#define DEVICE_CNT 1
#define DEVICE_NAME "demo_drv"

/*Demo设备向系统注册操作功能,声明设备操作的功能接口函数*/
struct file_operations Demo_ops={
};

/*系统初始化*/
static int __init Demo_Init(void)
{	
	int ret = -1;	

	ret = register_chrdev(DEMO_MAJOR, DEVICE_NAME, &Demo_ops);
	if(ret){	
		printk("Demo_module failed with %d\n[--kernel--]",ret);		
		return 	
	}	
	else{
		printk("Demo_driver register success!!![--kernel--]\n");	
	}	

	return 0;
}

/*系统卸载*/
static void __exit Demo_Exit(void)
{	
	unregister_chrdev(DEMO_MAJOR, DEVICE_NAME);
	printk("Demo Driver unregister success!!![--kernel--]\n");	
}

/*驱动程序版本及GPL协议证书信息*/
MODULE_DESCRIPTION("Simple driver module");
MODULE_LICENSE("GPL");

/*内核模块入口,相当于main()函数,完成模块初始化*/
module_init(Demo_Init);
/*卸载时调用的函数入口,完成模块卸载*/
module_exit(Demo_Exit);

obj-m := demo_drv.o
KERNELDIR := /lib/modules/3.2.14/build
PWD := $(shell pwd)

default:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:	
	rm -f *.ko *.mod.c *.o ~core.depend

将这两个文件放在同一个目录下

make
sudo insmod demo_drv.ko
dmesg

在这里插入图片描述

sudo rmmod demo_drc
dmesg

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-73urJiMZ-1583671464008)(F:\Users\iSIka\AppData\Roaming\Typora\typora-user-images\image-20200308203948175.png)]


  1. 但这不妨碍它成为合法的设备 ↩︎

  2. 这不是唯一的定义方式,例如,模块由多个源文件生成时,则使用另外的语法。 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值