以前的文章裏面說了簡單的.ko文件編譯. 這裏繼續深刻下去. 固然, 仍是從驅動的Hello, world!開始.node
首先是源碼部分, 這裏因爲是內核, 因此c庫的函數就不能用了, 好比printf這樣的, 要用printk替代, 這裏的k就是指kernel. 而後**__init和__exit意味着只有初始化和卸載纔會執行函數, 也就是都只執行一次. module_init和module_exit**理解爲註冊函數就好了.linux
#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**文件以外的其它生成文件. 下面給出常規寫法和改進寫法:ios
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
複製代碼
來編譯生成模塊, 以後安裝和卸載.shell
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, 反而就沒法編譯成功了. 這裏若是有大佬能夠告知我爲何, 評論區見, 提早筆芯. 因此這裏暫時無論這個參數了.bash
固然, 能夠用改進的Makefile再操做一次, 此次用lsmod查看一下安裝的模塊, 用dmesg查看信息是否打印出來.函數
成功看到模塊和打印的消息:ui
接下來更進一步, 寫一下驅動代碼, 這裏能夠自定義驅動的open, ioctl等等函數. 這裏的MAJOR_NUM和DEVICE_NAME宏要記一下, 一個是設備節點號, 一個是設備名稱.spa
#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函數的.3d
#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看下打印的信息:
目前來看, 內核驅動模塊好像比用戶態程序難不了多少, 可是當程序複雜下去, 調試就會愈加困難了, 不比用戶態. 不少時候, 一個錯誤會很致命, 不少時候, 一個錯誤錯得徹底看不懂. 喜歡記得點贊, 有意見或者建議評論區見哦.