编译驱动模块

 

ubuntu版本:ubuntu-gnome-16.04-desktop-amd64,gnome版

---------------------------------------------------------------------------------

 

本文主要介绍如何在内核外编译内核模块,即:

how to build an out-of-tree kernel module.

 

1. 代码hello.c

#include <linux/module.h> //所有模块都需要的头文件
#include <linux/init.h> // init&exit相关宏
#include <linux/kernel.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("baoli");
MODULE_DESCRIPTION("hello world module");

static int __init hello_init(void)
{
printk(KERN_WARNING "hello world.\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_WARNING "hello exit!\n");
}

module_init(hello_init);
module_exit(hello_exit);

 

 

 

2. Makefile

ifneq ($(KERNELRELEASE),)
obj-m :=hello.o
else
KDIR :=/lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.order
endif

 

3. 编译测试

make
sudo insmod hello.ko
dmesg
sudo rmmod hello
内核信息如下:

[156596.317933] hello world.

[156604.933930] hello exit!

 

 

inux设备驱动有两种加载方式insmod和modprobe,下面谈谈它们用法上的区别:

1.insmod一次只能加载特定的一个设备驱动,且需要驱动的具体地址。写法为:

insmod drv.ko

2.modprobe则可以一次将有依赖关系的驱动全部加载到内核。不加驱动的具体地址,但需要在安装文件系统时是按照make modues_install的方式安装驱动模块的。驱动被安装在/lib/modules/$(uname -r)/...下。写法为:

modprob drv

 

路径

/lib/modules/4.15.0-128-generic/kernel/drivers/char

 

 

4. Makefile分析

4.1 多个源文件

如果你有一个模块名为 module.ko, 是来自 多个源文件( 姑且称之为, file1.c 和 file2.c ), 正确的书写应当是:

obj-m := module.o

module-objs := file1.o file2.o

 

4.2 clean

rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.order

可以换成:

make -C $(KDIR) M=$(PWD) clean

 

4.3 KERNELRELEASE

这个 makefile 在一次典型的建立中要被读 2 次。

KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次(刚开始)读取执行此Makefile时,KERNELRELEASE没有被定义(为空),所以make将读取执行else之后的内容。如果make的目标是clean,直接执行clean操作,然后结束。当make的目标为all时,-C(KDIR)指明跳转到内核源码目录下读取那里的Makefile,M=(PWD)表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容(指 obj-m :=hello.o)。else之前的内容为kbuild语法的语句,指明模块源码中各文件的依赖关系,以及要生成的目标模块名。

 

4.4 obj-m

obj-m :=hello.o表示编译连接后将生成hello.o模块。

module-objs := file1.o file2.o file3.o表示module.o 由file1.o,file2.o与file3.o 链接生成。

在modules.txt中提到:

obj-m := <module_name>.o

 

The kbuild system will build <module_name>.o from <module_name>.c,

and, after linking, will result in the kernel module <module_name>.ko.

The above line can be put in either a "Kbuild" file or a "Makefile."

When the module is built from multiple sources, an additional line is

needed listing the files:

<module_name>-y := <src1>.o <src2>.o ...

 

4.5 $(KDIR)

/lib/modules/$(shell uname -r)/build 是内核top makefile所在路径。

$(shell uname -r):是调用shell命令显示内核版本,在我系统上是4.4.0-109-generic

 

4.6 kbuild makefile

Kbuild系统使用Kbuild Makefile来编译内核或模块。当Kernel Makefile被解析完成后,Kbuild会读取相关的Kbuild Makefile进行内核或模块的编译。Kbuild Makefile有特定的语法指定哪些编译进内核中、哪些编译为模块、及对应的源文件是什么等。内核及驱动开发人员需要编写这个Kbuild Makefile文件。

目标定义是Kbuild Makefile的主要部分,也是核心部分。主要是定义了要编译的文件,所有的选项,以及到哪些子目录去执行递归操作。 最简单的Kbuild makefile 只包含一行:

例子: obj-y += foo.o

该例子告诉Kbuild在这目录里,有一个名为foo.o的目标文件。foo.o将从foo.c 或foo.S文件编译得到。 如果foo.o要编译成一模块,那就要用obj-m了。所采用的形式如下:

例子: obj-$(CONFIG_FOO) += foo.o

$(CONFIG_FOO)可以为y(编译进内核) 或m(编译成模块)。如果CONFIG_FOO不是y 和m,那么该文件就不会被编译联接了。

 

4.7 -C $KDIR and M=$PWD

以下在内核文档/kbuild/modules.txt中有介绍。

-C $KDIR

The directory where the kernel source is located.

"make" will actually change to the specified directory

when executing and will change back when finished.

 

M=$PWD

Informs kbuild that an external module is being built.

The value given to "M" is the absolute path of the

directory where the external module (kbuild file) is

located.

 

4.8 modules

When building an external module, only a subset of the "make"

targets are available.

 

make -C $KDIR M=$PWD [target]

 

The default will build the module(s) located in the current

directory, so a target does not need to be specified. All

output files will also be generated in this directory. No

attempts are made to update the kernel source, and it is a

precondition that a successful "make" has been executed for the

kernel.

 

modules

The default target for external modules. It has the

same functionality as if no target was specified. See

description above.

 

modules_install

Install the external module(s). The default location is

/lib/modules/<kernel_release>/extra/, but a prefix may

be added with INSTALL_MOD_PATH (discussed in section 5).

 

clean

Remove all generated files in the module directory only.

 

help

List the available targets for external modules.

 

 

 

 

 

当内核模块不再需要时,可以通过将/etc/rc.d/rc.local文件中对应的modprobe命令删除,但需要重启计算机才能生效。此时,可以通过modprobe -r命令来立即删除内核模块:

[root@centos ~]# modprobe -r ip_vs

修改内核参数

临时调整内核参数

​ Linux内核参数随着系统的启动会被写入内存中,我们可以直接修改/proc目录下的大量文件来调整内核参数,并且这种调整会立即生效。

开启内核路由转发功能(通过0或1设置开关):

[root@centos ~]# echo "1" > /proc/sys/net/ipv4/ip_forward

禁止所有的icmp回包(禁止其他主机ping本机)功能

[root@centos ~]# echo "1" > /proc/sys/net/ipv4/icmp_echo_ignore_all

调整所有的进程可以打开的文件总数量(当大量的用户访问网站资源时,可能会因该数字过小而导致错误):

[root@centos ~]# echo "108248" >/proc/sts/fs/file-max

永久调整内核参数

​ 通过man proc可以获得大量关于内核参数的描述信息。但以上通过直接修改/proc相关文件的方式在系统重启后将不再有效,如果希望设置参数并永久有效,可以修改/etc/sysctl.conf文件,文件格式为“选项=值”,我们通过vim修改该文件将前面三个实例参数设置为永久有效;

[root@centos ~]# vim /etc/sysctl.conf

注意:

​ 通过sysctl.conf文件修改的内核参数不会立刻生效,修改完成后,使用sysctl -p命令可以使这些设置立刻生效。

 

 

设置自动启动:

在/etc/modules-load.d/ 加入配置文件

hello

 

 

 

Linux系统加载哪些内核模块,和配置文件有关系。

模块保存在/lib/modules/下。
使用/etc/modules-load.d/来配置系统启动时加载哪些模块。
使用/etc/modprobe.d/下配置模块加载时的一些参数,
也可以利用blacklist来屏蔽模块的自动加载。例如,在安装NVIDIA显卡驱动时,需要屏蔽开源的nouveau驱动,就可以将其加入blacklist。

 

 

新驱动:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "simple_driver"
#define BUF_LEN 1024

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Linux driver");

static int major;
static char msg[BUF_LEN];
static short read_pos;
static int times_opened = 0;

static int device_open(struct inode *inode, struct file *file)
{
times_opened++;
printk(KERN_INFO "Device has been opened %d time(s)\n", times_opened);
return 0;
}

static int device_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device successfully closed\n");
return 0;
}

static ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t *offset)
{
int bytes_read = 0;
if (*offset != 0)
return 0;

while (length && msg[read_pos] != 0)
{
put_user(msg[read_pos], buffer++);
length--;
bytes_read++;
read_pos++;
}

*offset += bytes_read;
return bytes_read;
}

static ssize_t device_write(struct file *filp, const char *buff, size_t len, loff_t *off)
{
int i;
for (i = 0; i < len && i < BUF_LEN; i++)
get_user(msg[i], buff + i);
read_pos = 0;
return i;
}

static struct file_operations fops = {
.open = device_open,
.read = device_read,
.write = device_write,
.release = device_release,
};

static int __init simple_driver_init(void)
{
major = register_chrdev(0, DEVICE_NAME, &fops);
if (major < 0)
{
printk(KERN_ALERT "Registering char device failed with %d\n", major);
return major;
}
printk(KERN_INFO "Simple driver module loaded with device major number %d\n", major);
return 0;
}

static void __exit simple_driver_exit(void)
{
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_INFO "Simple driver module unloaded\n");
}

module_init(simple_driver_init);
module_exit(simple_driver_exit);

 

 

 

Makefile:

obj-m += simple_driver.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

 

 

make

使用 mknod 命令手动创建设备文件,

sudo mknod /dev/simple_driver c <major_number> 0

 

 

参考:

https://blog.csdn.net/u012247418/article/details/83684214

https://icode.best/i/67879639754464

https://blog.csdn.net/kunyus/article/details/104989979

posted @   redrobot  阅读(326)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示