美文网首页学ubuntu程序员
内核必须懂(四): 撰写内核驱动

内核必须懂(四): 撰写内核驱动

作者: sean_depp | 来源:发表于2019-03-28 19:41 被阅读42次

    内核必须懂(一): 用系统调用打印Hello, world!
    内核必须懂(二): 文件系统初探
    内核必须懂(三): 重编Ubuntu18.04LTS内核4.15.0
    内核必须懂(四): 撰写内核驱动
    内核必须懂(五): per-CPU变量


    前言

    之前的文章里面说了简单的.ko文件编译. 这里继续深入下去. 当然, 还是从驱动的Hello, world!开始.


    驱动模块里的Hello, world!

    首先是源码部分, 这里由于是内核, 所以c库的函数就不能用了, 比如printf这样的, 要用printk替代, 这里的k就是指kernel.
    然后__init__exit意味着只有初始化和卸载才会执行函数, 也就是都只执行一次.
    module_initmodule_exit理解为注册函数就行了.

    #include<linux/kernel.h>
    #include<linux/init.h>
    #include<linux/module.h>
    
    MODULE_LICENSE("Dual BSD/GPL");
    MODULE_AUTHOR("Sean Depp");
    
    static int __init hello_init(void)
    {
            printk("Hello, sean!\n")  ;
            return 0;
    }
    
    static void __exit hello_exit(void)
    {
            printk("Exit, sean!\n");
    }
    
    module_init(hello_init);
    module_exit(hello_exit);
    

    Makefile常规写法就好, 没什么特别要说的. 当然, 你可以写的更有效一些, 比如编译完成之后删除除了.ko文件之外的其它生成文件. 下面给出常规写法和改进写法:

    obj-m:=helloKo.o
    
    PWD:=$(shell pwd)
    KER_DIR=/lib/modules/$(shell uname -r)/build
    
    all :
            make -C $(KER_DIR) M=$(PWD) modules
    clean :
            make -C $(KER_DIR) M=$(PWD) clean
    
    ifneq ($(KERNELRELEASE),)
            obj-m := helloKo.o
    else
            PWD := $(shell pwd)
            KER_DIR ?= /lib/modules/$(shell uname -r)/build
    default:
            $(MAKE) -C $(KER_DIR) M=$(PWD) modules
            rm *.order *.symvers *.mod.c *.o .*.o.cmd .*.cmd .tmp_versions -rf
    endif
    

    来编译生成模块, 之后安装和卸载.

    sudo make
    sudo insmod helloKo.ko
    sudo rmmod helloKo
    
    安装与卸载

    我想你看到了一个提示Makefile:934: "Cannot use CONFIG_STACK_VALIDATION=y, please install libelf-dev, libelf-devel or elfutils-libelf-devel", 很明显这是一个内核编译的参数没生效, 但是编译成功了. 于是我好奇就装了一下libelf-dev, 反而就无法编译成功了. 这里如果有大佬可以告知我为什么, 评论区见, 提前笔芯. 所以这里暂时不管这个参数了.看到了警告, 这是缺了个库, 最直接的解决方案就是, 安装这个库之后, 重编译内核. 否则其他方案都过于麻烦.

    当然, 可以用改进的Makefile再操作一次, 这次用lsmod查看一下安装的模块, 用dmesg查看信息是否打印出来.

    安装与卸载

    成功看到模块和打印的消息:

    lsmod dmesg

    自定义设备驱动

    接下来更进一步, 写一下驱动代码, 这里可以自定义驱动的open, ioctl等等函数. 这里的MAJOR_NUMDEVICE_NAME宏要记一下, 一个是设备节点号, 一个是设备名称.

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <linux/delay.h>
    
    #define    MAJOR_NUM    231
    #define    DEVICE_NAME  "hellodr"
    
    int DriverOpen( struct inode *pslINode, struct file *pslFileStruct )
    {
        printk( KERN_ALERT DEVICE_NAME " hello open.\n" );
        return(0);
    }
    
    
    ssize_t DriverWrite( struct file *pslFileStruct, const char __user *pBuffer, size_t nCount, loff_t *pOffset )
    {
        printk( KERN_ALERT DEVICE_NAME " hello write.\n" );
        return(0);
    }
    
    
    long DriverIOControl( struct file *pslFileStruct, unsigned int uiCmd, unsigned long ulArg )
    {
        printk( KERN_ALERT DEVICE_NAME " hello ioctl.\n" );
        return(0);
    }
    
    
    struct file_operations hello_flops = {
        .owner      = THIS_MODULE,
        .open       = DriverOpen,
        .write      = DriverWrite,
        .unlocked_ioctl = DriverIOControl
    };
    
    static int __init hello_init( void )
    {
        int ret;
    
        ret = register_chrdev( MAJOR_NUM, DEVICE_NAME, &hello_flops );
        if ( ret < 0 )
        {
            printk( KERN_ALERT DEVICE_NAME " can't register major number.\n" );
            return(ret);
        }
        printk( KERN_ALERT DEVICE_NAME " initialized.\n" );
        return(0);
    }
    
    
    static void __exit hello_exit( void )
    {
        printk( KERN_ALERT DEVICE_NAME " removed.\n" );
        unregister_chrdev( MAJOR_NUM, DEVICE_NAME );
    }
    
    module_init( hello_init );
    module_exit( hello_exit );
    MODULE_LICENSE( "GPL" );
    MODULE_AUTHOR( "Sean Depp" );
    

    用户态方面, 写个调用open和ioctl函数的.

    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <iostream>
    #include <sys/types.h>
    /*提供类型pid_t,size_t的定义*/
    #include <sys/stat.h>
    #include <sys/ioctl.h>
    /* BSD and Linux */
    #include <stropts.h>
    /* XSI STREAMS */
    #include <string.h>
    using namespace std;
    
    int main( void )
    {
        int fd;
        if ( (fd = open( "/dev/hellodr", O_RDWR ) ) < 0 )
        {
            cerr << strerror( errno ) << endl;
            return(-1);
        }
    
        ioctl( fd, 1, 0 );
        close( fd );
    
        return(0);
    }
    

    Makefile文件也是相似的.

    ifneq ($(KERNELRELEASE),)
        obj-m := helloDr.o
    else
        PWD := $(shell pwd)
        KER_DIR ?= /lib/modules/$(shell uname -r)/build
    default:
        $(MAKE) -C $(KER_DIR) M=$(PWD) modules
        rm *.order *.symvers *.mod.c *.o .*.o.cmd .*.cmd .tmp_versions -rf
    endif
    

    用g++和make编译一下文件, 来跑下. 如果你直接跑是不行的, 需要链接节点. 从lsmod打印的信息来看, 已经成功安装模块了. 然后你可以查看/proc/devices中, 也出现了设备名和设备号:

    lsmod 设备信息

    所以需要链接它们, 之后就可以成功运行了. 然后dmesg看下打印的信息:

    运行 dmesg

    最后

    目前来看, 内核驱动模块好像比用户态程序难不了多少, 但是当程序复杂下去, 调试就会越发困难了, 不比用户态. 很多时候, 一个错误会很致命, 很多时候, 一个错误错得完全看不懂. 喜欢记得点赞, 有意见或者建议评论区见哦.

    相关文章

      网友评论

        本文标题:内核必须懂(四): 撰写内核驱动

        本文链接:https://www.haomeiwen.com/subject/djqyvqtx.html