核心必須懂(四): 撰寫核心驅動

Sorrower發表於2019-03-28

前言

之前的文章裡面說了簡單的.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


最後

目前來看, 核心驅動模組好像比使用者態程式難不了多少, 但是當程式複雜下去, 除錯就會越發困難了, 不比使用者態. 很多時候, 一個錯誤會很致命, 很多時候, 一個錯誤錯得完全看不懂. 喜歡記得點贊, 有意見或者建議評論區見哦.

相關文章