目录
1 字符驱动设备
字符设备是以字节为单位的I/O传输,这种字符流的传输率通常比较低,常见的字符设备有鼠标、键盘、触摸屏等。
2 编写驱动并测试
可以先不去管代码具体的内容,后面会进行分析,最先要做的就是搞懂实验目的是什么,怎么去做。要注意的问题是,现在的实验环境是在用QEMU来模拟ARM开发环境,搭载 Linux4.0 的内核系统,那么编译驱动程序的时候需要在同样的Linux4.0 ARM开发环境下编译,才能成功地插入内核模块,因为编译内核模块的Linux版本与架构必须和植入内核模块的Linux版本与架构高度一致。
关于搭建所说的QEMU+ARM+Linux4.0的编译环境请查看第一篇Linux内核学习文章:https://blog.csdn.net/qq_32473685/article/details/103362844
文章在Linux内核版本上有所出入,但只要换成对应版本就好,操作无差别。
2.1 实验目的及操作步骤
实验目的:
1)编写一个简单的字符驱动设备,实现open,read,write方法。
2)能从用户空间测试编写的字符驱动设备,调用read( )函数查看。
操作步骤:
1)编写字符模块并编译
2)将字符模块放入到QEMU搭载的Linux4.0内核中并插入
3)编写用户层测试程序并编译
4)将测试程序放入到QEMU搭载的Linux4.0内核中并测试
2.2 简单的字节驱动模块
字符驱动代码如下:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/cdev.h>
#define DEMO_NAME "my_demo_dev"
static dev_t dev;
static struct cdev *demo_cdev;
static signed count=1;
static int demodrv_open(struct inode *inode,struct file *file)
{
int major =MAJOR(inode->i_rdev);
int minor =MINOR(inode->i_rdev);
printk("%s: major=%d,minor=%d\n",__func__,major,minor);
return 0;
}
static int demodrv_release(struct inode *inode,struct file *file)
{
return 0;
}
static ssize_t demodrv_read(struct file *file,char __user *buf,size_t lbuf,loff_t *ppos)
{
printk("%s enter\n",__func__);
return 0;
}
static ssize_t demodrv_write(struct file *file,const char __user *buf ,size_t count,loff_t *f_pos)
{
printk("%s enter\n",__func__);
return 0;
}
static const struct file_operations demodrv_fops={
.owner=THIS_MODULE,
.open=demodrv_open,
.release=demodrv_release,
.read=demodrv_read,
.write=demodrv_write
};
static int __init simple_char_init(void)
{
int ret;
ret=alloc_chrdev_region(&dev,0,count,DEMO_NAME);
if(ret){
printk("failed to allocate char device region");
return ret;
}
demo_cdev=cdev_alloc();
if(!demo_cdev){
printk("cdev_alloc failed\n");
goto unregister_chrdev;
}
cdev_init(demo_cdev,&demodrv_fops);
ret=cdev_add(demo_cdev,dev,count);
if(ret){
printk("cdev_add failed\n");
goto cdev_fail;
}
printk("successed register char device : %s\n",DEMO_NAME);
printk("Major numbner =%d,minor number =%d\n",MAJOR(dev),MINOR(dev));
return 0;
cdev_fail:
cdev_del(demo_cdev);
unregister_chrdev:
unregister_chrdev_region(dev,count);
return ret;
}
static void __exit simple_char_exit(void)
{
printk("removing device\n");
if(demo_cdev)
cdev_del(demo_cdev);
unregister_chrdev_region(dev,count);
}
module_init(simple_char_init);
module_exit(simple_char_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Henry Fordham");
MODULE_DESCRIPTION("simple chararcter device");
MODULE_ALIAS("Henry");
2.3 编译字节驱动模块
这里要注意,我们需要在QEMU+ARM的环境下插入内核模块,那么就要在QEMU+ARM的环境下编译模块,为了节省时间,可以先下载一个基础环境,如下:
https://github.com/figozhang/runninglinuxkernel_4.0
直接git clone这个环境下来,下载按照README安装,安装完后,编写刚才内核模块的Makefile,注意第一行BASEINCLUDE刚才下载下来的runninglinuxkernel_4.0,Makefile如下。
BASEINCLUDE ?= ~/tools/runninglinuxkernel_4.0
mytest-objs :=my_demodev.o
obj-m :=mydemo.o
all :
$(MAKE) -C $(BASEINCLUDE) M=$(PWD) modules;
clean:
$(MAKE) -C $(BASEINCLUDE) SUBDIRS=$(PWD) clean;
rm -f *.ko;
写完之后,make,可以看到my_demo.ko
2.4 插入到QEMU上的Linux内核中
首先,还是挂载一块ext4磁盘到Linux内核中,通过QEMU启动:
qemu-system-arm -M vexpress-a9 -smp 4 \
-m 1024M \
-kernel arch/arm/boot/zImage \
-append "rdinit=/linuxrc console=ttyAMA0 loglevel=8" \
-dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-nographic \
-sd ext4.img
如果不知道怎么创建一个ext4的磁盘,还是参考第一篇文章。
挂载我们的ext4磁盘到QEMU的mnt:
mount -t ext4 /dev/mmcblk0 /mnt/
同时在主机上将ext4挂载到一个文件目录,使之同步。具体参考第一篇文章。
QEMU中mnt下将mydemo.ko插入进去:
/mnt # ls
lost+found mydemo.ko
/mnt # insmod mydemo.ko
successed register char device : my_demo_dev
Major numbner =252,minor number =0
2.5 写用户层测试代码测试插入的内核模块
如下是用户级代码: test.c
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#define DEMO_DEV_NAME "/dev/demo_drv"
int main(){
char buffer[64];
int fd;
fd = open(DEMO_DEV_NAME, O_RDONLY);
if (fd < 0) {
printf("open device %s failed\n", DEMO_DEV_NAME);
return -1;
}
read(fd, buffer, 64);
close(fd);
return 0;
}
编译:
arm-linux-gnueabi-gcc test.c -o test --static
得到一个可执行文件test,将这个文件还是同步到qemu的内核文件系统ext4.img中,然后执行:
/ # ls mnt/
hello.ko lost+found mydemo.ko test
/ # cd mnt/
/mnt # insmod mydemo.ko
successed register char device : my_demo_dev
Major numbner =252,minor number =0
/mnt # mknod /dev/demo_drv c 252 0
/mnt # ls -l
total 646
-rw-r--r-- 1 0 0 55940 Dec 4 23:37 hello.ko
drwx------ 2 0 0 12288 Dec 4 14:24 lost+found
-rw-r--r-- 1 0 0 88788 Dec 4 16:24 mydemo.ko
-rwxr-xr-x 1 0 0 503004 Dec 18 14:51 test
/mnt # ./test
demodrv_open: major=252,minor=0
demodrv_read enter
可以看到,从用户层代码读取到了内核模块的信息。