前言
之前的文章裡面說了簡單的.ko檔案編譯. 這裡繼續深入下去. 當然, 還是從驅動的Hello, world!開始.
驅動模組裡的Hello, world!
首先是原始碼部分, 這裡由於是核心, 所以c庫的函式就不能用了, 比如printf這樣的, 要用printk替代, 這裡的k就是指kernel. 然後**__init和__exit意味著只有初始化和解除安裝才會執行函式, 也就是都只執行一次. module_init和module_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檢視資訊是否列印出來.
成功看到模組和列印的訊息:
自定義裝置驅動
接下來更進一步, 寫一下驅動程式碼, 這裡可以自定義驅動的open, ioctl等等函式. 這裡的MAJOR_NUM和DEVICE_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中, 也出現了裝置名和裝置號:
所以需要連結它們, 之後就可以成功執行了. 然後dmesg看下列印的資訊:
最後
目前來看, 核心驅動模組好像比使用者態程式難不了多少, 但是當程式複雜下去, 除錯就會越發困難了, 不比使用者態. 很多時候, 一個錯誤會很致命, 很多時候, 一個錯誤錯得完全看不懂. 喜歡記得點贊, 有意見或者建議評論區見哦.