關注“技術簡說”,一步一步教你開發linux內核和驅動
hello world!是廣大程序員入門一門新語言的第一步。
今天,我們來看一個hello驅動,希望這是大家入門linux內核驅動的良好開局。
我的環境是ubuntu 14.04,內核版本 4.4.0-31-generic,本節我會開發一個基于ubuntu 14.04下的最簡單的hello驅動,帶大家領略驅動的魅力。
開發linux內核驅動需要以下4個步驟:
- 編寫hello驅動代碼
- 編寫makefile
- 編譯和加載hello驅動
- 編寫應用程序測試hello驅動
驅動代碼如下 helloDev.c,這是一個最小、最簡單的驅動,我去掉了其他的不相干代碼,盡量讓大家能了解驅動本身。
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>
#define BUFFER_MAX (10)
#define OK (0)
#define ERROR (-1)
struct cdev *gDev;
struct file_operations *gFile;
dev_t devNum;
unsigned int subDevNum = 1;
int reg_major = 232;
int reg_minor = 0;
char *buffer;
int flag = 0;
int hello_open(struct inode *p, struct file *f)
{
printk(KERN_EMERG"hello_openrn");
return 0;
}
ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)
{
printk(KERN_EMERG"hello_writern");
return 0;
}
ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l)
{
printk(KERN_EMERG"hello_readrn");
return 0;
}
int hello_init(void)
{
devNum = MKDEV(reg_major, reg_minor);
if(OK == register_chrdev_region(devNum, subDevNum, "helloworld")){
printk(KERN_EMERG"register_chrdev_region ok n");
}else {
printk(KERN_EMERG"register_chrdev_region error n");
return ERROR;
}
printk(KERN_EMERG" hello driver init n");
gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);
gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
gFile->open = hello_open;
gFile->read = hello_read;
gFile->write = hello_write;
gFile->owner = THIS_MODULE;
cdev_init(gDev, gFile);
cdev_add(gDev, devNum, 3);
return 0;
}
void __exit hello_exit(void)
{
cdev_del(gDev);
unregister_chrdev_region(devNum, subDevNum);
return;
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
有了驅動文件之后,我們還需要一個Makefile才能把驅動編譯出來:
ifneq ($(KERNELRELEASE),)
obj-m := helloDev.o
else
PWD := $(shell pwd)
KDIR:= /lib/modules/4.4.0-31-generic/build
#KDIR := /lib/modules/`uname -r`/build
all:
make -C $(KDIR) M=$(PWD)
clean:
rm -rf *.o *.ko *.mod.c *.symvers *.c~ *~
endif
linux應用層程序在編譯的時候,需要鏈接c運行時庫和glibc庫。那驅動需不需要呢?
驅動也需要,但是驅動不能鏈接和使用應用層的任何lib庫,驅動需要引用內核的頭文件和函數。所以,編譯的時候需要指定內核源碼的地址。為了開發方便,也可以安裝內核開發包,之后引用這個內核開發包的目錄也可以。本例為:/lib/modules/4.4.0-31-generic/build
驅動文件和Makefile都有了,那么接下來就可以編譯和加載驅動了!
在驅動目錄下,執行make進行編譯:
編譯出來的驅動文件,名稱為:helloDev.ko
接下來把這個驅動加載到內核:
helloDriver加載成功,打印出了:
[11837.379638] register_chrdev_region ok
[11837.379642] hello driver init
可見,執行insmod的時候,驅動文件里的hello_init被調用了。
那驅動文件里的hello_exit什么時候會被調用呢?
可能聰明的你已經猜到了,那就是執行 rmmod helloDev.ko的時候。
本節來看驅動的測試。
我們需要編寫一個應用層的程序來對hello驅動進行測試:(test.c)
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#define DATA_NUM (64)
int main(int argc, char *argv[])
{
int fd, i;
int r_len, w_len;
fd_set fdset;
char buf[DATA_NUM]="hello world";
memset(buf,0,DATA_NUM);
fd = open("/dev/hello", O_RDWR);
printf("%drn",fd);
if(-1 == fd) {
perror("open file errorrn");
return -1;
}
else {
printf("open successern");
}
w_len = write(fd,buf, DATA_NUM);
r_len = read(fd, buf, DATA_NUM);
printf("%d %drn", w_len, r_len);
printf("%srn",buf);
return 0;
}
編譯并執行,發現錯誤,找不到設備文件:
這是因為還沒有創建hello驅動的設備文件,我們為hello驅動手動創建設備文件:
root@ubuntu:/home/jinxin/drivers/helloDev# mknod /dev/hello c 232 0
備注:這里的232和0要跟驅動文件里定義的主次設備號對應起來!
然后再次執行測試程序,發現成功了:
root@ubuntu:/home/jinxin/drivers/helloDev# ./test
3
open successe
0 0
root@ubuntu:/home/jinxin/drivers/helloDev#
然后再次執行dmesg查看驅動輸出,發現驅動里的hell_open, hello_write, hello_read被依次調用了。
這就是一個完整的、最簡單的驅動的開發和測試的流程。
我想大家可能會有幾個問題:
1.驅動測試的時候為什么要有設備文件,設備文件的作用是什么?hello驅動的設備文件創建的時候為什么要指定主設備號為232, 此設備號為0?
2.對/dev/hello執行write()調用的時候,怎么就調用到了驅動里的hello_write()里去了?
3.驅動代碼里的register_chrdev_region這些函數都是干什么的?實現的地方在哪里?
4.測試程序的read和write的返回值為什么都是0?
針對以上可能的問題,敬請期待《linux內核驅動第2講》,我會一一回答以上問題。
關注“技術簡說”,一步一步教你開發linux內核和驅動






