前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >内核必须懂(四): 撰写内核驱动

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

作者头像
sean_yang
发布2019-04-01 11:25:02
1.3K0
发布2019-04-01 11:25:02
举报
文章被收录于专栏:Sorrower的专栏

前言

之前的文章里面说了简单的.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


最后

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019.03.28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 驱动模块里的Hello, world!
  • 自定义设备驱动
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档