1 驱动相关概念
2 内核模块编程
内核模块编写实例代码+注释
#include <linux/init.h>
#include <linux/module.h>
//入口函数,安装内核模块时执行
static int __init mycdev_init(void)
{
//static 修饰当前函数只能在本文件使用
//int 函数的返回值类型,如果函数规定返回值但是没有加返回值,编译会报错
//mycdev_init函数名,可以自己起名字
//void表示函数无参数,当没有参数时void一定要加,不然报错
//__init的作用是用来告诉编译器当前代码保存在.init.text段中
//#define __init __section(".init.text")
//linux内核也会有自己的链接脚本 vmlinux.lds,这个链接脚本里规定了不同的内容在
//内存中的什么位置
return 0;
}
//出口函数,卸载内核模块时执行
static void __exit mycdev_exit(void)
{
//#define __exit __section(".exit.text")
//__exit指定出口函数保存在.exit.text段中
}
//用于声明入口函数
module_init(mycdev_init);
//用于声明出口函数
module_exit(mycdev_exit);
//声明当前内核模块遵循GPL协议
MODULE_LICENSE("GPL");
内核模块编译(外部编译Makefile)
#保存UBUNTU内核源码路径
KERNELDIR := /lib/modules/$(shell uname -r)/build
#KERBELDIR保存开发板内核源码路径
#KERNELDIR := /home/ubuntu/linux-5.10.61/
#PWD保存当前内核模块的路径
PWD := $(shell pwd)
all:
#make modules是模块化编译命令
#make -C $(KERNLEDIR) 执行make之前先切换到KERNELDIR对应的路径
#M=$(PWD)表示进行模块化编译的路径是PWD保存的路径
make -C $(KERNELDIR) M=$(PWD) modules
clean:
#编译清除
make -C $(KERNELDIR) M=$(PWD) clean
#将obj-m保存的文件单独链接为内核模块
obj-m := demo.o
通用版本的Makefile
modname ?= demo
arch ?= arm
ifeq ($(arch),arm) #通过命令行传过来的架构决定怎么编译
#KERBELDIR保存开发板内核源码路径
KERNELDIR := /home/ubuntu/linux-5.10.61/
else
#保存UBUNTU内核源码路径
KERNELDIR := /lib/modules/$(shell uname -r)/build
endif
#PWD保存当前内核模块的路径
PWD := $(shell pwd)
all:
#make modules是模块化编译命令
#make -C $(KERNLEDIR) 执行make之前先切换到KERNELDIR对应的路径
#M=$(PWD)表示进行模块化编译的路径是PWD保存的路径
make -C $(KERNELDIR) M=$(PWD) modules
clean:
#编译清除
make -C $(KERNELDIR) M=$(PWD) clean
#将obj-m保存的文件单独链接为内核模块
obj-m := $(modname).o
3 内核消息打印函数printk函数的使用
代码示例
#include <linux/init.h>
#include <linux/module.h>
// 入口函数,安装内核模块时执行
static int __init mycdev_init(void)
{
// static 修饰当前函数只能在本文件使用
// int 函数的返回值类型,如果函数规定返回值但是没有加返回值,编译会报错
// mycdev_init函数名,可以自己起名字
// void表示函数无参数,当没有参数时void一定要加,不然报错
//__init的作用是用来告诉编译器当前代码保存在.init.text段中
// #define __init __section(".init.text")
// linux内核也会有自己的链接脚本 vmlinux.lds,这个链接脚本里规定了不同的内容在
// 内存中的什么位置
printk(KERN_ERR "hello world\n");
int a=10;
printk(KERN_ERR "%d\n",a);
return 0;
}
// 出口函数,卸载内核模块时执行
static void __exit mycdev_exit(void)
{
// #define __exit __section(".exit.text")
//__exit指定出口函数保存在.exit.text段中
}
// 用于声明入口函数
module_init(mycdev_init);
// 用于声明出口函数
module_exit(mycdev_exit);
// 声明当前内核模块遵循GPL协议
MODULE_LICENSE("GPL");
4 内核模块传参
#include <linux/init.h>
#include <linux/module.h>
int a=10;
module_param(a,int,0664);//声明变量a可以进行命令行传参
MODULE_PARM_DESC(a,"this is a int value");//添加要传参的变量描述
static int __init mycdev_init(void)
{
printk("a=%d\n",a);
return 0;
}
static void __exit mycdev_exit(void)
{
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
5 内核导出符号
1 定义demo1.c,demo1.c中完成函数的定义
#include <linux/init.h>
#include <linux/module.h>
int add(int i,int j)
{
return i+j;
}
//生成add的符号表文件
EXPORT_SYMBOL(add);
static int __init mycdev_init(void)
{
return 0;
}
static void __exit mycdev_exit(void)
{
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
2 创建demo2.c,里面完成demo1.c里面函数的调用
#include <linux/init.h>
#include <linux/module.h>
extern int add(int i,int j);
static int __init mycdev_init(void)
{
printk("调用模块1函数执行结果为:%d",add(3,5));
return 0;
}
static void __exit mycdev_exit(void)
{
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");