1. 程式人生 > 其它 >linux驅動開發

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驗證