Ubuntu编译并安装新驱动程序
内容
以编译模块的方法在ubuntu内核中增加一个新的设备驱动程序,功能为实现简单的字符设备(如键盘)读写。
环境
ubuntu版本:16.04.6
内核版本:4.15.0-74-generic
虚拟机:VMware
步骤
1.安装源码和工具包
执行命令查看是否安装源码。
ls -l /usr/src
执行命令查看内核版本。
uname -r
查看当前内核是否有对应的源码,若无需要去官网下载当前内核版本对应的源码,并将其解压至/usr/src文件夹中。清华开源镜像站
下载工具包:
apt-get install build-essential
2.编译驱动程序hello.c
hello.c
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/slab.h> #include <linux/uaccess.h> MODULE_LICENSE("GPL"); static int device_file_major_number = 0; static const char device_name[] = "hello_driver"; int my_open(struct inode *, struct file *); int my_release(struct inode *, struct file *); ssize_t my_read(struct file *, char __user *, size_t , loff_t *); ssize_t my_write(struct file *, const char __user *, size_t , loff_t *); struct file_operations fops = { .owner=THIS_MODULE, .read=my_read, .write=my_write, .release=my_release, .open=my_open, }; #define BUF_SIZE 100 struct cdd_cdev{ struct cdev cdev; struct device *dev_device; u8 led; char kbuf[BUF_SIZE]; }; int my_open(struct inode *inode, struct file *file) { struct cdd_cdev *pcdevp = NULL; printk("my_open() execute successfully!\n"); pcdevp = container_of(inode->i_cdev, struct cdd_cdev, cdev); printk("led = %d\n", pcdevp->led); file->private_data = pcdevp; return 0; } int my_release(struct inode *inode, struct file *file) { printk("my_release() execute successfully!\n"); return 0; } ssize_t my_read (struct file *file, char __user *buf, size_t count, loff_t *offset) { int ret = 0; struct cdd_cdev *cdevp = file->private_data; printk("my_read() execute successfully!\n"); ret = copy_to_user(buf, cdevp->kbuf, count); printk("kernel kbuf content:%s\n", cdevp->kbuf); return ret; } ssize_t my_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) { int ret = 0; struct cdd_cdev *cdevp = file->private_data; printk("my_write() execute successfully!\n"); ret = copy_from_user(cdevp->kbuf, buf, count); return ret; } int __init my_init(void) { int result = 0; printk( KERN_NOTICE "hello-driver: my_init() is called." ); result = register_chrdev( 0, device_name, &fops ); if( result < 0 ) { printk( KERN_WARNING "hello-driver: can\'t register character device with errorcode = %i", result ); return result; } device_file_major_number = result; printk( KERN_NOTICE "hello-driver: registered character device with major number = %i and minor numbers 0...255" , device_file_major_number ); return 0; } void __exit my_exit(void) { printk( KERN_NOTICE "hello-driver: my_exit() is called" ); if(device_file_major_number != 0) { unregister_chrdev(device_file_major_number, device_name); } } module_init(my_init); module_exit(my_exit);
- 编写Makefile文件
ifeq ($(KERNELRELEASE),) #ifeq后面一定要加空格 #非命令行前面不能以tab开头,必须以空格开头 KERNELDIR ?= /lib/modules/$(shell uname -r)/build M=$(PWD) modules PWD := $(shell pwd) modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules #必须以tab开头 modules_install: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install #必须以tab开头 clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module* modules* #必须以tab开头 .PHONY: modules modules_install clean else obj-m :=hello.o #空格开头 endif
备注:
- gcc编译方式只适用于2.4版本以下的内核,编译2.4以上版本的内核需使用make编译。
- 源文件需以.c为后缀,笔者曾尝试编译.cc文件,结果报错。
- Makefile文件名的M必须为大写,否则编译出错。
- 编写Makefile文件时只需把上述代码中的hello替换为自己编写的源文件名除去.c后缀。
- 以root用户执行make命令。
3.执行make,编译源码
make
编译成功后输出如图,若输出结果与图示不同,则极大可能编译失败。如若输出很多的Entering directory xxx 以及 Leaving directory directory xxx,最后并显示Error时,很可能是因为内核源码版本有问题。笔者在最开始使用的是最新版Ubuntu18.04和5.3.0版本的内核,但make的时候一直出错,在几度崩溃后笔者选择在凌晨2点的夜里带着无尽的怒火关闭了电脑上床入睡(顺带做了一晚上的梦…),并在第二天决定更换稍微旧一点的版本,即本文开端的环境所述,最终make顺利编译成功。
4.安装模块
执行命令,安装成功后应无任何输出。
insmod hello.ko
5.创建设备文件
因为笔者在源码的注册设备文件函数register_chrdev()中传入的第一个参数为0,系统会为新设备随机分配一个未使用的主设备号,所以在创建设备文件前需要获得文件主设备号。
cat /proc/dev | grep hello
得到文件主设备号后我们接着创建设备文件
mknod /dev/hello c 主设备号 从设备号(可设为0)
6.编写测试程序
当做到这一步时,即将接近尾声!
test.c
#include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<stdio.h> #include<unistd.h> #define N 512 char buf[N]; int main(){ int fd; if((fd=open("/dev/hello",O_RDWR))<0){ printf("open error!\n"); return -1; } if(read(fd,buf,N)<0){ printf("read error!\n"); return -1; } printf("read from /dev/hello is : %s\n",buf); printf("input second buf:"); scanf("%[^\n]",buf); //忽略空格直至换行 if(write(fd,buf,N+1)<0){ printf("write error!\n"); return -1; } if(read(fd,buf,N)<0){ printf("read error!\n"); return -1; } printf("second read from /dev/hello is : %s\n",buf); close(fd); return 0; }
编译测试文件并执行
gcc test.c -o test
./test
Congratulation !
End