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

2019-04-01 11:25:02 浏览数 (1)

前言

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


驱动模块里的Hello, world!

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

代码语言:javascript复制
#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文件之外的其它生成文件. 下面给出常规写法和改进写法:

代码语言:javascript复制
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
代码语言:javascript复制
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

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

代码语言:javascript复制
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宏要记一下, 一个是设备节点号, 一个是设备名称.

代码语言:javascript复制
#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函数的.

代码语言:javascript复制
#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文件也是相似的.

代码语言:javascript复制
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


最后

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

0 人点赞