[root@hgx driver_test]# cat /etc/redhat-release CentOS Linux release 8.2.2004 (Core) [root@hgx driver_test]# uname -r 4.18.0-305.3.1.el8.x86_64
内核源码下载 https://vault.centos.org
cat /etc/redhat-release #查看Centos版本
uname -r #查看内核版本
lsmod #现实已经安装的驱动 rmmod #卸载驱动 insmod #安装驱动。insmod xxx.ko dmesg #显示开机信息,内核加载的信息
KDIR =/usr/src/kernels/$(shell uname -r)
KDIR 是驱动程序的内核目录, 这是centos的内核头文件目录。driver_hello.c
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <asm/uaccess.h> static int my_init(void) { return 0; } static void my_exit(void) { return; } module_init(my_init); module_exit(my_exit); // MODULE_xxx这种宏作用是用来添加模块描述信息 // 描述模块的许可证 MODULE_LICENSE("GPL"); // 描述模块的作者 MODULE_AUTHOR("aston"); // 描述模块的介绍信息 MODULE_DESCRIPTION("module test"); // 描述模块的别名信息 MODULE_ALIAS("alias xxx");
Makefile
CONFIG_MODULE_SIG=n obj-m += driver_hello.o KDIR =/usr/src/kernels/$(shell uname -r) all: $(MAKE) -C $(KDIR) \ SUBDIRS=$(PWD) \ modules clean: rm -rf *.o *.ko *.mod.* *.symvers *.order rm -rf .tmp_versions .driver_hello.*
Make编译,编译之后会生成.order,.ko,.o等文件,使用 insmod xxx.ko
加载驱动
同样使用 rmmod xxx
卸载驱动
实现open, write函数,实现一个简单的内核态到用户态的数据拷贝
driver_hello.c
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> // copy_to_user, copy_from_user 所在的函数库 #include <linux/uaccess.h> // printk 日志是写在内核日志里面的,使用 dmesg 查看 static int driver_hello_open(struct inode *inode, struct file *file) { printk(KERN_INFO "driver_hello_open \n"); return 0; } static int driver_hello_release(struct inode *inode, struct file *file) { printk(KERN_INFO "driver_hello_release \n"); return 0; } char kbuf[100]; static ssize_t driver_hello_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) { // 使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间中的一个buf中 // memcpy(kbuf, ubuf);不行,因为2个buf不在一个地址空间中 int ret = copy_from_user(kbuf, ubuf, count); if (ret) { printk(KERN_ERR "copy_from_user fail\n"); return -EINVAL; } printk(KERN_INFO "copy_from_user success..\n"); return count; } ssize_t driver_hello_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { int ret = copy_to_user(ubuf, kbuf, count); if(ret) { printk(KERN_ERR "copy_from_user fail\n"); return -EINVAL; } printk(KERN_INFO "copy_to_user success..\n"); return count; } // file_operations 函数讲解。https://www.cnblogs.com/chen-farsight/p/6181341.html static const struct file_operations driver_hello_ops = { .owner = THIS_MODULE, .open = driver_hello_open, .release = driver_hello_release, .write = driver_hello_write, .read = driver_hello_read, }; #define MYNAME "driver_hello" static int mymajor; // __init关键字告诉链接器将代码放在内核对象文件中的专用部分中。 //本节事先对内核所知,并在模块加载和init函数完成后释放。这仅适用于内置驱动程序,不适用于可加载模块。内核将在启动序列期间首次运行驱动程序的init函数。 static int __init my_init(void) { mymajor = register_chrdev(0, MYNAME, &driver_hello_ops); if (mymajor < 0) { printk(KERN_ERR "register_chrdev fail\n"); return -EINVAL; } printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor); return 0; } static void __exit my_exit(void) { printk(KERN_INFO "chrdev_exit helloworld exit\n"); // 在module_exit宏调用的函数中去注销字符设备驱动 unregister_chrdev(mymajor, MYNAME); return; } module_init(my_init); module_exit(my_exit); // MODULE_xxx这种宏作用是用来添加模块描述信息 // 描述模块的许可证 MODULE_LICENSE("GPL"); // 描述模块的作者 // MODULE_AUTHOR("aston"); // 描述模块的介绍信息 // MODULE_DESCRIPTION("module test"); // 描述模块的别名信息 // MODULE_ALIAS("alias xxx");
make #会根据 Makefile 文件编译驱动文件 dmesg #查看驱动设备是否加载正确 cat /proc/devices | grep driver_hello #查看注册的设备编号 mknod /dev/driver_hello c 243 0 #给驱动设备driver_hello编号234创建文件设备 #mknod的设备文件可以使用rm -rf 删除 rm -rf /dev/driver_hello
测试代码 driver_hello_test.c
#include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h> int main(int argc, char const *argv[]) { int fd = open("/dev/driver_hello", O_RDWR); printf("file fd: %d \n", fd); if(fd < 0) { printf("error to open file \n"); } char *buff = "This is my frist driver for linux \n"; int count = write(fd, buff, strlen(buff) + 1); printf("write count: %d \n", count); char rbuf[50]; count = read(fd, &rbuf, sizeof(rbuf)); printf("read count: %d \n", count); printf("read rbuf: %s \n", rbuf); return 0; }
适用 gcc编译运行即可
中文文档:https://www.kernel.org/doc/html/latest/translations/zh_CN/core-api/kernel-api.html
英文文档:https://www.kernel.org/doc/html/latest/search.html?q=malloc&check_keywords=yes&area=default
module verification failed: signature and/or required key missing - tainting kernel
该错误为内核没有签名造成的,linux内核从3.7 开始加入模块签名检查机制, 校验签名是否与已编译的内核公钥匹配。目前只支持RSA X.509验证, 模块签名验证并非强制使用, 可在编译内核时配置是否开启
事实上, linux 的kernel module 加载过程中存在诸多检查, 模块签名验证只是第一步, 后面还会有 vermagic 和 CRC验证