linux驱动开发

linux驱动简单案例

环境

[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 #显示开机信息,内核加载的信息

实现一个简单驱动程序代码

示例程序

  1. driver_hello.c
  2. Makefile
    其中 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 卸载驱动

用户态如何调用驱动程序

修改driver_hello.c代码

实现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编译运行即可

内核API文档

中文文档: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

问题记录

  1. module verification failed: signature and/or required key missing - tainting kernel

该错误为内核没有签名造成的,linux内核从3.7 开始加入模块签名检查机制, 校验签名是否与已编译的内核公钥匹配。目前只支持RSA X.509验证, 模块签名验证并非强制使用, 可在编译内核时配置是否开启
事实上, linux 的kernel module 加载过程中存在诸多检查, 模块签名验证只是第一步, 后面还会有 vermagic 和 CRC验证

上一篇:java版selnium_demo


下一篇:干货| app自动化测试之Andriod微信小程序的自动化测试